Beginning AngularJS
13 & 14 June 2015, Cowork South Bay
Troy Miles
Resources
http://www.slideshare.net/rockncoder/beginning-
angularjs
https://github.com/Rockncoder/ng-contacts
https://gith...
Troy Miles
Over 35 years of programming experience
Blog: http://therockncoder.blogspot.com/
Twitter: @therockncoder
Email:...
Agenda Day One
Introduction
Tools
Using Yeoman
To Do App
Testing
Animation
Services/Modules
Controller As
Filters
Agenda Day Two
Deployment
Providers
Contacts App
$http & Promises
$resource
Testing ajax calls
Firebase
Custom Directives
...
Lab #1 - Setup Check
In the labs directory
Launch the hello.html web page
You should see a greeting displayed
Lab Solution
Browser expect web applications to be delivered via a
web server
While most browser will allow a web page to ...
Lab #2 - jQuery Binding
From the labs folder open the binding.html file
Write JavaScript to transfer the text contents of t...
jQuery Example
<!DOCTYPE html>

<html>

<head lang="en">

<meta charset="UTF-8">

<title>Binding</title>

<script src="../...
What’s Wrong with jQuery
jQuery is a very popular library
It is used on 50% of all web sites
It is a library not a framewo...
AngularJS Example
<!DOCTYPE html>

<html ng-app>

<head lang="en">

<meta charset="UTF-8">

<title>NG-Binding</title>

<sc...
Strict Mode
Strict mode allows you to opt in to a more restrictive
version of JavaScript
Things usually permitted in JS be...
Invoking Strict Mode
Before any other statements place the following
statement
"use strict"; or 'use strict’;
Works on bot...
Other JavaScript MVC
Frameworks
Backbone.js
Knockout
EmberJS
Other frameworks & ToDoMVC
Backbone.js
Created by Jeremy Ashkenas
in 2010
19 kb production version
(minimized, not gzipped)
One dependency -
Undersco...
Knockout
Created by Steve Sanderson in
2010
47 kb production version
(minimized, not gzipped)
Uses MVVM pattern
Two way da...
Ember
Created by Yehuda Katz and
Tom Dale in 2011
Convention over configuration
Ember Data, a separate
package, handles RES...
AngularJS
Created by Miško Hevery and
Adam Abrons in 2009
JavaScript MVC
106 kb production version
(minimized, not gzipped...
AngularJS Key Features
Two Way Data-binding
Model View Controller
Dependency Injection
Deep Linking
HTML Templates
Directi...
Two Way Data-binding
In AngularJS, binding is built into the framework
Replaces text content of HTML element with the valu...
Model View Controller
Uses MVC or MVVM or MV* depends on who you ask
The goal is clear separation of concerns
The model is...
Dependency Injection
A software design pattern that implements inversion of
control and allows a program design to follow ...
Deep Linking
One feature that web sites have over desktop apps are
bookmarks
Deep linking allows AngularJS to restore stat...
HTML Templates
There are a lot of templating libraries
AngularJS instead uses HTML for its templates
AngularJS templates c...
Directives
Possibly the best thing in AngularJS
Directives extend the capabilities of HTML
Merge the declarative nature of...
Testability
AngularJS was engineered with testing in mind
It supports both unit and integration tests
For unit tests it wo...
npm
Included with Node.js
Installs many open source projects
https://www.npmjs.com/
Yeoman
Kickstarts a new project
Adheres to best practices
Uses Generators
http://yeoman.io/
Bower
Package manager for web applications
Keeps track of your libraries like jQuery, Bootstrap, etc
Dependencies kept in ...
Grunt
JavaScript Task Runner
Does mundane work for you
Hundreds of plugins available
http://gruntjs.com/
AngularJS Directives
ng-app
ng-controller
ng-model
ng-bind
ng-repeat
ng-if
ng-switch
ng-include
ng-view
ng-src / ng-href
ng-bind vs ng-model
ng-bind is one way data binding, aka output
ng-bind renders a property on scope
ng-bind has a shortcut...
Name Mangling
There are basic incompatibilities between names used in
HTML and those in JavaScript
HTML permits dashes and...
Name Mangling
So all of the following attributes equal timePicker:
data-time-picker
x:time-picker
time_picker
Lab - Greet-o-matic
Open the greet-o-magic.html file
Make the page functional
The user should be able to enter their name i...
Two Way Data-binding
<!DOCTYPE html>

<html ng-app>

<head lang="en">

<meta charset="UTF-8">

<title>Greet-o-matic</title...
$scope
An object which refers to the application model
The glue between the controller and the view
The execution context ...
Code Along - Todo App
A Todo app is the hello world app of JavaScript MVC
frameworks
It shows how to create an app which c...
Code Along - Todo App Steps
create shell page with
angular link
create script tag for JS
add ng-app with module
name ‘ToDo...
Code Along - Todo App Steps
add markup
add functionality to methods
clear text box after added
Jasmine
Latest version 2.2, but we will be using 2.0.2
The default unit tester for AngularJS
Others will also work
Behavio...
Describe - test suite
Describe is a global jasmine function
Two params
string - name of the test suite
function - implemen...
it - specs
it is a global jasmine function
Looks like describe
A spec contains one or more expectations
If all expectation...
Expectations
Expect function
One param
The actual
Chained to a Matcher function
Matchers
Take the output of the expect function and compare it
to something
Implement a boolean compare between actual val...
Some matchers
toBe - compares using ===
toEqual - works for literal variables and objects
toMatch - for regular expression...
Some matchers (CONTINUE)
toBeNull - compares against null
toBeTruthy - truthy boolean casting
toBeFalsy - falsy boolean ca...
Some matchers (CONTINUE)
toBeLessThan
toBeGreaterThan
toBeCloseTo - precision math comparison
toThrow - should throw an ex...
beforeEach / afterEach
Are setup and teardown functions
called before and after each spec it
this
beforeEach, it, and afte...
Disabling suites and specs
prepend an 'x' before describe or it
specs inside a disabled suite are not ran
Unit Testing 3As
Arrange - Set up object to be tested
Act - Act on the object
Assert - Test the state of the object
TDD vs BDD
Dev writes test
Run tests - fail
Implement tests in code
Run tests - pass
Refactor code
Dev writes behavior and...
Views
template - String contain HTML to display
templateUrl - Relative URL of file holding HTML or id of
DOM element holdin...
Inline View Template
The id name in the script tag matches…
templateUrl value of the route
template stored in index.html o...
Providers
What are providers?
Types of providers
Services
Factories
Providers
What are providers?
Objects that are instantiated and wired together
automatically by the injector service
The injector cr...
Types of providers
Constants
Value
Decorator
Provider
Service
Factory
Services
Substitutable objects that are wired together using DI
Used to organize and share code across app
Only instantiat...
Factories
Introduction to shared components
Dependency injection deep dive
Building custom factories & services
Persisting...
Lab - ng-contacts
We are going to super size the Todo app into a
contacts
We will Firebase as our backend
We can develop l...
Firebase
A powerful API to store and sync data in realtime
Offer free developer accounts
Both backend data and hosting
No ...
Firebase Data Limits
A node's key max length 768
A node can't be nested deeper than 32 levels
Any unicode character except...
Firebase Sizes
One child value - 100 mb UTF-8 encoded
SDK write value max - 16 mb UTF-8
REST write value max - 100 mb UTF-...
Firebase Writing Data
set()
Saves data to the specified Firebase reference
Any existing data at the path is overwritten
upd...
Firebase Installation
Add script tag for firebase
Add script tag for AngularFire
Add firebase.json file
Create a firebase refe...
Filters
Understanding Filters
A tour of built-in filters
Building custom Filters
Lab
Understanding Filters
Used to format data displayed to user
Strictly front-end, doesn’t change model data
Accessible using...
A tour of built-in filters
currency
date
json
lowercase
uppercase
number
filter
limitTo
orderBy
Building custom filters
tempApp.filter('minimum', [function () {

return function (arrTemp, minimum) {

var filteredArray =...
Lab - Using Filters
Let’s add a search filter to our contacts app
Directives
Introduction to Directives
jQuery integration
Using a jQuery UI Widget
Directives
Markers on a DOM element that attach a behavior to it
Can be an attribute, element name, comment, or CSS
The HT...
Directives Names
<div timePicker></div>
<div time-picker></div>
<div time:picker></div>
<div time_picker></div>
<div x-tim...
Directive Location
Tag name: <timePicker></timePicker>
Attribute: <div data-rnc-time-picker></
div>
Class: <div class=“tim...
Built-in Directives
ng-app
ng-bind
ng-controller
ng-href
ng-readonly
ng-repeat
ng-src
ng-submit
ng-transclude
ng-view
jQuery Integration
AngularJS includes a mini version of jQuery called
jqLite
But it is perfectly compatible with the jQuer...
Using a jQuery Widget
app.directive('timePicker', function () {

var today = new Date(new Date().toDateString());

return ...
Lab - jQuery UI Widget
Let’s add a jQuery UI calendar widget to our contact
app
We will essentially wrap it to make it “an...
Summary
Modern Web Apps
Compared AngularJS’ way to jQuery’s
AngularJS’ Core Concepts
Controllers
Todo App
Contacts App
Summary
Firebase
Custom Directives
Filters
Integrating jQuery Widgets with AngularJS
Troy Miles
Over 30 years of programming experience
Blog: http://therockncoder.blogspot.com/
Twitter: @therockncoder
Email:...
Upcoming SlideShare
Loading in …5
×

Beginning AngularJS

1,380 views
1,243 views

Published on

Published in: Software
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,380
On SlideShare
0
From Embeds
0
Number of Embeds
19
Actions
Shares
0
Downloads
82
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Beginning AngularJS

  1. 1. Beginning AngularJS 13 & 14 June 2015, Cowork South Bay Troy Miles
  2. 2. Resources http://www.slideshare.net/rockncoder/beginning- angularjs https://github.com/Rockncoder/ng-contacts https://github.com/Rockncoder/todo
  3. 3. Troy Miles Over 35 years of programming experience Blog: http://therockncoder.blogspot.com/ Twitter: @therockncoder Email: rockncoder@gmail.com GitHub: https://github.com/Rockncoder
  4. 4. Agenda Day One Introduction Tools Using Yeoman To Do App Testing Animation Services/Modules Controller As Filters
  5. 5. Agenda Day Two Deployment Providers Contacts App $http & Promises $resource Testing ajax calls Firebase Custom Directives Wrap-up
  6. 6. Lab #1 - Setup Check In the labs directory Launch the hello.html web page You should see a greeting displayed
  7. 7. Lab Solution Browser expect web applications to be delivered via a web server While most browser will allow a web page to run from a file, most won’t allow it to access other files If your machine is setup correctly, you will see a greeting
  8. 8. Lab #2 - jQuery Binding From the labs folder open the binding.html file Write JavaScript to transfer the text contents of the input tag with the id of firstName, to the span with the id of showName The code should be interactive and update as the user types Write you code in the empty script tag near the end of the page jQuery is already included on the page
  9. 9. jQuery Example <!DOCTYPE html>
 <html>
 <head lang="en">
 <meta charset="UTF-8">
 <title>Binding</title>
 <script src="../lib/jquery-1.10.2.min.js"></script>
 </head>
 <body>
 <label for="firstName">Enter Your Name:</label>
 <input type="text" id="firstName"/>
 <h2>Display Your Name Below</h2>
 <label for="showName">Show Your Name:</label>
 <span id="showName"></span>
 
 <script>
 $( document ).ready( function(){
 $('#firstName').on("keyup", function(){
 $('#showName').text(this.value);
 });
 });
 </script>
 </body>
 </html>
  10. 10. What’s Wrong with jQuery jQuery is a very popular library It is used on 50% of all web sites It is a library not a framework It is difficult code to test It can lead to the creation of spaghetti code
  11. 11. AngularJS Example <!DOCTYPE html>
 <html ng-app>
 <head lang="en">
 <meta charset="UTF-8">
 <title>NG-Binding</title>
 <script src="../lib/angular.min.js"></script>
 </head>
 <body>
 <label>Enter Your Name:</label>
 <input type="text" ng-model="firstName"/>
 <h2>Display Your Name Below</h2>
 <label>Show Your Name:</label>
 <span>{{firstName}}</span>
 
 <script>
 </script>
 </body>
 </html>
  12. 12. Strict Mode Strict mode allows you to opt in to a more restrictive version of JavaScript Things usually permitted in JS become errors Can be applied to an entire script or individual functions Strict mode is a JavaScript best practice
  13. 13. Invoking Strict Mode Before any other statements place the following statement "use strict"; or 'use strict’; Works on both the script and function level Be careful of mixing strict and non-strict mode files
  14. 14. Other JavaScript MVC Frameworks Backbone.js Knockout EmberJS Other frameworks & ToDoMVC
  15. 15. Backbone.js Created by Jeremy Ashkenas in 2010 19 kb production version (minimized, not gzipped) One dependency - Underscore.js, optional jQuery Three core concepts: models, collections, & views Uses lots of custom events
  16. 16. Knockout Created by Steve Sanderson in 2010 47 kb production version (minimized, not gzipped) Uses MVVM pattern Two way data-binding No dependencies Supports all mainstream browsers
  17. 17. Ember Created by Yehuda Katz and Tom Dale in 2011 Convention over configuration Ember Data, a separate package, handles RESTful data Handlebars.js, a separate package, handles templates 337 kb production version (minimized, not gzipped)
  18. 18. AngularJS Created by Miško Hevery and Adam Abrons in 2009 JavaScript MVC 106 kb production version (minimized, not gzipped) Declarative programming for UI Imperative programming for business logic
  19. 19. AngularJS Key Features Two Way Data-binding Model View Controller Dependency Injection Deep Linking HTML Templates Directives Testability
  20. 20. Two Way Data-binding In AngularJS, binding is built into the framework Replaces text content of HTML element with the value of the expression {{ expression }} <ANY ng-bind=“expression”>…</ANY> <ANY class=“ng-bind: expression;”>…</ANY>
  21. 21. Model View Controller Uses MVC or MVVM or MV* depends on who you ask The goal is clear separation of concerns The model is only concerned with data The view presents the data to the user The controller applies the business logic
  22. 22. Dependency Injection A software design pattern that implements inversion of control and allows a program design to follow the dependency inversion principle Allows a dependency to be passed to an object Allows code to clearly state dependencies Leads to code which is easier to debug and test Makes it possible to minimize apps without breaking them
  23. 23. Deep Linking One feature that web sites have over desktop apps are bookmarks Deep linking allows AngularJS to restore state based on a URL Application can use hyperlinks to navigate users around
  24. 24. HTML Templates There are a lot of templating libraries AngularJS instead uses HTML for its templates AngularJS templates can pass HTML validators Designers will feel at home Easy to learn
  25. 25. Directives Possibly the best thing in AngularJS Directives extend the capabilities of HTML Merge the declarative nature of HTML to the imperative nature of JavaScript
  26. 26. Testability AngularJS was engineered with testing in mind It supports both unit and integration tests For unit tests it works well with Jasmine Karma is the test runner
  27. 27. npm Included with Node.js Installs many open source projects https://www.npmjs.com/
  28. 28. Yeoman Kickstarts a new project Adheres to best practices Uses Generators http://yeoman.io/
  29. 29. Bower Package manager for web applications Keeps track of your libraries like jQuery, Bootstrap, etc Dependencies kept in bower.json file http://bower.io/
  30. 30. Grunt JavaScript Task Runner Does mundane work for you Hundreds of plugins available http://gruntjs.com/
  31. 31. AngularJS Directives ng-app ng-controller ng-model ng-bind ng-repeat ng-if ng-switch ng-include ng-view ng-src / ng-href
  32. 32. ng-bind vs ng-model ng-bind is one way data binding, aka output ng-bind renders a property on scope ng-bind has a shortcut {{}} ng-bind is preferred over shortcut ng-model is for two-way data binding ng-model is intended for form elements <input ng-model='userName' />
  33. 33. Name Mangling There are basic incompatibilities between names used in HTML and those in JavaScript HTML permits dashes and colons, JavaScript does not To convert to JavaScript delete any initial x-, data- First letters after are capitalized delete dashes, underscores, and colons
  34. 34. Name Mangling So all of the following attributes equal timePicker: data-time-picker x:time-picker time_picker
  35. 35. Lab - Greet-o-matic Open the greet-o-magic.html file Make the page functional The user should be able to enter their name in either input tag and have it reflect in the other and in the span tag You shouldn’t need to write any JavaScript
  36. 36. Two Way Data-binding <!DOCTYPE html>
 <html ng-app>
 <head lang="en">
 <meta charset="UTF-8">
 <title>Greet-o-matic</title>
 <link rel="stylesheet" href="../css/bootstrap.css"/>
 <script type="text/javascript" src="../libs/angular.js"></script>
 </head>
 <body>
 <div class="container" >
 <h1>Greet-o-matic</h1>
 <div class="col-lg-6">
 <input type="text" ng-model="userName" placeholder="Enter name here"/>
 </div>
 <div class="col-lg-6">
 <input type="text" ng-model="userName" placeholder="or over here"/>
 </div>
 <hr/>
 <p>Hello <span>{{userName}}</span>,<br/>Have a nice day!</p>
 </div>
 </body>
 </html>
  37. 37. $scope An object which refers to the application model The glue between the controller and the view The execution context for expressions Provides APIs $watch - observes model $apply - propagates model changes to AngularJS
  38. 38. Code Along - Todo App A Todo app is the hello world app of JavaScript MVC frameworks It shows how to create an app which creates, reads, updates, and deletes data (CRUD) Let’s build one together
  39. 39. Code Along - Todo App Steps create shell page with angular link create script tag for JS add ng-app with module name ‘ToDo’ create angular module create todo controller create todos array create add method create delete method create complete method
  40. 40. Code Along - Todo App Steps add markup add functionality to methods clear text box after added
  41. 41. Jasmine Latest version 2.2, but we will be using 2.0.2 The default unit tester for AngularJS Others will also work Behavior Driven Development (BDD) approach
  42. 42. Describe - test suite Describe is a global jasmine function Two params string - name of the test suite function - implementation of the suite Can be nested
  43. 43. it - specs it is a global jasmine function Looks like describe A spec contains one or more expectations If all expectations true, it is a passing spec If any expectation fails, it is a failing spec
  44. 44. Expectations Expect function One param The actual Chained to a Matcher function
  45. 45. Matchers Take the output of the expect function and compare it to something Implement a boolean compare between actual value and expected Reports to Jasmine if the expectation is true or false Any matcher can be negated with a not before it
  46. 46. Some matchers toBe - compares using === toEqual - works for literal variables and objects toMatch - for regular expressions toBeDefined - compares against 'undefined' toBeUndefined - also compares against ‘undefined'
  47. 47. Some matchers (CONTINUE) toBeNull - compares against null toBeTruthy - truthy boolean casting toBeFalsy - falsy boolean casting toContain - finds an item in array
  48. 48. Some matchers (CONTINUE) toBeLessThan toBeGreaterThan toBeCloseTo - precision math comparison toThrow - should throw an exception
  49. 49. beforeEach / afterEach Are setup and teardown functions called before and after each spec it this beforeEach, it, and afterEach share the same this it is cleared before call spec call any beforeEach not included in a describe block is executed before any Jasmine test can use this to add custom matchers
  50. 50. Disabling suites and specs prepend an 'x' before describe or it specs inside a disabled suite are not ran
  51. 51. Unit Testing 3As Arrange - Set up object to be tested Act - Act on the object Assert - Test the state of the object
  52. 52. TDD vs BDD Dev writes test Run tests - fail Implement tests in code Run tests - pass Refactor code Dev writes behavior and specs Run specs - fail Implement specs in code Run specs - pass Refactor code
  53. 53. Views template - String contain HTML to display templateUrl - Relative URL of file holding HTML or id of DOM element holding HTML
  54. 54. Inline View Template The id name in the script tag matches… templateUrl value of the route template stored in index.html or other HTML file 54 <script id="contact.html" type="text/ng-template">
 <h2>Contact</h2>
 <hr/>
 <p>For more information email rockncoder@gmail.com</p>
 <hr/>
 </script>
  55. 55. Providers What are providers? Types of providers Services Factories Providers
  56. 56. What are providers? Objects that are instantiated and wired together automatically by the injector service The injector creates two kinds of objects: services - defined by the developer specialized objects - Angular framework pieces, controllers, directives, filters, or animations
  57. 57. Types of providers Constants Value Decorator Provider Service Factory
  58. 58. Services Substitutable objects that are wired together using DI Used to organize and share code across app Only instantiated when an app component depends on it Singletons Built-in services always start with “$”
  59. 59. Factories Introduction to shared components Dependency injection deep dive Building custom factories & services Persisting data to a Web API service
  60. 60. Lab - ng-contacts We are going to super size the Todo app into a contacts We will Firebase as our backend We can develop locally and then deploy
  61. 61. Firebase A powerful API to store and sync data in realtime Offer free developer accounts Both backend data and hosting No real arrays (limitation due to contention problem) Used by AngularJS.org site https://www.firebase.com/
  62. 62. Firebase Data Limits A node's key max length 768 A node can't be nested deeper than 32 levels Any unicode character except .$[]/ plus the ASCII control keys of 0-31 and 127
  63. 63. Firebase Sizes One child value - 100 mb UTF-8 encoded SDK write value max - 16 mb UTF-8 REST write value max - 100 mb UTF-8 Nodes in a read operation 100 million
  64. 64. Firebase Writing Data set() Saves data to the specified Firebase reference Any existing data at the path is overwritten update() push() transaction
  65. 65. Firebase Installation Add script tag for firebase Add script tag for AngularFire Add firebase.json file Create a firebase reference object
  66. 66. Filters Understanding Filters A tour of built-in filters Building custom Filters Lab
  67. 67. Understanding Filters Used to format data displayed to user Strictly front-end, doesn’t change model data Accessible using declarative or imperative syntax {{ expression [| filter_name[:parameter_value] ... ] }} $scope.originalText = 'hello';
 $scope.filteredText = $filter('uppercase') ($scope.originalText);
  68. 68. A tour of built-in filters currency date json lowercase uppercase number filter limitTo orderBy
  69. 69. Building custom filters tempApp.filter('minimum', [function () {
 return function (arrTemp, minimum) {
 var filteredArray = [];
 var min = minimum ? minimum : 15;
 angular.forEach(arrTemp, function (value, key) {
 if (value.temp >= min) filteredArray.push(value);
 });
 return filteredArray;
 };
 }]);

  70. 70. Lab - Using Filters Let’s add a search filter to our contacts app
  71. 71. Directives Introduction to Directives jQuery integration Using a jQuery UI Widget
  72. 72. Directives Markers on a DOM element that attach a behavior to it Can be an attribute, element name, comment, or CSS The HTML compiler traverses the DOM at bootstrap and matches directives to DOM elements
  73. 73. Directives Names <div timePicker></div> <div time-picker></div> <div time:picker></div> <div time_picker></div> <div x-time-picker></div> <div data-time-picker></div>
  74. 74. Directive Location Tag name: <timePicker></timePicker> Attribute: <div data-rnc-time-picker></ div> Class: <div class=“time-picker;”></div> Comment: <!— directive:time-picker —>
  75. 75. Built-in Directives ng-app ng-bind ng-controller ng-href ng-readonly ng-repeat ng-src ng-submit ng-transclude ng-view
  76. 76. jQuery Integration AngularJS includes a mini version of jQuery called jqLite But it is perfectly compatible with the jQuery jQuery must be loaded before Angular or it won’t see it
  77. 77. Using a jQuery Widget app.directive('timePicker', function () {
 var today = new Date(new Date().toDateString());
 return {
 require: '?ngModel',
 link: function ($scope, $element, $attrs, ngModel) {
 var initialized = false;
 
 ngModel = ngModel || {
 "$setViewValue": angular.noop
 };
 
 // where is the missing time value?
 setTimeout(function () {
 initialized = $element.timepicker()
 .on('changeTime', function (ev, ui) {
 var sec = $element.timepicker('getSecondsFromMidnight');
 ngModel.$setViewValue(sec * 1000);
 console.log("sec = " + sec);
 });
 });
 
 ngModel.$render = function (val) {
 if (!initialized) {
 //If $render gets called before our timepicker plugin is ready, just return
 return;
 }
 $element.timepicker('setTime', new Date(today.getTime() + val));
 }
 }
 }
 });
  78. 78. Lab - jQuery UI Widget Let’s add a jQuery UI calendar widget to our contact app We will essentially wrap it to make it “angularized”
  79. 79. Summary Modern Web Apps Compared AngularJS’ way to jQuery’s AngularJS’ Core Concepts Controllers Todo App Contacts App
  80. 80. Summary Firebase Custom Directives Filters Integrating jQuery Widgets with AngularJS
  81. 81. Troy Miles Over 30 years of programming experience Blog: http://therockncoder.blogspot.com/ Twitter: @therockncoder Email: rockncoder@gmail.com GitHub: https://github.com/Rockncoder

×