Wednesday, October 3, 2012

Writing a terminal app with MeteorJS

Since Javascript is nowadays dominating more and more my daily web developing activities, I've been more interested in extending its usage to server side. A few days ago, while reading articles about Node.js, I found out a new excellent JS framework build on top of Node.js.

MeteorJS is a new open source framework build on Node.js. It's developed for concurrent browsing experience for web apps that are being simultaneously used by number of people. Building concurrent web apps like live chats, Google docs etc. can be achieved in a friction of time.

By default, Meteor uses build-in MongoDB for data storing which is quite easy to adapt. In Meteor's official examples, Handlebars is used for templating system but also alternative approaches such as AngularJS are supported. Meteor uses its own package manager, Smart Packages, providing support for favourite JS add-on's like jQuery.

If you feel like getting more familiar with Meteor, I suggest reading an excellent blog post by Andrew Scala about Meteor Fundamentals and Best Practices.


Running shell commands with Meteor



I got a nice idea for an app to learn the basics of Meteor. The web app simulates the usage of terminal by having a command line as input field and a div block for shell output. The app is considered only for testing purposes and to demonstrate the functionality of Meteor in practice.

If you're new to Meteor, download the SDK and create a new project. Replace the HTML and JS files with ones disclosed below and run the app.


<head>
  <title>MeteorJS terminal</title>
</head>

<body>
  {{> terminal}}
</body>

<template name="terminal">
 <pre>{{ window }}</pre>
<input id="command" type="text" value="{{ last_cmd }}" />
 <input type="button" value="Run" />
</template>

app.html
// A collection for stdouts
var Replies = new Meteor.Collection('replies');

if(Meteor.is_client) {

 // Start listening changes in Replies
    Meteor.autosubscribe(function() {
        Meteor.subscribe('replies');
    });
 
 // Set an observer to be triggered when Replies.insert() is invoked
 Replies.find().observe({
  'added': function(item) {
   // Set the terminal reply to Session
   Session.set('stdout', item.message);
  }
 });

 // Show the last command in input field
 Template.terminal.last_cmd = function() {
  return Session.get('last_cmd');
 };
 
 // Show the last shell reply in browser
 Template.terminal.window = function() {
  return Session.get('stdout');
 };

 // Add an event listener for Run-button
 Template.terminal.events = {
  'click [type="button"]': function() {
   var cmd = $('#command').val();
   Session.set('last_cmd', cmd);
   
   // Call the command method in server side
   Meteor.call('command', cmd);
  }
 };


}

if(Meteor.is_server) {
 var exec;
 
 // Initialize the exec function
 Meteor.startup(function() {
  exec = __meteor_bootstrap__.require('child_process').exec;
 });

 // Trigger the observer in Replies collection
    Meteor.publish('replies', function() {
        return Replies.find();
    });

 Meteor.methods({
  'command': function(line) {
   // Run the requested command in shell
   exec(line, function(error, stdout, stderr) {
    // Collection commands must be executed within a Fiber
    Fiber(function() {
     Replies.remove({});
     Replies.insert({message: stdout ? stdout : stderr});
    }).run();
   });
  }
 });

}
app.js

No comments:

Post a Comment