DailyJS

DailyJS

The JavaScript blog.


Tagserver
Featured

server node browser

jQuery Roundup: Ideal Forms 3, httpinvoke, jquery.brightness.js

Posted on .

Ideal Forms 3

Ideal Forms 3 (GitHub: elclanrs / jq-idealforms, License: GPL/MIT) by Cedric Ruiz is a library for working with responsive HTML5 forms. It uses JavaScript to adapt form controls to the size of the container, so it works without using media queries. It supports keyboard shortcuts, custom checkboxes and file inputs, and a date picker.

The CSS now uses Stylus, and it uses classes in the HTML markup for denoting field types.

httpinvoke

httpinvoke (GitHub: jakutis / httpinvoke, License: MIT, npm: httpinvoke, bower: httpinvoke) by Vytautas Jakutis is a module for making HTTP requests that works with Node and browsers. It's designed with a focus on promises, and Node's callback-style API:

httpinvoke('http://example.org', 'GET').then(function(res) {  
  console.log('Success', res.body, res.statusCode, res.headers);
}, function(err) {
  console.log('Failure', err);
});

The readme has full documentation for each API style and the accepted options. It's unit tested, and also available through Bower.

jquery.brightness.js

jquery.brightness.js is a small plugin by Kozo Yamagata that detects CSS background colours and returns the brightness. Calling $(selector).brightness() returns either 'light' or 'dark'. It's distributed as a Gist as it's a short and sweet little snippet.

Featured

server node browser

Harp

Posted on .

Harp

Alex Griekspoor sent me Harp (GitHub: sintaxi / harp, License: MIT, npm: harp) by Brock Whitten, a static web server with a focus on client-side scripting. It's built on connect, but shifts the responsibility of building client-side projects to the server:

I wanted a lightweight web server that was powerful enough for me to abandon web frameworks for dead simple front-end publishing.

It'll run as a command-line tool or Node module, so you can fit it into existing projects and use it for local development.

If you look at the source it's mostly based around middleware. A major chunk of this is terraform, a preprocessor that handles partials, layouts, metadata, and caching. Terraform is available through npm, so you could use it from outside of Harp.

I've worked with single page web apps that are entirely server independent, or traditional client/server applications. This approach mixes both of these paradigms, and seems to be generating a lot of interest. I found a recent Hacker News thread about it, and an introductory blog post that explains the development history so far.

Featured

tutorials server node lmawa nodepad npm

Node Tutorial Part 23: npm 1.0

Posted on .

Welcome to part 23 of Let's Make a Web App, a tutorial series about
building a web app with Node. This series will
walk you through the major areas you'll need to face when building your
own applications. These tutorials are tagged with
lmawa.

Click to show previous tutorials.

npm 1.0

I upgraded to npm 1.0.3 recently. It generated a list of packages that
were incompatible, I haven't bothered reinstalling them yet.

Isaac's installation instructions seem to have solidified at:

curl http://npmjs.org/install.sh | sh

... but read the npm README before doing anything!

Changes in Nodepad

It takes a while to get used to npm 1.x, but I like the changes. A big
change is the default installation path: running npm install
package
will install package in the local
./node_modules folder.

What this means for Nodepad is we no-longer need the
require('package@version') syntax.

I've updated Nodepad to work with npm 1.0: commit
52e6b1
.

npm install

From Nodepad's directory, running npm install will build
and install the dependencies to ./node_modules. That means
it's completely self-contained from everything else on your system.
Running npm g install will install all of the
dependencies in NODE_PATH/nodepad/ - again, making the
dependencies self-contained.

Using Nodepad is now fairly simple:

$ git clone git://github.com/alexyoung/nodepad.git
$ cd nodepad
$ npm install
$ mongod
$ node app.js

This is very similar to how npm bundle used to work. When
using Express apps written by other people, their documentation may
suggest running npm bundle, but npm install
should work.

Search vs. List

The npm ls command used to search available packages. It
now lists local packages:

$ npm ls -g
├─┬ express@2.3.2 
│ ├── connect@1.4.0 
│ ├── mime@1.2.1 
│ └── qs@0.1.0 
├── highlight@0.1.0 
├── jade@0.10.6 
├── markdown@0.2.1 
├── n@0.4.1 

Using npm search will search remote packages:

$ npm search nodepad
nodepad  A notepad written with Node  =alexyoung

Why the Change?

Isaac has been blogging and discussing npm 1.0 for some time on the
Node blog and npm discussion group. A major contributing
factor is Node's 0.4 changes to the module loading system. These changes
were mentioned in the Node 0.4
announcement
:

require() now has a primitive understanding of package.json. It looks for the main script. This allows require() to work on package directories directly.

And:

A specially named directory, node_modules/, is searched in the current directory for any modules. This will hopefully encourage programmers to bundle modules rather than rely on a global namespace.

It might feel awkward to have to adapt existing applications to work
with npm 1.0, but the changes should be minimal and should make module
management easier for everyone.

Featured

tutorials server node lmawa nodepad backbone.js

Node Tutorial Part 22: Backbone (Again)

Posted on .

Welcome to part 22 of Let's Make a Web App, a tutorial series about
building a web app with Node. This series will
walk you through the major areas you'll need to face when building your
own applications. These tutorials are tagged with
lmawa.

Click to show previous tutorials.

I hadn't finished rebuilding the search interface using Backbone. The
old code used simple jQuery events and Ajax to manage the search field,
form submission, and document list. The search text input management
looked like this:

$('input[name="s"]').focus(function() {
  var element = $(this);
  if (element.val() === 'Search')
    element.val('');
});

$('input[name="s"]').blur(function() {
  var element = $(this);
  if (element.val().length === 0)
    element.val('Search');
});

This translates quite easily to Backbone using a view and some events:

var SearchView = Backbone.View.extend({
  el: $('#header .search'),

  events: {
    'focus input[name="s"]': 'focus',
    'blur input[name="s"]': 'blur'
  },

  focus: function(e) {
    var element = $(e.currentTarget);
    if (element.val() === 'Search')
      element.val('');
  },

  blur: function(e) {
    var element = $(e.currentTarget);
    if (element.val().length === 0)
      element.val('Search');
  }
}

The view's main element is the search form, and the focus
and blur events are bound in a similar way to the old code.

Next I instantiate the class in AppView, which acts as a
main container view:

AppView = Backbone.View.extend({
  initialize: function() {
    this.documentList = new DocumentList();
    this.searchView = new SearchView();
  }
});

Working With DocumentList

The existing DocumentList manages the left-hand-side list
of documents, which the search code needs to work with. Using the old
code could have confused things a little bit here, because it altered
the raw HTML rather than working through Backbone.

This is the perfect place for the Show All link that appears when a
search is active:

DocumentList = Backbone.View.extend({
  el: $('#document-list'),
  Collection: Documents,

  // This supports the search system:
  events: {
    'click #show-all': 'showAll',
  },

  initialize: function() {
    _.bindAll(this, 'render', 'addDocument', 'showAll');
    this.Collection.bind('refresh', this.render);
  },

  // ...

  showAll: function(e) {
    e.preventDefault();
    this.el.html('');
    this.Collection.fetch();
    appView.searchView.reset();
  }

This code is mostly the same, but it adds the showAll
method and event binding. Using this.Collection.fetch will
reload all the documents from the server.

As an aside: I was looking at a way of making this work with a
subclassed version of the DocumentList collection that uses
/search.json instead of the usual document list URL, but I
couldn't find a way of making Backbone POST with a request
parameter.

Searching

To actually search, I've used the original jQuery Ajax call:

SearchView = Backbone.View.extend({
  el: $('#header .search'),

  events: {
    'focus input[name="s"]': 'focus',
    'blur input[name="s"]': 'blur',
    'submit': 'submit'
  },

  initialize: function(model) {
    _.bindAll(this, 'search', 'reset');
  },

  focus: function(e) {
    var element = $(e.currentTarget);
    if (element.val() === 'Search')
      element.val('');
  },

  blur: function(e) {
    var element = $(e.currentTarget);
    if (element.val().length === 0)
      element.val('Search');
  },

  submit: function(e) {
    e.preventDefault();
    this.search($('input[name="s"]').val());
  },

  reset: function() {
    this.el.find("input[name='s']").val('Search');
  },

  search: function(value) {
    $.post('/search.json', { s: value }, function(results) {
      appView.documentList.el.html('Show All');

      if (results.length === 0) {
        alert('No results found');
      } else {
        for (var i = 0; i < results.length; i++) {
          var d = new Document(results[i]);
          appView.documentList.addDocument(d);
        }
      }
    }, 'json');
  }
});

I've also added the reset method which sets the search
input to Search after pressing Show All.

Notice that DocumentList.addDocument is reused here. In a
larger application, this kind of reuse is very important and is where
Backbone starts to show its value. Another thing to note is
SearchView could easily be in its own file, allowing us to
avoid a monolithic client-side JavaScript file that becomes hard to
manage.

Conclusion

Writing good code with client-side frameworks like Backbone requires a
lot of work, but it does take away some of the drudgery when working
with data and binding it to interfaces. The search code looks cleaner
than it did before, but even though it's quite simple it would be
interesting to refactor it.

This week's commit was
2b8e083.

Featured

tutorials server node lmawa nodepad

Node Tutorial Part 21: Connection Management

Posted on .

Welcome to part 21 of Let's Make a Web App, a tutorial series about
building a web app with Node. This series will
walk you through the major areas you'll need to face when building your
own applications. These tutorials are tagged with
lmawa.

Click to show previous tutorials.

Package Updates

I've updated all the packages to the latest versions. Express is now on
2.2.2!

HTML Preview

In the Backbone.js tutorials we already added a stub to
DocumentControls called showHTML. This is
meant to show the HTML preview, which can be obtained by viewing a
document with .html appended to the URL. I added a
convenience function to the model for getting the right URL:

  Document = Backbone.Model.extend({
    Collection: Documents,

    url: function() {
      return this.urlWithFormat('json');
    },

    urlWithFormat: function(format) {
      return this.get('id') ? '/documents/' + this.get('id') + '.' + format : '/documents.json';
    },

    // ...

Then added the event 'click #html-button': 'showHTML' to
DocumentControls and updated showHTML:

showHTML: function(e) {
  var model = this.model;
  e.preventDefault();
  $.get(this.model.urlWithFormat('html'), function(data) {
    console.log($(window).height());
    $('#html-container').html(data);
    $('#html-container').dialog({
      title: model.get('title'),
      autoOpen: true,
      modal: true,
      width: $(window).width() * 0.95,
      height: $(window).height() * 0.90
    });
  });
}

I'm using dialog from jQuery UI to display the contents of
the HTML document.

Express Responses

It was possible to make some of my server-side methods hang, like this
one:

app.post('/search.:format?', loadUser, function(req, res) {
  Document.find({ user_id: req.currentUser.id, keywords: req.body.s ? req.body.s : null },
                [], { sort: ['title', 'descending'] },
                function(err, documents) {
    switch (req.params.format) {
      case 'json':
        res.send(documents.map(function(d) {
          return { title: d.title, id: d._id };
        }));
      break;
    }
  });
});

This is poor style because an unrecognised format would cause the app to
hang. You should avoid this as much as possible when building Express
apps and always send something back to the browser:

app.post('/search.:format?', loadUser, function(req, res) {
  Document.find({ user_id: req.currentUser.id, keywords: req.body.s ? req.body.s : null },
                [], { sort: ['title', 'descending'] },
                function(err, documents) {
    switch (req.params.format) {
      case 'json':
        res.send(documents.map(function(d) {
          return { title: d.title, id: d._id };
        }));
      break;

      // Here
      default:
        res.send('Format not available', 400);
      break;
    }
  });
});

TJ Holowaychuk pointed this out to me in the comments.

I'm not sure if a 400 Bad Request is the best way to respond though.
What about 406 Not Acceptable? I'm sure there's a standard convention,
but I read through part of
rfc2616 and couldn't decide.

An additional way to safeguard against hung connections is by using
middleware to disconnect them after a period of time. The
Connect-timeout middleware by Guillermo Rauch could be used to do this. Long-running
connections are still allowed with this middleware by using the
clearTimeout method which gets added to the request
objects.

It's fairly easy to use:

var connectTimeout = require('connect-timeout@0.0.1'),
    // ...

app.configure(function() {
  app.use(connectTimeout({ time: 10000 }));
  // ...

There are options for error code and time.

This week's code is commit
2fde220
.