0

Scripts ignored in insert/replace responses?

When I use a <yml:a> or <yml:form> tag with the insert or replace attribute, scripts seems to be stripped out of the response. Is this by design or is it a defect? If the former, that probably ought to be documented in the YML reference. If the latter, would it be easy to fix?

What I'm trying to do is attach event listeners to elements in a response fetched this way. If anyone can suggest a workaround, I'd appreciate that as well.

Thanks,
Jared

by
7 Replies
  • Update: I've got a workaround working in Firefox. What I did is add a submit handler to my form that initiates polling of the insert destination element. When its contents change, I use script to find the desired elements in the newly inserted HTML DOM, and to attach event listeners to them. Here's a snippet:

    CODE
    addEventListener($("trade-button").parentNode, "submit", function(e) {
    var oldChild = $("trade-form-holder").firstChild;
    var i = setInterval(function() {
    if ($("trade-form-holder").firstChild != oldChild) {
    clearInterval(i);
    onTradeFormLoaded();
    }
    }, 100);
    });
    function onTradeFormLoaded() {
    var cancelButton = $$("trade-form-holder", "input", "cancel-trade")[0];
    addEventListener(cancelButton, "click", function(e) {
    $("trade-form-holder").innerHTML = "";
    });
    // Attach additional event listeners...
    }


    The $$ call above finds the input element with CSS class "cancel-trade" inside the element with id "trade-form-holder". For the curious, my $$ function looks like this:

    CODE
    function $(id) {
    return document.getElementById(id);
    }
    function $$(id, tagName, className) {
    var a = $(id).getElementsByTagName(tagName), b = [];
    var re = new RegExp("\\b" + className + "\\b");
    for (var i = 0; i < a.length; i++) {
    var e = a[i];
    if (e.className.match(re)) {
    b.push(e);
    }
    }
    return b;
    }


    It would be much simpler to use document.getElementById to locate the desired trade form controls, but that fails (due to the different flavor of ID cajoling being done to content inserted via a <yml:form> submission).

    Unfortunately, the approach I've detailed here fails in IE 7. The reason is that in IE only, element.className returns an empty string for elements inserted via <yml:form> submission. element.getAttribute("className") and element.getAttribute("class") fare no better. My IE DOM Inspector can't get inside frames, so I can't tell for sure whether the cajoling step is inlining the styles, and that's why there's no className, or whether the Caja element wrappers are just failing to return class names.

    Anyway, I've lost my enthusiasm for the insert/replace magic of <yml:a> and <yml:form>. I'm going to give gadgets.io.makeRequest a try for my AJAX, and insert HTML by setting .innerHTML. That appears to work just fine.
    0
  • Here's some more documentation in case you want to take a look. Basically, the caja runtime sanitizer (run during innerHTML inserts and such) is much more strict on JavaScript / CSS. I opened up a bug several months ago to revise this behavior but it's not a simple task. The method we use for accomplishing these tasks is to return the HTML with id's or generic node types. We then loop through those nodes and assign events after they have been inserted on the page.

    Here is a sample on our docs of what I mean: http://developer.yahoo.com/yos/code_exs/ca...nthandling.html

    Here is pretty well the same sample I put together on my blog but not all split up into doc writer format ;)- Jon
    0
  • Your event handling examples are great. Thanks for those. I've got event handling working just fine.

    Let me try to clarify the problems I've brought up in this thread. I understand that scripts cannot be inserted via .innerHTML, and thus they cannot be included in <yml:form ... replace="..."> responses. That's unfortunate, but I can live with it by putting scripts in full pages only. Several issues remain.

    Issue 1
    If elements in the HTML returned by a <yml:form ... replace="foo"> submission need event handlers, how do I attach them? There's no way for my scripts that were already in the page to register for notification when a yml:form's response content has been added to the page. So I have to poll to see when the new content has arrived. (See my polling code earlier in this thread.) Ideally I would just register a callback to execute when the content has been inserted.

    Issue 2
    So my polling scheme has worked, and now I'm trying to write code to register an event listener on a new HTML element that has just been added to the page. Let's say its element ID is "bar". The problem is, document.getElementById("bar") doesn't find it, since the ID mangling that's done for <yml:form ... replace="..."> responses is not the standard ID cajoling done for the rest of the page. For example, <div id="bar"> becomes <div id="bar-cajoled"> in a standard page. However, if a <yml:form ... replace="..."> submission returns <div id="bar"> then it becomes something like <div id="bar-yap-box-0-6d4495d1-yap-md-XELf7L4g"> and document.getElementById("bar") doesn't find it.

    Issue 3
    Since I can't find elements inserted by a <yml:form> by ID, I'm now going to try identifying them by CSS class name. This works in Firefox, but not in IE. (I'm currently testing with IE 7.) In IE, none of the HTML elements inserted via <yml:form> have a class name. At least that's the way it appears to my script. Not a single element has a non-empty value for .className, .getAttribute("class"), or .getAttribute("className").

    So if element IDs and CSS class names are out, how else am I supposed to identify elements? I've stooped to tag names, and it feels dirty. "Give me the eighth INPUT element in that DOM tree," I write. Very brittle.
    0
  • Hey Jared,

    The insert / replace of the yml:form tag, even though in production, seems to still have some issues associated with it. The bug I filed to update the documentation was set to depend on the completion of another bug - so that's most likely the reason that the documentation wasn't made available.

    With that in mind, I've been thinking about this issue and here's what I think I would do in this situation:

    I would recreate the yml:form tag as a regular form. When the submit button is clicked a JavaScript function would be called to run an AJAX request (using gadgets.io.makeRequest). The form can be parsed and the relevant form data can be inserted into a POST request. This POST request would hit your server side script, do the validation (the same thing that you would call in the params of the yml:form), and then return your new form content. Once the AJAX request finishes, you can insert the new HTML via innerHTML, which would then cajole your id's. After this you should be able to assign event handlers as you would have before. I've had much more luck with building out my own AJAX handlers and have been able to assign events that way. Hopefully those id's should match up - if they don't, if you send me a sample of what you're trying to do I'll do what I can to put together a workaround sample.

    Thanks,
    Jon

    QUOTE (Jared @ Apr 16 2009, 05:10 PM) <{POST_SNAPBACK}>
    Your event handling examples are great. Thanks for those. I've got event handling working just fine.

    Let me try to clarify the problems I've brought up in this thread. I understand that scripts cannot be inserted via .innerHTML, and thus they cannot be included in <yml:form ... replace="..."> responses. That's unfortunate, but I can live with it by putting scripts in full pages only. Several issues remain.

    Issue 1
    If elements in the HTML returned by a <yml:form ... replace="foo"> submission need event handlers, how do I attach them? There's no way for my scripts that were already in the page to register for notification when a yml:form's response content has been added to the page. So I have to poll to see when the new content has arrived. (See my polling code earlier in this thread.) Ideally I would just register a callback to execute when the content has been inserted.

    Issue 2
    So my polling scheme has worked, and now I'm trying to write code to register an event listener on a new HTML element that has just been added to the page. Let's say its element ID is "bar". The problem is, document.getElementById("bar") doesn't find it, since the ID mangling that's done for <yml:form ... replace="..."> responses is not the standard ID cajoling done for the rest of the page. For example, <div id="bar"> becomes <div id="bar-cajoled"> in a standard page. However, if a <yml:form ... replace="..."> submission returns <div id="bar"> then it becomes something like <div id="bar-yap-box-0-6d4495d1-yap-md-XELf7L4g"> and document.getElementById("bar") doesn't find it.

    Issue 3
    Since I can't find elements inserted by a <yml:form> by ID, I'm now going to try identifying them by CSS class name. This works in Firefox, but not in IE. (I'm currently testing with IE 7.) In IE, none of the HTML elements inserted via <yml:form> have a class name. At least that's the way it appears to my script. Not a single element has a non-empty value for .className, .getAttribute("class"), or .getAttribute("className").

    So if element IDs and CSS class names are out, how else am I supposed to identify elements? I've stooped to tag names, and it feels dirty. "Give me the eighth INPUT element in that DOM tree," I write. Very brittle.
    0
  • Jon, thanks for looking into this.

    This isn't just a <yml:form> issue. It affects both <yml:a> and <yml:form>. The handling of insert/replace responses seems to be the same for both.

    The issue is very easy to reproduce. Here's a tiny example.

    Put this in your app's landing page:

    CODE
    <yml:a insert="container" params="foo.html"><span id="link-child">Click me!</span></yml:a>
    <div id="container"></div>

    <script type="text/javascript">
    function $(id) {
    return document.getElementById(id);
    }
    function listen(el, eventType, fn) {
    if (el.addEventListener) {
    el.addEventListener(eventType, fn, false);
    } else if (el.attachEvent) {
    el.attachEvent(eventType, fn);
    }
    }
    var link = $("link-child").parentNode;
    alert("link=" + link);
    listen(link, "click", function(e) {
    var i = setInterval(function() {
    var el = $("container").firstChild;
    if (el) {
    clearInterval(i);
    alert("foo=" + $("foo"));
    alert("el=" + el);
    alert("el.id=" + el.id);
    }
    }, 100);
    });
    </script>


    Put this in foo.html:
    CODE
    <span id="foo">Foo</span>


    View your app and click the link. This is what comes out in the console:
    CODE
    link=<A>
    foo=null
    el=<SPAN>
    el.id=


    Notice that I have a reference to the returned <span> in el but my attempt to look it up by ID fails. What's more, its ID is not properly reported by the .id property.

    Jared
    0
  • Also, thanks for suggesting a workaround.

    For my needs, it turned out to be simplest to include all of the HTML that I needed to reference via script up front, but hidden via display:none. Then I just show/hide various parts of the DOM as the user interacts. For example, if I want form A to lead to form B, and form B to lead to form C, I include all three forms in the page up front, and then switch from one form to the next using script after a form submission succeeds. I have to poll to detect form submission success, but I can then use script to propagate information from one form's submission response into the next form before showing it.

    Jared
    0
  • Getting back to the original topic of this thread...

    The Yahoo platform should be able to accept scripts returned in response to <yml:a> and <yml:form> ajax calls, or at least a single inline script at the end of the response. All the HTML before the script could be inserted the same way it is today, and then the script could be executed immediately thereafter using caja.eval, no?
    0
  • QUOTE (Jared @ Apr 23 2009, 12:54 PM) <{POST_SNAPBACK}>
    Getting back to the original topic of this thread...

    The Yahoo platform should be able to accept scripts returned in response to <yml:a> and <yml:form> ajax calls, or at least a single inline script at the end of the response. All the HTML before the script could be inserted the same way it is today, and then the script could be executed immediately thereafter using caja.eval, no?

    Hi there - Why not use iomakerequest to do your request instead of yml:a?
    0
  • Hi Xav. Thanks for the suggestion. I'm actually using <yml:form>. The <yml:a> example was just to illustrate to Jon that <yml:a> has the same problems as <yml:form>.

    gadgets.io.makeRequest is handy, but inconvenient for form submissions because you have to write code to encode the form data into a POST body. Do you know of small, reliable library that does this and works in YAP?
    0

Recent Posts

in YAP