5

I am working within the constraints of a content management system that sometimes forces me to do some odd things because I don't have full control of the HTML code.

In this instance, I have a situation where I want one DIV to not be nested inside another DIV. Because of the CMS settings, I can't just re-order the DIVs in the HTML code.

So, I have this:

<div class="blog">
   <div class="free-me">
      Hello world
   </div>
</div>

What I need is:

<div class="blog">
</div>
<div class="free-me">
   Hello world
</div>
<div class="blog">
</div>

This would be as simple as copying and pasting some lines if I could access the HTML. But, since I can't just change the HTML, what I want to know is if there's any way I can use CSS or JavaScript to close off the blog DIV so that free-me can be a sibling.

I tried this (even though I kind of knew it wouldn't work):

.free-me::before {
    content: '</div>';
}

As suspected, that just rendered text that said </div> into the page.

For JavaScript, I'm certainly no expert, but it seems like you can only insert complete elements, with opening and closing tags, with a command like document.createElement('div'); I can't see a clear way to take an existing DIV and close it off.

Is there a way, either with CSS or JavaScript, I can force a DIV to close or insert an ending </div> tag?

Note: Preferably a non-jQuery solution.

Questioner
  • 7,133
  • 16
  • 61
  • 94
  • 2
    By the time any JS runs, the browser is already going to have taken its best guess at making your markup valid. I'm curious why you have a need to generate completely invalid html markup like that (5 opening `
    ` tags, and only 1 is closed). Maybe this is more of an A-B problem.
    – CollinD Apr 11 '17 at 09:30
  • 1
    Please recheck your question , I'm guessing that's not the output you want. – Dan Philip Bejoy Apr 11 '17 at 09:33
  • Will _all_ the parent `div`s have the same classname (`.blog`, in your example)? Will _all_ the child `div`s have the classname (`.free-me`, in your example)? Can the parents contain multiple children with that classname? Finally, is ES6 an option for you? – Shaggy Apr 11 '17 at 09:36
  • What about start you code in editor with `` in order to write outside of content and create your own before to finish with '
    ` ? Is `free-me` an element you have created or is it generated by CMS and you can only write inside ?
    – Alex - DJDB Apr 11 '17 at 09:37
  • What is the purpose of doing this? Is it just to get the `free-me` to be siblings? In that case there are other ways of moving nodes with JS. – Winter Apr 11 '17 at 09:40
  • Ps. there is always a way to write JS without jQuery ;) – Winter Apr 11 '17 at 09:41
  • Check out something like http://stackoverflow.com/questions/20910147/how-to-move-all-html-element-children-to-another-parent-using-javascript – Winter Apr 11 '17 at 09:43
  • 1
    @Winter, thanks for your question. Note in the question that I don't just make `free-me` a sibling of `blog`, `free-me` splices `blog` so that, in essence, blog starts up again after `free-me` is finished. Though, if I have the ability to make `free-me` a sibling, I can probably go from there. – Questioner Apr 11 '17 at 12:06
  • @Questioner are you saying that you want to split the content before and after free-me into two divs, with free-me in between? – Salman A Apr 11 '17 at 12:15
  • @SalmanA, that was my original intent, but it seems that in my situation I can achieve my end goals by just adjusting the parent/child relationship, as was done in the accepted answer. – Questioner Apr 11 '17 at 12:29

3 Answers3

5

If you just want to pop out one div out of the other after rendering I suggest you use jQuery insertAfter method. I also use a CMS and know exactly how you feel.

$(".free-me").insertAfter(".blog");
.blog {
  border: 1px solid red;
  width: 100px;
  height: 20px;
  text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="blog">
  <div class="free-me">
    Hello world
  </div>
</div>
Dan Philip Bejoy
  • 4,321
  • 1
  • 18
  • 33
  • If that is the case go for an `id` instead of classes. It is a meagre matter adding and index `[0]` to the selector. Even with multiple elements with the same classes we can narrow it down the required elements using very specific selectors by adding parents to it. – Dan Philip Bejoy Apr 11 '17 at 12:15
  • 01) You'll have to develop a thick skin around here, downvotes are part & parcel of the experience and the vast majority of them will not be explained. 02) Consider that the downvoter might have been called away while in the process of commenting. 03) Try to determine for yourself why an answer might be downvoted, it will help improve your future answers - in this instance, for example, the question specifically asks for a non-jQuery solution and, as @SalmanA pointed out, this will fail with multiple `.blog` elements. (I was the downvoter and it was for both those reasons that I downvoted.) – Shaggy Apr 11 '17 at 12:18
  • @Shaggy I appreciate your concerns and thanks for explaining why. The one who answers might not be able to see their mistakes until someone points it out to them. – Dan Philip Bejoy Apr 11 '17 at 12:21
  • @DanPhilip ignore my previous comment. I have asked OP for clarification. – Salman A Apr 11 '17 at 12:22
3

You can achieve this with JavaScript by looping through all the elements with the classname .free-me and inserting into them DOM before their parent elements' next siblings, which will also remove them from their parent elements at the same time.

(function(){
    var children=document.querySelectorAll(".blog>.free-me"),
        x=children.length,
        child,parent;
    while(x--){
        child=children[x];
        parent=child.parentNode;
        // Uncomment below if you would prefer to
        // explicitly remove the child from the parent.
        //parent.removeChild(child);
        parent.parentNode.insertBefore(child,parent.nextSibling);
    }
})();
div{
    margin:5px;
    padding:10px;
}
.blog{
    background:#000;
}
.free-me{
    background:#090;
}
.ignore-me{
    background:#f00;
}
<div class="free-me"></div>
<div class="blog"></div>
<div class="blog">
    <div class="free-me"></div>
</div>
<div class="blog">
    <div class="free-me"></div>
    <div class="free-me"></div>
</div>
<div class="blog">
    <div class="free-me"></div>
    <div class="ignore-me"></div>
    <div class="free-me"></div>
    <div class="free-me"></div>
</div>

However, if your parent elements contain multiple children, some of which will have the .free-me class and some of which won't, and you wish to maintain the order of all child elements then the solution is a little more complex (Note: this Snippet was a little rushed so could probably be cleaned up a good bit.)

(function(){
    var blogs=document.querySelectorAll(".blog"),
        x=blogs.length,
        grandparent,parent,clone,children,child,y;
    while(x--){
        parent=blogs[x];
        // You can remove the following if statement
        // if you wish to have any pre-existing empty .blog
        // elements removed from the DOM.
        if(parent.querySelector(".free-me")){
            grandparent=parent.parentNode;
            children=parent.children;
            y=children.length;
            while(y--){
                child=children[y];
                if(child.classList.contains("free-me")){
                    if(clone){
                        grandparent.insertBefore(clone,parent.nextSibling)
                        clone=0;
                    }
                    grandparent.insertBefore(child,parent.nextSibling);
                }else{
                    if(!clone)
                        clone=parent.cloneNode(0);
                    clone.insertBefore(child,clone.firstChild);
                }
            }
            grandparent.removeChild(parent);
        }
    }
})();
div{
    margin:5px;
    padding:10px;
}
.blog{
    background:#000;
}
.free-me{
    background:#090;
}
.ignore-me{
    background:#f00;
}
<div class="free-me">1</div>
<div class="blog"></div>
<div class="blog">
    <div class="free-me">2</div>
</div>
<div class="blog">
    <div class="free-me">3</div>
    <div class="free-me">4</div>
</div>
<div class="blog">
    <div class="free-me">5</div>
    <div class="ignore-me">6</div>
    <div class="ignore-me">7</div>
    <div class="free-me">8</div>
</div>
<div class="blog">
    <div class="ignore-me">9</div>
    <div class="free-me">10</div>
    <div class="ignore-me">11</div>
    <div class="free-me">12</div>
</div>
<div class="blog">
    <div class="ignore-me">13</div>
</div>
Shaggy
  • 6,696
  • 2
  • 25
  • 45
  • Are there issues if `.blog` has no next siblings or will it just be inserted after anyway? – Razzildinho Apr 11 '17 at 09:56
  • No, no issues; if you run the Snippets, you'll see that the children of the last parent are inserted at the end of the document. – Shaggy Apr 11 '17 at 09:57
  • Thank you for this answer. I felt compelled to explain that I gave the check to the other answer even though that answer used jQuery and yours didn't because, while you conformed more to my request, in the end, the other answer was the one I could get to work. Note that I'm sure your code is correct, it's probably something particular about my environment that is causing trouble. – Questioner Apr 11 '17 at 12:27
  • Thanks for commenting, @Questioner and no problem. If you'd like to update your question to provide more details on your implementation or comment with the problem(s) you encountered with my solution, I'd be happy to try to update it. A couple of things that may be causing an issue for you could be the IIFE, a clash with existing variable names in your JS or your `.blog` elements containing non-element nodes (e.g., text nodes). – Shaggy Apr 11 '17 at 13:05
2

You cannot add closing tags with JavaScript. However, you can move elements inside DOM using JavaScript.

Assuming that you want to split the content inside blog around the "free-me" div, here is vanilla JavaScript solution (might not work in lame-a** browsers):

document.addEventListener("DOMContentLoaded", function() {
  var oldBlog = document.querySelector(".blog");
  var newBlog = document.createElement("div");
  var refNode = oldBlog.querySelector(".free-me");

  // move items after ref node from old blog to new blog
  while (refNode.nextSibling) {
    newBlog.appendChild(refNode.nextSibling);
  }

  // move ref node after old blog
  oldBlog.parentNode.insertBefore(refNode, oldBlog.nextSibling);

  // move new blog after ref node
  refNode.parentNode.insertBefore(newBlog, refNode.nextSibling);

  // set class name
  newBlog.className = "blog";
});
<div class="blog">
  Above
  <div>Above</div>
  <!-- Above -->
  <div class="free-me">Hello world</div>
  Below
  <div>Below</div>
  <!-- Below -->
</div>

This will produce the following DOM:

<div class="blog">
  Above
  <div>Above</div>
  <!-- Above -->
</div>
<div class="free-me">Hello world</div>
<div class="blog">
  Below
  <div>Below</div>
  <!-- Below -->
</div>

If you are trying to insert ads inside the content then be advised: moving an iframe inside the DOM using this trick will force it to reload; this might inflate ad impressions.

Shaggy
  • 6,696
  • 2
  • 25
  • 45
Salman A
  • 262,204
  • 82
  • 430
  • 521
  • Upvoted for the nice, simple handling of non-element nodes like text & comments, which I didn't cover in my solution. However, like Dan's solution, this won't work in the case of multiple `.blog` and/or `.free-me` elements. I'd also suggest cloning the old `.blog` element, rather than creating a new one so that all other attributes & class names are retained. – Shaggy Apr 11 '17 at 13:29
  • 1
    By the way, I edited out the jQuery `script` tag for you. – Shaggy Apr 11 '17 at 13:30