2

I'm desperate having spent well over an hour trying to troubleshoot this. I am trying to access a node in the DOM which is created from an ASP.NET control. I'm using exactly the same id and I can see that they match up when looking at the HTML source code after the page has rendered. Here's my [MODIFIED according to suggestions, but still not working] code:

ASP.NET Header

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
    <script type="text/javascript">
    $(document).ready(
        var el = document.getElementById('<%= txtBox.ClientID %>');
        el.onchange = alert('test!!');
    )
    </script>
</asp:Content>

ASP.NET Body

<asp:TextBox ID="txtBox" runat="server"></asp:TextBox>

Resulting Javascript & HTML from above

<script type="text/javascript">
$(document).ready(
    var el = document.getElementById('MainContent_txtBox');
    el.onchange = alert('test!!');
)
</script>
...
<textarea name="ctl00$MainContent$txtBox" id="MainContent_txtBox"></textarea>

I can only assume that the script is loading before the control id has been resolved, yet when I look at the timeline with Chrome's "Inspect Element" feature, it appears that is not the case. When I created a regular textarea box to test and implement the identical code (different id of course), the alert box fires.

What on earth am I missing here? This is driving me crazy >.<

EDIT: Wierd code that works, but only on the initial page load; firing onload rather than onchange. Even jQuery says that .ready doesn't work properly apparently. Ugh!!

$(document).ready(function() {
    document.getElementById('<%= txtBox.ClientID %>').onchange = alert('WORKING!');
})
Chiramisu
  • 4,687
  • 7
  • 47
  • 77
  • 2
    Aside from the document-ready issues, the syntax of your `onclick` is incorrect. You need to assign `onclick` to point to a function, not call it as if it was a function. So try changing it to something like `el.onclick = function() { alert("test"); };`, or you can do `el.onclick = myFunction;` (note no brackets after the name of your function) where elsewhere on the page you've defined `function myFunction() { ... }`. – nnnnnn Jul 22 '11 at 01:02
  • Ack!! I'm tired for today. I'll pick this up again tomorrow and determine an answer then. Thanks guys ;) – Chiramisu Jul 22 '11 at 01:37

3 Answers3

5

Assuming the rendered markup does appear in that order, the problem is that the element doesn't yet exist at the time your JavaScript is attempting to locate it.

Either move that JS below the element (preferably right at the end of the body) or wrap it in something like jQuery's document ready event handler.

Update:

In response to your edits, you're almost there but (as others have mentioned) you need to assign a function to the onchange event, not the return result of alert(). Something like this:

$(document).ready(function() {
  // Might as well use jQuery to attach the event since you're already using
  //  it for the document ready event.
  $('#<%= txtBox.ClientID %>').change(function() {
    alert('Working!');
  });
});

By writing onchange = alert('Working');, you were asking JavaScript to assign the result of the alert() method to the onchange property. That's why it was executing it immediately on page load, but never actually in response to the onchange event (because you hadn't assigned that a function to run onchange).

Dave Ward
  • 59,815
  • 13
  • 117
  • 134
  • I will give that a try and get back to you. Thanks so much for the quick response :) – Chiramisu Jul 22 '11 at 00:13
  • Ok, I got it working by moving the script to the bottom (that'll work for now), however, apparently the HTMLTextArea DOM object doesn't have an onclick event according to Chrome Inspect Element. The event I actually want this code to fire on, is when the contents of the textbox are changed. – Chiramisu Jul 22 '11 at 00:40
  • 1
    @Chiramisu - textareas definitely have a click event, but your syntax is incorrect. See my comment attached to your original question. If you want to know about when the contents change you probably want to use `onchange` rather than `onclick` (noting that onchange isn't fired as you type, only when the element loses focus; you can trap `onkeyup` or `onkeypress` to look for changes as they happen). – nnnnnn Jul 22 '11 at 01:05
  • @Dave Ward: I finally got it thanks to you. I appreciate you hanging in there with me while I worked through it. I'm starting to get a bit more understanding as to how jQuery works. Interesting syntax. Pretty easy to get lost in all the () & {} haha. Cheers ;) – Chiramisu Jul 22 '11 at 18:16
2

Pick up jQuery.

Then you can

$(function()
{
    var el = document.getElementById('<%= txtBox.ClientID %>');
    el.onclick() { alert('test!!'); }
});
BradLaney
  • 2,384
  • 1
  • 19
  • 28
  • 1
    1) suggest's OP uses a sizeable javascript library with very powerful selector and event-handling support, 2) uses a single feature to wrap vanilla JavaScript code that would benefit from JQuery :) – tomfumb Jul 22 '11 at 00:22
  • Does it have to be in a function? This is code I want to execute no matter what. – Chiramisu Jul 22 '11 at 00:25
  • @tomfumb - Who woulda thunk it? – ChaosPandion Jul 22 '11 at 00:30
  • 1
    @Chiramisu the function wrap just means that the code won't be executed until the document is ready - so all of your elements are guaranteed to exist in the DOM (though they will not necessarily be populated if they contain images or other external content). This is different to putting code in the document body's onload event handler as this won't be executed until all external content has been retrieved. – tomfumb Jul 22 '11 at 00:44
  • Got it... Still can't figure out which event to use though. I'm pretty sure I'm doing it right, now, I think. SO frustrating :( – Chiramisu Jul 22 '11 at 00:58
  • If you don't want to use jQuery you can achieve approximately the same thing with little effort by using a plain `` syntax. I'd recommend jQuery though, not just for easy document-ready functionality but for good cross-browser event support - especially since you already know you're wanting to handle events. – nnnnnn Jul 22 '11 at 00:59
  • @tomfumb I know. I just didn't want to suggest him having to learn so much so fast just to do an onload. I often don't get the answer because I go "overboard" with fixing peoples code so I try to keep it to a minimum. Guess I get bashed either way, eh? – BradLaney Jul 22 '11 at 19:21
1

Other answers have pointed out the error (attempting to access DOM nodes before they are in the document), I'll just point out alternative solutions.

Simple method

Add the script element in the HTML below the closing tag of the element you wish to access. In its easiest form, put it just before the closing body tag. This strategy can also make the page appear faster as the browser doesn't pause loading HTML for script. Overall load time is the same however, scripts still have to be loaded an executed, it's just that this order makes it seem faseter to the user.

Use window.onload or <body onload="..." ...>

This method is supported by every browser, but it fires after all content is loaded so the page may appear inactive for a short time (or perhaps a long time if loading is dealyed). It is very robust though.

Use a DOM ready function

Others have suggested jQuery, but you may not want 4,000 lines and 90kb of code just for a DOM ready function. jQuery's is quite convoluted so hard to remove from the library. David Mark's MyLibrary however is very modular and quite easy to extract just the bits you want. The code quality is also excellent, at least the equal of any other library.

Here is an example of a DOM ready function extracted from MyLibrary:

var API = API || {};

(function(global) {

  var doc = (typeof global.document == 'object')? global.document : null;

  var attachDocumentReadyListener, bReady, documentReady,
      documentReadyListener, readyListeners = [];
  var canAddDocumentReadyListener, canAddWindowLoadListener,
      canAttachWindowLoadListener;

  if (doc) {
    canAddDocumentReadyListener = !!doc.addEventListener;
    canAddWindowLoadListener    = !!global.addEventListener;
    canAttachWindowLoadListener = !!global.attachEvent;

      bReady = false;
      documentReady = function() { return bReady; };
      documentReadyListener = function(e) {
        if (!bReady) {
          bReady = true;
          var i = readyListeners.length;
          var m = i - 1;
          // NOTE: e may be undefined (not always called by event handler)
          while (i--) { readyListeners[m - i](e); }
        }
      };

      attachDocumentReadyListener = function(fn, docNode) {
        docNode = docNode || global.document;
        if (docNode == global.document) {
          if (!readyListeners.length) {
            if (canAddDocumentReadyListener) {
              docNode.addEventListener('DOMContentLoaded',
                                        documentReadyListener, false);
            }
            if (canAddWindowLoadListener) {
              global.addEventListener('load', documentReadyListener, false);
            }
            else if (canAttachWindowLoadListener) {
              global.attachEvent('onload', documentReadyListener);
            } else {
              var oldOnLoad = global.onload;              
              global.onload = function(e) { 
                  if (oldOnLoad) { 
                    oldOnLoad(e); 
                  }
                  documentReadyListener();
              };
            }
          }
          readyListeners[readyListeners.length] = fn;
          return true;
        }
        // NOTE: no special handling for other documents
        // It might be useful to add additional queues for frames/objects
        else {
          if (canAddDocumentReadyListener) {
            docNode.addEventListener('DOMContentLoaded', fn, false);
            return true;
          }
          return false;
        }
      };

      API.documentReady = documentReady;
      API.documentReadyListener = documentReadyListener;
      API.attachDocumentReadyListener = attachDocumentReadyListener;

  }
}(this));

Using it for your case:

function someFn() {
  var el = document.getElementById('MainContent_txtBox');
  el.onclick = function() { alert('test!!');
}

API.attachDocumentReadyListener(someFn);

or an anonymous function can be supplied:

API.attachDocumentReadyListener(function(){
  var el = document.getElementById('MainContent_txtBox');
  el.onclick = function() { alert('test!!');
};

Very simple DOM ready functions can be done in 10 lines of code if you just want one for a specific case, but of course they are less robust and not as reusable.

RobG
  • 142,382
  • 31
  • 172
  • 209