Javascript: String.replace()

I’ve always used String.replace() in Javascript pretty much like this:

"Some <foo>".replace('<foo>', 'bar');
// 'Some bar'

Or with a regular expression if I was feeling fancy or the pattern to match was more complex:

"Some <foo>".replace(/<\w+>/, 'bar');
// 'Some bar'

"Some <baz> and a bit of <foo>".replace(/<\w+>/g, 'bar');
// 'Some bar and a bit of bar'

What I didn’t realize until this week is that you can also pass a callback function to replace(), which makes it possible to perform more complicated (and also possibly easier to follow) manipulation of the matched portions of the string before they’re replaced.

You could even use this to create a super simple (and, sure, probably inadvisable) templating system:

var variables = { foo: 'one', bar: 'two', baz: 'sea' };
var template = "{{foo}} if by land, {{bar}} if by {{baz}}";

template.replace(/\{\{(\w+)\}\}/g, function(match, group, offset, string) {
    return variables[group];
});
// 'one if by land, two if by sea'

The function is called once for each match within the string, and each parenthesized group in the match gets passed as a parameter to the callback function (which does mean the number of function parameters is variable, based on how many groups you have in your regular expression). Here I’ve used grouping to pull out the variable name inside the {{ }} delimiters and use it as an object property.

Not a Javascript feature I will use every day, but definitely one that will come in handy occasionally (and most likely be much cleaner than whatever code I might have written without it).

An art school lesson

The first studio class I attended my first week of college was three-dimensional design. I was extremely nervous, and things started strangely. The professor was absent, and had left a TA who instructed each student to each choose an art postcard from a collection pinned to the wall, with no further guidelines.

Once everyone in the class had chosen a postcard, we were told our assignment was to recreate these paintings in three dimensions. Newspaper and tape were distributed. We had a few hours to put together a rough proposal of our ideas for how to build our projects.

I can’t remember the artist or title of painting I chose, though they were all abstract or stylized in some way that made translating the image to three dimensions challenging.

What I do remember is what the professor said to me once I had hesitantly presented my idea to him: “That looks great. But you should make it twice that size.” A simple suggestion, but an intimidating one for me–I hardly knew how I was going to build this project at all, let alone at a large scale. But he was the professor, so I took his advice.

There wasn’t much for me to do after that except start working, so I did. What I realized is that you don’t have to know how the entire thing is going to come together when you begin something: just start with a small part, learn as you go, and build from there. It wasn’t completely smooth sailing, but my project turned out great, and I felt awesome about making something much more ambitious than I thought I could.

I still think about that experience often when I am starting something new. “Great, but make it bigger!” isn’t useful advice for everyone, but for me, as a person who tends to be more pragmatic and conservative than I need to be, it makes a big difference.

I don’t make ambitious art projects as often anymore, but I find “make it bigger!” applies well to programming, too.

When I started my collaborative drawing app, I barely knew how any of the parts would work. Handling real-time events seemed intimidating, and so did working with TCP sockets. But I figured I could learn enough to build a tiny terminal-based chat, and I did. Once that existed, rewriting what I had using EventMachine seemed straightforward, and then it didn’t seem so difficult to switch to using websockets instead.

Setting a goal that seemed slightly scary and working towards it a small piece at a time got me much farther than I would have thought I could at the beginning. An excellent thing to remember any time a new programming task or skill seems daunting.

The old year and the new one

2013 was probably the most eventful year I’ve had in my post-school life: in the space of ten days this past August, I got married, moved to a new (big!) city and started a new job as a “real” programmer.

Though it seems implausible even to me, I didn’t have any of these things planned at the beginning of 2013. I applied to Hacker School on a whim around New Year’s 2012, and deciding to attend set all of these things in motion. (Well, that, and the Rhode Island legislature finally getting their act together on gay marriage.)

I never did write a substantial Hacker School reflection post, but I am without question a better and more confident programmer, I have a bunch of new friends and my new job (at Photoshelter) makes me much happier every day. I’d definitely call that as a success.

I’m not one to talk about my personal life on the internet much (at least now that the livejournal chronicling my teenage years is gone, hah), but beyond all of the awesome tech-related things that I accomplished this year, getting to marry my favorite person was still the best.


I expect 2014 will be a little quieter, and I’m more than content with that, but I have put some thought into things I’d like to do this year. I’m being not-particularly-ambitious in the hopes I’ll be more likely to actually accomplish everything.

  • Finish reading and working through The Elements of Computing Systems (aka nand2tetris)
  • Work through at least a few chapters of SICP
  • Build some toy projects with AngularJS and React, and possibly give Clojure/ClojureScript a try
  • Blog more (I’m doing a friendly blog-post-a-week competition, starting this week, so I hope it will help!)
  • More generally, do more substantial things and fewer bite-size things (e.g.: less Twitter, more books; less tiny iOS puzzle games, more getting through the huge backlog of things I would like to play …)

I was thinking along these lines before I stumbled upon this blog post, but Michael Lopp’s essay The Builder’s High is a more eloquent explanation of my fuzzier thoughts.

Full-bleed images with TCPDF

The current PHP project I’m working on needs to generate PDFs, and I’m using the TCPDF class to do so. TCPDF, while powerful, has documentation that is … not very user-friendly, to put it mildly.

I ran into problems trying to display an SVG file as a full-bleed image (that is, I didn’t want white space between the edges of the PDF document and the image). I would set the document size and the SVG display size to the same values, and set the document margins to zero, and yet, infuriatingly, my SVG file would display just slightly smaller than the full page size.

After some searching and experimentation, I found another margin property that needed to be changed. To remove all of the margins on a TCPDF-generated PDF file, you need to set the bottom margin of each page via the setPageOrientation method as well as setMargins.

The bottom margin was creating whitespace on the file’s right edge as well, because the SVG file was maintaining its original aspect ratio.

The full code to remove all of the margins on the page was this (the header and footer also have to be disabled):

<?php
$pdf = new TCPDF(/* ... */);
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pdf->setMargins(0, 0, 0, true);
$pdf->setPageOrientation('', false, 0);
// the third argument above is the bottom margin size

Getting started with Grunt

I love Grunt. I wouldn’t want to work on a JavaScript project much bigger than a file or two without it, now that I’ve realized how much time and hassle it saves me.

But getting started using it involves a lot of friction. Even though I’m familiar with the tool I still drag my feet setting it up on a new project.

Yesterday, so that I would no longer have this problem, I created a template Gruntfile for myself. It contains the tasks I use on nearly every project:

  • JavaScript linting (with JSHint)
  • file concatenation and compression (using Uglify)
  • file watching (so that I don’t have to invoke Grunt manually every time I change a file)
  • Growl notifications for warnings and errors (so you don’t have to waste screen space keeping your terminal visible)

With the exception of Growl, these are all official Grunt packages.

This would be a great starter configuration for someone new to Grunt, and once you have this setup working it is easy to find and add new tasks. Using it is pretty straightforward, but I’ll explain the important details below. (I’ve also saved this file as a gist if you want to save your own changes.)

module.exports = function(grunt) {
  var defaultTasks = ['jshint', 'concat', 'uglify']; // used for watch as well

  var files = [ /* your source files */ ];

  grunt.initConfig({
    jshint: { all: files.concat(['Gruntfile.js']) },

    concat: {
      all: {
        src: files,
        dest: 'project.all.js',
      }
    },

    uglify: {
      all: { files: { 'project.min.js': files } }
    },

    watch: {
      all: {
        files: files,
        tasks: defaultTasks,
        options: { debounceDelay: 250 }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-jshint');

  grunt.registerTask('default', defaultTasks);

  var growl = require('growl');

  ['warn', 'fatal'].forEach(function(level) {
    grunt.util.hooker.hook(grunt.fail, level, function(opt) {
      growl(opt.name, {
        title: opt.message,
        image: 'Console'
      });
    });
  });
};

A note: These instructions assume you are using OSX.

Requirements

Grunt requires Node.js and npm to be installed (npm, Node’s package manager, is included in the Node installer).

Grunt comes in two parts, one that only needs to be installed once and a second that is installed for each project.

The first time you set up Grunt, you should run npm -g install grunt-cli. This makes the grunt command available on your system. (More information here.)

Then, from the root directory of the project you’d like to use Grunt with, run the following to install the packages you need via npm:

npm install grunt
npm install grunt-contrib-concat
npm install grunt-contrib-uglify
npm install grunt-contrib-watch
npm install grunt-contrib-jshint
npm install growl

The Growl notifications also require that Growl be installed, as well as the Growl command line tool growlnotify (downloadable here).

Configuration

You should save the Gruntfile to your project’s root directory with the filename Gruntfile.js.

Gruntfiles are just JavaScript, so you can use variables and functions as you normally would. I’ve set up this Gruntfile so that you only have to specify the locations of your project’s JavaScript files once, at the top of the file.

var files = [ 'main.js', 'src/**/*.js' ];

This array of file paths will be passed to each Grunt task automatically. Grunt supports glob paths as well (so src/**/*.js will include every file with the extension .js in any subfolder of src).

If you need specify files in a more particular fashion, the supported syntax is extremely flexible and powerful, but I rarely find myself needing that much complexity.

To actually use the tasks you’ve configured, run grunt watch from the root directory of your project. Your JavaScript files will be automatically checked for errors, concatenated and minified each time you modify a file, and your JavaScript build process will be much less tedious!