0

I'm working with templates, so I can only edit so much of the environment. I have a tabbed navigation set up. Within one of the tabs, I have a div that I want to have a fixed position that is outside/on top of everything else on the page.

This isn't the actual code I'm working with, but it's similar enough that I can use it as an example. The "outsidediv" div is placed in the "London" div. But I want to make it so you see outsidediv even if you click into the Paris or Tokyo tabs.

I've tried using z-index and every kind of position property I can think of to get this to work. I cannot move the outsidediv from where it is.

.tab {
  overflow: hidden;
  border: 1px solid #ccc;
  background-color: #f1f1f1;
}

.tab button {
  background-color: inherit;
  float: left;
  border: none;
  outline: none;
  cursor: pointer;
  padding: 14px 16px;
  transition: 0.3s;
  font-size: 17px;
}

.tab button:hover {
  background-color: #ddd;
}


.tab button.active {
  background-color: #ccc;
}


.tabcontent {
  display: none;
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-top: none;
}

#outsidediv {
  position: fixed;
  top: 80px;
  right: 50px;
  background: pink;
  }
<div class="tab">
  <button class="tablinks" onclick="openCity(event, 'London')">London</button>
  <button class="tablinks" onclick="openCity(event, 'Paris')">Paris</button>
  <button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>

<div id="London" class="tabcontent">
  <h3>London</h3>
  <p>London is the capital city of England.</p>
  <div id="outsidediv">This div should appear no matter which tab is selected.</div>
</div>

<div id="Paris" class="tabcontent">
  <h3>Paris</h3>
  <p>Paris is the capital of France.</p> 
</div>

<div id="Tokyo" class="tabcontent">
  <h3>Tokyo</h3>
  <p>Tokyo is the capital of Japan.</p>
</div>

<script>
function openCity(evt, cityName) {
  var i, tabcontent, tablinks;
  tabcontent = document.getElementsByClassName("tabcontent");
  for (i = 0; i < tabcontent.length; i++) {
    tabcontent[i].style.display = "none";
  }
  tablinks = document.getElementsByClassName("tablinks");
  for (i = 0; i < tablinks.length; i++) {
    tablinks[i].className = tablinks[i].className.replace(" active", "");
  }
  document.getElementById(cityName).style.display = "block";
  evt.currentTarget.className += " active";
}
</script>
Robby
  • 843
  • 3
  • 19
  • 53
  • Why does it have to be inside the London div? "I have a div that I want to have a fixed position that is outside/on top of everything else on the page." Why not do just that? Place it outside/on top of everything else on the page. Leave its CSS exactly the same. – TLC Apr 13 '20 at 20:29
  • Because of the templating system I am using, it has to be within a div. – Robby Apr 13 '20 at 20:34
  • Unfortunately "display: none" hides all children, nothing you can do against that with css, see https://stackoverflow.com/questions/12956937/display-html-child-element-when-parent-element-is-displaynone – Johannes Stadler Apr 13 '20 at 20:37

3 Answers3

1

I don't know that there is any other way to do this other than to move the div so that it's outside of the tab, and give it a very high z-index because once you hide a tab with display:none the entire tab (and its descendants) won't be rendered.

If you can't modify the HTML template structure, you could use JavaScript to move it with this one line:

document.body.appendChild(document.getElementById("outsidediv"));

I've also modified/updated your existing JavaScript code so that you aren't using .getElementsByClassName() or using inline HTML event attributes, which makes the code much more efficient and more in line with modern development methodologies. See the comments in the code for details.

/* Just add this class when you want to hide something 
   and remove when you want to show */
.hidden { display:none; }

.tab {
  overflow: hidden;
  border: 1px solid #ccc;
  background-color: #f1f1f1;
}

.tab button {
  background-color: inherit;
  float: left;
  border: none;
  outline: none;
  cursor: pointer;
  padding: 14px 16px;
  transition: 0.3s;
  font-size: 17px;
}

.tab button:hover {
  background-color: #ddd;
}

.tab button.active {
  background-color: #ccc;
}


.tabcontent {
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-top: none;
}

#outsidediv {
  position: fixed;
  z-index:9999;
  top: 80px;
  right: 50px;
  background: pink;
}
<div class="tab">
  <!-- Don't use inline HTML event attributes. Do your JavaScript in JavaScript-->
  <button class="tablinks">London</button>
  <button class="tablinks">Paris</button>
  <button class="tablinks">Tokyo</button>
</div>

<div id="London" class="tabcontent hidden">
  <h3>London</h3>
  <p>London is the capital city of England.</p>
  <div id="outsidediv">This div should appear no matter which tab is selected.</div>
</div>

<div id="Paris" class="tabcontent hidden">
  <h3>Paris</h3>
  <p>Paris is the capital of France.</p> 
</div>

<div id="Tokyo" class="tabcontent hidden">
  <h3>Tokyo</h3>
  <p>Tokyo is the capital of Japan.</p>
</div>

<script>
// Move the div that should stay on top to just after the tab divs
document.body.appendChild(document.getElementById("outsidediv"));

// Just set your event up at the parent of the buttons
// and use event delegation to leverage event bubbling
document.querySelector(".tab").addEventListener("click", openCity);

function openCity(event) {
  // Never, never, never use `getElementsByClassName()`
  // and instead, use `.querySelectorAll()`. You can 
  // then loop through the resulting collection with
  // `.forEach()`, which makes looping without indexes
  // possible
  
  // Hide all the tab conttent
  document.querySelectorAll(".tabcontent").forEach(function(tab){
    // Avoid inline styles when possible and just apply/remove classes
    tab.classList.add("hidden");
  });
  
  // Remove the active tab
  document.querySelectorAll(".tablinks").forEach(function(tabLink){
    tabLink.classList.remove("active");
  });
  
  // Loop over the content again
  document.querySelectorAll(".tabcontent").forEach(function(tab){
    // If the text of the clicked button matches the text of the tab header...
    if(event.target.textContent === tab.querySelector("h3").textContent){
      tab.classList.remove("hidden");      // Unhide the tabcontent
      event.target.classList.add("active"); // Make the tab active
    }
  });
}
</script>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • I can't move the div from where it is. – Robby Apr 13 '20 at 20:36
  • 2
    @Robby See my updated answer where I just move the `div` programatically and update your existing code. – Scott Marcus Apr 13 '20 at 21:02
  • I would love for this to work! However, I'm getting this error: Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'. – Robby Apr 14 '20 at 14:41
  • I do have that appendChild line in an external .js file, if that matters. – Robby Apr 14 '20 at 14:53
  • @Robby What that means is that the argument you are passing to `.appendChild` isn't a valid reference to an element in your document. In my example, I'm passing `document.getElementById("outsidediv")`, which is the node to move (the `div` that should always be visible). Make sure that this line of code is processed AFTER the entire document has been parsed into memory. This is usually done by placing your `script` tags just prior to the CLOSING `body` tag of the HTML file. – Scott Marcus Apr 14 '20 at 15:09
  • I can confirm that the `script` tags are just prior to the closing body tag. – Robby Apr 14 '20 at 15:20
  • @Robby Add this line right before the `.appendChild` line and see what is reported in your Console: `console.log(document.getElementById("outsidediv"))` – Scott Marcus Apr 14 '20 at 16:25
  • I don't see anything new in the Console using that line. I think I found the problem though. outsidediv doesn't get created on page load. So I think I'm going to have to figure out a way to not call the appendChild script until it exists. – Robby Apr 14 '20 at 16:40
  • @Robby Ah, yes, well that would do it. The element must exist before that line can be executed. – Scott Marcus Apr 14 '20 at 16:51
1

You can make an additional function to add the outsidediv. Before adding the element, first check if it exists on the page and if so remove it, then add it to the current active tab element.

Check below:

function openCity(evt, cityName) {
  var i, tabcontent, tablinks;
  var div = document.getElementById("outsidediv");
  if(div !== null) { // if outsidediv exists on the page, remove it
    div.remove();
  }
  tabcontent = document.getElementsByClassName("tabcontent");
  for (i = 0; i < tabcontent.length; i++) {
    tabcontent[i].style.display = "none";
  }
  tablinks = document.getElementsByClassName("tablinks");
  for (i = 0; i < tablinks.length; i++) {
    tablinks[i].className = tablinks[i].className.replace(" active", "");
  }
  let cityDiv = document.getElementById(cityName);  // made cityDiv variable
  cityDiv.style.display = "block";
  evt.currentTarget.className += " active";
  cityDiv.append( makeOutsideDiv() );  // add the outsidediv to the active element
}

function makeOutsideDiv() {
  let div = document.createElement("div");
  div.innerHTML = "This div should appear no matter which tab is selected.";
  div.id = "outsidediv";
  return div;
}
.tab {
  overflow: hidden;
  border: 1px solid #ccc;
  background-color: #f1f1f1;
}

.tab button {
  background-color: inherit;
  float: left;
  border: none;
  outline: none;
  cursor: pointer;
  padding: 14px 16px;
  transition: 0.3s;
  font-size: 17px;
}

.tab button:hover {
  background-color: #ddd;
}


.tab button.active {
  background-color: #ccc;
}


.tabcontent {
  display: none;
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-top: none;
}

#outsidediv {
  position: fixed;
  top: 80px;
  right: 50px;
  background: pink;
  }
<div class="tab">
  <button class="tablinks" onclick="openCity(event, 'London')">London</button>
  <button class="tablinks" onclick="openCity(event, 'Paris')">Paris</button>
  <button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>

<div id="London" class="tabcontent">
  <h3>London</h3>
  <p>London is the capital city of England.</p>
  <div id="outsidediv">This div should appear no matter which tab is selected.</div>
</div>

<div id="Paris" class="tabcontent">
  <h3>Paris</h3>
  <p>Paris is the capital of France.</p> 
</div>

<div id="Tokyo" class="tabcontent">
  <h3>Tokyo</h3>
  <p>Tokyo is the capital of Japan.</p>
</div>
Ivan86
  • 5,695
  • 2
  • 14
  • 30
0

I used visibility instead of display, and each inactive tab I am setting position:fixed and sending it way above the rendered area of the page (out of sight and out of mind). Then when the tab is clicked again I'm setting it back to position:relative with no offset.

Here's the fiddle: https://jsfiddle.net/tm1wyax6/1/

<div class="tab">
  <button class="tablinks" onclick="openCity(event, 'London')">London</button>
  <button class="tablinks" onclick="openCity(event, 'Paris')">Paris</button>
  <button class="tablinks" onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>

<div id="London" class="tabcontent">
  <h3>London</h3>
  <p>London is the capital city of England.</p>
  <div id="outsidediv">This div should appear no matter which tab is selected.</div>
</div>

<div id="Paris" class="tabcontent">
  <h3>Paris</h3>
  <p>Paris is the capital of France.</p> 
</div>

<div id="Tokyo" class="tabcontent">
  <h3>Tokyo</h3>
  <p>Tokyo is the capital of Japan.</p>
</div>

<script>
function openCity(evt, cityName) {
  var i, tabcontent, tablinks;
  tabcontent = document.getElementsByClassName("tabcontent");
  for (i = 0; i < tabcontent.length; i++) {
    tabcontent[i].style.visibility = "hidden";
    tabcontent[i].style.position = "fixed";
    tabcontent[i].style.top = "-1000px";
  }
  tablinks = document.getElementsByClassName("tablinks");
  for (i = 0; i < tablinks.length; i++) {
    tablinks[i].className = tablinks[i].className.replace(" active", "");
  }
  document.getElementById(cityName).style.visibility = "visible";
  document.getElementById(cityName).style.position = "relative";
  document.getElementById(cityName).style.top = "0";
  evt.currentTarget.className += " active";
}
</script>

CSS:

.tab {
  overflow: hidden;
  border: 1px solid #ccc;
  background-color: #f1f1f1;
}

.tab button {
  background-color: inherit;
  float: left;
  border: none;
  outline: none;
  cursor: pointer;
  padding: 14px 16px;
  transition: 0.3s;
  font-size: 17px;
}

.tab button:hover {
  background-color: #ddd;
}


.tab button.active {
  background-color: #ccc;
}


.tabcontent {
  visibility: hidden;
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-top: none;
}

#outsidediv {
  position: fixed;
  top: 80px;
  right: 50px;
  background: pink;
  visibility: visible;
  }
TLC
  • 371
  • 3
  • 6