Monthly Archiv: February, 2015

Smartphone Remote Control with Node.js and Socket.io

smartphone-remote-control-for-your-presentation

Wouldn’t it be cool to use your smartphone as a remote control? It turns out it is not difficult at all! You don’t even need to know how to write native mobile apps – your phone has a fully capable web browser with support for web sockets, which opens a lot of possibilities. In this short tutorial, we are going to use Node.js and Socket.io to remotely control a presentation that is running on your laptop with your phone.

There are a lot of cool HTML5 presentation libraries out there and it would be a waste of effort to create this functionality from scratch. This is why we will be using Reveal.js – it will handle animations and transitions between slides, as well as support for keyboard and touch events.

We won’t be making a dedicated remote control interface. Instead, we will synchronize the presentation that is opened on your phone with that on your computer using websockets. This will allow you not only to control the presentation, but see its mobile version on your phone, which will help you keep track of the current slide.

The Idea

The technique we are going to use is very simple. Reveal.js puts the current slide number in the URL as a hash (e.g. http://example.com/#/1). We will send this hash to all other connected devices, which will transition them to the new slide automatically. This will make it possible for people to just open the URL in a browser and sabotaging your presentation, so we will require all devices to enter a pass code before connecting.

It is worth mentioning that Reveal.js already has an API, so we could have used that to synchronize the two presentations. But the hash change technique is simpler and will work with any presentation library, so we chose to go with it instead.

Our slideshow

Our slideshow

Running our Example

You can run this example locally, or by deploying it to a hosting provider with node.js support like Heroku. Running it locally is easier, but you must have node.js and npm installed. Running it on Heroku requires you to have the heroku toolbelt installed and signing up for an account.

To run our code locally:

  1. Download the code from the button near the beginning of the article.
  2. Make sure you have node.js installed. If needed, install it.
  3. Unzip the archive you downloaded to a folder.
  4. Open a terminal and cd to the folder.
  5. Run npm install to install the required libraries
  6. Run node app.js to start the presentation
  7. Open http://localhost:8080 on your computer and enter your pass key (by default it is “kittens“).
  8. Open http://<your computer’s local ip address> on your phone and enter the same pass key.
  9. Have fun!

To run the code on Heroku:

  1. Download the code from the button near the beginning of the article.
  2. Unzip it to a folder.
  3. Open a terminal and cd to the folder.
  4. Create a git repository, and commit.
  5. Create a new Heroku App
  6. Run git push heroku master.
  7. Visit the URL of the app on every device you want to connect. The default pass key is “kittens”.

Read more about deploying node.js apps to heroku here. If you use a different cloud hosting provider, the last three steps will be different.

The Code

But enough talk, let’s see the code! There are only two JavaScript files – app.js for the server side, and script.js for the browser. You can run the app in Node.js 0.10+ or in io.js.

For the backend, we use express and socket.io. It’s main responsibility is listening for and responding to socket.io events. With express.static, we serve the files in the public folder to the world. We have a public/index.html file which contains the code for the presentation. It is served automatically by express.static, so we don’t need a “/” route.

app.js

// This is the server-side file of our mobile remote controller app.
// It initializes socket.io and a new express instance.
// Start it by running 'node app.js' from your terminal.


// Creating an express server

var express = require('express'),
	app = express();

// This is needed if the app is run on heroku and other cloud providers:

var port = process.env.PORT || 8080;

// Initialize a new socket.io object. It is bound to 
// the express app, which allows them to coexist.

var io = require('socket.io').listen(app.listen(port));


// App Configuration

// Make the files in the public folder available to the world
app.use(express.static(__dirname + '/public'));


// This is a secret key that prevents others from opening your presentation
// and controlling it. Change it to something that only you know.

var secret = 'kittens';

// Initialize a new socket.io application

var presentation = io.on('connection', function (socket) {

	// A new client has come online. Check the secret key and 
	// emit a "granted" or "denied" message.

	socket.on('load', function(data){

		socket.emit('access', {
			access: (data.key === secret ? "granted" : "denied")
		});

	});

	// Clients send the 'slide-changed' message whenever they navigate to a new slide.

	socket.on('slide-changed', function(data){

		// Check the secret key again

		if(data.key === secret) {

			// Tell all connected clients to navigate to the new slide
			
			presentation.emit('navigate', {
				hash: data.hash
			});
		}
	});
});

console.log('Your presentation is running on http://localhost:' + port);

And here is our JavaScript for the front-end, which listens for hashchange events and sends socket.io messages to the server.

public/assets/js/script.js

$(function() {

	// Apply a CSS filter with our blur class (see our assets/css/styles.css)
	
	var blurredElements = $('.homebanner, div.reveal').addClass('blur');

	// Initialize the Reveal.js library with the default config options
	// See more here https://github.com/hakimel/reveal.js#configuration

	Reveal.initialize({
		history: true		// Every slide will change the URL
	});

	// Connect to the socket

	var socket = io();

	// Variable initialization

	var form = $('form.login'),
		secretTextBox = form.find('input[type=text]');

	var key = "", animationTimeout;

	// When the page is loaded it asks you for a key and sends it to the server

	form.submit(function(e){

		e.preventDefault();

		key = secretTextBox.val().trim();

		// If there is a key, send it to the server-side
		// through the socket.io channel with a 'load' event.

		if(key.length) {
			socket.emit('load', {
				key: key
			});
		}

	});

	// The server will either grant or deny access, depending on the secret key

	socket.on('access', function(data){

		// Check if we have "granted" access.
		// If we do, we can continue with the presentation.

		if(data.access === "granted") {

			// Unblur everything
			blurredElements.removeClass('blurred');

			form.hide();

			var ignore = false;

			$(window).on('hashchange', function(){

				// Notify other clients that we have navigated to a new slide
				// by sending the "slide-changed" message to socket.io

				if(ignore){
					// You will learn more about "ignore" in a bit
					return;
				}

				var hash = window.location.hash;

				socket.emit('slide-changed', {
					hash: hash,
					key: key
				});
			});

			socket.on('navigate', function(data){
	
				// Another device has changed its slide. Change it in this browser, too:

				window.location.hash = data.hash;

				// The "ignore" variable stops the hash change from
				// triggering our hashchange handler above and sending
				// us into a never-ending cycle.

				ignore = true;

				setInterval(function () {
					ignore = false;
				},100);

			});

		}
		else {

			// Wrong secret key

			clearTimeout(animationTimeout);

			// Addding the "animation" class triggers the CSS keyframe
			// animation that shakes the text input.

			secretTextBox.addClass('denied animation');
			
			animationTimeout = setTimeout(function(){
				secretTextBox.removeClass('animation');
			}, 1000);

			form.show();
		}

	});

});

It’s Slideshow Time!

With this our smartphone remote control is ready! We hope that you find our experiment useful and have fun playing with it.

New Free Open Source C/C++ Compiler from Apple: write your own computer programs

The Clang compiler has been added to the Free C/C++ Compilers and Interpreters page. This is an open source compiler, developed primarily by Apple, that compiles C, C++, Objective C and Objective C++ code.

For those wondering why it took so long for this compiler to be listed here, I honestly thought it was already listed. Blame an overlong page that has so many compilers that I've forgotten what is listed and what is not. To date, it links to some 35 free C/C++ compilers, and that's only because I've split off a number of cross compilers to their own page.

Quick Tip: Add Keyboard Shortcuts To Your Web App

quick-tip-add-keyboard-shortcuts-to-your-web-app

We power users love our keyboard shortcuts. We use them everywhere – in our code editor, in Photoshop, in gmail. And we hate it when we hit Ctrl+S in a web app, only to see our browser offering to download it. Adding shortcuts to your application is not hard at all. In this quick tip, we will show you how to do it, by using Mousetrap.js.

1. Single Keys

Single keys are easy. You can do it with a simple event listener for keypress on the document object. But with Mousetrap, it is even better.

After hitting the run button, focus the editor by clicking on it. Otherwise, it won’t register your key presses and the demo won’t work.

(Play with our code editor on Tutorialzine.com)

2. Alternative Symbols

Mousetrap shines when listening for more complex key combinations like capital letters and special symbols.

(Play with our code editor on Tutorialzine.com)

3. Key combinations

Combinations that involve the Control key are equally easy (see the next example for how to listen for both Control and the OS X Command key).

(Play with our code editor on Tutorialzine.com)

4. Multiple Combinations

Passing an array instead of a string lets you listen for multiple key combinations at once. This is useful when you have to listen for combinations which involve the Control (for Windows and Linux) and Command (for Mac) keys.

(Play with our code editor on Tutorialzine.com)

5. Sequences

This type of shortcuts are very powerful, and are used in apps like gmail. Works with array keys as well!

(Play with our code editor on Tutorialzine.com)

Conclusion

This was our quick tip on keyboard shortcuts. If you’ve used keyboard hotkeys before, or are brave enough to experiment with them in your next project, do share the results with us in the comments below.

Making a Single Page App Without a Framework

making-a-simple-page-app-without-a-framework

The idea behind single page applications (SPA) is to create a smooth browsing experience like the one found in native desktop apps. All of the necessary code for the page is loaded only once and its content gets changed dynamically through JavaScript. If everything is done right the page shouldn’t ever reload, unless the user refreshes it manually.

There are many frameworks for single page applications out there. First we had Backbone, then Angular, now React. It takes a lot of work to constantly learn and re-learn things (not to mention having to support old code you’ve written in a long forgotten framework). In some situations, like when your app idea isn’t too complex, it is actually not that hard to create a single page app without using any external frameworks. Here is how to do it.

Note: To run this example after downloading it, you need a locally running webserver like Apache. Our demo uses AJAX so it will not work if you simply double-click index.html for security reasons.

The Idea

We will not be using a framework, but we will be using two libraries – jQuery for DOM manipulation and event handling, and Handlebars for templates. You can easily omit these if you wish to be even more minimal, but we will use them for the productivity gains they provide. They will be here long after the hip client-side framework of the day is forgotten.

The app that we will be building fetches product data from a JSON file, and displays it by rendering a grid of products with Handlebars. After the initial load, our app will stay on the same URL and listen for changes to the hash part with the hashchange event. To navigate around the app, we will simply change the hash. This has the added benefit that browser history will just work without extra effort on our part.

The Setup

Our project's folder

Our project’s folder

As you can see there isn’t much in our project folder. We have the regular web app setup – HTML, JavaScript and CSS files, accompanied by a products.json containing data about the products in our shop and a folder with images of the products.

The Products JSON

The .json file is used to store data about each product for our SPA. This file can easily be replaced by a server-side script to fetch data from a real database.

products.json

[
  {
    "id": 1,
    "name": "Sony Xperia Z3",
    "price": 899,
    "specs": {
      "manufacturer": "Sony",
      "storage": 16,
      "os": "Android",
      "camera": 15
    },
    "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam tristique ipsum in efficitur pharetra. Maecenas luctus ante in neque maximus, sed viverra sem posuere. Vestibulum lectus nisi, laoreet vel suscipit nec, feugiat at odio. Etiam eget tellus arcu.",
    "rating": 4,
    "image": {
      "small": "/images/sony-xperia-z3.jpg",
      "large": "/images/sony-xperia-z3-large.jpg"
    }
  },
  {
    "id": 2,
    "name": "Iphone 6",
    "price": 899,
    "specs": {
      "manufacturer": "Apple",
      "storage": 16,
      "os": "iOS",
      "camera": 8
    },
    "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam tristique ipsum in efficitur pharetra. Maecenas luctus ante in neque maximus, sed viverra sem posuere. Vestibulum lectus nisi, laoreet vel suscipit nec, feugiat at odio. Etiam eget tellus arcu.",
    "rating": 4,
    "image": {
      "small": "/images/iphone6.jpg",
      "large": "/images/iphone6-large.jpg"
    }
  }
]

The HTML

In our html file we have several divs sharing the same class “page”. Those are the different pages (or as they are called in SPA – states) our app can show. However, on page load all of these are hidden via CSS and need the JavaScript to show them. The idea is that only one page can be visible at a time and our script is the one to decide which one it is.

index.html

<div class="main-content">

	<div class="all-products page">

		<h3>Our products</h3>

		<div class="filters">
			<form>
				Checkboxes here
			</form>
		</div>

    <ul class="products-list">
      <script id="products-template" type="x-handlebars-template">​
        {{#each this}}
          <li data-index="{{id}}">
            <a href="#" class="product-photo"><img src="{{image.small}}" height="130" alt="{{name}}"/></a>
            <h2><a href="#"> {{name}} </a></h2>
            <ul class="product-description">
              <li><span>Manufacturer: </span>{{specs.manufacturer}}</li>
              <li><span>Storage: </span>{{specs.storage}} GB</li>
              <li><span>OS: </span>{{specs.os}}</li>
              <li><span>Camera: </span>{{specs.camera}} Mpx</li>
            </ul>
            <button>Buy Now!</button>
            <p class="product-price">{{price}}$</p>
            <div class="highlight"></div>
          </li>
        {{/each}}
      </script>

    </ul>

	</div>


	<div class="single-product page">

		<div class="overlay"></div>

		<div class="preview-large">
			<h3>Single product view</h3>
			<img src=""/>
			<p></p>

			<span class="close">&times;</span>
		</div>

	</div>

	<div class="error page">
		<h3>Sorry, something went wrong :(</h3>
	</div>

</div>

We have three pages: all-products (the product listing), single-product (the individual product page) and error.

The all-products page consists of a title, a form containing checkboxes for filtering and a <ul> tag with the class “products-list”. This list is generated with handlebars using the data stored in products.json, creating a <li> for each entry in the json. Here is the result:

The Products

The Products

Single-product is used to show information about only one product. It is empty and hidden on page load. When the appropriate hash address is reached, it is populated with product data and shown.

The error page consist of only an error message to let you know when you’ve reached a faulty address.

The JavaScript Code

First, lets make a quick preview of the functions and what they do.

script.js

$(function () {

	checkboxes.click(function () {
		// The checkboxes in our app serve the purpose of filters.
		// Here on every click we add or remove filtering criteria from a filters object.

		// Then we call this function which writes the filtering criteria in the url hash.
		createQueryHash(filters);
	});



	$.getJSON( "products.json", function( data ) {
		// Get data about our products from products.json.

		// Call a function that will turn that data into HTML.
		generateAllProductsHTML(data);

		// Manually trigger a hashchange to start the app.
		$(window).trigger('hashchange');
	});


	$(window).on('hashchange', function(){
		// On every hash change the render function is called with the new hash.
		// This is how the navigation of our app happens.
		render(window.location.hash);
	});


	function render(url) {
		// This function decides what type of page to show 
		// depending on the current url hash value.
	}


	function generateAllProductsHTML(data){
		// Uses Handlebars to create a list of products using the provided data.
		// This function is called only once on page load.
	}


	function renderProductsPage(data){
		// Hides and shows products in the All Products Page depending on the data it recieves.
	}


	function renderSingleProductPage(index, data){
		// Shows the Single Product Page with appropriate data.
	}

	function renderFilterResults(filters, products){
		// Crates an object with filtered products and passes it to renderProductsPage.
		renderProductsPage(results);
	}

	function renderErrorPage(){
		// Shows the error page.
	}

	
	function createQueryHash(filters){
		// Get the filters object, turn it into a string and write it into the hash.
	}

});

Remember that the concept of SPA is to not have any loads going on while the app is running. That’s why after the initial page load we want to stay on the same page, where everything we need has already been fetched by the server.

However, we still want to be able to go somewhere in the app and, for example, copy the url and send it to a friend. If we never change the app’s address they will just get the app the way it looks in the beginning, not what you wanted to share with them. To solve this problem we write information about the state of the app in the url as #hash. Hashes don’t cause the page to reload and are easily accessible and manipulated.

On every hashchange we call this:

function render(url) {

		// Get the keyword from the url.
		var temp = url.split('/')[0];

		// Hide whatever page is currently shown.
		$('.main-content .page').removeClass('visible');


		var map = {

			// The Homepage.
			'': function() {

				// Clear the filters object, uncheck all checkboxes, show all the products
				filters = {};
				checkboxes.prop('checked',false);

				renderProductsPage(products);
			},

			// Single Products page.
			'#product': function() {

				// Get the index of which product we want to show and call the appropriate function.
				var index = url.split('#product/')[1].trim();

				renderSingleProductPage(index, products);
			},

			// Page with filtered products
			'#filter': function() {

				// Grab the string after the '#filter/' keyword. Call the filtering function.
				url = url.split('#filter/')[1].trim();

				// Try and parse the filters object from the query string.
				try {
					filters = JSON.parse(url);
				}
				// If it isn't a valid json, go back to homepage ( the rest of the code won't be executed ).
				catch(err) {
					window.location.hash = '#';
				}

				renderFilterResults(filters, products);
			}

		};

		// Execute the needed function depending on the url keyword (stored in temp).
		if(map[temp]){
			map[temp]();
		}
		// If the keyword isn't listed in the above - render the error page.
		else {
			renderErrorPage();
		}

	}

This function takes into consideration the beginning string of our hash, decides what page needs to be shown and calls the according functions.

For example if the hash is ‘#filter/{“storage”:[“16″],”camera”:[“5″]}’, our codeword is ‘#filter’. Now the render function knows we want to see a page with the filtered products list and will navigate us to it. The rest of the hash will be parsed into an object and a page with the filtered products will be shown, changing the state of the app.

This is called only once on start up and turns our JSON into actual HTML5 content via handlebars.

function generateAllProductsHTML(data){

    var list = $('.all-products .products-list');

    var theTemplateScript = $("#products-template").html();
    //Compile the template​
    var theTemplate = Handlebars.compile (theTemplateScript);
    list.append (theTemplate(data));


    // Each products has a data-index attribute.
    // On click change the url hash to open up a preview for this product only.
    // Remember: every hashchange triggers the render function.
    list.find('li').on('click', function (e) {
      e.preventDefault();

      var productIndex = $(this).data('index');

      window.location.hash = 'product/' + productIndex;
    })
  }

This function receives an object containing only those products we want to show and displays them.

function renderProductsPage(data){

    var page = $('.all-products'),
      allProducts = $('.all-products .products-list > li');

    // Hide all the products in the products list.
    allProducts.addClass('hidden');

    // Iterate over all of the products.
    // If their ID is somewhere in the data object remove the hidden class to reveal them.
    allProducts.each(function () {

      var that = $(this);

      data.forEach(function (item) {
        if(that.data('index') == item.id){
          that.removeClass('hidden');
        }
      });
    });

    // Show the page itself.
    // (the render function hides all pages so we need to show the one we want).
    page.addClass('visible');

  }

Shows the single product preview page:

function renderSingleProductPage(index, data){

    var page = $('.single-product'),
      container = $('.preview-large');

    // Find the wanted product by iterating the data object and searching for the chosen index.
    if(data.length){
      data.forEach(function (item) {
        if(item.id == index){
          // Populate '.preview-large' with the chosen product's data.
          container.find('h3').text(item.name);
          container.find('img').attr('src', item.image.large);
          container.find('p').text(item.description);
        }
      });
    }

    // Show the page.
    page.addClass('visible');

  }

Takes all the products, filters them based on our query and returns an object with the results.

function renderFilterResults(filters, products){

      // This array contains all the possible filter criteria.
    var criteria = ['manufacturer','storage','os','camera'],
      results = [],
      isFiltered = false;

    // Uncheck all the checkboxes.
    // We will be checking them again one by one.
    checkboxes.prop('checked', false);


    criteria.forEach(function (c) {

      // Check if each of the possible filter criteria is actually in the filters object.
      if(filters[c] && filters[c].length){


        // After we've filtered the products once, we want to keep filtering them.
        // That's why we make the object we search in (products) to equal the one with the results.
        // Then the results array is cleared, so it can be filled with the newly filtered data.
        if(isFiltered){
          products = results;
          results = [];
        }


        // In these nested 'for loops' we will iterate over the filters and the products
        // and check if they contain the same values (the ones we are filtering by).

        // Iterate over the entries inside filters.criteria (remember each criteria contains an array).
        filters[c].forEach(function (filter) {

          // Iterate over the products.
          products.forEach(function (item){

            // If the product has the same specification value as the one in the filter
            // push it inside the results array and mark the isFiltered flag true.

            if(typeof item.specs[c] == 'number'){
              if(item.specs[c] == filter){
                results.push(item);
                isFiltered = true;
              }
            }

            if(typeof item.specs[c] == 'string'){
              if(item.specs[c].toLowerCase().indexOf(filter) != -1){
                results.push(item);
                isFiltered = true;
              }
            }

          });

          // Here we can make the checkboxes representing the filters true,
          // keeping the app up to date.
          if(c && filter){
            $('input[name='+c+'][value='+filter+']').prop('checked',true);
          }
        });
      }

    });

    // Call the renderProductsPage.
    // As it's argument give the object with filtered products.
    renderProductsPage(results);
  }

Shows the error state:

function renderErrorPage(){
    var page = $('.error');
    page.addClass('visible');
  }

Stringifies the filters object and writes it into the hash.

function createQueryHash(filters){

    // Here we check if filters isn't empty.
    if(!$.isEmptyObject(filters)){
      // Stringify the object via JSON.stringify and write it after the '#filter' keyword.
      window.location.hash = '#filter/' + JSON.stringify(filters);
    }
    else{
      // If it's empty change the hash to '#' (the homepage).
      window.location.hash = '#';
    }

  }

Conclusion

Single page applications are perfect when you want give your project a more dynamic and fluid feel, and with the help of some clever design choices you can offer your visitors a polished, pleasant experience.

Powered by Gewgley