Loading ...
Sorry, an error occurred while loading the content.

javascript include semantics

Expand Messages
  • Alan Pinstein
    Hi all - I ve been using YUI for a while now, but am having a problem since updating to 0.12.1. My problem is related to including javascript code. The
    Message 1 of 7 , Dec 28, 2006
    • 0 Attachment
      Hi all - I've been using YUI for a while now, but am having a problem
      since updating to 0.12.1.

      My problem is related to including javascript code. The normative
      way, by using <script> tags in the head element, works fine.

      However, in my quest to be as optimal as possible for my framework, I
      am loading javascript on-demand, in the following fashion:

      PHOCOA.importJS('/webapp/www/framework/yui/yahoo/yahoo.js');
      PHOCOA.importJS('/webapp/www/framework/yui/yahoo-dom-event/yahoo-dom-
      event.js');

      where PHOCOA.importJS() does:

      var js = new Ajax.Request(
      path,
      { asynchronous: false, method: 'get' }
      );
      eval(js.transport.responseText);


      Why do I do this? Well, in my application, some web pages use AJAX to
      load popups that use other YUI widgets; the widgets used in the popup
      are not used in the "base" file. Thus, instead of having the web page
      developer <script> include all required YUI js files that *might* be
      used by the popups, this mechanism allowed the popups to dynamically
      load JS code only when needed by the server-side code creating the
      popup HTML.

      In 0.11.3, this worked perfectly.

      However, changes made to 0.12.1 have broken this capability, which I
      do know has to do with scope and eval().

      From what I understand (and my knowledge of JS is not terribly
      advanced) if code is structured properly, then even objects "defined"
      in eval() are still globally accessible. Whether this is by design or
      just happened to work before I don't know.

      I am wondering if anyone can elucidate me on the issues involved, and
      whether or not I am trying something ridiculous or not.

      For instance, yahoo.js changed in the latest code from:

      window.YAHOO = window.YAHOO || {};

      to

      if (typeof YAHOO == "undefined") {
      /**
      * The YAHOO global namespace object
      * @class YAHOO
      * @static
      */
      alert('creating YAHOO');
      var YAHOO = {};
      }

      Which doesn't work with the eval() setup. However, changing

      var YAHOO = {}
      to
      window.YAHOO = {}

      fixes the YAHOO object, but then YAHOO.util.Event doesn't work.

      So I decided it's time for me to learn really how this works.

      The worst case scenario is that I can just "include" all js files in
      the "base" HTML file, regardless of whether they're eventually used
      by popups. It's less efficient, but at least it'd work.

      Alternatively, if it's a support-able thing to have the yahoo js eval
      ()-safe, that'd be preferable.

      Looking forward to your comments!

      Thanks,
      Alan
    • Eric Miraglia
      Alan, What an interesting question you raise here. Thanks for sharing this. With the usual caveat that I have not tested this extensively, I believe that you
      Message 2 of 7 , Dec 28, 2006
      • 0 Attachment
        Alan,

        What an interesting question you raise here.  Thanks for sharing this.

        With the usual caveat that I have not tested this extensively, I believe that you can make your approach compatible with the new construct in v0.12.1 by doing the following when you eval yahoo.js:

        eval(js.transport.responseText); //your existing line
        window.YAHOO=YAHOO;

        Here is what I assume is happening (I'm making some assumptions about your code beyond what you pasted into your message):  Your eval line is executing within a function of some kind — this is probably the PHOCOA.importJS() function.  In yahoo.js, the following line (22) is used to define the YAHOO global object:

        var YAHOO = {};

        When you eval yahoo.js and the JavaScript engine executes that line, YAHOO is not assigned to the global scope; rather, it is scoped locally to the function in which the eval statement is issued.  YAHOO in the global scope — that is, window.YAHOO — is never created.  By comparison, when yahoo.js runs as an included script, the same line does create YAHOO in the global scope because it is not wrapped in a function within the yahoo.js file.

        By explicitly assigning the contents of the local YAHOO variable to the global window.YAHOO, you make that global variable available to be further populated by other YUI scripts that you eval in your on-demand system.  (I see that you tried something similar to this without success; however, when I take this approach with the yahoo.js script I am able to load and use Event Utility via XHR and eval successfully thereafter.)

        Let me know how that solution works for you — again, I'm making some assumptions here, but I believe this is likely to be the root of the issue.

        Regards,
        Eric

        ______________________________________________
        Eric Miraglia
        Yahoo! User Interface Library



        On Dec 28, 2006, at 6:16 PM, Alan Pinstein wrote:

        Hi all - I've been using YUI for a while now, but am having a problem
        since updating to 0.12.1.

        My problem is related to including javascript code. The normative
        way, by using <script> tags in the head element, works fine.

        However, in my quest to be as optimal as possible for my framework, I
        am loading javascript on-demand, in the following fashion:

        PHOCOA.importJS('/webapp/www/framework/yui/yahoo/yahoo.js');
        PHOCOA.importJS('/webapp/www/framework/yui/yahoo-dom-event/yahoo-dom-
        event.js');

        where PHOCOA.importJS() does:

        var js = new Ajax.Request(
        path,
        { asynchronous: false, method: 'get' }
        );
        eval(js.transport.responseText);

        Why do I do this? Well, in my application, some web pages use AJAX to
        load popups that use other YUI widgets; the widgets used in the popup
        are not used in the "base" file. Thus, instead of having the web page
        developer <script> include all required YUI js files that *might* be
        used by the popups, this mechanism allowed the popups to dynamically
        load JS code only when needed by the server-side code creating the
        popup HTML.

        In 0.11.3, this worked perfectly.

        However, changes made to 0.12.1 have broken this capability, which I
        do know has to do with scope and eval().

        >From what I understand (and my knowledge of JS is not terribly
        advanced) if code is structured properly, then even objects "defined"
        in eval() are still globally accessible. Whether this is by design or
        just happened to work before I don't know.

        I am wondering if anyone can elucidate me on the issues involved, and
        whether or not I am trying something ridiculous or not.

        For instance, yahoo.js changed in the latest code from:

        window.YAHOO = window.YAHOO || {};

        to

        if (typeof YAHOO == "undefined") {
        /**
        * The YAHOO global namespace object
        * @class YAHOO
        * @static
        */
        alert('creating YAHOO');
        var YAHOO = {};
        }

        Which doesn't work with the eval() setup. However, changing

        var YAHOO = {}
        to
        window.YAHOO = {}

        fixes the YAHOO object, but then YAHOO.util.Event doesn't work.

        So I decided it's time for me to learn really how this works.

        The worst case scenario is that I can just "include" all js files in
        the "base" HTML file, regardless of whether they're eventually used
        by popups. It's less efficient, but at least it'd work.

        Alternatively, if it's a support-able thing to have the yahoo js eval
        ()-safe, that'd be preferable.

        Looking forward to your comments!

        Thanks,
        Alan


      • Alan Pinstein
        ... Heh, thanks! ... So, I can t do that directly, because of course it s a generic function... but I did edit the yahoo.js function to do that, and yes, it
        Message 3 of 7 , Dec 28, 2006
        • 0 Attachment
          > What an interesting question you raise here. Thanks for sharing this.

          Heh, thanks!

          > With the usual caveat that I have not tested this extensively, I
          > believe that you can make your approach compatible with the new
          > construct in v0.12.1 by doing the following when you eval yahoo.js:
          >
          > eval(js.transport.responseText); //your existing line
          > window.YAHOO=YAHOO;

          So, I can't do that directly, because of course it's a generic
          function... but I did edit the yahoo.js function to do that, and yes,
          it does work, but of course just for the "YAHOO" base object.

          Where I ran into problems is in YAHOO.util.Event which somehow
          doesn't show up in the global namespace... I don't want to get into
          having to "hack up" the yui js or provide hacks for it in my loader;
          I think a general and correct solution is needed.

          > Here is what I assume is happening (I'm making some assumptions
          > about your code beyond what you pasted into your message): Your
          > eval line is executing within a function of some kind — this is
          > probably the PHOCOA.importJS() function. In yahoo.js, the
          > following line (22) is used to define the YAHOO global object:
          >
          > var YAHOO = {};
          >
          > When you eval yahoo.js and the JavaScript engine executes that
          > line, YAHOO is not assigned to the global scope; rather, it is
          > scoped locally to the function in which the eval statement is
          > issued. YAHOO in the global scope — that is, window.YAHOO — is
          > never created. By comparison, when yahoo.js runs as an included
          > script, the same line does create YAHOO in the global scope because
          > it is not wrapped in a function within the yahoo.js file.

          Yes, I agree that's what is happening... I don't know of a way (at
          least cross-platform) to "eval" in a global context.

          What I discovered previously, and I *think* it's a real solution, is
          that JS libraries can be coded in such a way that they are "eval-
          safe", the definition of "eval-safe" being that there is no
          difference whether the script was included with a <script> tag or via
          eval(scriptText).

          YUI 0.11.3 seemed to be eval-safe.

          Obviously *one* of the things required to be eval-safe is to add
          variables to window.* rather than just declaring them, since that's
          one thing that changed and broke. However, I am not sure why
          YAHOO.util.Event broke.

          I am curious if this is a consideration of the YUI team. I kinda
          imagine that it is (or at least was) because without it being a
          consideration, there's pretty much no chance that even 0.11.3
          would've worked in this way. It's too easy to break for it to have
          been accidental.

          > Let me know how that solution works for you — again, I'm making
          > some assumptions here, but I believe this is likely to be the root
          > of the issue.

          I did some more research, and there are some other approaches, mostly
          adding <script> elements to the DOM. But these cause race condition
          as they are not synchronous; it does cause the JS to be included
          properly (ie in global scope), but if you have JS code in your main
          page, you can't be sure that the includes will finish prior to your
          code's execution. You can sorta hack this too, with a simple "wait"
          loop, but that's not so pretty either:

          // in main HTML page, to trigger code that requires on-demand js
          loading:
          bootstrap();
          function bootstrap()
          {
          try {
          if (YAHOO.util.Event)
          {
          YAHOO.util.Event.onAvailable('mapContainer', setupMap);
          }
          } catch (e) {
          setTimeout(bootstrap, 50);
          }
          }

          But that's pretty ugly.

          I thought about maybe using the technique after adding the <script>
          element to the head, to see if I could watch for some property on the
          DOM that would let me know that the script had finished loading, but
          I couldn't come up with anything. That would work, because it'd
          combine the proper scoping with synchronous loading. Also, it'd even
          be parallel-izable (if you're including lots of files dynamically)
          with an interface like:

          // in main HTML file
          // these will all execute serially, adding <script> tags to the
          <head>, but the "loading" of the js will occur using the browser's
          normal parallelization
          importJS('a.js');
          importJS('b.js');
          importJS('c.js');
          importJS('d.js');

          waitForDynamicJS(); // would block until it could be detected that a/
          b/c/d.js were all loaded.

          // do stuff that requires those includes

          So anyway, those are my thoughts. I spent about 10-15 hours on this
          issue when I first made my system, and it worked great until I got
          hit with this hiccup. Of course it's caused by IE which gets the
          lovely and useless "operation aborted" error due to, well, who knows
          why! "Operation aborted" is a whole nightmare of its own.

          Regards,
          Alan
        • Eric Miraglia
          Alan, I ll let others weigh in on this as well, but a few quick comments here... ... When I eval the current yahoo.js (v0.12.1) and then make
          Message 4 of 7 , Dec 28, 2006
          • 0 Attachment
            Alan,

            I'll let others weigh in on this as well, but a few quick comments here...

            I did edit the yahoo.js function to do that, and yes,  
            it does work, but of course just for the "YAHOO" base object.

            Where I ran into problems is in YAHOO.util.Event which somehow  
            doesn't show up in the global namespace... I don't want to get into  
            having to "hack up" the yui js or provide hacks for it in my loader;  
            I think a general and correct solution is needed.

            When I eval the current yahoo.js (v0.12.1) and then make window.YAHOO=YAHOO within that function, I am subsequently able to load event.js via XHR, eval it as is, and use it from the global scope.  In other words, evaling event.js populates the global window.YAHOO.util.Event object as desired; I can then, anywhere on the page, call YAHOO.util.Event.addListener(), for example.  Is there a public url where we could look at your example where this fails?  (I'm testing in FF2, primarily; is it only in IE that you see this fail?)

            What I discovered previously, and I *think* it's a real solution, is  
            that JS libraries can be coded in such a way that they are "eval- 
            safe"...I am curious if this is a consideration of the YUI team. I kinda  
            imagine that it is (or at least was) because without it being a  
            consideration, there's pretty much no chance that even 0.11.3  
            would've worked in this way. It's too easy to break for it to have  
            been accidental.

            The construct as of 0.11.x was not structured deliberately to accommodate dynamic loading.  However, the revision in 0.12.0 was in part motivated by a desire to allow YUI to be sandboxed completely — by allowing YAHOO to be a local variable (whereas it was forced to be global in 0.11.x), implementers can theoretically sandbox the entire YUI library without touching the global space.  This has some obvious advantages, albeit not relevant for most implementations.

            I did some more research, and there are some other approaches, mostly  
            adding <script> elements to the DOM. ...

            Dynamic script nodes for YUI inclusion are certainly an option here.  You may want to customize the YUI files to add callbacks at the end of the files to alert you in a reliable, cross-platform manner to the fact that the script has fully loaded; not optimal, but not too significant, and that could be automated in your build process.

            Beyond that, I would try one very simple strategy: Take the body of yahoo.js and include it inline in the <head> of your document (or include it as an external script file, using a far-futures expires header to ensure good interpage caching).  With that in place, I believe your on-demand loading of the remainder of the scripts (event.js, etc.) will work as desired.  This is not perfectly optimal, but it's a very minor overhead (~2KB) and may be the most reliable and performant approach.

            Regards,
            Eric

            ______________________________________________
            Eric Miraglia
            Yahoo! User Interface Library



          • Alan Pinstein
            ... Ah, you know what? I am trying the new yahoo-dom-event.js. That one didn t work. I ll try the event.js and see if that works. ... Interesting, so the goal
            Message 5 of 7 , Dec 29, 2006
            • 0 Attachment
              > When I eval the current yahoo.js (v0.12.1) and then make
              > window.YAHOO=YAHOO within that function, I am subsequently able to
              > load event.js via XHR, eval it as is, and use it from the global
              > scope. In other words, evaling event.js populates the global
              > window.YAHOO.util.Event object as desired; I can then, anywhere on
              > the page, call YAHOO.util.Event.addListener(), for example. Is
              > there a public url where we could look at your example where this
              > fails? (I'm testing in FF2, primarily; is it only in IE that you
              > see this fail?)

              Ah, you know what? I am trying the new yahoo-dom-event.js. That one
              didn't work. I'll try the event.js and see if that works.

              >> What I discovered previously, and I *think* it's a real solution, is
              >> that JS libraries can be coded in such a way that they are "eval-
              >> safe"...I am curious if this is a consideration of the YUI team. I
              >> kinda
              >> imagine that it is (or at least was) because without it being a
              >> consideration, there's pretty much no chance that even 0.11.3
              >> would've worked in this way. It's too easy to break for it to have
              >> been accidental.
              >
              > The construct as of 0.11.x was not structured deliberately to
              > accommodate dynamic loading. However, the revision in 0.12.0 was
              > in part motivated by a desire to allow YUI to be sandboxed
              > completely — by allowing YAHOO to be a local variable (whereas it
              > was forced to be global in 0.11.x), implementers can theoretically
              > sandbox the entire YUI library without touching the global space.
              > This has some obvious advantages, albeit not relevant for most
              > implementations.

              Interesting, so the goal is to actively make it not in the global
              space... isn't the entire library based off of YAHOO.*? Seems that
              one variable in the global name space isn't too polluting...

              I am also curious about the advantages of complete sandboxing...
              please elaborate!

              >> I did some more research, and there are some other approaches, mostly
              >> adding <script> elements to the DOM. ...
              >
              > Dynamic script nodes for YUI inclusion are certainly an option
              > here. You may want to customize the YUI files to add callbacks at
              > the end of the files to alert you in a reliable, cross-platform
              > manner to the fact that the script has fully loaded; not optimal,
              > but not too significant, and that could be automated in your build
              > process.

              I really want to avoid that. I try as to hack libs as little as
              possible for maintenance reasons.

              If everything is off of YAHOO.* and all I have to do is edit the
              yahoo.js to put YAHOO in the global space, then I don't mind that.

              > Beyond that, I would try one very simple strategy: Take the body of
              > yahoo.js and include it inline in the <head> of your document (or
              > include it as an external script file, using a far-futures expires
              > header to ensure good interpage caching). With that in place, I
              > believe your on-demand loading of the remainder of the scripts
              > (event.js, etc.) will work as desired. This is not perfectly
              > optimal, but it's a very minor overhead (~2KB) and may be the most
              > reliable and performant approach.

              I'll try this if needed, but I bet the issue is with yahoo-dom-
              event.js vs event.js. I'll try that and see.

              Thanks for the quick and thorough responses!

              Alan
            • Alan Pinstein
              This indeed was the problem... yahoo-dom-event.js is the problem. It was not eval-safe either... I didn t realize initially that it replaced yahoo.js as well,
              Message 6 of 7 , Dec 29, 2006
              • 0 Attachment
                This indeed was the problem... yahoo-dom-event.js is the problem. It
                was not eval-safe either...

                I didn't realize initially that it replaced yahoo.js as well, so
                until just moments ago I was using BOTH yahoo.js (which I corrected
                for scope) and yahoo-dom-event.js (which I had not corrected) and
                thus yahoo-dom-event was adding the event functions to the *local*
                YAHOO rather than the *global* one that was set up in yahoo.js.

                Thanks for walking me through this!

                Also, please do explain the purposeful sandboxing; I am curious about
                the advantages.

                Thanks,

                Alan

                On Dec 29, 2006, at 9:58 AM, Alan Pinstein wrote:

                >> When I eval the current yahoo.js (v0.12.1) and then make
                >> window.YAHOO=YAHOO within that function, I am subsequently able to
                >> load event.js via XHR, eval it as is, and use it from the global
                >> scope. In other words, evaling event.js populates the global
                >> window.YAHOO.util.Event object as desired; I can then, anywhere on
                >> the page, call YAHOO.util.Event.addListener(), for example. Is
                >> there a public url where we could look at your example where this
                >> fails? (I'm testing in FF2, primarily; is it only in IE that you
                >> see this fail?)
                >
                > Ah, you know what? I am trying the new yahoo-dom-event.js. That one
                > didn't work. I'll try the event.js and see if that works.
              • Eric Miraglia
                Alan, I m glad you got it working -- it makes perfect sense that including both yahoo.js and yahoo-dom-event.js would have caused the problem. You should be
                Message 7 of 7 , Dec 29, 2006
                • 0 Attachment
                  Alan,

                  I'm glad you got it working -- it makes perfect sense that including both yahoo.js and yahoo-dom-event.js would have caused the problem.  You should be able to use the solution I suggested after loading yahoo-dom-event.js, moving the local YAHOO variable to window.YAHOO after you eval yahoo-dom-event.js.

                  Re: Purposeful Sandboxing
                  Note what I said in my earlier post: When you include yahoo.js as script include, it does create YAHOO in the global namespace.  The expectation is that most users will want that and will implement in that fashion.  But by using var YAHOO = {} instead of window.yahoo = {}, the implementer is given the option of scoping the entire YUI library locally.  This would, in theory, permit someone to run multiple versions of the library on the same page, for example.  Although we're not advocating that usage, we believe we shouldn't preclude it, either.  Hence the change in 0.12.

                  Regards,
                  Eric

                  ______________________________________________
                  Eric Miraglia
                  Yahoo! User Interface Library



                  On Dec 29, 2006, at 10:12 AM, Alan Pinstein wrote:

                  This indeed was the problem... yahoo-dom-event.js is the problem. It
                  was not eval-safe either...

                  I didn't realize initially that it replaced yahoo.js as well, so
                  until just moments ago I was using BOTH yahoo.js (which I corrected
                  for scope) and yahoo-dom-event.js (which I had not corrected) and
                  thus yahoo-dom-event was adding the event functions to the *local*
                  YAHOO rather than the *global* one that was set up in yahoo.js.

                  Thanks for walking me through this!

                  Also, please do explain the purposeful sandboxing; I am curious about
                  the advantages.

                  Thanks,

                  Alan

                  On Dec 29, 2006, at 9:58 AM, Alan Pinstein wrote:

                  >> When I eval the current yahoo.js (v0.12.1) and then make
                  >> window.YAHOO=YAHOO within that function, I am subsequently able to
                  >> load event.js via XHR, eval it as is, and use it from the global
                  >> scope. In other words, evaling event.js populates the global
                  >> window.YAHOO.util.Event object as desired; I can then, anywhere on
                  >> the page, call YAHOO.util.Event.addListener(), for example. Is
                  >> there a public url where we could look at your example where this
                  >> fails? (I'm testing in FF2, primarily; is it only in IE that you
                  >> see this fail?)
                  >
                  > Ah, you know what? I am trying the new yahoo-dom-event.js. That one
                  > didn't work. I'll try the event.js and see if that works.


                Your message has been successfully submitted and would be delivered to recipients shortly.