Javascript / JQuery new table row - graceful slide in



  • Obligatory preface: I'm normally not a UI coder - this project is my first real foray in UI design and implementation.

    One of the things I would like to do for my chat display project is to have smooth scrolling of table data as it becomes available.

    Namely, I have a non scrollable chat window (fixed width/height - the form itself is resizable, but the chat area is only as big as the form window. It doesn't change unless the form is resized)

    I use the following to push new rows to my table (edits are fine, this is just what I came up with)

    I've tried several variations of the following CSS to get it to ease in, but i've been 100% unsuccessful.

    tr
    {
            -webkit-transition: all 5.9s ease-in; 
    	-moz-transition: all 0.9s ease-in; 
    	-o-transition: all 0.9s ease-in; 
    	transition: all 0.9s ease-in; 
    }
    

    Script

        <script>
    		function AddChatRow(message) {
    			var chatData = JSON.parse(message);
    			//alert(chatData.MessageUsername);
    			var table = document.getElementById("ChatTextArea");
    			var lastRow = table.rows.length;
    			var row = table.insertRow(lastRow - 1);
    			var ChatUser = row.insertCell(0);
    			var ChatMessage = row.insertCell(1);
    			ChatUser.innerHTML = chatData.UserBadges + "<font color=\"" + chatData.UserColor + "\">" + chatData.MessageUsername + "</font>:";
    			ChatUser.className = "ChatUser"
    			ChatUser.id = "ChatUser"
    			ChatMessage.innerHTML = chatData.UserMessage;
    			ChatMessage.className = "ChatMessage"
    			ChatMessage.id = "ChatMessage"
    		}
    	</script>
    

    HTML

    <div class="Container">
    		<div class="HeightTaker">
    			<div id="ChatContainer" class="ChatContainer Wrapper Container Inverse">
    				<table id="ChatTextArea" class="ChatTextArea">
    					<thead>
    					</thead>
    					<tbody>
    						<tr id="ChatRowData">
    							<td class="ChatUser">TournyMasterBot:</td>
    							<td class="ChatMessage">Thank you for using <a href="http://bot.menu">Bot.Menu</a> Chat Viewer version 0.0.0.1</td>
    						</tr>
    						<tr class="LastRow">
    							<td class="ChatUser"></td>
    							<td class="ChatMessage"></td>
    						</tr>
    					</tbody>
    				</table>
    			</div>
    		</div>
    	</div>
    

    KNOWNS

    • Window size
    • Once a row is created, it's size does not change.
    • Max width for the username table cell
    • Possibly max width for chat cell, through fancy maths.

    UNKNOWNS

    • Actual row size
    • Initial row height

    [Example Screenshots]

    Fixed form window



  • Your best bet is probably to move away from using a table and just use styled divs.



  • I'm good with that, but I don't know how to add new divs like new table rows...

    This looks promising,

    http://jsfiddle.net/39F88/



  • Are you using jQuery? If so, it's as easy as:

    var newRow = $('<div>').blah(); /* Build a row here */
    
    $('containerId').append(newRow);
    

    or prepend() if you want to insert at the top.



  • Assuming your table has a fixed height and overflow:hidden (so the newly inserted rows are hidden once there are too many of them):
    Even though it’s not scrollable by the user, your table is scrollable by JavaScript, so you can use this to animate it.
    table.clientHeight is the (fixed) height of your table; table.scrollHeight is the height of the content inside; table.scrollTop is the scroll position (0 if you’re at the top of the content, 42 if you’re 42 pixels away from the top of the content).
    To scroll to the bottom of your content, do table.scrollTop = table.scrollHeight - table.clientHeight.
    In your case, that means animating table.scrollTop from its current value to table.scrollHeight - table.clientHeight.
    So, in jQuery terms:

    $(table).animate({scrollTop: table.scrollHeight - table.clientHeight}, 5000);
    


  • Yes, I am using jquery. (I tend to switch between javascript and jquery due to ease of implementation, I'm just starting to get fully in to the UI portion, most of my time has just been getting the source data, and building out features - but I want a decent UI before I present the new version to people.)



  • How do I use that? When placed in head,

    (Yeah, I really am that big of a noob to jquery.)

    Ok. I'm just a fucking idiot, the jquery script path was just wrong.

    Move along, nothing to see here.



  • @Matches said:

    How do I use that? When placed in head,

    Do not put it in <head>, put it at the end of AddChatRow (it will animate the scrolling to the end of the table when executed — it’s not event-based or something).

    In case you haven’t done so yet, you’ll also need to include the JQuery source file:

    <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
    


  • What does the 5000 represent in your script? It doesn't appear to be transition milliseconds.



  • That’s strange because it’s what it should be doing.
    See http://api.jquery.com/animate/.
    I used this code to test it out:

    <html>
    <head>
    <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
    <meta charset="utf-8" />
    <title>Untitled</title>
    <style>
    div {
    	border: 1px solid black;
    	width: 100px;
    	height:100px;
    	overflow: hidden;
    }
    </style>
    </head>
    <body>
    <div id="scrollable">
    a<br />
    b<br />
    c<br />
    d<br />
    e<br />
    f<br />
    g<br />
    </div>
    <button id="animate">Animate</button>
    <script type="text/JavaScript">
    $('#animate').click(function () {
    	var scrollable = document.getElementById('scrollable');
    	$(scrollable).animate({scrollTop: scrollable.scrollHeight - scrollable.clientHeight}, 5000);
    });
    </script>
    </body>
    </html>
    


  • That's definitely what I want, but that's not what's happening.

    <!DOCTYPE html>
    
    <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>TournyMasterBot - Simple Chat Display</title>
    	<link href="Style.css" rel="stylesheet" />
    	<script src="Scripts/jquery-2.1.1.min.js"></script>
    	<script>
    		function AddChatRow(message) {
    			var chatData = JSON.parse(message);
    			//alert(chatData.MessageUsername);
    			var table = document.getElementById("ChatTextArea");
    			var lastRow = table.rows.length;
    			var row = table.insertRow(lastRow - 1);
    			var ChatUser = row.insertCell(0);
    			var ChatMessage = row.insertCell(1);
    			ChatUser.innerHTML = chatData.UserBadges + "<font color=\"" + chatData.UserColor + "\">" + chatData.MessageUsername + "</font>:";
    			ChatUser.className = "ChatUser"
    			ChatUser.id = "ChatUser"
    			ChatMessage.innerHTML = chatData.UserMessage;
    			ChatMessage.className = "ChatMessage"
    			ChatMessage.id = "ChatMessage"
    			$(table).animate({ scrollTop: table.scrollHeight - table.clientHeight }, 5000);
    		}
    
    		
    	</script>
    </head>
    <body>
    	<div class="Container">
    		<div class="HeightTaker">
    			<div id="ChatContainer" class="ChatContainer Wrapper Container Inverse">
    				<table id="ChatTextArea" class="ChatTextArea">
    					<thead>
    					</thead>
    					<tbody>
    						<tr id="ChatRowData">
    							<td class="ChatUser">TournyMasterBot:</td>
    							<td class="ChatMessage">Thank you for using <a href="http://bot.menu">Bot.Menu</a> Chat Viewer version 0.0.0.1</td>
    						</tr>
    						<tr class="LastRow">
    							<td class="ChatUser"></td>
    							<td class="ChatMessage"></td>
    						</tr>
    					</tbody>
    				</table>
    			</div>
    		</div>
    	</div>
    </body>
    </html>
    

    Possibly relevant: I have jquery-2.1.1.min.js as my jquery version



  • What are you seeing instead?

    I'm thinking what's happening is that the scroll is happening when you add the new row to the DOM, before jQuery gets its mitts on it.

    You might try this order:

    1. save scrollTop on the table/whatever container
    2. append row
    3. restore saved scrollTop
    4. do jQuery animate


  • There's no animation at all. Testing though - vind is getting the div container as the scrollable object, whereas i'm using the table variable thats the literal table element.



  • Does your table have a scrollbar? scrollTop is meaningless in an element without a scrollbar, and I don't think tables have them.

    Put your table in a DIV, set the DIV's height to 100% and overflow: scroll, then scroll the DIV.

    EDIT: in which case you can can the code I put above to save and restore scrollTop, that would be unnecessary. But you do need to set a style so the scrollbar won't appear or it'll look ugly as shit.



  • My table (and div) do not have scrollbars. Can you create hidden scrollbars? (No scrollbar is a design choice)



  • Can you show the CSS that you use?

    One additional problem I see is that you don’t create the new row at the end of the table, but before LastRow (if I’m not mistaken). If you do this, LastRow will be temporarily pushed out view when you insert a row, then the animation will put it back into place. You should probably put that row in another non-scrollable table/div.



  • Of course. I don't know the CSS for it off the top of my head, but tons of sites do that.



  • /*Editing this document changes the chat display*/
    
    /* This changes the entire page */
    body
    {
    	background-color: ghostwhite;
    }
    
    /* Modifys the html page, and containers - these are used to size the table*/
    html, body, .Container
    {
        height: 100%;
    	overflow: hidden;
        margin:0;
        padding:0;
    }
    .Container:before
    {
        content: '';
        height: 100%;
        float: left;
    }
    .HeightTaker
    {
        position: relative;
        z-index: 1;
    }
        .HeightTaker:after
        {
            content: '';
            clear: both;
            display: block;
        }
    .Wrapper
    {
        position: absolute;
        width: 100%;
        height: 100%;
        overflow: hidden;
    }
    
    .Inverse, .Inverse > *
    {
        -moz-transform: rotateX(180deg);
        -ms-transform: rotateX(180deg);
        -o-transform: rotate(180deg);
        -webkit-transform: rotateX(180deg);
        transform: rotateX(180deg);
    }
    
    .ChatContainer
    {
    	height: 99%;
    	margin: 0px 0px 0px 0px;
    }
    /* End content container setup */
    
    
    /* Manage Chat specific container settings */
    table, th, td
    {
    	padding: 2px 5px 2px 5px;
    	border: 0px solid black;
    	height: auto;
    	overflow-wrap:break-word;
    	word-wrap: break-word;
    	overflow:hidden;
    	
    }
    
    table
    {
    	table-layout: fixed;
    	width: 100%;
    }
    
    tr
    {
    	
    }
    
    td
    {
    	font-family: 'Segoe UI';	
    }
    
    /* Div that wraps all of the chat */
    .ChatTextArea
    {
    	
    }
    
    /* Username display div */
    .ChatUser {
    	width: 120px;
    	text-align: left;
    	vertical-align: top;
    	word-wrap: break-word;
    	font-size: 14px;
    	font-weight: bold;
    	vertical-align: top;
    }
    
    /* User chat div */
    .ChatMessage {
    	width: 100%;
    	text-align: left;
    	vertical-align: top;
    	word-wrap: break-word;
    	font-size: 14px;
    	font-weight: normal;
    	vertical-align: top;	
    }
    
    /* The placeholder last row, you shouldn't modify this. */
    .LastRow {
    	height: 100%;
    	border-left: none;
    	border-right: none;
    	border-bottom: none;
    }
    
    /* End chat message setup */
    


  • @Matches said:

    Can you create hidden scrollbars?

    That’s what overflow:hidden does.



  • I know overflow: hidden does that, I was responding to

    @blakeyrat said:

    Put your table in a DIV, set the DIV's height to 100% and overflow: scroll, then scroll the DIV.



  • @VinDuv said:

    That’s what overflow:hidden does.

    You could do it that way, but make sure your Y overflow is still scroll. You only want the X overflow to be hidden. (If you set Y overflow to hidden, you won't be able to scroll the DIV at all. Not even manually.)

    Then arrange the element so the scrollbar is offscreen or behind something that'll hide it.



  • Your scrollable item here is #ChatContainer, not #ChatTextArea (it’s the one with a fixed height and an overflow) -- try replacing

    $(table).animate({ scrollTop: table.scrollHeight - table.clientHeight }, 5000);
    

    with

    var scrollable = document.getElementById('ChatContainer');
    $(scrollable).animate({ scrollTop: scrollable.scrollHeight - scrollable.clientHeight }, 5000);
    


  • Yeah, I did that, it didn't work :D<Kill the emoticon>

    Namely, if i use '5000' or '0', it's exactly the same display speed, and line 'shuffle' speed. (Not you slow scroll demo)



  • Where does the scrollbar appear currently? Attached to a DOM element, or Window?

    @Matches said:

    Namely, if i use '5000' or '0', it's exactly the same display speed, and line 'shuffle' speed.

    Which is what? Instant? 1 second? 17 seconds?



  • There is no scrollbar at all, it's a c# winform application that is a docked webbrowser control using IE 11 emulation.

    @blakeyrat said:

    Which is what? Instant? 1 second? 17 seconds?

    instant



  • If it's a docked web browser, there's a scrollbar. You may have disabled it in WinForms, though, which means it won't appear on the screen but it still exists.

    Do you have a way of testing this code with a real JS debugger?

    EDIT: to rephrase my question somewhat, what's causing it to scroll currently?

    Adding a new row to a table, making it taller than the viewport, will cause the scrollbar to appear (if it was previously hidden), but it won't cause the viewport to scroll.

    So why is yours scrolling one line at a time?

    EDIT EDIT: and to guess, I'd say you hid the scrollbar in WinForms, but you're manipulating it somewhere with a IEControl.ScrollTo(1000000000);.



  • The best choice there would be accessing the actual html page output (I point it at a local HTML file, pasted above) and using chrome debugging tools.

    I don't readily have a way to test feeding the javascript row in, but the JSON that generates it is this:

    {"MessageService":"Twitch","MessageChannel":"botmenu","MessageTimestamp":"2014-10-05T17:30:57.2695767Z","MessageReceivedTimestamp":"2014-10-05T17:30:57.2695767Z","MessageUsername":"tournymasterbot","UserRole":null,"UserSubscriber":null,"UserFollower":null,"UserOwner":"false","UserStaff":null,"UserBadges":"","UserColor":"#B22222","UserMessage":"asdfg"}
    

    If I knew how to use the JS debugger better, I could probably use chrome console



  • @blakeyrat said:

    You could do it that way, but make sure your Y overflow is still scroll. You only want the X overflow to be hidden. (If you set Y overflow to hidden, you won't be able to scroll the DIV at all. Not even manually.)

    See my previous sample:
    http://what.thedailywtf.com/t/javascript-jquery-new-table-row-graceful-slide-in/3772/10?u=vinduv
    (nice oneboxing there…)

    overflow is hidden in both directions, but I’m still able to use JavaScript to scroll in the Y direction.



  • The 'scroll' happens because I'm shoving a row in at the bottom of the table, but not moving the view - so the table just pushes itself up off the top of the screen.



  • @VinDuv said:

    overflow is hidden in both directions, but I’m still able to use JavaScript to scroll in the Y direction.

    Even in Firefox?

    If so, that's relatively recent. Didn't work a year and a half ago.



  • @Matches said:

    The 'scroll' happens because I'm shoving a row in at the bottom of the table, but not moving the view - so the table just pushes itself up off the top of the screen.

    But that doesn't happen naturally in a web browser, is what I'm saying. There's something in WinForms causing that.

    EDIT: you aren't removing a row from the top of the table as you add one to the bottom, are you?



  • Sorry, i think the specific behavior is caused by:

    <div class="HeightTaker">
    			<div id="ChatContainer" class="ChatContainer Wrapper Container Inverse">
    
    html, body, .Container
    {
        height: 100%;
    	overflow: hidden;
        margin:0;
        padding:0;
    }
    .Container:before
    {
        content: '';
        height: 100%;
        float: left;
    }
    .HeightTaker
    {
        position: relative;
        z-index: 1;
    }
        .HeightTaker:after
        {
            content: '';
            clear: both;
            display: block;
        }
    .Wrapper
    {
        position: absolute;
        width: 100%;
        height: 100%;
        overflow: hidden;
    }
    
    .Inverse, .Inverse > *
    {
        -moz-transform: rotateX(180deg);
        -ms-transform: rotateX(180deg);
        -o-transform: rotate(180deg);
        -webkit-transform: rotateX(180deg);
        transform: rotateX(180deg);
    }
    
    


  • Ok well if you dive into complex CSS, you've lost me. Just refactor your code until it matches the known-working JSFiddle page.



  • Laymans terms: It just flips the X axis 180 degrees so that instead of populating things at the top of the div, it populates it at the bottom.



  • Can you change .Wrapper’s overflow: hidden to overflow:auto and see if you get a scrollbar which scrolls across the right content?



  • That's... an interesting solution?



  • I'm open to gutting it and doing anything else, it was found on the interwebs and appeared to do what I wanted. CSS hackery at it's best.

    @VinDuv

    Fuck you discourse, quit eating my @mention when you're replacing my image.



  • @Matches said:

    Laymans terms: It just flips the X axis 180 degrees so that instead of populating things at the top of the div, it populates it at the bottom.

    Now that sounds suspicious… I’m not sure how it interacts with scrolling (my guess would be “very badly”).



  • I'm surprised that 1) it works, and 2) someone actually THOUGHT of that. To solve a problem that's literally a one-liner in JS ("element.scrollTop = -9999999").



  • Hey, I didn't claim to know anything about web UI, I'm normally a winform / library coder. Web is all voodoo and black magic to me. The number of WTFs in my 2 page setup probably exceeds a ratio of @accalia's 6:1 for sockbot.



  • To ask a stupid question, why don't you just use WinForm's superior drawing and animation tools for this?

    I'm guessing the answer is, "because you also want a view available from a browser", but I figured I'd ask. At least you'd be able to properly debug C# code without crossing the "browser COM object layer" and getting into no-man's land.



  • Ultimately, it's because I want a hosted solution where the server runs the core services, and the MVC application runs this front end client (with some modifications to handle user sessions and only needing one feed for a channel instead of many connections to the same channel for different users for viewing purposes)

    So you're not far off with the 'view in browser' statement.



  • For something like this, you'd be better-off debugging the browser version of it first, then installing that in the WinForm once it's known to be working.


  • SockDev

    @Matches said:

    @accalia's 6:1 for sockbot.

    i suspect i'm failing utterly for sockbot. the WTF:SLOC ratio is not nearly that bad...



  • Certainly true, this version is easier to deliver prototype changes of than the full MVC solution though. (The MVC solution is probably 60-75% complete with still a few weeks of effort left. This single page prototype took about a full day of hacking the relevant MVC pieces out, and into a two page setup)

    I'll keep looking for a solution to scroll, since this will impact my MVC version too.



  • Well the solution is to not do hacky shit in CSS to replace one-liners in JS. Especially when the app already requires JS. Haha.



  • So show me how to do the one liner with js



  • It's already in this thread. Even the animated version, if you allow jQuery one-liners.


Log in to reply
 

Looks like your connection to What the Daily WTF? was lost, please wait while we try to reconnect.