Dojo 1.6 dialog widget losing content attachpoints - javascript

We've recently upgraded a project from Dojo 1.5 to 1.6 [for the basic IE9 support] and its broken one of our widgets. We have a dialog widget [dijit.Dialog] with another widget within it [any widget here will produce the same behavior], the expected behavior is for the inner widget to be placed within the dialog widget's contentNode, and each of their attachpoints to be available through the main widget.
This isn't happening, instead we only find the dialog's attachpoint. More details below:
We're doing something like this in our main widget's template:
<div dojoType="dijit.Dialog" dojoAttachPoint="dia_widget"
autofocus="true" draggable="false" open="false" preventCache="true">
<div dojoType="dijit.form.ValidationTextBox" doLayout="false" dojoAttachPoint="val_widget">
</div>
</div>
This provides us a parent widget [main_widget] with two attach points: dia_widget and val_widget, which could be accessed within the main widget [after the buildRendering() function] by calling this.dia_widget and this.val_widget respectively.
After upgrading to 1.6.0 things go awry.
My guess is that when a dialog widget gets parsed it removes the domNode of its content into its new containerNode. When the rootnode is having attachpoints assigned, since the domNode is no longer available, it wont get added.
I've been debugging and from what I can see buildRendering() calls _attachTemplateNodes() in dijit/_Templated.js. This loops over the nodes in the widget, looking for their dojoattachpoint attribute and pushing them in the widget's _attachpoints attribute.
The first loop has two nodes [our dia_widget and val_widget] nodes. At this point it then begins parsing each node individually, beginning by calling buildRendering() on dia_widget and finding the attachpoints of all its children.
At some point here the main_widget's dom is modified to include the parsed/rendered dia_widget. The loop then returns to the main node _attachTemplateNodes() call, where it only sees dia_widget as a child node, having lost the val_widget entry. Since the node isn't found, it never gets added as an attachpoint in main_widget, and we can't access it down the line.
I hope this isn't too convoluted to follow, I'm new to Dojo and chasing through the code in firebug wasn't the most fun experience in the world, so I've written things up as they make sense to me.
I'm left wondering why the behavior varies from 1.5 to 1.6, is this simply a feature/parameter I should be passing to the dialog to behave as it did before? Is this a bug in the way I'm creating the nested widget? Is this a [known?] bug in dojo itself?
Thanks,
vic

There are some changes in 1.6.0 that potentially adds a "lang" attribute to dynamically-loaded content. Sometimes this "lang" attribute value is undefined and causes problems.
See if you are having problems with a "lang" attribute -- if so, this is likely to go away with 1.6.1.

Related

How to re-render hyperHTML to the same element after content change

I am trying to support the same type of thing as React.Children
My code looks like
const elem = document.getElementById("profile")
const render = hyperHTML.bind(elem);
const name = elem.textContent
render`<b>Hi ${name}</b>`
So the API looks like
<div id="profile">alax</div> 🢂 <div id="profile"><b>Hi alax</b></div>
and I am using MutationObserver to rerender on content change
But if the content is changed. hyperHTML says its rending to the right element.. but the element keeps its innerHtml(No update)
I can see the <!--_hyper: -2001947635;--> is removed then the content is set but setting up the render & hyperHTML.bind again does nothing
Any thoughts would be great! Thx
Update
The fix to the above problem is to call hyperHTML.bind`` then your normal render using hyperHTML will work
Context -
I am using hyperHTML to create a custom element library(hyper-element)
My use case: I work in a mix-tech project (some people use jQuery)
Side note, on the why. I want to support something like partial templates
Example of a partial template:
<user-list data="[{name:'ann',url:''},{name:'bob',url:''}]">
<div>{#name}</div>
</user-list>
Output:
<user-list data="[{name:'ann',url:''},{name:'bob',url:''}]">
<div>ann</div>
<div>bob</div>
</user-list>
This is one use of setting custom content in an element you control
At the moment I have the setting of the content by 3-party working/re-rending
https://jsfiddle.net/k25e6ufv/16/
My problem is now: it is rending another custom element and getting the pass content to child element
It looks like hyperHTML is setting the child element's content in front to the element and creating the element without setting the content
Scroll down to bottom of source to see implementation!
https://jsfiddle.net/k25e6ufv/14/
Rending crazy-cats:
Html`
xxx: ${this.wrapedContent} zzzz
`
Current output:
wrapedContent: ppp time:11:35:48 ~ crazy-cats: **Party 11:35:48** xxx: zzzz
<crazy-cats>Party 11:37:21 xxx: <!--_hyper: -362006176;--> zzzz </crazy-cats>
Desired output:
wrapedContent: ppp time:11:35:48 ~ crazy-cats: xxx: **Party 11:35:48** zzzz
<crazy-cats> xxx: Party 11:37:21 zzzz </crazy-cats>
I will try to answer as best as I can, but I'll start saying that when asking for help, it'd be much easier/better to show the simplest use case you are trying to solve.
There is a lot of "surrounding" code in your fiddles so that I'll try to answer only to hyperHTML related bits.
hyper-element ?
I am not sure what's the goal of the library but hyperHTML exposes hyper.Component, and there's also an official HyperHTMLElement class to extend, which does most of the things you manually implement in your examples.
I'll keep answering your questions but please consider trying, at least, the official alternative and maybe push some change there if needed.
partial templates
hyperHTML pattern and strength is the Template Literal standard. Accordingly, to generate TL from the DOM would require either parsing of the content or code evaluation. Both solutions aren't the way to go.
Custom Elements require JavaScript to work, and without JS your partial template is useless and also potentially confusing for the user/consumer.
You don't want to define what to do with the data in the layout, you want to define a Custom Element behavior within the class that defines it.
That means: get rid of old-style in-DOM output, and simply use the Custom Element class to define its content. You maintain the related class only instead of maintaining a layout that has no knowledge about how the CE should represent that data.
TL;DR the following is a bad hyperHTML pattern:
<user-list data="[{name:'ann',url:''},{name:'bob',url:''}]">
<div>{#name}</div>
</user-list>
all you want to do is to write this:
<user-list data="[{name:'ann',url:''},{name:'bob',url:''}]"></user-list>
but be careful, the data attribute in hyperHTML is special only if passed through the template literal. If you want to pass JSON to the component, call the attribute differently.
// hyperHTML data is special, no need to use JSON
render`<c-e data=${{as: 'it is'}}></c-e>`
Above snippet is different from having JSON as data attribute text so your example should use data-json name, and the class should remember to JSON.parse(this.dataset.json) in its constructor (or have an attribute observer that does that for you)
hyperHTML owns elements
When you write:
it looks like hyperHTML is setting the child element's content in front to the element and creating the element without setting the content
you are assuming you should care at all what hyperHTML does: you shouldn't.
The only thing you should understand is that hyperHTML owns the node it handles. If you trash those nodes via different libraries or manually, you are doing something wrong.
hyperHTML(document.body)`<p>hello ${'world'}</p>`;
// obtrusive libraries ... later on ...
document.body.textContent = 'bye bye';
// hyperHTML still owns the body content
hyperHTML(document.body)`<p>hello ${'world'}</p>`;
Above snippet is perfectly fine and totally wrong at the same time.
You don't update the body content manually, you don't interfere with its content via jQuery or other libraries, and you should never trash the content at all.
Once you chose hyperHTML to handle a bound context, that's it, you've made your choice.
This is true for pretty much every library on this world. If you use Angular to create something and you mess it all via jQuery, that breaks. If you write backbone templates and you mess later on with their content manually, that breaks.
If you bind an element to hyperHTML and you mess it up with other libraries, that breaks.
The only thing that won't break are wires, meaning the moment you create a wire, you can append it directly and that's actually a DOM node so it will be there, and it will be handled by hyperHTML.
Yet you should use hyperHTML to handle those changes, never jQuery or JS itself.
The output is right
When you say that the output should not contain the comment you are assuming you should care what output is produced via hyperHTML: you shouldn't!
hyperHTML uses comments as delimiters and these are absolutely fine for both performance, being unaffected by repaint and reflows, and for partial changes like the following one:
hyperHTML(document.body)`<p>${'a'} b ${'c'}</p>`
Both a and c will have a comment as anchor node to be able to update their content with anything later on.
hyperHTML(document.body)`<p>${[list, of, nodes]} b ${otherThing}</p>`
You change interpolations? All good, hyperHTML knows what to replace and where.
force-own the content
If you use a different template literal to re-populate a bound node you are trashing the cache and creating new content.
At that point you are better off with innerHTML because all the features of hyperHTML will be gone.
To start with, if your content can change so much, use an array.
hyper(document.body)`${['text']}`;
// you can clean up the text through empty array
hyper(document.body)`${[]}`;
// re-populate it with new content
hyper(document.body)`${['a', 'b', 'c']}`;
Above example is still better than changing template because all the optimizations for the content will be already there.
However, if you want to be sure the node the initial one created via hyperHTML, assuming no third parts script mutate/trash that node, you can use a wire.
const body = hyper()`<p>my ${'content'}</p>`;
document.body.textContent = '';
document.body.appendChild(body);
It's a bit extreme but at least faster.
As Summary
It looks like you are trying to sneak in hyperHTML into an application that trashes layout all the time through different third parts libraries.
Unless you create a closed Shadow DOM reference and you drop partial template through layout, you'll always have issues with libraries based on side effects with DOM content, libraries that mutates elements they don't own.
In hyperHTML the ownership concept is key, like in React you cannot change at runtime the defined JSX for the component, you should never try to change at runtime the defined template literal for hyperHTML.
Now, as much as I'd like to solve all your issues, I feel like it's right to ask you: are you sure hyperHTML is really the solution for your current app? It looks like surrounding side-effects caused by third parts libraries would constantly break your expectations if you don't use closed mode Shadow DOM and hyperHTML only to update your DOM.

What is the correct way to set a div as the dropdown for a Dojo widget?

I asked a related question about this recently: Dojo _hasDropDown - How do I declaratively bind the properties?. However, I've been struggling with this problem for about 30 hours by now (20 of those before the last question), and I don't feel like I'm any closer. I'd normally edit the old question, but it would change the scope too much and invalidate existing answers.
My problem:
I have the following combobox and Memory store combined with a Struts2 request-scope servlet as part of machineOverview.jsp:
<div data-dojo-type="dojo/store/Memory"
data-dojo-id="machineNameStore"
data-dojo-props="<s:property value='%{getNameJsonString()}'/>"></div>
<s:set name="MachineName" value="machineSearchView.name"
scope="request"></s:set>
<div data-dojo-type="dijit/form/ComboBox"
data-dojo-props="store:machineNameStore, searchAttr:'name', value:'${MachineName}'"
name="machineSearchView.name" id="machineSearchView.name"></div>
I have this custom Dojo Widget in js/widgets/templates/ExpandableSearchComponent.html:
<div class="${baseClass}">
<div data-dojo-type="dijit/form/TextBox"
name="${SearchViewFieldName}TextBox"
class="${baseClass}TextBox"
data-dojo-props="placeholder:'${fieldName}'"></div>
<div class="${baseClass}Container" data-dojo-attach-point="containerNode"></div>
</div>
With this Javascript behind it in js/widgets/ExpandableSearchComponent.js:
/**
* Javascript for ExpandableSearchComponent
*/
define([ "dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin",
"dojo/text!./templates/ExpandableSearchComponent.html"],
function(declare, _WidgetBase, _TemplatedMixin, template) {
return declare("js/widgets/ExpandableSearchComponent", [ _WidgetBase,
_TemplatedMixin ], {
templateString : template,
SearchViewFieldName : "",
fieldName : ""
});
});
What I'm trying to do is essentially replace the first snippet with the below:
<div data-dojo-type="js/widgets/ExpandableSearchComponent"
data-dojo-props="SearchViewFieldName: 'machineSearchView.name', fieldName: 'Name:'">
<div data-dojo-type="dojo/store/Memory"
data-dojo-id="machineNameStore"
data-dojo-props="<s:property value='%{getNameJsonString()}'/>"></div>
<s:set name="MachineName" value="machineSearchView.name"
scope="request"></s:set>
<div data-dojo-type="dijit/form/ComboBox"
data-dojo-props="store:machineNameStore, searchAttr:'name', value:'${MachineName}'"
name="machineSearchView.name" id="machineSearchView.name"></div>
</div>
When this is done, it should just show a dijit/form/TextBox. When I click this Dijit Textbox, a dropdown should appear with in the dropdown the dojo/store/Memory and the dijit/form/ComboBox. Then, once the Combobox has had an option selected, both the Combobox and the dropdown should collapse again and the dijt/form/TextBox should show the value that was entered in the ComboBox, prefixed with the placeholder.
What I've tried:
I've tried a bunch of things:
I've tried binding _hasDropDown declaratively, which doesn't work.
I've tried binding it programmatically, during which I come into numerous problems with figuring out what to put in the define parameters, what to put in the function parameters, what to put in the declare parameters and what functions to return. I keep getting console errors in Chrome that are hard to figure out why they are happening, like my TextBox not being defined properly or my template Mixins not working properly,...
I've tried putting the original comboBox and Memory store into the widget directly, but I abandoned that path after 1 day because it was a fragile and complicated construction and it wasn't really what we wanted in the end either.
What I'm looking for:
I'm not looking for someone to implement this for me. I'm looking for a working example of using either dijit/_HasDropDown or dijit/popup used in a custom widget that includes:
The HTML of the widget;
The javascript code-behind of the widget;
How to use this widget in a page;
If necessary any related javascript for the page itself.
I've checked the Dojo documentation. There is no tutorial for either of the above dijit components. There is an API reference, but that only contains the Javascript, and even then just for how the API works.
The _HasDropDown Mixin API reference has the Javascript for how to add it to a widget, but not the HTML, and it shows how to create such a widget programmatically, but again without HTML code to explain what objects it is binding the widget to. It also uses Widgets, not a div, which I want to use for formatting reasons.
The dijit/popup API reference has the same issues as the _HasDropDown Mixin reference, meaning it shows the Javascript, but not the HTML or how to use it.
both of these API reference documents confuse me and I can't figure out how to implement them. I've also tried Googling, but most hits are for older versions of Dojo, from before the introduction of AMD modules.
As said a couple of times now, I can't figure out how to make these work. The documentation provided by Dojo explains part but not all of it and it's unclear to me how this all works. Can I even even use a widget in this manner? Will I have to write the textbox and the javascript to create the dropdowns directly into the source of my main page?
I'm hoping for a solution that allows me to just surround the element I want to convert with a div that has a data-dojo-type tag, so I have something semi-portable, but if that's not possible, I'll have to confer with my coworkers.
Clarification: I have read the sources for FilteringSelect, _HasDropDown, Select, ComboBox and some other Dojo components, but my big problem right now is that I can't figure out how they wire their dropDown to the right node.
Since you are looking for examples, have you checked the sources for the dijit widgets that use _HasDropDown? The documentation mentions Select, ComboBox, DropDownButton and DateTextBox. Their sources should give you a good sense for how to use the mixin, especially in conjunction with the reference pages explaining their use with programmatic and declarative examples.
For example the Select widget source: https://github.com/dojo/dijit/blob/1.10.6/form/Select.js and its reference page: http://dojotoolkit.org/reference-guide/1.10/dijit/form/Select.html
To make thing easier I suggest that you first refine the custom widget that must be dropped, programmaticaly place it as a normal widget and style it. It should then be very easy to use it as a drop down following the examples of the dijit widgets that use _HasDropDown.

How to trace what happens to a DOM Element?

I'm using the masked input plugin for a web app at work. I'm applying masks using a class selector in $(document).ready():
$(".Primary_Phone_Number").mask("(999) 999-9999");
$(".ZipCodeMask").mask("99999");
$(".StateMask").mask("aa");
$(".date").mask("99/99/9999");
However, everything except the Phone number is losing it's mask. After document ready, if I run these again in the console, they get and retain their mask.
It's a large web app, each page has 1000s of lines of javascript and there are a lot of diverse selectors flying around making changes as well as a lot of ajax calls. We're only testing the web app in IE since it's an internal project. Is there anything available in IE10 to let me know when a particular DOM Element is getting manipulated?
If you use Chrome (and you can use it for debugging, if the problem reproduces), you can put a breakpoint on DOM modification.
Inspect the element, right click on it, choose "Break on..." and choose the event type (e.g. subtree modification). More info on this.
If you still want to do it in IE (9+), you can use Mutation Events and break the JS execution when the element is modified.
E.g.
el.addEventListener("DOMSubtreeModified", function(ev) {
debugger;
}. false);

Is $.empty() enough for big ajaxy apps?

Been working on an App and since it's getting a bit too big I've thinking of ways to improve memory management since the app runs mostly on Javascipt. So every time a navigation item is clicked I would call the jquery empty then show the html via ajax. ex:
//$.ajaxSetup(); called before this
//$this is the attached element
$.ajax({success:function(data){
$this.empty().html(data.output).fadeIn(400);
//more javascript stuff like loading tinymce or jquery ui
}});
is this enough to prevent memory leaks? I'm not entirely sure what empty does but I'm assuming it removes all DOM elements within that div along with any other objects and events? btw. You can find the app here http://webproposalgenerator.com/ and http://webproposalgenerator.com/demo.
any tips on improving the performance/security or any feedback at all would be greatly appreciated.
$.fn.empty should be enough, it deletes all data and events associated to the elements and then deletes the elements. It also calls .widget("destroy") on all jquery-ui widget.js based widgets that are defined on those elements.
It is also important to note that jquery's $.fn.html method calls $.fn.empty() on the given element before appending html, therefore, if you are using $.fn.html, you don't have to call $.fn.empty
actually my guess was that .html implies .empty anyway, also I'm not sure that's true. for the perforamnce part: according to jqfundamentals excelent book it is a recommanded best practice to add content while the element is in .detach() from the DOM. tried to lock at the code for advice but didn't find it. nice site btw

Question - Setting dynamic HTML using Javascript to iFrames on Windows Mobile 6.1 - IE Mobile6

(excuse me if this is not the right forum to post - i couldn't find anything related to non-native programming and related to this topic)
I Am trying to set a dynamic HTML into an iFrame on the webpage. I have tried a couple of things but none of them seem to work. I m able to read the innerHTML but can't seem to update it.
// Able to read using
document.getElementById('iFrameIdentifier').innerHTML;
// On Desktop IE, this code works
document.getElementById('iFrameId').contentWindow.document.open();
document.getElementById('iFrameId').contentWindow.document.write(dynamicHTML);
document.getElementById('iFrameId').contentWindow.document.close();
Ideally the same function should work as how it works for div's but it says 'Object doesn't support this method or property".
I have also tried document.getElementById('iFrameId').document.body.innerHTML.
This apparently replaces the whole HTML of the page and not just the innerHTML.
I have tried out a couple of things and they didn't work
document.getElementById('iFrameId').body.innerHTML
document.frames[0].document.body.innerHTML
My purpose is to have a container element which can contain dynamic HTML that's set to it.
I've been using it well till now when I observed that the setting innerHTML on a div is taking increasing amount of time because of the onClicks or other JS methods that are attached to the anchors and images in the dynamic HTML. Appears the JS methods or the HTML is some how not getting cleaned up properly (memory leak?)
Also being discussed - http://www.experts-exchange.com/Programming/Languages/Scripting/JavaScript/Q_26185526.html#a32779090
I have tried a couple of things but none of them seem to work.
Welcome to IEMobile! Nothing you know about DOM scripting applies here.
Unfortunately, cross-iframe scripting does not appear to be possible in IEMobile6-7.
frameelement.contentDocument (the standard DOM method) isn't available
frameelement.contentWindow.document (the IE6-7 workaround version) isn't available
the old-school Netscape window.frames array only works for frames, not iframes
having the child document pass up its document object to the window.parent only works for frames, not iframes. In an iframe, window.parent===window.
So the only ways forward I can see are:
use frames instead of iframes. Nasty. Or,
use document.cookie to communicate between parent and child: the child document is just a script, that checks for a particular cookie in document.cookie on a poller, and when it's found that's a message from the parent, and it can write some HTML or whatever. Slow and nasty. Or,
using the server-side to inject content into the frames, passing it in as an argument to a script. Slow, nasty, and potentially insecure. Or,
avoid frames completely (best, if you can). Or,
drop support from IEMobile6-7 (best for preserving your sanity, if you can get away with it!)
Appears the JS methods or the HTML is some how not getting cleaned up properly (memory leak?)
Yes, probably. IEMobile6-7(*) is close to unusable at dynamic HTML. It gives you a lovely flavour of what scripting used to be like for us poor gits back in the Netscape 4 days.
Try to avoid creating and destroying lots of nodes and event handlers. Keep the page as static as possible, re-using element nodes where possible and setting text node data properties in preference to tearing everything down and making anew with createElement or innerHTML. Use an event stub (onclick="return this._onclick()") in the HTML together with writing to _onclick if you need to set event handlers from JavaScript, in preference to recreating the HTML with a new event handler (or just trying to set the property, which of course doesn't work in IEMobile). Avoid long-running single pages when you can.
It'll still crash, but hopefully it'll take longer.
*: that is, the versions of IE present on WinMo before version 6.1.4, where it became the infinitely better IEMobile8, marketed as “Internet Explorer Mobile 6” (thank you Microsoft).
Okay, I kinda resolved the issues that i was facing earlier and the bigger issue which was setting HTML to an iFrame on IEMobile. But i still have one more PIA which is related to double scollbars - which i am currently looking into. There seems to be more poor souls facing similar problem - if i fix that too. I will post an update here.
How did i finally write to iFrame on IEMobile?
Have 2 divs one to wrap the iFrame and the other to write inside an iFrame.
document.getElementById('OuterDiv').innerHTML = '';
document.getElementById('OuterDiv').innerHTML = '<iframe id="iFrameId" src="somefile.html"></iframe>';
This creates an iFrame each time and in the somefile.html on load there is a InnerDiv.innerHTML which doesn't seem to leak the memory.
In the somefile.html there will be an onLoad method which will fetch the HTML (explained below on how i managed to get it) and do a
document.getElementById('InnerDiv').innerHTML = dynamicHTML;
How did I manage to pass the HTML between parent and child iFrame
As well explained by #bobince earlier, one has to rely on 3rd party service like a cookie or a server to pass around the data between parent and the child iFrame.
I infact used an ActiveXControl to set and get data from the parent and child iFrame's javascript respectively. I won't recommend doing this if you have to introduce an ActiveX Control just for this. I accidentally already have one which I use to get the Dynamic HTML in the first place.
If you need any help you can DM me - Twitter #Swaroop
Thanks #bobince for your help. I am marking this one as an answer because it says what i did to fix the issue.

Categories