How to keep a particular div active inside the DOM after AJAX update

Hi all

I have a page with a selection of different AJAX calls, simple getJSON which updates the main results container <div id="results"></div>.

Example

<div id="results"></div> // all json is render here
<div id="content"></div> // div used outside of the results dataset

If somebody clicks on a link within any of the data items, the results are then updated so the <div id="content"></div> is positioned below the item clicked, like so:

<div id="results">
data
data (clicked)
<div id="content"></div>
data
data
</div>

This is good and exactly how its supposed to work.

Though the problem is, if a user now requests another dataset (getJSON) everything inside <div id="results"></div> is removed and replaced - including <div id="content"></div> which is needed for the next dataset.

So now, I only have

<div id="results"></div> // all json is render here

Iā€™m wondering is there a way to save/keep <div id="content"></div> on the page and not be lost/removed when the results are updated?

Thanks,
Barry

Just realised I could maybe use .after AJAX success:


success: function (data) {
        ...
        $('#results').html(html);
        $('#results').after('<div id="content"></div>');
      },

Would this be the best way?

The <div id="content"></div> container also has a lot of markup and html elements within. So can become a little messy pasting all this into the success method.

Barry

You can simply keep a reference to the #content element like so:

var $results = $('#results')
var $content = $('#content')

// Then at some point
$results.after($content)

That reference will be kept even after the element got removed from the DOM.

1 Like

Arrh, yes this could work.
Would that keep all the elements within the content container?

Example:
Minified for viewing

<div id="content" class="content flexInner">
  <div class="data">
    <div id="item"></div>
    <div class="column" style="margin-top: 15px;">
      <div class="related">
       ...
      </div>
    </div>
  </div>
</div>

So if I use below snippet, this would store all the above into this $content var?
var $content = $('#content')

That reference will be kept even after the element got removed from the DOM

Silly question, but does that mean it is kept in memory even though it has been removed from the page?

ā€“ another possibility
I was just looking at a possible .clone, though I think what you suggest with the reference is better.

$("button").click(function(){
    $("#content").clone().appendTo("body");
});

Thanks, Barry

Yes and yesā€¦ as for garbage collection, itā€™s kept as long as the variable referencing that element can still be reached from anywhere in your code. For example, in this case

$('#my-button').click(function () {
  var $content = $('#content')

  $content.remove()
})

the element will get removed from memory when the callback finishes. But in this case

var $content = $('#content')

$('#my-button').click(function () {
  $content.remove()
})

the reference is kept as long as the event listener is activeā€¦ have a look here for details.

That would generally be possible too, but then you have the same element in the DOM twice without any needā€¦ and an immediate problem would be the duplicate ID.

Nice to know, just reading up on this thanks.

As I found out today. Lots of duplication.

If the button wasnā€™t clicked, the #content would not be removed. Resulting in duplication when other json calls were called as the #content was initially outside of #results.

I was excited hoping to get everything working today :disappointed:

For some reason, even though the new #content was replaced in the correct area the click event wasnā€™t firing, or something wasnā€™t fitting together.

Iā€™m going to build a test suite using a similar approach so you can see what the problem might be. Tricky one, just canā€™t figure out whatā€™s going wrong :upside_down:

Will post back once I have everything built.


Also looking to build some sort of AJAX modular/function as page is becoming very saturated with DRY AJAX calls, only the parameters are changing. Probably open a new thread for this.

Chat soon,
Barry

Hi @m3g4p0p

This is not an exact duplicate of my setup, though everything is working in a similar way.
After striping this down, the issue seems it could maybe be with the .more click event I have setup, the function that positions the #content under the .wrapblock divs.

In order

  1. Click a couple of more links - this will position the #content under the row of divs which stays under the row/div also on resize.
  2. Now with the #content active and showing, select another category from the dropdown - this will refresh the results
  3. Click on the more links again, and youā€™ll see the #content div always stays at the bottom

I need it to work as things do before we make the change with the dropdown.
And the data inside #content holder is just for viewing purposes, full list of people.

Codepen

Hope you can help, any questions let me know.

And are the references positioning correctly - global vars at this point.

var $results = $('#results')
var $content = $('#content')

Barry

The problem is that you also set

$allBlocks = $('.wrapblock');

but when you replace the HTML of the results, those elements stored in $allBlocks wonā€™t be attached to the DOM any more, so your .filter() function wonā€™t work. So in this case, you actually do have to query for the blocks anew (or at any rate update the collection after the result list got populated).

Thanks @m3g4p0p

I was tinkering around with this a while back. Trying to run the function within the ajax success.
This makes sense now that you mention.

How would I do that?
Do I need to move the below function

$(function(){
	$allBlocks = $('.wrapblock');
})

An example maybe :slight_smile:

Cheers, Barry

Instead of

$(function () {
  $allBlocks = $('.wrapblock')
})

// ... inside AJAX succes callback
  $allBlocks
    .filter(function (idx, ele) {
      return $(ele).position().top === position.top
    })
    .last()
    .after($('#content'))

just

$('.wrapblock')
  .filter(function (idx, ele) {
    return $(ele).position().top === position.top
  })
  .last()
  .after($content)

Itā€™s generally good practice to store $() results in variables, but this is one occasion where you need a ā€œfreshā€ query.

PS: But you can (and should) reuse the $content variable here ā€“ that element wonā€™t change.

Yes Iā€™ll make note of this seems a lot cleaner.
Thanks in advance.

And sorry for my ignorance @m3g4p0p, but I just donā€™t understand how I can fit this together.

All the work is done within the click .more click event.
Do I need to add the logic from this into both AJAX success calls and still have this within the click event?

I ran a few tests but keep getting the error

Uncaught ReferenceError: position is not defined

Would be great if you could update/amend the Codepen :nerd:

Barry

Iā€™ve slightly cleaned up the Codepen if this helps, thanks.

Barry

Here you goā€¦

1 Like

Thanks a lot :grinning:
And sorry for the pestering @m3g4p0p been heavy involved with this for days :upside_down:

This is working very good, much appreciated!

I see you completely removed

var $allBlocks = [];
$(function(){
	$allBlocks = $('.wrapblock');
})

And replaced

$allBlocks.filter(function(idx, ele){
    return $(ele).position().top == position.top;
  })

with

$('.wrapblock').filter(function(idx, ele){
    return $(ele).position().top == position.top;
  })

Much cleaner and less code :sunglasses:
Iā€™ll just need to digest how this is working now.

Further testing with some live data, hopefully no glitches :slight_smile:

Cheers, Barry

1 Like

No worries! :-)

Just one small issue that keeps popping up.

Uncaught DOMException: Failed to execute ā€˜insertBeforeā€™ on ā€˜Nodeā€™: The new child element contains the parent.

This only happens sometimes, and mainly on the top three items.

The error points to .after($content) inside the more click handler:

$(document).on('click', '.more', function() {
  ...
  .after($content); //this line is hightlight as the error
});

Maybe this has something to do with my current setup, though if you have any brain storms or knowledge why this would happen. Great to hear back.

Thanks,
Barry

Can you provide the exact steps necessary to reproduce the problem?

A little tricky to build the exact steps, lots of files running within a CMS not sure what is casuing this. Iā€™ll see if I can fix it or build a test case to show.

It seems only the top 3 items have this issue and a second click makes the page jump to the top bypassing preventdefault. I currently have 3 items per row which has me thinking maybe something to do with the positioning.

But, just guess work really.
Could be other files conflicting. Though as the error says, pointing to .after($content);

Cool, was just wondering maybe you had ran into this issue before.

No worries, thnaks anyhow - so close :slight_smile:

Barry

Oh I thought it would occur in the pen tooā€¦ well generally speaking this error means that somehow the .wrapblock elements slipped into the $content. Under the hood, the jQuery .after() method uses .insertBefore(); a vanilla JS implementation might look roughly like this:

function after (node) {
  this.parentNode.insertBefore(node, this.nextSibling)
}

And if node in the above snippet contains this.parentNode, youā€™ll get that error as you canā€™t insert a node into itself.

But yeah I know reproducing a bug can be harder than actually fixing itā€¦ ^^

1 Like

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