Sample Angular Website

Leave a comment Standard

So I’ve seen a lot of people using AngularJS but I see very few examples of a website made using it. That’s why I decided to post an example website to give you an idea of what a full website looks like using AngularJS. It has samples of some of the things you’ll commonly find in a website made entirely with Angular:

  •  Navigation & Routing
  • Providers & Directives
  • Controllers & Config Blocks
  • Data Calculations
  • Filtering

Sorry if you’re looking for Angular 2.0, this was made with Angular 1.5.8. View, fork and/or edit the files on Plunker!

Advertisements

HTML5 Tutorial: Simple Music Player

Leave a comment Standard

So this is a pretty simple example but I was playing around with flash for my chat room and this was byproduct of that experimentation. It’s funny how even when I’m trying to learn something new I do my best to make it useful for other situations which is a good habit to get into if you’re not doing it already.

Try out the working demo or download the code

Caveats

This will only work on a browser that is HTML5 compliant and has support for audio. That means older browsers and most versions of Internet Explorer are not supported. Firefox, Chrome and Opera work as well as the newest versions of Internet Explorer.

The Audio Tag

If you’re familiar with HTML then most of the index file below will look pretty familiar. The thing you really have to pay attention to is the new <audio> tag. It looks like this:

<audio id="rock" src="http://whiteoakstables.net/chat/audio/rock.wav" preload="auto"></audio>

You may notice that it looks a whole lot like a hyperlink with the src that defines the location of the file. Preload is an optional attribute, it tells your browser to start downloading the file as soon as the DOM, or page elements, have finished loading. In addition to setting preload to true I’ve added an ID attribute so I can reference this particular song in my music player.

The Index File

<!DOCTYPE html>
<html>
 <head>
 <script data-require="jquery@*" data-semver="2.1.3" src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
 <link data-require="bootstrap-glyphicons@*" data-semver="3.2.1" rel="stylesheet" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/img/glyphicons-halflings.png" />
 <link data-require="bootstrap@*" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
 <script data-require="bootstrap@*" data-semver="3.3.1" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
 <link rel="stylesheet" href="style.css" />
 <script src="script.js"></script>
 </head>
 <body>
 <h1 class="text-center">Bootstrap HTML5 Music Player</h1>
 <audio id="rock" src="http://whiteoakstables.net/chat/audio/rock.wav" preload="auto"></audio>
 <audio id="blues" src="http://whiteoakstables.net/chat/audio/blues.wav" preload="auto"></audio>
 <audio id="electronica" src="http://whiteoakstables.net/chat/audio/electronica.wav" preload="auto"></audio>
 <audio id="classical" src="http://whiteoakstables.net/chat/audio/classical.wav" preload="auto"></audio>
 <audio id="latin" src="http://whiteoakstables.net/chat/audio/latin.wav" preload="auto"></audio>
 <audio id="indie" src="http://whiteoakstables.net/chat/audio/indie.wav" preload="auto"></audio>
 <div class="audiocontroller">
 <i class="audio-logo glyphicon glyphicon-headphones"></i>
 <i class="dynamiclink glyphicon glyphicon-play" id="play"></i></a>
 <i class="dynamiclink glyphicon glyphicon-pause" id="pause"></i>
 <select id="currentTrack">
 <option value="rock">Rock</option>
 <option value="blues">Blues</option>
 <option value="electronica">Electronica</option>
 <option value="classical">Classical</option>
 <option value="latin">Latin</option>
 <option value="indie">Indie</option>
 </select>
 <i class="dynamiclink glyphicon glyphicon-stop" id="stop"></i>
 </div>
 <p class="text-center">
 <a id="author" href="http://design1online.com" target="_blank">by Design1online.com, LLC</a>
 </p>
 </body>
</html>

The Javascript File

//Simple HTML5 Music Player by design1online.com, LLC

var currentTrack = null;
var paused = false;

$(document).ready(function() {
 currentTrack = $('#currentTrack').val();
 
 $("#play").click(function() { play(); });
 $("#stop").click(function() { stop(); });
 $("#pause").click(function() { pause(); });
});

function play() {

 //set the track back to the beginning
 if (!paused) {
 stop();
 currentTrack = $('#currentTrack').val();
 
 //make sure track starts from the begining each time
 document.getElementById(currentTrack).load();
 }

 //play the track
 document.getElementById(currentTrack).play();
}
 
//stop playing the current track
function stop() {
 if (currentTrack) {
 document.getElementById(currentTrack).pause();
 paused = false;
 }
}

//stop the track but don't go back to the beginning
function pause() {
 paused = true;
 document.getElementById(currentTrack).pause();
}

How It All Works

So now let’s talk about the javascript file. At first glance it looks relatively simple because it is! When you want to select a track you simply ask for it using getElementById and then you tell it to play or to pause. One thing that is sadly lacking is a stop() functionality. With html5 you can only pause an audio file — which does stop the playback but it also keeps the audio file at the same position as when you called pause on it. This means that if you wanted to play the audio file from the beginning again and you just called play() it would continue playing from where it left off. That’s where the load() function comes into play — this will tell the audio track to start loading if it wasn’t set to preload and it will reset the track back to the beginning.

The only other thing to mention is the currentTrack variable. I use this to keep track of which of the songs is currently selected and the user wasn’t to listen to.

Best Tools To Test Your Website’s Responsiveness

Comment 1 Standard

Since converting my game websites to be fully responsive and use HTML5 I’ve gone through a lot of different responsiveness testing tools. So far these are my favorites:

  1. Web Developer – addon for Firefox. This takes the cake for me because it’s already built into the right click menu on my browser so it’s really easy to invoke whenever and wherever. On top of that it’s designed to show you a number of different responsive views at one time and you can customize the views that it shows when it launches. Of course it comes with a whole host of other nifty tools to aid in your web development so it gets a raving 5 stars from me. It’s not as fully fleshed out in Chrome but there is a Chrome version as well. The only downside to this tool is that if you change something and you want to see an update of your changes you can’t just refresh the screen that pops up in a new tab. Instead you have to close the tab and launch the responsive view from the right click menu. Not a huge bother but it could be improved upon.
  2. http://quirktools.com/screenfly/ – simple, intuitive interface. Just enter your website and you’re well on your way for an easy experience to view your site on phones, tables and even televisions. The downside of this website is that you have to reload it every time you want to check what a different page of your website would look like.
  3. http://ipadpeek.com/ – the ipad and iphone emulator. It looks like an ipad and it does a pretty good job emulating an ipad and iphone — it will even show screen rotations. The only downside to this is that it doesn’t let you change version of the ipad because I believe the newest AIR 2 has a larger screen than the one in this emulator but I could be wrong.
  4. http://www.mobilephoneemulator.com/ – the multi-phone emulator. So this isn’t nearly as easy to use as the item in the number 2 spot which is why it’s bumped down a spot on my list even though it has a much wider array of phones you can test your site on including some of the popular android, htc and samsung phones. Just pick the make and model and enter your website and this site will render it to the proper screen size.
  5. http://ready.mobi/– last but not least. Although this doesn’t rank high in the visual department this is an excellent tool for evaluating what issues your markup may present to mobile users. Things like images exceeding certain file size and dimensions (which make for a slow mobile experience) and how your file sizes will impact your loading times are why this site makes my list. The downside is that it doesn’t visually show you what your responsive site will look like but it takes an analytical approach which is super helpful and informative regardless.

Now it looks like you’re ready to check out these 11 unconventional programming tips to improve your programming skills!

Node JS Tutorial: How To Create A Simple Server

Leave a comment Standard

This will allow you to quickly setup a working node server in just a few easy steps.

1. Install Node.js

For Windows/Linux
Go to http://nodejs.org and download the
latest version of node for the operating system you want to install it on.

Open node and test if it’s working by typing 2+2 and hitting enter.

For Mac OS X
Visit Homebrew and then open your
command line and run (If you’re prompted to install command line tools go ahead and do that). :

ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

When that’s done run you can make sure your install was successful by running:

brew doctor

Then it’s a good idea run this to make sure you have the most up to date copy:

brew update

Then install node by running:

brew install node

Test your install by opening a command line and typing this to see the node version:

node -v

2. Install NPM

This package will help you install lots of node related libraries. You can find out more
about it by visiting https://npmjs.org.
Open a command line and type:

npn install express body-parser

If you get an error on Windows that says something along the lines of

Error: ENOENT, stat...

This means the NPM directory is missing from your filesystem. Navigate to where NPM should be as displayed by the error message and create an empty folder called npm.

3. Create These Simple Server Files

First let’s make our index.html. This should go in the directory you want to serve this index file from. So on a server it would be something along the lines of htdocs/public_html/directory/ or var/www/html and on your local computer it should be whatever directory you did your npm install.

Create the index.html file:

<html ng-app="app">
  <head>
    <title>My First Node Server File</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="css/stylesheet.css">
  </head>
  <body ng-controller="appCtrl">
    <div class="page-header">
      <h1>
        <i class="glyphicon glyphicon-certificate"></i>
        Congratulations!
      </h1>
    </div>
    <section>You have successfully created your first HTML5 node server.</section>
  </body>
  <footer>
    <a href="http://design1online.com" target="_blank">Tutorials by Design1Online.com, LLC</a>
  </footer>
</html>

Now let’s create the server.js file:

'use strict';

// Importing express and body parser libraries
var express = require('express');
var bodyParser = require('body-parser');

//this is a built in node library that handles the file system
var fs = require('fs');


/**
* Server configs
*/

/**
* The port to run your node server on
* 
* If you're running this on a web server this should be 80
* If you're running this locally try 8080 or 9080
*/
var BASE_PORT = 8080;

/** 
* The root directory of your files
*
* By default it uses the current folder this file is in
*/
var ROOT_DIR = __dirname + '/';
ROOT_DIR = fs.realpathSync(ROOT_DIR);
if (!fs.existsSync(ROOT_DIR)) {
	console.log('Error: cannot find working directory: ' + ROOT_DIR);
	exit();
}

/**
* Create an instance of express
*/
var app = express();

/**
 * Adds a simple logging, "mounted" on the root path.
 * Using Express middleware
 **/
app.use(function(req, res, next) {
	console.log('%s %s', req.method, req.url);
	next();
});

/**
 * Allows us to parse http body parameters as json
 **/
app.use(bodyParser.json());

app.use(express.static(ROOT_DIR));

app.listen(BASE_PORT, function() {
	console.log('Node server started @ http://localhost:' + BASE_PORT);
	console.log('Serving static files from ' + ROOT_DIR);
	console.log('Press Ctrl + c for server termination');
});

4. Start the Server

Go to the directory you installed npm in and that your index.html and server.js file reside. This is where you want to start the node server. In the console type:

node server.js

Now open your browser. If you installed node on a server then navigate to your index page on your website. You should see your new node js index file. If you’re running the node server on your local machine then type in http://localhost:BASE_PORT and replace BASE_PORT with the port number you configured in the server.js file. You’ll see your index file in your browser.

Congrats! You’ve successfully create your first node server.

Google Maps Tutorial: You Are Here in 3 Easy Steps

Comments 2 Standard

I’ve been playing with Google maps lately and I thought I would share this little tidbit. It still needs some refinement (ie fetching your location from IP for non-html5 compliant browsers and letting you set your location) but it may give you the start you were looking for.

1. Create a Google maps API key

You can find the instructions on the Google maps Getting Started documentation. Basically you need to generate the key for the website that will be hosting this code so that Google will honor the API request.

2. Create a PHP File

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <meta charset="utf-8">
    <title>You Are Here</title>
    <style>
      html, body {
        height: 100%;
        margin: 0px;
        padding: 0px
      }
            
      #map-canvas {
        float: right;
        height: 100%;
        margin: 0px;
        padding: 0px
      }
    </style>
    <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>
        <script src="javascript.js"></script>
  </head>
  <body>
    <div id="map-canvas" />
  </body>
</html>

3. Create a Javascript File

I called mine javascript.js. If you change the name you’ll need to update it in your .php file above.

/**
* Purpose: ask the browser for the user's location (html5)
* Preconditions: html5 is enabled on the browser
* Postconditions: map initialized with user's position if browser is html5 enabled and position given
**/
function getLocation()
{
    if (navigator.geolocation)
        navigator.geolocation.getCurrentPosition(initMap);
    else
        alert("Geolocation is not supported by this browser.");
}

/**
* Purpose: start the google map
* Preconditions: position object
* Postconditions: map centered around the user's position with a zoom of 15
**/
function initMap(position) {

    console.log('Your position is:', position.coords.latitude, position.coords.longitude);
    
  var mapOptions = {
    zoom: 15,
    center: new google.maps.LatLng(position.coords.latitude, position.coords.longitude)
  };

  var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
    var position = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
 
    setMarker(map, position, "You!", "You Are Here");
}

/**
* Purpose: set a marker on a map
* Preconditions: map reference, position, title of the marker window, message in the marker window
* Postconditions: marker event listener added to the map
**/
function setMarker(map, position, title, message) {

     var marker = new google.maps.Marker({
      position: position,
      map: map
    });
        
  marker.setTitle(title);
        
  var infowindow = new google.maps.InfoWindow({
    content: message
  });

  google.maps.event.addListener(marker, 'click', function() {
    infowindow.open(marker.get('map'), marker);
  });
}

google.maps.event.addDomListener(window, 'load', getLocation);

Demo Working Code

Want to see what it does? Fork this plunker to try it out and see the code. Remember you have to be using a HTML5 compliant browser and give it permission to view your location.

Angular UI Tutorial: TinyMCE Directive

Leave a comment Link

This is a really useful plunker that allows you to integrate tinyMCE into your angular projects as a directive. You can apply the WYSIWYG to multiple textareas on a page or in angular UI tabs too. This isn’t my plunker but it’s still a super useful one. Enjoy!

Advanced Angular Chatroom Tutorial: No Database or Sockets Required

Leave a comment Standard

This is an expansion on my basic angular chatroom to add new functionality. In this version of the chat room there is now a popup color picker to change font colors, a way to change from one channel to another, click-able hyperlinks, HTML messages and emoticons.

1. Create a PubNub Account

To get started you will need to create a PubNub account. This will handle all the chat messages for you so you don’t need a server or database in order to quickly and easily create your chat room. There is a free plan and several paid for plans available for you to choose from. Once your account is created you’ll need to find your subscribe key and publish key.

2. Create the chat file

Now you’re going to create the main chat file. This will display your chat room. At the top of the file replace the subscribe_key and publish_key with the one you see in your newly created account.

index.html

<!doctype html>
<html ng-app="angular_chat">
  <head>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
    <script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
    <div pub-key="PUB_KEY_GOES_HERE" sub-key="SUB_KEY_GOES_HERE" ssl="off" origin="pubsub.pubnub.com" id="pubnub"></div>
    <script src="http://cdn.pubnub.com/pubnub-3.1.min.js"></script>
    <script src="angular-chat.js"></script>
    <script src="angular-colorpicker.js"></script>
    <script src="angular-colorpicker-modal.js"></script>
    <link rel="stylesheet" href="style.css" rel="stylesheet">
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
  </head>
  <body>
  <div class='container-fluid' ng-controller="chatCtrl">
    <div ng-include="'chatHeader.html'"></div>
    <div ng-Show="!loggedIn" id="login">
      <h2>Login</h2>
      <label for="username">
        <b>Username:</b>
      </label>
      <label>
        <input type="text" ng-model="message.username" />
      </label>
      <span ng-click="attemptLogin()">
        <i></i> Go Chat
      </span>
    </div>
    <div ng-Show="loggedIn" id="chat">
      <table>
        <tr ng-repeat="chat in chatMessages">
            <td style="width: 30%;">
              <b>{{chat.username}}</b><br/>
              <small>
               {{chat.date}} @ {{chat.time}}
              </small>
            </td>
            <td colspan="2">
              <font color="{{chat.color}}"><span ng-bind-html-unsafe="chat.text"></span></font>
            </td>
        </tr>
        <tr ng-show="chatMessages.length == 0">
          <td colspan="3">No messages yet!</td>
        </tr>
      </table>
    </div>
    <form ng-Show="loggedIn" ui-keypress="{13:'postMessage()'}">
      <div id="inputMessage">
        <div ng-controller="ColorPickerCtrl">
          <script type='text/ng-template' id='fontcolor.html'>
            <div>
              <h3>Change Font Color</h3>
            </div>
            <div ng-modal="colorPicker">
              <ng-color-circle height="250" width="250" model-object="colorpicker" model-property="color"></ng-color-circle>
              <div style="background-color:{{colorpicker.color}}">Selected Color</div>
            </div>
            <div>
              <button ng-click="ok()">Done</button>
            </div>
          </script>
          <span ng-click="open()" id="fontModalButton" style="background:{{color}} !important;">
            <i></i>
          </span>
        </div>
        <span ng-click="toggleEmoticons = !toggleEmoticons">
          <img ng-src="{{emoticon_url}}{{emoticons[':)']}}" />
        </span>
        <input type="text" placeholder="Say hello!" ng-model="message.text" />
        <span ng-click="postMessage()">
          <i></i> Post
        </span>
      </div>
      <span ng-show="toggleEmoticons == true" id="emoticonList" ng-repeat="(key, image) in emoticons">
        <img ng-src="{{emoticon_url}}{{image}}" ng-click="insertEmoticon(key)" />
      </span>
    </form>
    <div ng-include="'chatFooter.html'"></div>
  </div>
  </body>
</html>

3. Create the header and footer files

chatHeader.html

<div>
  <h1>Ng-Angular Chat</h1>
  <h5><i></i> using Bootstrap and PubNub</h5>
  <p>&nbsp;</p>
  <div ng-show="realtimeStatus == 0">
    <span><i></i></span>
  </div>
  <div ng-show="realtimeStatus == 1">
    <span><i></i></span>
  </div>
  <div ng-show="realtimeStatus == 2">
    <span><i></i></span>
  </div>
  <div id="channelDropdown"><select ng-model="selectedChannel" ng-change="initChat(selectedChannel)" ng-options="obj.value as obj.text for obj in chatChannels"></select></div>
  <div ng-show="loggedIn" id="logout">
    <span ng-click="attemptLogout()"><i></i> Logout</span>
  </div>
</div>
<p>&nbsp;</p>
<div ng-show="errorMsg">
  <i></i> <b>Error:</b> {{errorMsg}}
</div>

chatFooter.html

<div>
  2013 &copy; design1online.com
</div>

4. Create the stylesheet

This example is using bootstrap however we still need some additional tweaks to get it looks just right.

style.css

#chat {
 height: 31em; 
 overflow: auto;
 background-color: #EEE;
 border-radius: .25em;
 margin-bottom: 1em;
}

#logout {
  float: right;
  margin-top: -1.35em;
}

#channelDropdown {
  margin-right: .3em;
  margin-left: .3em;
  float: left;
  margin-top: -1.35em;
}

#fontModalButton {
  height: 21px;
}

#selectedColor {
  width: 100%;
  border: 1px solid #000000; 
}

.alert {
  padding: 8px !important;
}

.status {
  float: left;
  margin-top: -1em;
}

.left {
  float: left;
  margin-right: .25em;
}

.icon {
  cursor: pointer;
}

5. Create the angular files

angular-chat.js

/***
 * File: angular-chat.js
 * Author: Jade Krafsig
 * Source: design1online.com, LLC
 * License: GNU Public License
 ***/

/***
 * Purpose: load bootstrap ui angular modules
 * Precondition: none
 * Postcondition: modules loaded
 ***/
angular.module('angular_chat', ['ui.bootstrap', 'directive.colorPicker']);

/***
 * Purpose: load the existing chat logs
 * Precondition: none
 * Postcondition: chat logs have been loaded
 ***/
function chatCtrl($scope, $http) { 

  /***
   * Configurable global variables
   ***/
  $scope.messageLimit = 50;
  $scope.defaultUsername = "Guest";
  $scope.selectedColor = "#000000";

  $scope.chatChannels = [
    {
      text: "Basic Channel",
      value: "angular_chat", 
      default: true
    },
    {
      text: "Advanced Channel",
      value: "angular_chat_advanced"
    },
  ];

  $scope.emoticon_url = "http://www.freesmileys.org/smileys/smiley-basic/";
  $scope.emoticons = {
    ':-)' : 'biggrin.gif',
    ':)' : 'biggrin.gif',
    ':D' : 'laugh.gif',
    ':-D' : 'laugh.gif',
    ':-|' : 'mellow.gif',
    ':|' : 'mellow.gif',
    ':-p' : 'tongue.gif',
    ':p' : 'tongue.gif',
    ':-(' : 'sad.gif',
    ':(' : 'sad.gif'
  };

  /***
   * Static global variables, do not change
   ***/
  $scope.loggedIn = false;
  $scope.errorMsg;
  $scope.realtimeStatus = 0;
  $scope.toggleEmoticons = false;

  /***
   * Purpose: clear the message object
   * Precondition: none
   * Postcondition: message object has been reset
   ***/
  $scope.clearMsg = function() {
    $scope.message = {
      username: $scope.defaultUsername,
      email: 'n/a',
      text: ''
    };
  }

  /***
   * Purpose: listen for a font color change broadcast
   * Precondition: color value
   * Postcondition: selected color has been set
   ***/
  $scope.$on('colorChange', function(obj, color){
    $scope.selectedColor = color;
  });    

  /***
   * Purpose: load the existing chat logs
   * Precondition: none
   * Postcondition: chat logs have been loaded
   ***/
  $scope.chatLogs = function() {
    PUBNUB.history( {
      channel : $scope.selectedChannel,
      limit   : $scope.messageLimit
    }, function(messages) {
      // Shows All Messages
      $scope.$apply(function(){
        $scope.chatMessages = messages.reverse();          
      }); 
    });
   }

  /***
   * Purpose: load the existing chat logs
   * Precondition: none
   * Postcondition: chat logs have been loaded
   ***/
   $scope.attemptLogin = function() {
    $scope.errorMsg = "";

    if (!$scope.message.username) {
      $scope.errorMsg = "You must enter a username.";
      return;
    }

    if (!$scope.realtimeStatus) {
      $scope.errorMsg = "You're not connect to PubNub.";
      return;
    }

    $scope.loggedIn = true;
   }

  /***
   * Purpose: remove error message formatting when the message input changes
   * Precondition: none
   * Postcondition: error message class removed from message input
   ***/
  $scope.$watch('message.text', function(newValue, oldValue) {
    if (newValue) {
      $("#inputMessage").removeClass("error");
      $scope.errorMsg = "";
    }
  }, true);

  /***
   * Purpose: trying to post a message to the chat
   * Precondition: loggedIn
   * Postcondition: message added to chatMessages and sent to chatLog
   ***/
  $scope.postMessage = function() {

    //make sure they are logged in
    if (!$scope.loggedIn) {
      $scope.errorMsg = "You must login first.";
      return;
    }

    //make sure they enter a chat message
    if (!$scope.message.text) {
      $scope.errorMsg = "You must enter a message.";
      $("#inputMessage").addClass("error");
      return;
    }

    //set the message date
    d = new Date();
    $scope.message.date = d.getDay() + "/" + d.getMonth() + "/" + d.getFullYear();
    $scope.message.time = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();

    //change font color
    $scope.message.color = $scope.selectedColor;

    //replace hyperlinks with clickable urls
    $scope.message.text = $scope.replaceURLWithLink($scope.message.text);

    //replace smileys
    $scope.message.text = $scope.replaceEmoticons($scope.message.text);

    //send the message to the selected channel
    PUBNUB.publish({
      channel : $scope.selectedChannel,
      message : $scope.message
    });

    //reset the message input box
    $scope.message.text = "";
  };

  /***
   * Purpose: connect and access pubnub channel
   * Preconditions: pubnub js file init
   * Postconditions: pubnub is waiting and ready
   ***/
  $scope.initChat = function(newChannel) {
    if (newChannel)
      $scope.selectedChannel = newChannel;

    if (!$scope.chatChannels.length) {
      $scope.errorMsg = "Missing chat channels, check config.";
      return;
    }

    if (!$scope.selectedChannel) {
      $scope.errorMsg = "You must select a channel.";
      return;
    }

    //clear out any chat messages from a previous channel
    $scope.chatMessages = Array();

    //unsubscribe to the active channel
    angular.forEach($scope.chatChannels, function(channel, key){
      PUBNUB.unsubscribe({channel: channel.value});
    });

    //subscribe to the selected channel
    PUBNUB.subscribe({
      channel    : $scope.selectedChannel,
      restore    : false, 
      callback   : function(message) { 
          //update messages with the new message
          $scope.$apply(function(){
          $scope.chatMessages.unshift(message);          
        }); 
      },

      error      : function(data) {
        $scope.errorMsg = data;
      },

      disconnect : function() {   
        $scope.$apply(function(){
          $scope.realtimeStatus = 0;
        });
      },

      reconnect  : function() {   
        $scope.$apply(function(){
          $scope.realtimeStatus = 1;
        });
      },

      connect    : function() {
        $scope.$apply(function(){
          $scope.realtimeStatus = 2;
          //load the chat logs
          $scope.chatLogs();
        });
      }
    });
  }

  /***
   * Purpose: trying to post a message to the chat
   * Precondition: loggedIn
   * Postcondition: message added to chatMessages and sent to chatLog
   ***/
  $scope.attemptLogout = function() {
    $("#inputMessage").removeClass("error");
    $scope.clearMsg();
    $scope.loggedIn = false;
  }

  /***
   * Purpose: set the chat channel to the default channel value
   * Precondition: at least one channel defined
   * Postcondition: selectedChannel set to the default
   ***/
  $scope.defaultChannel = function() {
    var chatChannel;

    angular.forEach($scope.chatChannels, function(channel, key){

     if (!chatChannel)
      chatChannel = channel;

     if (channel.default) {
      chatChannel = channel;
      return;
     }
    });

    $scope.selectedChannel = chatChannel.value;
  }

  /***
   * Purpose: add an emoticon to the message input
   * Precondition: emoticon has been selected
   * Postcondition: emoticon value appended to message input
   ***/
  $scope.insertEmoticon = function(selected) {
    $scope.message.text = $scope.message.text + " " + selected + " ";
  }

  /***
   * Purpose: regex to replace emoticons with image html
   * Precondition: text to replace emoticons in
   * Postcondition: any emoticons found have been replaced with html images
   ***/
  $scope.replaceEmoticons = function(text) {
    patterns = [];
    metachars = /[[\]{}()*+?.\\|^$\-,&#\s]/g;

    // build a regex pattern for each defined property
    for (var i in $scope.emoticons) {
      if ($scope.emoticons.hasOwnProperty(i)){ // escape metacharacters
        patterns.push('('+i.replace(metachars, "\\$&")+')');
      }
    }

    // build the regular expression and replace
    return text.replace(new RegExp(patterns.join('|'),'g'), function (match) {
      return typeof $scope.emoticons[match] != 'undefined' ?
        '<img src="'+$scope.emoticon_url+$scope.emoticons[match]+'"/>' :
        match;
    });
  }

  /***
   * Purpose: replace url text with a clickable link (opens in a new window)
   * Precondition: text to replace urls in
   * Postcondition: urls have been replaced with a clickable hyperlink
   ***/
  $scope.replaceURLWithLink = function(text) {
    var exp = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
    return text.replace(exp,"<a href='$1' target='_blank'>$1</a>"); 
  }

  /***
   * Purpose: Initialize the chatroom
   * Preconditions: none
   * Postconditions: none
   ***/
  $scope.clearMsg();
  $scope.defaultChannel();
  $scope.initChat();

}

angular-colorpicker-modal.js

/***
 * File: angular-colorpicker-modal.js
 * Author: Jade Krafsig
 * Source: design1online.com, LLC
 * License: GNU Public License
 ***/

/***
 * Purpose: modal template controller
 * Preconditions: $rootScope, $modal and ui-bootstrap module
 * Postconditions: creates a modal window using the specified template
 *  and broadcasts the selected color back to the main controller when
 *  the window is closed
 ***/
var ColorPickerCtrl = function ($rootScope, $scope, $modal) {

  $scope.open = function () {
    $scope.color = "#000000";

    var modalInstance = $modal.open({
      templateUrl: 'fontcolor.html',
      controller: 'ColorpickerModalCtrl'
    });

    //when the color changes broadcast back to the main chat controller
    modalInstance.result.then(function (data) {
      $scope.color = data.color;
      $rootScope.$broadcast('colorChange',  $scope.color);
    });
  }
}

//create a modal popup window and pass it back to the modal parent controller
/***
 * Purpose: modal controller
 * Preconditions: instance of the modal being created
 * Postconditions: functionality defined for all modals created of $modalInstance
 ***/
var ColorpickerModalCtrl = function ($scope, $modalInstance) {
  $scope.colorpicker = {};
  $scope.colorpicker.color = "#000000";

  $scope.ok = function () {
    $modalInstance.close({color: $scope.colorpicker.color});
  };
};

angular-colorpicker.js

/***
 * Method taken from Brian Grinstead and modified the return 
 * of rounded r,g and b.
 * @https://github.com/bgrins/TinyColor/blob/master/tinycolor.js
 ***/
(function(angular) {
  var ngColorPicker = angular.module('directive.colorPicker', []);

  function hsvToRgb(h, s, v) {
    h*=6;
    var i = ~~h,
      f = h - i,
      p = v * (1 - s),
      q = v * (1 - f * s),
      t = v * (1 - (1 - f) * s),
      mod = i % 6,
      r = [v, q, p, p, t, v][mod] * 255,
      g = [t, v, v, q, p, p][mod] * 255,
      b = [p, p, t, v, v, q][mod] * 255;

      return [~~r, ~~g, ~~b, "rgb("+ ~~r + "," + ~~g + "," + ~~b + ")"];
  }

  function rgbToHex(r, g, b) {
    return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
  }

  ngColorPicker.directive('ngColorCircle', ['$timeout', function($timeout) {
    return {
      restrict  : 'E',
      replace   : true,
      scope     : '@=',
      template  : '<div>'
      +             '<canvas id="colorCircle">'
      +             '</canvas>'
      +           '</div>',
      compile: function compile(tElement, tAttrs, transclude) {
        return {
          pre: function preLink(scope, iElement, iAttrs, controller) {
            var canvas        = document.getElementById("colorCircle"),
                context       = canvas.getContext('2d'),
                width         = canvas.width = iAttrs.width ||  300,
                height        = canvas.height = iAttrs.height || 300,
                imageData     = context.createImageData(width, height),
                pixels        = imageData.data,
                oneHundred    = 100,
                two55         = 255,
                circleOffset  = 10,
                diameter      = width - circleOffset * 2,
                wheelPixel    = circleOffset * 4 * width + circleOffset * 4;

            iElement.css({
              width: width,
              height: height
            });

            scope.radius            = diameter / 2;
            scope.radiusPlusOffset  = scope.radius + circleOffset;
            scope.radiusSquared     = scope.radius * scope.radius;
            scope.currentY          = oneHundred;
            scope.currentX          = -scope.currentY;

            for (y = 0; y < height; y++) {
              for (x = 0; x < width; x++) {
                var rx  = x - scope.radius,
                    ry  = y - scope.radius,
                    d   = rx * rx + ry * ry,
                    rgb = hsvToRgb((Math.atan2(ry, rx) + Math.PI) / (Math.PI * 2),
                          Math.sqrt(d) / scope.radius, 1); 

                pixels[wheelPixel++] = rgb[0];
                pixels[wheelPixel++] = rgb[1];
                pixels[wheelPixel++] = rgb[2];
                pixels[wheelPixel++] = d > scope.radiusSquared ? 0 : two55;
              }
            }

            context.putImageData(imageData, 0, 0);
          },
          post: function postLink(scope, iElement, iAttrs, controller) { 
            $(iElement).click(function(event) {
              scope.currentX = event.pageX - this.offsetLeft - scope.radiusPlusOffset || scope.currentX;
              scope.currentY = event.pageY - this.offsetTop - scope.radiusPlusOffset || scope.currentY;

              var theta = Math.atan2(scope.currentY, scope.currentX),
                  d = scope.currentX * scope.currentX + scope.currentY * scope.currentY;

              if (d > scope.radiusSquared) {
                scope.currentX = scope.radius * Math.cos(theta);
                scope.currentY = scope.radius * Math.sin(theta);
                theta = Math.atan2(scope.currentY, scope.currentX);
                d = scope.currentX * scope.currentX + scope.currentY * scope.currentY;
              }

              var color = hsvToRgb((theta + Math.PI) / (Math.PI * 2),
                Math.sqrt(d) / scope.radius, 1);
              var hex = rgbToHex(color[0], color[1], color[2]);

              $timeout(function(){
                scope.$parent[iAttrs.modelObject][iAttrs.modelProperty] = hex;                
              });
            });
          }
        };
      }
    };
  }]);

  ngColorPicker.directive('ngColorPicker', [function() {
    return {
      restrict: 'E',
      replace: true,
      scope : '@=',
      template: '<table>'
      +           '<tr>'
      +           '<td ng-repeat="color in colorList">'
      +             '<div style="width: 8px; height: 8px; border: {{color.select}}px solid #000; padding: 5px; background-color: {{color.color}}" ng-click="selectColor(color)">'
      +             '</div>'
      +           '<td>'
      +           '</tr>'
      +         '</table>',
      compile: function compile(tElement, tAttrs, transclude) {
        return {
          post: function postLink(scope, iElement, iAttrs, controller) { 
            scope.modelObject   = iAttrs.modelObject;
            scope.modelProperty = iAttrs.modelProperty;
            scope.colorList = [];
            angular.forEach(colors, function(color) {
              scope.colorList.push({
                color : color,
                select : 1
              });
            });
          }
        };
      },
      controller: function($scope, $element, $timeout) {
        $scope.selectColor = function(color) {
          for (var i = 0; i < $scope.colorList.length; i++) {
            $scope.colorList[i].select = 1;
            if ($scope.colorList[i] === color) {
              $scope[$scope.modelObject][$scope.modelProperty] = color.color;
              $scope.colorList[i].select = 2;
            }
          }
        };
      }
    };
  }]);
})(window.angular);

6. Working Demo

That’s it. Now you can test your files or you can be lazy and fork my working version on plunker or fork it on GitHub.