0

I am trying to apply changes to the DOM when a <tr> is clicked. When clicked, it should add data-state=enabled and data-display=expanded to the clicked <tr> while applying data-state=disabled and data-display=collapsed to every other <tr>.

So it should look like highlighting the clicked row while disabling the other rows.

Then, when a row is highlighted, and a user clicks elsewhere, it should reset to default, i.e. data-state=enabled and data-display=collapsed for all <tr>'s

Currently, I have it working so that when a <tr> is clicked, that row is highlighted and all others disabled. However, the script is missing the reset to default because if another <tr> is clicked, it immediately highlights that one and disables the rest.

I would like to do this in vanilla javascript but I am also open to using jQuery if it is substantially easier and won't affect performance noticably.

Here is the JSbin with working code to see where it's at: https://jsbin.com/kirati/

And the code so far:

<table class="table">
                    <thead>
                        <tr>
                            <th class="sort-key asc"><a href="#">Pet Name </a></th>
                            <th><a href="#">Owner (Last, First)</a></th>
                            <th><a href="#">Species</a></th>
                            <th><a href="#">Breed</a></th>
                            <th><a href="#">ID</a></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>
                                Fluffy
                                <div class="table-row__expanded-content">
                                    <data-key>Sex: </data-key> <data-value>Male</data-value><br />
                                    <data-key>DOB: </data-key> <data-value>12/08/2010</data-value> <br />
                                    <data-key>Weight: </data-key> <data-value>20 lbs</data-value> <br />
                                    <data-key>Location: </data-key> <data-value>Kennel 2B</data-value> <br />
                                    <data-key>Temperament: </data-key> <data-value>Aggresive</data-value> <br />
                                    <data-key>Allergies: </data-key> <data-value>Sulfa, Penicillin, Peanuts</data-value>
                                </div>
                            </td>
                            <td>Anderson, James</td>
                            <td>Dog</td>
                            <td>Bulldog-Shitzu</td>
                            <td>ABCDE1234567</td>
                        </tr>
                        <tr>
                            <td>Feather</td>
                            <td>Michelle Charlotte, <br /> Angel Vanessa</td>
                            <td>Cat</td>
                            <td>American Bobtail</td>
                            <td>FGHIJ1234567</td>
                        </tr>
                        <tr>
                            <td>Fluffer-Nutter</td>
                            <td>Rakerstraw, Rickey</td>
                            <td>Dog</td>
                            <td>American Eskimo</td>
                            <td>KLMNO1234567</td>
                        </tr>
                        <tr>
                            <td>Farley</td>
                            <td>Cunningham, Stephanie</td>
                            <td>Dog</td>
                            <td>Pomeranian</td>
                            <td>PQRST1234567</td>
                        </tr>
                        <tr>
                            <td>Fuzzy</td>
                            <td>Venice, Harding</td>
                            <td>Cat</td>
                            <td>Burmese</td>
                            <td>UVWXY1234567</td>
                        </tr>
                        <tr class="alphabet-label">

                            <td>G</td>
                            <td></td>
                            <td></td>
                            <td></td>
                            <td></td>
                        </tr>
                        <tr>
                            <td>Goldie</td>
                            <td>Cherilyn, Mitchener</td>
                            <td>Dog</td>
                            <td>Chihuahua-Maltese</td>
                            <td>ZABCD1234567</td>
                        </tr>
                    </tbody>
                </table>

And the Javascript

window.onload = function () {

    var tablerow = document.body.getElementsByTagName('tr');
    console.log(tablerow);

     // Convert the HTMLCollection into a true javascript Array, so we can do "stuff" with it       
    var tablerowArr = Array.prototype.slice.call(tablerow);
    console.log(tablerowArr);

// Do stuff
    tablerowArr.forEach(function (value, i) {
        console.log(i, value);

        tablerow[i].onclick = function (e) {
            //console.log("clicked!");


            var newArr = tablerowArr.slice(i, i + 1);
            //console.log(tablerow);
            console.log(i);
            //console.log(tablerowArr);
            console.log('newArr', newArr);


            tablerowArr.forEach(function (value, i) {

                // first reset all instances of data-XXX  
                tablerowArr[i].setAttribute('data-display', "collapsed");
               // tablerowArr[i].setAttribute('data-state', "enabled");

                // Set the <tr> data-display attribute to expanded/collapsed on click
                newArr[0].setAttribute('data-display', tablerowArr[i].getAttribute('data-display') === "collapsed" ? "expanded" : "collapsed");
                //tablerowArr[i].setAttribute('data-display', tablerowArr[i].getAttribute('data-display') === "collapsed" ? "expanded" : "collapsed");

                // Set the <tr> data-state attribute to enabled/disabled on click
                newArr[0].setAttribute('data-state', newArr[0].getAttribute('data-state') === "disabled" ? "enabled" : "enabled");
                tablerowArr[i].setAttribute('data-state', newArr[0].getAttribute('data-state') === "enabled" ? "disabled" : "enabled");

            });

            e.preventDefault();
        };
    });
};
TetraDev
  • 16,074
  • 6
  • 60
  • 61
  • try this solution maybe it may help [How to change HTML Object element data attribute value in javascript](http://stackoverflow.com/questions/4836290/how-to-change-html-object-element-data-attribute-value-in-javascript) or this one [Set data attribute using JavaScript](http://stackoverflow.com/questions/11286661/set-data-attribute-using-javascript) – Gardezi Oct 14 '15 at 14:59
  • "use jQuery if it is substantially easier and won't affect performance noticably" - it always is substantially easier and it doesn't affect performance – freedomn-m Oct 14 '15 at 15:12
  • I prefer VanillaJS because it helps me better understand JavaScript rather than relying on "magic" from cool libraries like jQuery. Of course there is a time and place for libraries because they make some stuff super easy - but there are times when pureJS is just fine (and helps my understanding grow). – TetraDev Oct 14 '15 at 16:37
  • Possible duplicate of [Set data attribute using JavaScript](https://stackoverflow.com/questions/11286661/set-data-attribute-using-javascript) – Heretic Monkey Aug 07 '18 at 20:19

4 Answers4

1

Here is a pure javascript example in the below jsfiddle:

http://jsfiddle.net/pya9jzxm/14

    var tbody = document.querySelector('tbody');
    var trs = tbody.querySelectorAll('tr');
    var tr, index = 0, length = trs.length;
    for (; index < length; index++) {
        tr = trs[index];
        tr.setAttribute('data-state', 'enabled');
        tr.setAttribute('data-display', 'collapsed');
        tr.addEventListener('click',
            function () {
                if (this.classList.contains('alphabet-label')) {
                    return;
                }
                var trIndex = 0, trLength = trs.length, hasExpanded = false;
                var state = 'disabled';
                if (tbody.querySelectorAll('[data-display="expanded"]').length > 0) {
                    hasExpanded = true;
                    state = 'enabled';
                }
                for (; trIndex < trLength; trIndex++) {
                    trs[trIndex].setAttribute('data-state', state);
                    trs[trIndex].setAttribute('data-display', 'collapsed');
                }
                if (!hasExpanded) {
                    this.setAttribute('data-state', 'enabled');
                    this.setAttribute('data-display', 'expanded');
                }
            }
        );
    }
};
AtheistP3ace
  • 9,611
  • 12
  • 43
  • 43
  • Thanks, I see that it works to highlight on click, however the part I was missing was getting it to reset to default after clicking off of a row which is highlighted. i.e. first click away from highlighted row makes every row is unhighlighted, second click then highlights the new row. – TetraDev Oct 14 '15 at 15:09
  • OK, so even clicking another row deselects everything first? Or just clicking away from any row in an area that is not a row? – AtheistP3ace Oct 14 '15 at 15:14
  • Updated fiddle where clicking other rows removes highlight first. and then click again adds it to new row. – AtheistP3ace Oct 14 '15 at 15:17
  • 1
    Thanks! You did it perfectly!! Pure JS too - even better :-) – TetraDev Oct 14 '15 at 15:22
  • One more thing - how to get it to not highlight the data like pawel did. Meaning it should only highlight actually data 's but not the first inside the – TetraDev Oct 14 '15 at 15:28
  • Removed some duplicated code if you are interested. Answer updated. – AtheistP3ace Oct 14 '15 at 15:28
  • Updated to answer to ignore header row as well. – AtheistP3ace Oct 14 '15 at 15:30
  • Amazing. Last update: instead of all starting at "disabled", I'd like all rows to start "enabled". I tried adjusting line 5 to `tr.setAttribute('data-state', 'enabled');` however now it starts enabled, then first click disables all (undesired), then it starts the procedure like normal. So it should all be enabled at start and then go right into the highlighting. – TetraDev Oct 14 '15 at 15:37
  • My mistake, I should clarify: `data-state=enabled` and `data-display=collapsed` is the default **and** the reset for all. On highlight, `data-display=expanded` and `data-state` remains enabled (for the clicked row) and all others `data-display=collapsed` and `data-state=disabled`. Then on click away, apply the reset which puts `data-stated=enabled` and `data-display=collapsed` for all (including previously highlighed row) – TetraDev Oct 14 '15 at 15:54
  • 1
    That's the one!! Version 7. Thank you once again for the excellent support. I'm working on a big project for a company. – TetraDev Oct 14 '15 at 16:11
  • Haha. Cool. No problem! Happy to help! Good luck on the project! – AtheistP3ace Oct 14 '15 at 16:12
  • Just for fun here is a little cleaner version 8 removing duplicated code. http://jsfiddle.net/pya9jzxm/8/ – AtheistP3ace Oct 14 '15 at 16:16
  • 1
    Neat! I like the cleanliness of it. As they say, keep your code DRY. – TetraDev Oct 14 '15 at 16:28
  • 1
    Lol you're on a roll! – TetraDev Oct 14 '15 at 16:36
  • OK, I am stopping after this but that display variable is useless. I removed it. – AtheistP3ace Oct 14 '15 at 16:39
  • Hey I figured another update out - I combined part of version 4 with version 11 and made this: I needed to skip the row where it says "G" because that's just a label row and not actual row data, so it should not be highlightable. http://jsfiddle.net/pya9jzxm/13/ – TetraDev Oct 14 '15 at 16:52
  • 1
    All is perfect now. Thanks again - Cheers! – TetraDev Oct 14 '15 at 16:59
  • One more update to your edit. For sake of possibly using multiple classes I suggest using classList.contains – AtheistP3ace Oct 14 '15 at 17:07
  • Hey AtheistP3ace, would you take a look at my other question? It's about how to convert this code we did here to support multiple tables on a page instead of just one: http://stackoverflow.com/questions/34516684/convert-script-to-handle-multiple-tables-independently – TetraDev Dec 29 '15 at 18:29
  • Yea I can take a look. Give me a little. At work right now. – AtheistP3ace Dec 29 '15 at 18:39
0

in jQuery:

$(function() {
    $('html').click(function() {
        /* return to default state */
        $('.table tbody tr').data('state', 'enabled').data('display', 'expanded');
    });
    $('.table tbody tr').on('click', function(e) {
        /* stop the html click event */
        e.stopPropagation();
        /* set attributes for all rows */
        $('.table tbody tr').data('state', 'disabled').data('display', 'collapsed');
        /* set attributes for the clicked row */
        $(this).data('state', 'enabled').data('display', 'expanded');
    });
});

I've commented the function, to clarify how it works.

GreyRoofPigeon
  • 17,833
  • 4
  • 36
  • 59
  • just double check $(this).data( do you not mean $(this).attr( ? – Seabizkit Oct 14 '15 at 15:06
  • When I added this to my JSbin with jquery loaded, the script did not work, using (this).data and (this).attr - see JS bin with original code: https://jsbin.com/xukiyoragu – TetraDev Oct 14 '15 at 15:11
  • @freedomn-m do you mind posting a link to support your claim as I have one which directly contradict you https://api.jquery.com/data/ – Seabizkit Oct 14 '15 at 15:42
  • @TetraDev see my answer for jquery. also may i suggest you change some of this to work off html classes instead of data attributes, unless there is some other framework which you are relying on. – Seabizkit Oct 14 '15 at 15:46
  • @freedomn-m the one modifies the DOM the other does not.... so ask yourself the same question. He wants to modify **the DOM** not underlining model data... so ... I hope that helps you understand the diff **between attr and data**. **He wants to modify the actual HTML in the page.** – Seabizkit Oct 14 '15 at 15:55
  • @freedomn-m i thought it was quite clear! May I suggest that you change your other comment **attr("state") isn't the same as attr("data-state")** as i have no idea what you are trying to say. Now that you know the diff between data and attr maybe you can word that a little clearer. thx – Seabizkit Oct 14 '15 at 16:09
  • no offence, but you could read you own comment, to see that you did not. there is no shame in that!, FYI you made me google data, and triple!!! check it. It wasn't clear to you, you should not include everyone in that... and no one said that last line! – Seabizkit Oct 14 '15 at 16:20
  • Lol you guys are funny. But thanks because I learned something about .data and .attr - although I'm curious what the use case would be for changing DOM data using .data? – TetraDev Oct 14 '15 at 16:33
  • @Seabizkit Not sure why I'm referenced in all those comments - exactly what are you referring to? – freedomn-m Oct 14 '15 at 16:44
  • **deletes all his comments** but the last one, weird...anyway @TetraDev JQuery .data does not modify the DOM... its keep meta information in javascript about a given JQuery element... but best read up on it, I had to as i wasn't sure hence the original comment (double check..)... as i wasn't sure, after checking due to someone else's comment saying it was incorrect, i had to check for myself what data does. – Seabizkit Oct 14 '15 at 18:05
0

Using vanilla JS and event delegation. DEMO

var tablerow = document.body.getElementsByTagName('tr');
// Convert the HTMLCollection into a true javascript Array, so we can do "stuff" with it       
var tablerowArr = Array.prototype.slice.call(tablerow);
// store the highlighted row
var highlighted;

// extracted function to enable/disable rows
var enable = function(row){
    row.setAttribute('data-display', "expanded");
    row.setAttribute('data-state', "enabled");
}
var disable = function( row ){
    row.setAttribute('data-display', "collapsed");
    row.setAttribute('data-state', "disabled");
}
// on click anywhere
document.body.addEventListener('click', function(e){
    var target = e.target;
    // check if event target or any of its parents is a TR
    while( target.parentNode && target.nodeName.toLowerCase() != 'tr' ){
        target = target.parentNode;   
    }
    // if s row was higlighted disable all rows
    if( highlighted ){
        tablerowArr.forEach(  disable );
        // and remove the reference to highlighted
        highlighted = null;
    }
    // no row is highlighted
    else {
        tablerowArr.forEach( function(row){
            // if the event target is a row, highlight it
            if(row == target) {
                enable( row );
                // and store it as the currently highlighted
                highlighted = row;
            }
            // if it's not the event target then disable
            else {
                disable( row ); 
            }
        });
    }
});
pawel
  • 35,827
  • 7
  • 56
  • 53
  • Works great except it is also missing the "reset to default" after clicking off of a row which is highlighted. i.e. first click away from highlighted row makes every row is unhighlighted, second click then highlights the new row. – TetraDev Oct 14 '15 at 15:25
  • @TetraDev I have focused on the "clicking anywhere" part to remove highlights. Fixed now to remove highlight on first click. – pawel Oct 14 '15 at 15:52
  • Thanks - that part works. But I did not communicate clearly - on the reset, all rows should go back to default `data-state-enabled` and `data-display=collapsed`. Right now, when the "reset" is applied, all rows are disabled and collapsed (should be enabled and collapsed) – TetraDev Oct 14 '15 at 16:00
0

Which is very simular to @LinkinTED

Please inspect the html before you say this does not work.. I've tested it myself.

May i suggest that you instead modify the class... to change the effects.

$(function() {

    var $row = $('.table tbody tr');

    $('html').click(function() {
        $row.attr('state', 'enabled').attr('display', 'expanded');
    });

    $row.on('click', function(e) {
        e.stopPropagation();
        $row
          .attr('state', 'disabled')
          .attr('display', 'collapsed');

      //console.log(e);

        $(this)
          .attr('state', 'enabled')
          .attr('display', 'expanded');
    });
});
Seabizkit
  • 2,417
  • 2
  • 15
  • 32
  • It works if I edit the attr to data-state and data-display (that's what my css required). Although it is only missing the "reset" when clicking to another row after being highlighted. It resets if I click anywhere in the DOM which is not a but it should also reset even when clicking another before applying the next highlight. – TetraDev Oct 14 '15 at 15:45
  • OK but is this just to: highlight row, and show hidden data when clicked, if so I would change this to add and remove classes instead of working off attributes. You could just add a check in to meet your additional requirement. Also if you don't like the rest variable you could change it. – Seabizkit Oct 14 '15 at 15:50
  • It's not a matter of 'inspecting the html' because OP is using *css* which is looking for attributes "data-state" so `attr("state")` isn't the same as `attr("data-state")` – freedomn-m Oct 14 '15 at 15:58
  • I wanted to avoid using classes to change states because I want to keep my classes clean and minimal, and use html5 data- properties for best practice. Just my personal preference. – TetraDev Oct 14 '15 at 16:08
  • @TetraDev ... just so you are aware... maybe im wrong but i dont think so... the more correct way.... even with html5 and attr's, to do this, is with classes.... if you think im wrong... you could ask this on SO. (but remember i called it ;-)), you could using common classes like "hide" part of bootstrap, and you already using css... so... not sure why you think classes is not the correct way to modify the dom. attr are usually used to carry info, not to affect styling...just an FYI, hope this was useful. – Seabizkit Oct 14 '15 at 16:14
  • Just to add: classes are for changing the appearance on the page. You're changing the appearance by "highlighting", so should be a class. – freedomn-m Oct 14 '15 at 16:16
  • You may be correct. Although I don't prefer the bootstrap method of littering my html with classes. I abstract all classes away into the CSS and use SASS as a preprocessor to manipulate my display. See this for my reasoning behind data-xxx: http://toddmotto.com/stop-toggling-classes-with-js-use-behaviour-driven-dom-manipulation-with-data-states/ – TetraDev Oct 14 '15 at 16:19
  • I consider it to be more of a state change than a highlighting. The highlighting is only the effect of the state change - the state is the primary focus. – TetraDev Oct 14 '15 at 16:20