Shuhei Kagawa

Clean up before exiting in Haskell

Apr 6, 2016 - Haskell

Once upon a time (or a several days ago), I was reading Programming in Haskell. When I ran 9.7's Game of Life, which shows Game of Life animation on the terminal, the terminal's cursor was flickering and annoying. So I tried to hide it when starting and show when exiting.

import System.Process (system)

main :: IO ()
main = do
  -- Hide the cursor
  system "tput civis"
  -- Show the Game of Life
  life glider
  -- Show the cursor (but the code does not reach here!)
  system "tput cvvis"
  return ()

life :: Board -> IO ()
glider :: Board

But the code does not reach the line that shows the cursor because life is a infinite loop. If I quit the program with Ctrl+C, the cursor remains hidden.

So I wrote a function that loops a -> IO a until interrupted by a signal, referring to unix - Killing a Haskell binary - Stack Overflow. It manages a state of whether the program was interrupted in a MVar and stops the loop when interrupted.

import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, tryTakeMVar)
import System.Posix.Signals (Handler, Handler(CatchOnce), installHandler, sigINT, sigTERM)

loopUntilInterruption :: (a -> IO a) -> a -> IO ()
loopUntilInterruption p init = do
  v <- newEmptyMVar
  installHandler sigINT (handler v) Nothing
  installHandler sigTERM (handler v) Nothing
  loop v p init

handler :: MVar () -> Handler
handler v = CatchOnce $ putMVar v ()

loop :: MVar () -> (a -> IO a) -> a -> IO ()
loop v p prev = do
  x <- p prev
  val <- tryTakeMVar v
  case val of
    Just _ -> return ()
    Nothing -> loop v p x >> return ()

In the Game of Life, I changed the type of life so that it returns the result of its previous result and loop with loop. Now the clean up code will be called when interrupted by a signal.

import System.Process (system)

main :: IO ()
main = do
  -- Hide the cursor
  system "tput civis"
  -- Loop until interrupted
  loopUntilInterruption life glider
  -- Show the cursor (the code will reach here now!)
  system "tput cvvis"
  return ()

life :: Board -> IO Board
glider :: Board

And they lived happily ever after.

One-time binding for ng-if

Apr 5, 2016 - JavaScript, AngularJS

AngularJS's one-time binding is useful to reduce the number of watches. It stops watching its expression once it becomes defined. It kindly keeps watching while the value is undefined for cases like asynchronous data fetching. But the kindness can be a pitfall especially for directives that take boolean expressions like ng-if.

Here's an ordinary piece of AngularJS template. It shows 'Something' when obj.prop exists.

<div ng-if="::obj.prop">Something</div>

It works almost fine. But it keeps watching the expression when the message is hidden. Guess what?

Yes! It keeps watching while the expression is undefined. Let's make sure that it's always boolean.

<div ng-if="::!!obj.prop">Something</div>

Here we see the birth of a new operator ::!!!

Freeze panes with CSS and a bit of JavaScript

Jan 11, 2016 - CSS, JavaScript

People want fixed-header table. It reminds you what the columns are while you scroll down the table. There are a bunch of fixed-header-table tutorials out there. But most of them kill one of the greatest features of HTML table. The automatic sizing of cells according to their contents. This is because they usually prepare separate <table>s for header and body.

Also, most of the tutorials only fix a header at the top. But my colleagues wanted more. They wanted to fix headers at the top and left. Just like Microsoft Excel's Freeze Panes feature. So what we want now is:

  1. Automatic sizing of cells according to their contents
  2. Freeze panes

We want to use only one <table> to easily achive the goal 1. But at the same time, we want to separate the movement of the headers from other cells to achieve the goal 2. Then CSS3's transform property came to my mind. It allows you to transform only rendering of an element without interfering its sibling nodes.

So I gave it a shot and here's the result.

JS Bin on jsbin.com

transform: translate()

I used JavaScript to dynamically set transform: translate(x, y); to the header cells sending them back to the top/left edge of the container.

container.addEventListener("scroll", function() {
  var x = container.scrollLeft;
  var y = container.scrollTop;

  leftHeaders.forEach(function(leftHeader) {
    leftHeader.style.transform = translate(x, 0);
  });
  topHeaders.forEach(function(topHeader, i) {
    if (i === 0) {
      topHeader.style.transform = translate(x, y);
    } else {
      topHeader.style.transform = translate(0, y);
    }
  });
});

function translate(x, y) {
  return "translate(" + x + "px, " + y + "px)";
}

border-collapse: separate;

Another trick is border-collapse: separate;. We usually use border-collapse: collapse; for tables but it leaves borders behind the cells. With border-collapse: collapse;, we can make the borders transformed together with the header cells.

table {
  border-collapse: separate;
  border-spacing: 0;
}

th,
td {
  border-bottom: 1px solid #ccc;
  border-right: 1px solid #ccc;
}

Dummy top left header cell

The last trick is a kind of shame. It's the top left header cell that should be fixed horizontally and vertically. We can achieve it by the transform property but the other top header cells hides it. I tried to make it rise with z-index but didn't work. So I created a dummy element that had the same size with the cell.

var topLeft = document.createElement("div");
var computed = window.getComputedStyle(columnHeaders[0]);
container.appendChild(topLeft);
topLeft.classList.add("top-left");
topLeft.style.width = computed.width;
topLeft.style.height = computed.height;
.top-left {
  background: #eee;
  border-right: 1px solid #ccc;
  border-bottom: 1px solid #ccc;
  box-sizing: border-box;
  position: absolute;
  top: 0;
  left: 0;
}

Compatibility and performance

As far as I've tested with the latest Chrome, Firefox and Safari on Mac, it worked well without any performance issue. I'll test it with more large table on other browsers including IE and add the result later.