Handle rowspan and colspan while converting "Table" into "Div" - javascript

I am converting table structured data into div using below code.
$('#content').html($('#content').html()
.replace(/<tbody/gi, "<div id='table'")
.replace(/<tr/gi, "<div style='overflow:auto;padding-top:15px;'")
.replace(/<\/tr>/gi, "</div>")
.replace(/<td/gi, "<span style='float:left;margin-right:20px;'")
.replace(/<\/td>/gi, "</span>")
.replace(/<\/tbody/gi, "<\/div"));
It works good mostly in all scenario except ROWSPAN & COLSPAN case.
How can I handle that design issue while converting Table into Div ?
I am stuck in that.

Maybe this gets you in the right direction:
.replace(/rowspan="/gi, 'class="rowspan-')
.replace(/colspan="/gi, 'class="colspan-')
Then make styles for the classes (e.g. rowspan-2 or colspan-3 etc.). However, this doesn't solve cases where one element has both row- and colspan, but it's a start.
A better way would be:
var copyAttr = function(old, $new) {
for(var i = 0,
attributes = old.attributes;
i < attributes.length; i++) {
$new.attr(attributes[i].name, attributes[i].value);
}
return $new;
}
$('#content').find('tbody').each(function() {
var $new = copyAttr(this, $('<div id="table"></div>');
$(this).replaceWith($new);
}).end().find('tr').each(function() {
var $new = copyAttr(this, $('<div class="tr"></div>');
$(this).replaceWith($new);
}).end().find('td').each(function() {
var $new = copyAttr(this, $('<span class="td"></span>');
$(this).replaceWith($new);
});
So now you have replaced the whole table structure with divs and spans with all the attributes the table elements had. Next you can change the row- and colspan attributes to classes.
$('#table .td').each(function() {
var $t = $(this);
$t
.addClass('rowspan-'+$t.attr('rowspan'))
.removeAttr('rowspan')
.addClass('colspan-'+$t.attr('colspan'))
.removeAttr('colspan');
});

Why are you converting table structured data into div instead of just outputting div structured data in the first place? I don't really get that
You can try using CSS:
.tablewrapper
{
position: relative;
}
.table
{
display: table;
}
.row
{
display: table-row;
}
.cell
{
display: table-cell;
border: 1px solid red;
padding: 1em;
}
.cell.empty
{
border: none;
width: 100px;
}
.cell.rowspanned
{
position: absolute;
top: 0;
bottom: 0;
width: 100px;
}
Some example table which you should get:
<div class="tablewrapper">
<div class="table">
<div class="row">
<div class="cell">
Top left
</div>
<div class="rowspanned cell">
Center
</div>
<div class="cell">
Top right
</div>
</div>
<div class="row">
<div class="cell">
Bottom left
</div>
<div class="empty cell"></div>
<div class="cell">
Bottom right
</div>
</div>
</div>
</div>
In you case this will look like:
.replace(/rowspan="/gi, 'class="rowspanned cell')
This example works in all major browsers except for Internet Explorer 7.

Related

Two Column Accordion with Separate Full Width Divs

The intension is to have a two column accordion, without limiting the "expand" field to the left or right column. The catch is that there will be multiple on one page. This is already created, but only button 1 is working. With the way my JS is going, it will get very very repetitive - I am looking for assistance with re-writing the JS to be multiple click friendly. Fiddle: https://codepen.io/ttattini/pen/abLzaaY
EDIT: It would also be perfect if one dropdown would close as the next is opened
HTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="row">
<div id="column">
<button id="button">I am Button #1</button>
<button id="button">I am Button #3</button>
</div>
<div id="column">
<button id="button">I am Button #2</button>
<button id="button">I am Button #4</button>
</div>
</div>
<div id="hidden">
<p id="content"> So here I am #1</p>
</div>
<div id="hidden">
<p id="content"> So here I am #2</p>
</div>
<div id="hidden">
<p id="content"> So here I am #3</p>
</div>
<div id="hidden">
<p id="content"> So here I am #4</p>
</div>
CSS
#hidden {
background: #ccc;
margin-top: 2%;
overflow: hidden;
transition: height 200ms;
height: 0; /* <-- set this */
}
#button {
padding: 10px;
margin-top: 5px;
width:50%;
margin-left: 10%;
cursor: pointer;
}
#row {
display: flex;
}
#column {
flex: 50%;
}
JS
$(function() {
var b = $("#button");
var w = $("#hidden");
var l = $("#content");
b.click(function() {
if (w.hasClass('open')) {
w.removeClass('open');
w.height(0);
} else {
w.addClass('open');
w.height(l.outerHeight(true));
}
});
});
The biggest issue is that you're using IDs when you should be using classes. IDs must be unique to each element in a page. When you repeat an ID, JS will only target the first element using that ID. That's why only the first one is working.
The second issue is that, because of the way the script is written, it will only target a single element. What you need to do is get all the elements you want to target by something like their class name and then loop through them, applying the event listener to each one and its appropriate children.
EDIT: Here is an example from some code I wrote for a page with multiple accordions a few weeks ago in vanilla JS
//Below I establish a counting variable and find all the accordions on the page
const acc = document.getElementsByClassName( 'accordion' );
let i;
//Looping through each accordion
for ( i = 1; i <= acc.length; i++ ) {
//Identify target for the event listener. In this case, a heading for each accordion, which I've numbered e.g. "title-1"
const title = 'title-' + i;
const label = document.getElementById( title );
//Identify target content, in this case a list that has a unique ID e.g. "list-1"
const listNum = 'list-' + i;
const list = document.getElementById( listNum );
//Add event listener to heading that toggles the active classes
label.addEventListener( 'click', function() {
label.classList.toggle( 'accordion--active' );
});
}
Of course, there's more than one way to skin a cat, but this is a working example.
I have tracked the clicked event of each button and showed the corresponding hidden content with the use of data- attribute.
I have used vanilla JavaScipt instead of jQuery.
const buttons = document.querySelectorAll('.button');
const hiddens = document.querySelectorAll('.hidden');
buttons.forEach((btn) => {
btn.addEventListener('click', btnClicked)
function btnClicked(e) {
hiddens.forEach((hidden) => {
if(e.target.dataset.btn == hidden.dataset.content) {
hidden.classList.toggle('height')
} else {
hidden.classList.remove('height')
}
})
}
})
.hidden {
background: #ccc;
margin-top: 2%;
padding-left:2%;
overflow: hidden;
transition: height 200ms;
height: 0; /* <-- set this */
}
.hidden.height {
height: 50px;
}
.button {
padding: 10px;
color: white;
background-color: #2da6b5;
border: none;
margin-top: 5px;
width:90%;
margin-left: 5%;
cursor: pointer;
}
.button:hover {
filter: brightness(.9);
}
#row {
display: flex;
}
.column {
flex: 50%;
}
<div id="row">
<div class="column">
<button class="button" data-btn="one">I am Button #1</button>
<button class="button" data-btn="three">I am Button #3</button>
</div>
<div class="column">
<button class="button" data-btn="two">I am Button #2</button>
<button class="button" data-btn="four">I am Button #4</button>
</div>
</div>
<div class="hidden" data-content="one">
<p class="content"> So here I am #1</p>
</div>
<div class="hidden" data-content="two">
<p class="content"> So here I am #2</p>
</div>
<div class="hidden" data-content="three">
<p class="content"> So here I am #3</p>
</div>
<div class="hidden" data-content="four">
<p class="content"> So here I am #4</p>
</div>
Also, please do not use the same ID at multiple elements.

Javascript : finding a specific previous element on list and adding class

I have a list like this.
Inside each .list item there is a html button :
<div class="list">
<button>.list</button>
</div>
Also, each item can be inside a .bloc element
<div class="list"><button>.list</button></div>
<div class=bloc>
<div class="list"><button>.list</button></div>
</div>
When I click on the button, I would like the previous .list item to have the .active class like so :
Well it’s pretty easy with jquery and i've done that, it’s work pretty well :
$('.list button').on('click', function() {
$(this).closest('.list').prev('.list').addClass('active');
});
BUT i have some specific cases :
Sometimes the list items can be hidden and a list with hidden class can’t have .active class :
Or more complicated. You have to go up on each item one by one and put the active class to the first which does not have the hidden class :
I did the mechanics for items without class hidden, but I'm afraid I'm going in the wrong direction because the number of cases is getting bigger and bigger. Ain't there a smarter way ? :o
$('.list button').on('click', function() {
if ($(this).closest('.list').prev().length === 0) {
if ($(this).closest('.bloc').length) {
$(this).closest('.bloc').prev('.list').addClass('active');
$(this).closest('.bloc').prev('.bloc').find('.list:last-child').addClass('active');
} else {
$(this).closest('.list').next('.list').addClass('active');
}
}
if ($(this).closest('.list').prev('.bloc').length) {
$(this).closest('.list').prev('.bloc').find('.list:last-child').addClass('active');
}
$(this).closest('.list').prev('.list').addClass('active');
}
Rather than use .closest .prev and .next you can use the overload to .index which will give you the index within an existing collection.
var idx = collection.index(element);
select all your .list items into a jquery object/collection
when clicking get the index within that collection
subtract 1 to get the previous .list item within that collection
The basic scenarios are covered with $(".list") :
// collate the list first
var list = $(".list");
// add click handler
list.click(function() {
// confirm there are no duplicates
// comapred with $(this).index() which is the index within the parent
console.log(list.index(this), $(this).index())
$(".active").removeClass("active");
var idx = list.index(this);
if (idx > 0)
list.eq(idx-1).addClass("active");
});
.list { border:1px solid #CCC; height: 20px; }
.bloc { border:1px solid #444; padding: 5px; }
.active { border:1px solid red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='wrapper'>
<div class='bloc'>
<div class='list'></div>
<div class='list'></div>
</div>
<div class='list'></div>
<div class='list'></div>
</div>
All the other use-cases are then just a case of providing the correct selector up-front, with otherwise exactly the same code
var list = $(".wrapper>.bloc:not(.hidden)>.list:not(.hidden),.wrapper>.list:not(.hidden)");
I've tried to recreate some of your scenarios, but if there's one that's missing, please comment and I'll ensure it fits (within the remit of the question).
Giving:
var list = $(".wrapper>.bloc:not(.hidden)>.list:not(.hidden),.wrapper>.list:not(.hidden)")
list.click(function() {
$(".active").removeClass("active");
var idx = list.index(this);
if (idx > 0)
list.eq(idx-1).addClass("active");
});
.list { border:1px solid #CCC; height: 20px; }
.bloc { border:1px solid #444; padding: 5px; }
.active { border:1px solid red; }
.hidden { background-color: #ccc; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='wrapper'>
<div class='bloc'>
<div class='list'></div>
<div class='list'></div>
</div>
<div class='list hidden'></div>
<div class='bloc'>
<div class='list hidden'></div>
<div class='list hidden'></div>
</div>
<div class='list'></div>
<div class='bloc'>
<div class='list hidden'></div>
<div class='list'></div>
</div>
<div class='list'></div>
<div class='list'></div>
<div class='bloc hidden'>
<div class='list'></div>
<div class='list'></div>
</div>
<div class='list'></div>
<div class='list'></div>
</div>

Dynamically change nth-child number

ngx-datatable > div > datatable-body > datatable-selection:hover > datatable-scroller > datatable-row-wrapper:nth-child(n) > datatable-body-row > div.datatable-row-center.datatable-row-group > datatable-body-cell:nth-child(1) {
background-color: #E9F1FA !important;
}
This is my long selector. I am trying to highlight the entire column based on the user hovering over an item on a column. This works great, except that it only highlights whatever nth-child I am using on the last datatable-body-cell:nth-child(1) I can change it to any number and it works, but it isn't dynamic. I want it to only select the column that is being hovered over. I've tried datatable-body-cell:nth-child(n):hover and datatable-body-cell:hover and a lot of different varieties but it either highlights the whole table, or nothing at all, unless I specify the nth-child.
Is there a way I can dynamically change the nth-child based on the what child the user is hovering over (with CSS or Javascript)?
Any help would be appreciated!
You can use document.querySelector to get the column and set its style it is being hovered over on mouseenter and reset it back to normal on mouseleave.
var n = 1;//the number
document.querySelector('ngx-datatable > div > datatable-body > datatable-selection:hover > datatable-scroller > datatable-row-wrapper:nth-child('+n+') > datatable-body-row > div.datatable-row-center.datatable-row-group > datatable-body-cell:nth-child(1)').style.setProperty('background-color', '#E9F1FA', 'important');
Demo:
var children = document.querySelectorAll('div.child');
Array.prototype.slice.call(children).forEach(function(child){
var n = child.parentNode.getAttribute('data-num');
var parent = document.querySelector('div.table>div:nth-child('+n+')');
child.addEventListener('mouseenter', function(e){
parent.style.backgroundColor = "yellow";
this.style.backgroundColor = "red";
});
child.addEventListener('mouseleave', function(e){
var n = +this.parentNode.getAttribute('data-num');
parent.style.backgroundColor = "";
this.style.backgroundColor = "";
});
});
.table{
height: 250px;
width: 400px;
margin: 5px;
padding: 5px;
background-color: goldenrod;
}
.column{
background-color: dodgerblue;
margin: 5px;
}
.child{
border: 1px solid black;
}
<div class="table">
<div class="column" data-num="1">
<div class="child">1</div>
<div class="child">2</div>
<div class="child">3</div>
</div>
<div class="column" data-num="2">
<div class="child">1</div>
<div class="child">2</div>
<div class="child">3</div>
</div>
<div class="column" data-num="3">
<div class="child">1</div>
<div class="child">2</div>
<div class="child">3</div>
</div>
</div>

Add a div below inline-block wrapped row - Part 2

A solution suggested by #musicnothing in an older thread displays a content div below the row of inline divs, this works good when the div.wrapblock is clicked itself.
http://jsfiddle.net/SYJaj/7/
function placeAfter($block) {
$block.after($('#content'));
}
$('.wrapblock').click(function() {
$('#content').css('display','inline-block');
var top = $(this).offset().top;
var $blocks = $(this).nextAll('.wrapblock');
if ($blocks.length == 0) {
placeAfter($(this));
return false;
}
$blocks.each(function(i, j) {
if($(this).offset().top != top) {
placeAfter($(this).prev('.wrapblock'));
return false;
} else if ((i + 1) == $blocks.length) {
placeAfter($(this));
return false;
}
});
});
The issue I'm having.
I need to trigger the same effect, but by adding the click event to a link within the wrapblock itself.
My code is nearly identical.
What I have changed is the click event handle, from $('.wrapblock').click(function() to $('.more').on('click', function() I also needed to add .closest(".wrapblock") for the content div to position itself outside of the wrapblock.
$('.more').on('click', function() {
...
if ($blocks.length == 0) {
placeAfter($(this).closest(".wrapblock"));
return false;
}
Everything can be seen and tested http://jsfiddle.net/7Lt1hnaL/
Would be great if somebody could shed some light on how I can calculate which block it needs to follow with the offset method, thanks in advance.
As you can see in the latest fiddle example, the content div is not displaying below the row of divs.
I also apologise, I wanted to post on the thread in discussion but I only have a minor posting reputation which doesn't let me, thanks.
var $chosen = null;
var $allBlocks = [];
$(function(){
$allBlocks = $('.wrapblock');
})
$(window).on('resize', function() {
if ($chosen != null) {
$('#content').css('display','none');
$('body').append($('#content'));
$chosen.trigger('click');
}
});
$('.more').on('click', function() {
$chosen = $(this);
var position = $chosen.parent('.wrapblock').position();
$('#content').css('display','inline-block');
$allBlocks.filter(function(idx, ele){
return $(ele).position().top == position.top;
})
.last()
.after($('#content'));
});
.wrapblock
{
background: #963a3a;
display: inline-block;
width: 90px;
height: 90px;
color: white;
font-size: 14px;
text-align: left;
padding: 10px;
margin: 10px;
vertical-align:top;
position:relative;
}
#content
{
display:none;
vertical-align:top;
width:100%;
background: #5582c1;
font-size: 12px;
color: white;
padding: 10px;
}
.more {
position:absolute;
bottom:15px;
right:15px;
cursor:pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<div class="wrapblock">1
<span class="more" data-ref="1">more</span>
</div>
<div class="wrapblock">2
<span class="more" data-ref="2">more</span>
</div>
<div class="wrapblock">3
<span class="more" data-ref="3">more</span>
</div>
<div class="wrapblock">4
<span class="more" data-ref="4">more</span>
</div>
<div class="wrapblock">5
<span class="more" data-ref="5">more</span>
</div>
<div class="wrapblock">6
<span class="more" data-ref="6">more</span>
</div>
<div class="wrapblock">7
<span class="more" data-ref="7">more</span>
</div>
<div class="wrapblock">8
<span class="more" data-ref="8">more</span>
</div>
<div class="wrapblock">9
<span class="more" data-ref="9">more</span>
</div>
<div id="content">Some Content</div>
Seems to do what you want. Basically, it just filters down the set of all blocks to the row of the block you clicked on using the assumption that they'll all have the same vertical offset (top), then takes the last one, because jQuery will keep them in document order, so that'll be the last one in the layout row.
Oh, and I updated the fiddle http://jsfiddle.net/7Lt1hnaL/1/

How can I add up numbers that appear in class names?

Take the following code:
<div id="work">
<div class="large-{{columns}} large-offset-{{columns}} columns projects">
</div>
</div>
The idea is that <div class="large-{{columns}} large-offset-{{columns}} columns projects"> can be generated an indefinite amount of times inside #work, and {{columns}} generates a number between 0 and 12.
What I want to do is run some JavaScript that goes through the numbers generated by {{columns}} and every time the sum is about to surpass 12, the associated divs get wrapped inside a new div with class "row".
The resulting HTML might look like this:
<div id="work">
<div class="row">
<div class="large-8 large-offset-4 columns projects"></div>
</div>
<div class="row">
<div class="large-6 large-offset-0 columns projects></div>
<div class="large-6 large-offset-0 columns projects"></div>
</div>
<div class="row">
<div class="large-4 large-offset-0 columns projects"></div>
<div class="large-8 large-offset-0 columns projects"></div>
</div>
<div class="row">
<div class="large-12 large-offset-0 columns projects"></div>
</div>
</div>
How can I accomplish this?
You can extract the {{columns}} values from each div's class name with the following regular expression:
/large-(\d+)\s* large-offset-(\d+)/
This computes the delta that should be added to the running sum:
var matches = /large-(\d+)\s* large-offset-(\d+)/.exec(item.className),
delta = parseInt(matches[1], 10) + parseInt(matches[2], 10);
You can make new row divs with document.createElement and fill them with clones of the original divs.
Demonstration:
function makeRowDiv(buildRow) {
var row = document.createElement('div');
row.className = 'row';
for (var i = 0; i < buildRow.length; ++i) {
row.appendChild(buildRow[i]);
}
return row;
}
window.onload = function () {
var work = document.getElementById('work'),
items = work.getElementsByTagName('div'),
newWork = document.createElement('div');
var buildRow = [],
count = 0;
for (var i = 0; i < items.length; ++i) {
var item = items[i];
if (item.className.indexOf('columns') == -1) {
continue;
}
// Extract the desired value.
var matches = /large-(\d+)\s* large-offset-(\d+)/.exec(item.className),
delta = parseInt(matches[1], 10) + parseInt(matches[2], 10);
if (count + delta > 12 && buildRow.length != 0) {
newWork.appendChild(makeRowDiv(buildRow));
count = 0;
buildRow = [];
}
buildRow.push(item.cloneNode(true));
count += delta;
}
if (buildRow.length != 0) {
newWork.appendChild(makeRowDiv(buildRow));
}
// Replace work with newWork.
work.parentNode.insertBefore(newWork, work);
work.parentNode.removeChild(work);
newWork.id = 'work';
};
body {
font-family: sans-serif;
font-size: 14px;
color: #444;
}
#work .row {
padding: 1px;
margin: 8px;
background: #deedff;
border: 1px solid #c4d1e1;
}
#work .row div {
/* display: inline; */
padding: 1px 4px 2px 4px;
margin: 4px;
background: #fff3fc;
border: 1px solid #ded3dc;
}
#work .row div div {
/* display: inline; */
padding: 1px 4px 2px 4px;
margin: 4px;
background: #eee;
border: 1px solid #ddd;
}
p {
padding: 0;
margin: 0;
}
<div id="work">
<div class="large-8 large-offset-4 columns projects">
<div class="child-div"><p>8</p></div>
<div class="child-div"><p>4</p></div>
</div>
<div class="large-6 large-offset-0 columns projects">
<div class="child-div"><p>6</p></div>
</div>
<div class="large-3 large-offset-3 columns projects">
<div class="child-div"><p>3</p></div>
<div class="child-div"><p>3</p></div>
</div>
<div class="large-4 large-offset-0 columns projects">
<div class="child-div"><p>4</p></div>
</div>
<div class="large-8 large-offset-0 columns projects">
<div class="child-div"><p>8</p></div>
</div>
<div class="large-6 large-offset-6 columns projects">
<div class="child-div"><p>6</p></div>
<div class="child-div"><p>6</p></div>
</div>
</div>
If you have enough horizontal space, you can uncomment the CSS line /* display: inline; */ to see the children of each row div arranged side by side.
I would use split or replace to get your integers and sum them up as suggested here.
Example:
var str = 'large-8 large-offset-6';
var large = str.replace(/.*large-(\d+)/, '$1');
var offset = str.replace(/.*large-offset-(\d+)/, '$1');
Then use a solution such as this to get your wrappers.
Example:
var divs = $("#work > .columns");
var count = <count how many cols are need to reach sum>
for(var i = 0; i < divs.length; i+=count) {
divs.slice(i, i+count).wrapAll("<div class='new'></div>");
}
I'm sure you can clean it up and finish it off but should give you the idea. I will complete when I get time tonight.

Categories