Unable to append ajax data on preexisting tag

The form is posted to this same page and the page is returned with the same markup but processed data from the submitted form.
The code is such as

$.post(url, $('form').serialize(), function (data) {
	var h = $('<div/>').html(data), x = $('body').append(h).find($('div section'))
	console.log(x, x.text())
	
	$('main > section').empty().append(x);

	h.remove();
})

$('[name="bio_submit"]').click(); // auto submit form on load
// manually submitting the form produces the same result also

When the page loads, the section is emptied but console.log clearly shows that x carries content and simply refuses to give it up. I’ve tried find()ing other elements instead of section, like I’ve tried section descendants and also other tags existing on the page, even the footer; they all get appended but for some strange reason, section refuses to comply.
I’ve also tried unwrapping x and passing its outer HTML unto section using append function signature. It doesn’t work either. How do I go about it?

If the new content has the same structure as the original content, that line

$('main > section').empty().append(x);

would empty both the old and the new section since you already appended the new content to the body, and then just append another empty section. The solution would be not to append h in the first place – that step seems to be unnecessary anyway and would just leave an empty div artefact at the end of the body. You can thus shorten the callback function code to

var newContent = $('<div/>').html(data).find('section')
$('main > section').replaceWith(newContent)

No sir. The original section lives in the main tag while the fresh section is thrown into the new DIV and sent to the bottom of the body.[quote=“m3g4p0p, post:2, topic:262931”]
that step seems to be unnecessary anyway
[/quote]

The reason I’m appending it to the body first is that I observed jQuery can’t read the content or nodes on an object if they have no context. And the context property is only assigned when the object is fixed into somewhere i.e the body. The body is the easiest place to fix it since I end up cleaning it off the next instant.

Oddly, this works

$.post(l, $('form').serialize(), function (data) {
 var z = $('main > section').empty(),
 h = $('<div/>').html(data),
 x = $('body').append(h).find($('div section'))
				
 z.append(x);
 h.remove();
})

Meanwhile, you might want to remove this line $('[name="bio_submit"]').click(); since it fires automatically every time the DOM loads that section.

Okay, well hard to tell w/o seeing the actual markup. That your re-ordered code works suggests that the new content also includes a main tag though, which has a section child that is getting emptied.

Your observations are not correct – the two lines I posted would do the same.

Does that new content also include the script itself? Then you definitively shouldn’t append it to the document! It will get executed anew over and over again, and certainly cause other problems like piling up event listeners or overwriting global variables. You can keep that line then.

Lol. you don’t understand. First of all,

Is it? I said here

Which means there’s probably more than one section and I’m trying to contact the one currently in the main tag. The new section is in a DIV, which is why I didn’t select it with $('main &gt; section') but with

There was the difference there.

What do you think? :wink:

The point is, I have a an object with element names as keys and values being event handlers, some of them including ajax requests. Some of these requests load forms into this page that deserve to be hooked onto this handler. So that post call above may work once but in my case it’s anticipating new elements and the callback should be attached as they are dynamically created

I tried this first and when it didn’t work, I appended it to something and it worked. I console.logged the resulting object in both cases and what I observed was that the one that worked (the appended version) had a context property.

That the new section is inside a div doesn’t mean it isn’t also a direct child of a main element. Please compare child selectors vs. descendant selectors:

<div>
  <main>
    <section>...</section>
  </main>
</div>

Both $('div section') and $('main > section') would match the same section here. And after you emptied all $('main > section')s, the $('div section') in question is most probably empty as well; this is confirmed by the fact that it works if you empty the main > section before appending the AJAX response the DOM. Speaking of which…

Okay, so you’re really appending the entire page, including head, CSS and jQuery to the DOM. Again, I would not do that. Not only will this lead to hardly predictable behaviour, but it can also have quite an impact on the performance of your page as scripts and styles will get pulled and evaluated anew – which is also a waste of bandwidth.

There are other ways to deal with dynamically created elements. You could use event delegation, or wrap all relevant event bindings in a bindEvents() function (or something), which you could call again after the content was replaced.

I don’t know why it didn’t work for you, but if you post the code we can certainly fix it…

This is actually what the problem was from the onset. It’s the answer to the question “Unable to append ajax data on preexisting tag”. I was unwittingly emptying both tags at the same time since they both had a common main parent. And only had an epiphany later on.

Ok. So how do I filter only the section which is what I need? Keep in my mind that I’m unable to manipulate any jquery object if they have no context. I don’t remember the specific error it threw then but it was something in the region of “undefined context” and then a stack trace. In all the cases I’ve experienced that, appending it to the body (giving it a context i.e document) was a nifty workaround.

This morning, I tried reproducing the error for the purpose of this reply.

$.post(url, $('form').serialize(), function (data) {
 var z = $('main > section').empty(), h = $('<div/>').html(data).find($('div section'));
 console.log(h)
 z.append(h);

It didn’t throw any errors this time, but in the console, it logs [prevObject: init(1), context: undefined] meaning the newly created DIV has no context and as such, cannot find or perform as a DOM element.
So I agree your argument about bandwidth and overheard are valid but I cannot presently come up with a better alternative.

Doesn’t this mean reattaching the handler afresh? I mean, I think without explicitly calling removeEventListener or off, there is no clean way to escape reattaching the handler the few times the user will make those ajax calls during his stay on the page. I’ll post my handler for the callback here so you can demonstrate how you think I could create a bindEvents function.

    function boot(e) {
    
            e.preventDefault();
            var l = $(this).attr('href') || $(this).attr('action'), f = l.match(/([a-z]+)/)[1], p = $('<link/>').attr({href: `${f}.css`, rel: 'stylesheet', type: 'text/css'}), w = e.target, t = 0, b = {
    
                a: function() {
                    $('section').load(`${l} section`, function() {
                        if(l.indexOf('tran') > -1 && !t) {
                            $.getScript('r.js');
                            t = 1
                        }
                        else if (l.indexOf('rvt') > -1) $('a:contains("dashboard")').trigger('click');
    
                        // ideally, this should be wrapped in a condition confirming these elements are in the dom
                        $('.cus a').click(boot);
                        $('form').submit(boot);
                    });
                },
                form: function () {
                    $.post(l, $('form').serialize(), function (data) {
                        var z = $('main > section, main > section form').empty(), h = $('<div/>').html(data), x = $('body').append(h).find($('#transfer-complete, div section'));
                        
                        z.append(x);
                        $('#transfer-complete a').click(boot);
    
                        $('#submit-modal').show(155);
                        setTimeout(function(){$('#submit-modal').hide('slow');}, 3000);
    
                        h.remove();
                    })
                }
            };
            
            $('title').before(p);
        
            for (var d in b) {
                if (w.matches(d)) b[d]();
            }
        } 

The markup you posted earlier is correct. Any further explanations you need, you can simply ask for.
Note: the current version works. The bone of contention is an event delegation model that precludes appending the whole document each time and still attaching the handler to the desired elements.

Ah yes, here’s the problem: you’re trying to .find() the jQuery object $('div section'), but this will indeed only contain matches that are appended to the DOM already. You can just pass a regular selector here though, like

$('<div/>').html(data).find('section')

This will find any section inside the newly created <div/>, whether it is appended to the DOM or not. Similarly, you can find main > section etc.

After you .empty()ed the main > section, the corresponding event listeners will get removed by jQuery automatically, so there’s no need to do this explicitly (unless you’re still holding references to those “old” elements). Although it may have a similar structure, the new content will be entirely new elements, so you either need to attach those listeners anew or use event delegation (e.g. from the main > section)… but either would be better than executing the whole script again.

Well if you’re already using event delegation anyway, there’s no need for a bindEvents() function – the listener will stay active as long as its root element is attached to the DOM.

Edit: Or do you mean the listeners you’re attaching inside those b property functions? Attaching event listeners inside other listeners is usually quite problematic anyway, so I’d suggest to just refactor them all to event delegation instead… then you’re definitively on the safe side and won’t attach the same listener twice.

:joy: :joy::joy::joy::joy::joy::joy::joy: Oh @m3g4p0p you’re never gonna let me win are you? That selector was an oversight. I swear I used the selector correctly (without DIV) back then and it triggered the context error. Jesus! Why would I lie about it anyway?

Well, as you can see, those elements I’m attaching the listener to from within the b object are non-existent at runtime. They can as well be wiped off and re attached as well. The only ones that receive the callback at document ready is

$('aside div a').click(boot);
$('#staff-story-wrap form').submit(boot);

That’s why I said[quote=“m3g4p0p, post:9, topic:262931”]
// ideally, this should be wrapped in a condition confirming these elements are in the dom
[/quote]

Amy more issues Mr.? :grin:

Well I’m not a telepath, I can only help you on the basis of the code you’re providing. ;-) Now the problem is not (only) the div selector, but that you’re wrapping it in a jQuery object. If you dissect that construct, it would look something like this:

// Create a new div
var $div = $('<div/>') 

// Insert new content into the newly created div
$div.html(data) 

// Find a section in the document (!)
var $section = $('section') 

// Try to find the above $section in the new content -- obviously 
// without result since the new div has not not been appended to 
// the document when we performed that query
var $newSection = $div.find($section)

as opposed to

// Create a new div
var $div = $('<div/>') 

// Insert new content into the newly created div
$div.html(data) 

// Find a section within the new content (!), 
// which will give the desired result
var $section = $div.find('section')

There’s no need to check this in the first place if you use event delegation. jQuery has a convenient shorthand for this (IIRC we actually discussed that in a recent thread of yours):

$('main > section').on('click', 'aside div a', boot)
1 Like

Guess I’m not a fast learner. So, one last time what’s the difference between this

and what I have currently? They both don’t seem to relate. Nobody is clicking on section at an point in time. Aside and section both exist as siblings in the main tag. The form is in section. The a elements are also loaded into the section. The original event is triggered by clicking a elements inside the aside, this effects changes inside the section i.e loads other a elements that themselves need the handler attached to them.

The places a watcher on the section element for any click events that occur inside of it.
If the click event came from an element that matches ‘aside div a’, then the boot function will be executed for that element.

The main benefit of having that higher-level watcher, is that it will always be able to trigger on any suitable click events.

Compare it with:

$('aside div a').on('click', boot);

With that example, jQuery attaches the event to any matching elements, but only elements that exist at the current time that the jQuery script was executed. Any matching elements that are later on added to the page will not trigger the boot event.

The way to get around that problem, is to have jQuery watch for any added elements, so that the event can added to those elements when they later on get added. The usual way to do that is to place the watcher on the document itself.

$(document).on('click', 'aside div a', boot);

A problem with using document though is that it can be too broad, because it checks to see if the element matches what it’s looking for on every since click that occurs on the page.

The code that you asked about, moves the watcher down off the document and in to the main section instead, so that only elements that are later on added inside the main section will have the event attached to them.

$('main > section').on('click', 'aside div a', boot);
2 Likes

I’m afraid, I seem to still have a problem with this. You make it sound like they both co-exist as parent-child. They are both totally unrelated and as long as aside div a gets clicked, the boot is executed whether or not section is in the document tree.
So here’s a note I took to buttress my understanding of your story. I hope there isn’t any misconstruction on my part.


Event delegation

$('main > section').on('click', 'aside div a', boot) vs $('aside div a').on('click', boot);

The former is event delegation while the latter is the regular event handling we’re used to. Event delegation is used when we need to assess live objects of a certain selector rather than those cached at load time. In both cases, the handler is actually attached to $('aside div a'), but the difference is that in the ist scenario, when the event is triggered, the handler only runs when there are changes in the object list of the “certain element”.

A perfect example of a use case is assuming you have a container housing dynamic elements. Then these elements happen to have some behavior attached to them. In order to delegate this same behavior to successors of these dynamic elements, you’d have to place them in the incubator of event delegation. Instead of sitting there at load time, their signature/totem/DNA/selector is handed over to the guy triggering the event. What he does with it is, his handler doesn’t rely on the cached object of selectors as source of truth but he instead queries the DOM for a more up-to-date list to work with.

With that first example, the ‘aside div a’ is a filter of the main section. Only elements inside of that main section, that also match the ‘aside div a’ filter, will have the click event triggered on them.

If your elements are not being added to the main section, you will want to change that to be a common parent. If all else fails you can use the document element as a common parent.

This precisely is what I’ve been fighting to repeal. This main > section aside div a will select absolutely nothing. I keep saying they don’t exist in each other’s DOM trees. The markup of the page goes like this

<!DOCTYPE html>
 <html>
  <head>
    <title> Unrelated Tags </title>
  </head>

  <body>
   <main>
    <aside style=position:fixed>
     <div>
      <a href=javascript:useAjax() id=aside-div-a-1> I trigger changes in "section" </a>
     </div>

     <div>
      <a href=javascript:useAjax() id=aside-div-a-2> Me too </a>
     </div>
    </aside>

    <section>
     <form action=cpu.php method=post> </form>
     <div> My first sibling contains data to be processed
       and saved.
    </div>
     <p> Our parent is the screen that displays commands
       sent from the keyboard-like form tag. There are also other
       sections like our parent but they only get called up when
       "aside-div-a-n" is hit. Internally they are linked to each
       other like my parent is linked to the first child of that selector.
     </p>
     <span> Other sections don't contain any of this stuff.
        But for sure they all have their own forms too or at least
        an "a" tag that needs the function "useAjax()". They are
        made available when the appropriate "aside-div-a-n" is hit.
     </span>
    </section>

   <footer> Adios dog. </footer>

  </body>
</html>

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.