Tutorial: Creating an NPM-driven Website
A few weeks ago, the jQuery plugins website, which developers used to find and download plugins for the popular client-side library, was switched to read-only mode. Developers are now encouraged to use npm to publish and search for jQuery plugins.
This demonstrates how central npm has become to the JavaScript community. It originally arose as the package manager for node.js, but quickly proved itself versatile for any kind of JavaScript code, and developers started using it to host client-side libraries as well.
In this tutorial, we will show you how you can use npm to develop a small web application. We will start from scratch - we will set up a package.json, install a bunch of libraries and make things work nicely together.
How to install and use client-side libraries with npm
There are two ways to do this. We are going to use the simpler one, which is a great fit for small websites and apps:
- We will install the libraries that we need with npm. You can see a bunch of jQuery plugins here. To install one of them, run the command
npm install <package-name> --save
- npm creates the node_modules folder and places the libraries there.
- In our HTML file, we include the scripts and CSS files directly from the node_modules folder with <script> and <link> tags.
- When the time comes to put your web site/app online, just upload the node_modules folder together with the other files.
This is similar to how Bower works, but has the benefit that we are only using npm without installing additional package managers.
Setting things up
We are ready to start coding! However, there are a few things that you have to do first:
- Make sure that you have node.js installed. If you don't, download an installer for your OS and run it. This will also set up npm for you.
- Create a new empty folder for your new website. As an example, we will use
project-folder
throughout this tutorial. - Open a terminal (or a command prompt if you are on Windows) and navigate to the project folder (cd is your friend!).
- Type
npm init
. This will create an emptypackage.json
file. Press enter to use the defaults, if you don't know what info to supply.
Great! Now you've got an empty folder with a valid package.json inside it. package.json is a special file which is used by npm to write down the libraries you've installed so far, and details about your project.
Let's install some libraries
We are going to make a simple web app that will visualize addresses using Google Maps, and will let people save addresses in their browser's localStorage. For this purpose, we will need a bunch of libraries which are available on npm:
npm install bootswatch gmaps jquery moment --save
This will download and write to node_modules Bootswatch (Bootstrap with pretty themes applied), gmaps (an easy way for working with Google Maps), jQuery and moment.js (library for working with date and time in JavaScript). The --save flag will write them to package.json in addition to downloading them.
All that is left is to include these libraries in your HTML.
The HTML
We have a basic HTML5 document with a few Bootstrap components. Notice how we've included the bootswatch stylesheet and the libraries by directly specifying their path inside the node_modules folder.
index.html
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>Creating an Npm-driven Website</title> <link href="node_modules/bootswatch/flatly/bootstrap.min.css" type="text/css" rel="stylesheet" /> <link href="assets/css/styles.css" type="text/css" rel="stylesheet" /> </head> <body> <div class="container"> <h1>Your Google Maps Locations</h1> <form id="geocoding_form" class="form-horizontal"> <div class="form-group"> <div class="col-xs-12 col-md-6 col-md-offset-3"> <div class="input-group"> <input type="text" class="form-control" id="address" placeholder="Enter your location..."> <span class="input-group-btn"> <span class="glyphicon glyphicon-search" aria-hidden="true"></span> </span> </div> </div> </div> </form> <div class="map-overlay"> <p>Loading...</p> <div id="map"></div> </div> <div class="col-xs-12 col-md-6 col-md-offset-3 save-container"> <h4 id="save-location"></h4> <span class="glyphicon glyphicon-star-empty" aria-hidden="true"></span> </div> <div class="list-group col-xs-12 col-md-6 col-md-offset-3"> <span class="list-group-item active">Saved Locations</span> </div> </div> <!-- The jQuery library --> <script src="node_modules/jquery/dist/jquery.min.js"></script> <!-- The Moment.js library --> <script src="node_modules/moment/moment.js"></script> <!-- Including the Google Maps API and the GMaps library --> <script src="http://maps.google.com/maps/api/js?sensor=true"></script> <script src="node_modules/gmaps/gmaps.js"></script> <!-- Including our own JavaScript file --> <script src="assets/js/script.js"></script> </body> </html>
I have chosen the modern-looking Flatly theme from Bootswatch, which we installed a moment ago. In the HTML you can also see some of Bootstrap's grid classes, along with a list group for presenting the favorite locations.
The JavaScript
Our JavaScript file will handle saving to and reading from localStorage, creating Google Maps using the Gmaps library and converting from addresses to geographic coordinates. You can see the entire file below.
assets/js/script.js
$(function(){ var saveContainer = $('.save-container'), favoriteIcon = saveContainer.find('.glyphicon'), favoriteLocationsListGroup = $('.list-group'); var hasFavoriteLocations = false; // Initialize a google maps using the gmaps library. var map = new GMaps({ el: '#map', lat: '0', lng: '0', zoom: 1 }); // Initialize the favorite locations array which is kept in localStorage if(!localStorage.hasOwnProperty('favorite-locations')) { localStorage.setItem('favorite-locations', JSON.stringify([])); } hasFavoriteLocations = JSON.parse(localStorage.getItem('favorite-locations')).length ? true : false; // Form submit and Search icon handlers $('.glyphicon-search').click(showLocationByAddress); $('#geocoding_form').submit(showLocationByAddress); // Click handler on any of the favorite locations $(document).on('click','a.list-group-item', showLocationByCoordinates); // Click handler on the favorite(star) icon to become saved or removed $(document).on('click', '.glyphicon-star', removeFavoriteLocation); $(document).on('click', '.glyphicon-star-empty', saveFavoriteLocation); // If there are any favorite locations, append them to the favorite location list if(hasFavoriteLocations) { var array = JSON.parse(localStorage.getItem('favorite-locations')); favoriteLocationsListGroup.empty(); favoriteLocationsListGroup.append('<span class="list-group-item active">Saved Locations</span>'); array.forEach(function(item){ favoriteLocationsListGroup.append('<a class="list-group-item" data-lat="'+item.lat+'" data-lng="'+item.lng+'" data-createdAt="'+item.createdAt+'">'+item.address+'<span class="createdAt">'+moment(item.createdAt).fromNow()+'</span><span class="glyphicon glyphicon-menu-right"></span></a>'); }); favoriteLocationsListGroup.show(); } // This function presents the address which was entered in the text field in the map function showLocationByAddress(e) { e.preventDefault(); // Getting the coordinates of the entered address GMaps.geocode({ address: $('#address').val().trim(), callback: function(results, status) { if (status !== 'OK') return; var latlng = results[0].geometry.location, fullAddress = results[0].formatted_address, isLocationFavorite = false, locationsArray = JSON.parse(localStorage.getItem('favorite-locations')), saveLocation = $('#save-location'); var map = new GMaps({ el: '#map', lat: latlng.lat(), lng: latlng.lng() }); // Adding a marker on the wanted location map.addMarker({ lat: latlng.lat(), lng: latlng.lng() }); // Checking if this address exists in the favorites array if(locationsArray.length) { locationsArray.forEach(function (item) { if (item.lat == latlng.lat() && item.lng == latlng.lng()) { isLocationFavorite = true; } }); } // Adding the address to the html and setting data attributes with the coordinates saveLocation.text(fullAddress).attr({'data-lat': latlng.lat(), 'data-lng': latlng.lng()}); // Removing the active class from all favorite locations favoriteLocationsListGroup.find('a.list-group-item').removeClass('active-location'); // Changing the icon to become non-favorite if(!isLocationFavorite) { favoriteIcon.removeClass('glyphicon-star').addClass('glyphicon-star-empty'); } else { // Adding the active class and add the favorite icon on the given favorite location favoriteIcon.removeClass('glyphicon-star-empty').addClass('glyphicon-star'); // Find the entry in the favorite locations list that corresponds // to the current location, and mark it as active. favoriteLocationsListGroup.find('a.list-group-item[data-lat="'+latlng.lat()+'"][data-lng="'+latlng.lng()+'"]').addClass('active-location'); } // Show the html of the given location saveContainer.show(); } }); } // This functions is called when a favorite location is clicked. // It reads the coordinates and shows them in a map function showLocationByCoordinates(e) { e.preventDefault(); var elem = $(this), location = elem.data(); // Getting the address from the location's coordinates GMaps.geocode({ location: {lat: location.lat, lng: location.lng}, callback: function(results, status) { if (status !== 'OK') return; var fullAddress = results[0].formatted_address, saveLocation = $('#save-location'); var map = new GMaps({ el: '#map', lat: location.lat, lng: location.lng }); map.addMarker({ lat: location.lat, lng: location.lng }); // Adding the address to the html and setting // data attributes with the location's coordinates saveLocation.text(fullAddress); saveLocation.attr({ 'data-lat': location.lat, 'data-lng': location.lng }); // Adding colored background to the active favorite location and // removing the old active location favoriteLocationsListGroup.find('a.list-group-item').removeClass('active-location'); favoriteLocationsListGroup.find('a.list-group-item[data-lat="'+location.lat+'"][data-lng="'+location.lng+'"]').addClass('active-location'); // Add the favorite icon on the given location favoriteIcon.removeClass('glyphicon-star-empty').addClass('glyphicon-star'); // Show the html of the given location saveContainer.show(); // Clear the search field $('#address').val(''); } }); } // This function saves a location to favorites and adds it to localStorage function saveFavoriteLocation(e){ e.preventDefault(); var saveLocation = $('#save-location'), locationAddress = saveLocation.text(), isLocationFavorite = false, locationsArray = JSON.parse(localStorage.getItem('favorite-locations')); var location = { lat: saveLocation.attr('data-lat'), lng: saveLocation.attr('data-lng'), createdAt: moment().format() }; // Checking if this location is in the favorites array if(locationsArray.length) { locationsArray.forEach(function (item) { if (item.lat == location.lat && item.lng == location.lng) { isLocationFavorite = true; } }); } // If the given location is not in favorites, // add it to the HTML and to localStorage's array if(!isLocationFavorite) { favoriteLocationsListGroup.append( '<a class="list-group-item active-location" data-lat="'+location.lat+'" data-lng="'+location.lng+'" data-createdAt="'+location.createdAt+'">'+ locationAddress+'<span class="createdAt">'+moment(location.createdAt).fromNow()+'</span>' + '<span class="glyphicon glyphicon-menu-right"></span>' + '</span></a>'); favoriteLocationsListGroup.show(); // Adding the given location to the localStorage's array locationsArray.push({ address: locationAddress, lat: location.lat, lng: location.lng, createdAt: moment().format() }); localStorage.setItem('favorite-locations', JSON.stringify(locationsArray)); // Make the star icon full, to signify that this location is now favorite favoriteIcon.removeClass('glyphicon-star-empty').addClass('glyphicon-star'); // Now we have at least one favorite location hasFavoriteLocations = true; } } // This function removes a favorite location from the favorites list // and removes it from localStorage function removeFavoriteLocation(e){ e.preventDefault(); var saveLocation = $('#save-location'), isLocationDeleted = false, locationsArray = JSON.parse(localStorage.getItem('favorite-locations')); var location = { lat: saveLocation.attr('data-lat'), lng: saveLocation.attr('data-lng') }; // Removing the given location from the localStorage's Array if(locationsArray.length) { locationsArray.forEach(function (item, index) { if (item.lat == location.lat && item.lng == location.lng) { locationsArray.splice(index,1); isLocationDeleted = true; } }); } if(isLocationDeleted) { // Remove the given location from the favorites list favoriteLocationsListGroup.find('a.list-group-item[data-lat="'+location.lat+'"][data-lng="'+location.lng+'"]').remove(); localStorage.setItem('favorite-locations', JSON.stringify(locationsArray)); // Removing the favorite icon from the html favoriteIcon.removeClass('glyphicon-star').addClass('glyphicon-star-empty'); if(!locationsArray.length) { // There are no more favorite locations hasFavoriteLocations = false; favoriteLocationsListGroup.hide(); } else { hasFavoriteLocations = true; } } } });
The CSS
We mostly rely on Bootstrap with the Flatly theme to do the styling for us. However I did write a few additional CSS rules, which you can see in assets/css/styles.css in the downloadable zip with the source code.
To wrap it up
This concludes our tutorial! Npm has a huge number of JavaScript libraries, a lot of which are usable in the browser directly (for the rest we have Browserify, but this is a topic for another article). Do you think you will use npm in your client side development? Share your thoughts in our comment section.
Bootstrap Studio
The revolutionary web design tool for creating responsive websites and apps.
Learn more
Wow, that's a good article. Thanks Nick Anastasov. You are superman.
Thanks a lot. I love making people learn something from me. Hope you like my other articles too. :)
Great write up thank you. definitely npm over bower right out of the box no need to install another package
But if we have specific package Bower for this purpose then why use npm. I think it is cleaner to use Bower for frontend dependency management.
Thanks for this tutorial,
I am wondering why it takes long time for the page to load, try to refresh the page and you will see.
Thanks.
Nice tutorial! I love using npm to manage my AngularJS projects, it's nice to see it is being adopted for more widespread use with jQuery based applications. I'm curious though, since you are doing a bit of manual 2 way binding here, why you wouldn't use a framework like AngularJS that abstracts that a bit?
Still amazed how font-end web development is done nowadays with console commands and the like. At least, this tutorial showed me I'm not as outdated as I feared starting reading the article.
Very nice. This was simple and short, a great start for a npm newbie.