Tom Norian Site Services an on-call resource for a select clientele

Get a quick start on a ReactJS static, serverless site by cloning my git Repo.

This starter pack uses Webpack and Babel to compile bundled files into a public directory that can be placed on any cheap server or website enabled S3 bucket.

To make it better I’ve got all the settings to use SASS with ReactJS, and a Bootstrap Navbar to have a really nice “Hello World” up in no time.

This is not a ReactJS or webpack tutorial … just look at it as a “cheat-sheet for Webpack and ReactJS site launched using SASS and Bootstrap for compiled CSS creation”

What I’m Sharing:

  • a git repo to clone for a basic React site.
  • NPM commands necessary to load the Node dependencies
  • Package.json and wepback examples are also included in this post itself.

Ok, let’s get started !

demo: check out the live demo

GitHub repo: ReactStaticSiteStarter GitHub repository.

Download the zip file for the repo above (or clone it at your preference) … then …

npm i webpack@3.4.0 -D

npm i babel-core@6 babel-loader@6 babel-preset-react@6 -D

npm i react@15 react-dom@15 -D

npm i css-loader node-sass sass-loader extract-text-webpack-plugin -D

Check the package.json below to be sure !

LOOK! Only Dev dependencies!?

Yep - all the React code gets compiled into a js file

.. the SASS gets turned into simple CSS. You might want to use a small subsection of Bootstrap or another libary which you can include within the ‘src/css’ folder. The files will get treated correctly depending on whether they have a .scss or .css extension.

Create a basic HTML index file in the PUBLIC folder.

NOTE: I am using CDNs for Bootstrap and JQuery - This index.html is in the Repo already.


<!DOCTYPE html>
<html>
  <head>
    <script
  src="https://code.jquery.com/jquery-3.2.1.min.js"
  integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
  crossorigin="anonymous"></script>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
    <title>Tom's React Starter Kit</title>
    <link rel="stylesheet" href="css/bundle.css">
  </head>
  <body>
    <div id="content"></div>
    <script type="text/javascript" src="js/bundle.js">
    </script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

  </body>
</html>

 

All of the JSX is in the jsx folder inside the src folder. (yes this project also uses the JSX choice of syntax DSL with React)

Again, this is not a tutorial, but syntax can be tricky.

Here is how I configured my Webpack file to compile only the needed code into the public file.

// webpack.config.js


var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  entry: ['./src/jsx/app.jsx',  './src/css/custom.css', './src/css/main.scss',],
  output: {
    path: __dirname + '/public/js/',
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /(node_modules)/,
        loaders: ['babel-loader']
      },
      { 
        test: /\.css$/,
        loader: ExtractTextPlugin.extract(['css-loader']),
      },
      { 
        test: /\.(sass|scss)$/,
        loader: ExtractTextPlugin.extract(['css-loader', 'sass-loader'])
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin({ 
      filename: '../css/bundle.css',
      allChunks: true,
    })
  ]
}


This excellent article: great reference to enable SASS with webpack got me most of the way.

There were some changes you see in my file that differ from his. (things are always changing ! :) )

For good measure here is what your final package.json should look like. If we missed any dependencies go and fetch them with npm i modulename -D

// package.json

{
  "name": "ReactWithSassQuickStart",
  "version": "1.0.0",
  "description": "This is a starter kit to create a static ReactJS website with Sass, Bootstrap with Navbar",
  "main": "index.js",
  "scripts": {
    "build": "./node_modules/.bin/webpack",
    "build-watch": "./node_modules/.bin/webpack -w"
  },
  "author": "",
  "license": "ISC",
  "babel": {
    "presets": [
      "react"
    ]
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^6.4.1",
    "babel-preset-react": "^6.24.1",
    "css-loader": "^0.28.7",
    "extract-text-webpack-plugin": "^3.0.1",
    "node-sass": "^4.5.3",
    "react": "^15.6.2",
    "react-dom": "^15.6.2",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.19.0",
    "webpack": "^3.4.0"
  }
}

Again, here is the demo: check out the live demo

And here is the GitHub repo: ReactStaticSiteStarter GitHub repository.

Now, Compile and Deploy!

Simply run the script to see that it looks right on your local machine.

(the script is defined within the package.json file shown above)

root[master]>npm run build-watch

I serve it using the the node ‘static’ tool (install that globally: npm i static -g ) by typing

npm i static -g

public[master]> static

Take a look in your web browser at : http://127.0.0.1:8080/#

If it looks good, use your favorite server SCP tool or method to push the entire contents of your Public folder - including the ‘css’, ‘js’ and ‘images’ folders which now have compiled versions of your js and css files within them.

Presto.. you should be in action just like on the live demo page above.

As always, let me know anything I missed with comments and suggestions!




How many named colors are there on the web? Well, that is a complicated question that can be answered in the continually updated Wikipedia article on Web Colors .

Not all browsers support all colors and while the effective number of colors has grown, the list of named CSS colors I found that almost all browsers supported was about 140… I have 138 below I believe. The are other standards with multiple numbered shades of given colors would add up to a greater number. Again, you can read about it all at Wikipedia.

Would you like an array of all named the CSS colors widely accepted on all browsers? Named colors with their both their hexadecimal and their RGB codes available by index ?

Here is one…free of charge…all typed up and grouped in a rough way. I have them displayed below that and you can hover for the color name.

Do you like guessing colors? Try a color game that I made right after BootCamp.

I hand formatted this array a couple years ago before I knew what I was doing. I probably would have made them objects if I had it to do again but, as Tim Gunn says, if you need something like this quickly you can “make it work”.

var colors = [
    ["maroon", "#800000", 128, 0, 0],
    ["darkred", "#8B0000", 139, 0, 0],
    ["firebrick", '#B22222', 178, 34, 34],
    ["crimson", "#DC143C", 220, 20, 60],
    ["red", "#FF0000", 255, 0, 0],
    ["tomato", "#FF6347", 255, 99, 71],
    ["coral", "#FF7F50", 255, 127, 80],
    ["indianred", "#CD5C5C", 205, 92, 92],
    ["lightcoral", "#F08080", 240, 128, 128],
    ["darksalmon", "#E9967A", 233, 150, 122],
    ["salmon", "#FA8072", 250, 128, 114],
    ["lightsalmon", "#FFA07A", 255, 160, 122],
    ["bisque", "#FFE4C4", 255, 228, 196],
    ["blanchedalmond", "#FFEBCD", 255, 235, 205],
    ["peachpuff", "#FFDAB9", 255, 218, 185],
    ["wheat", "#F5DEB3", 245, 222, 179],
    ["navajowhite", "#FFDEAD", 255, 222, 173],
    ["cornsilk", "#FFF8DC", 255, 248, 220],
    ["saddlebrown", "#8B4513", 139, 69, 19],
    ["sienna", "#A0522D", 160, 82, 45],
    ["orangered", "#FF4500", 255, 69, 0],
    ["darkorange", "#FF8C00", 255, 140, 0],
    ["orange", "#FFA500", 255, 165, 0],
    ["brown", "#A52A2A", 165, 42, 42],
    ["gold", "#FFD700", 255, 215, 0],
    ["darkgoldenrod", "#B8860B", 184, 134, 11],
    ["goldenrod", "#DAA520", 218, 165, 32],
    ["palegoldenrod", "#EEE8AA", 238, 232, 170],
    ["darkkhaki", "#BDB76B", 189, 183, 107],
    ["khaki", "#F0E68C", 240, 230, 140],
    ["moccasin", "#FFE4B5", 255, 228, 181],
    ["antiquewhite", "#FAEBD7", 250, 235, 215],
    ["beige", "#F5F5DC", 245, 245, 220],
    ["lemonchiffon", "#FFFACD", 255, 250, 205],
    ["lightgoldenrodyellow", "#FAFAD2", 250, 250, 210],
    ["lightyellow", "#FFFFE0", 255, 255, 224],
    ["chocolate", "#D2691E", 210, 105, 30],
    ["peru", "#CD853F", 205, 133, 63],
    ["sandybrown", "#F4A460", 244, 164, 96],
    ["burlywood", "#DEB887", 222, 184, 135],
    ["tan", "#D2B48C", 210, 180, 140],
    ["rosybrown", "#BC8F8F", 188, 143, 143],
    ["yellow", "#FFFF00", 255, 255, 0],
    ["olive", "#808000", 128, 128, 0],
    ["darkolivegreen", "#556B2F", 85, 107, 47],
    ["olivedrab", "#6B8E23", 107, 142, 35],
    ["lawngreen", "#7CFC00", 124, 252, 0],
    ["chartreuse", "#7FFF00", 127, 255, 0],
    ["greenyellow", "#ADFF2F", 173, 255, 47],
    ["darkgreen", "#006400", 0, 100, 0],
    ["green", "#008000", 0, 128, 0],
    ["forestgreen", "#228B22", 34, 139, 34],
    ["lime", "#00FF00", 0, 255, 0],
    ["lightcyan", "#E0FFFF", 224, 255, 255],
    ["limegreen", "#32CD32", 50, 205, 50],
    ["lightgreen", "#90EE90", 144, 238, 144],
    ["palegreen", "#98FB98", 152, 251, 152],
    ["darkseagreen", "#8FBC8F", 143, 188, 143],
    ["mediumspringgreen", "#00FA9A", 0, 250, 154],
    ["springgreen", "#00FF7F", 0, 255, 127],
    ["yellowgreen", "#9ACD32", 154, 205, 50],
    ["seagreen", "#2E8B57", 46, 139, 87],
    ["mediumaquamarine", "#66CDAA", 102, 205, 170],
    ["mediumseagreen", "#3CB371", 60, 179, 113],
    ["lightseagreen", "#20B2AA", 32, 178, 170],
    ["darkslategray", "#2F4F4F", 47, 79, 79],
    ["teal", "#008080", 0, 128, 128],
    ["darkcyan", "#008B8B", 0, 139, 139],
    ["aqua", "#00FFFF", 0, 255, 255],
    ["cyan", "#00FFFF", 0, 255, 255],
    ["darkturquoise", "#00CED1", 0, 206, 209],
    ["turquoise", "#40E0D0", 64, 224, 208],
    ["mediumturquoise", "#48D1CC", 72, 209, 204],
    ["paleturquoise", "#AFEEEE", 175, 238, 238],
    ["aquamarine", "#7FFFD4", 127, 255, 212],
    ["powderblue", "#B0E0E6", 176, 224, 230],
    ["cadetblue", "#5F9EA0", 95, 158, 160],
    ["steelblue", '#4682B4', 70, 130, 180],
    ['cornflowerblue', "#6495ED", 100, 149, 237],
    ["deepskyblue", "#00BFFF", 0, 191, 255],
    ["dodgerblue", "#1E90FF", 30, 144, 255],
    ["lightblue", "#ADD8E6", 173, 216, 230],
    ["skyblue", "#87CEEB", 135, 206, 235],
    ["lightskyblue", "#87CEFA", 135, 206, 250],
    ["midnightblue", "#191970", 25, 25, 112],
    ["navy", "#000080", 0, 0, 128],
    ["darkblue", "#00008B", 0, 0, 139],
    ["mediumblue", "#0000CD", 0, 0, 205],
    ["blue", "#0000FF", 0, 0, 255],
    ["royalblue", "#4169E1", 65, 105, 225],
    ["blueviolet", "#8A2BE2", 138, 43, 226],
    ["indigo", "#4B0082", 75, 0, 130],
    ["darkslateblue", "#483D8B", 72, 61, 139],
    ["slateblue", "#6A5ACD", 106, 90, 205],
    ["mediumslateblue", "#7B68EE", 123, 104, 238],
    ["mediumpurple", "#9370DB", 147, 112, 219],
    ["darkmagenta", "#8B008B", 139, 0, 139],
    ["darkviolet", "#9400D3", 148, 0, 211],
    ["darkorchid", "#9932CC", 153, 50, 204],
    ["mediumorchid", "#BA55D3", 186, 85, 211],
    ["purple", "#800080", 128, 0, 128],
    ["thistle", "#D8BFD8", 216, 191, 216],
    ["plum", "#DDA0DD", 221, 160, 221],
    ["violet", "#EE82EE", 238, 130, 238],
    ["magenta", "#FF00FF", 255, 0, 255],
    ["orchid", "#DA70D6", 218, 112, 214],
    ["mediumvioletred", "#C71585", 199, 21, 133],
    ["palevioletred", "#DB7093", 219, 112, 147],
    ["deeppink", "#FF1493", 255, 20, 147],
    ["hotpink", "#FF69B4", 255, 105, 180],
    ["lightpink", "#FFB6C1", 255, 182, 193],
    ["pink", "#FFC0CB", 255, 192, 203],
    ["mistyrose", "#FFE4E1", 255, 228, 225],
    ["lavenderblush", "#FFF0F5", 255, 240, 245],
    ["linen", "#FAF0E6", 250, 240, 230],
    ["oldlace", "#FDF5E6", 253, 245, 230],
    ["papayawhip", "#FFEFD5", 255, 239, 213],
    ["seashell", "#FFF5EE", 255, 245, 238],
    ["mintcream", "#F5FFFA", 245, 255, 250],
    ["slategray", "#708090", 112, 128, 144],
    ["lightslategray", "#778899", 119, 136, 153],
    ["lightsteelblue", "#B0C4DE", 176, 196, 222],
    ["lavender", "#E6E6FA", 230, 230, 250],
    ["floralwhite", "#FFFAF0", 255, 250, 240],
    ["aliceblue", "#F0F8FF", 240, 248, 255],
    ["ghostwhite", "#F8F8FF", 248, 248, 255],
    ["honeydew", "#F0FFF0", 240, 255, 240],
    ["ivory", "#FFFFF0", 255, 255, 240],
    ["azure", "#F0FFFF", 240, 255, 255],
    ["snow", "#FFFAFA", 255, 250, 250],
    ["black", "#000000", 0, 0, 0],
    ["dimgray", "#696969", 105, 105, 105],
    ["gray", "#808080", 128, 128, 128],
    ["darkgray", "#A9A9A9", 169, 169, 169],
    ["silver", "#C0C0C0", 192, 192, 192],
    ["lightgray", "#D3D3D3", 211, 211, 211],
    ["gainsboro", "#DCDCDC", 220, 220, 220],
    ["whitesmoke", "#F5F5F5", 245, 245, 245],
    ["white", "#FFFFFF", 255, 255, 255]
];




I decided to take a look back at AngularJS this week after having spent the last few months with other front-end DSL environments and platforms. (EmberJS and, to the extent it qualifies, Jekyll/Liquid).

I think the time I spent learning EmberJS late in 2016 really began to get me in the mind-set of web components. More on EmberJS in other posts, and I’ll leave the “meta” discussion of web components up to the trend setters…I’m following you though!

AngularJS has been actively moving toward components itself, even in Angular 1.X which has an aura of being ‘deprecated’.

As RTFM works, you can find out these things! What’s more, some precursors to components were there in angularJS all along.

What I’m Sharing:

  • a basic example of a directive that can modularize “sortable tables”
  • a few examples of AngularJS bells and whistles in use
  • an example how you can pass different actions to the same reusable custom directive you create.

sortable table demo Really it is that last one, the passing of a function name to a directive, enabling the same directive to be used in different ways, that made me want to share this.

While I’m not sure I nailed the “AngularJS way”, the process had me taking a deeper dive.

The AngularJS website and style guides didn’t really have any great examples so, maybe seeing my syntax will get your juices flowing.

A quick reminder why to use directives

First, open up the (external) live demo to see the two sortable lists where we’ll reuse page segments that are more than flat information displays.

Second, open the code toggle below for something ugly!


 <div class="container" >
    <div class="col-md-12">
      <div ng-controller="RatingListController as list ">
      <div class="col-md-3 master-list">
        <table id="all-performer-table" class="table table-bordered table-striped">
          <thead ng-init="sortType='score'; sortReverse=true ">
            <tr>
              <td>
                <a href="#"  ng-click="sortType = '-name'; sortReverse = !sortReverse">
                  Select From Below
                  <span ng-show="sortType == '-name' && sortReverse" class="fa fa-caret-down"></span>
                  <span ng-show="sortType == '-name' && !sortReverse" class="fa fa-caret-up"></span>
                </a>
              </td>
              <td>
                <a href="#" ng-click="sortType = 'score'; sortReverse = !sortReverse">
                Score 
                  <span ng-show="sortType == 'score' && !sortReverse" class="fa fa-caret-down"></span>
                  <span ng-show="sortType == 'score' && sortReverse" class="fa fa-caret-up"></span>
                </a>
              </td>
              <td class="check-box-label">  
               +<span style="color: red;"> -</span>
              </td>
            </tr>
          </thead>  
          <tr ng-repeat="item in list.itemsInSet | orderBy:sortType:sortReverse">
            <td ng-class="{limeHighlight: item.selected}" >{{item.name}}</td>
            <td>{{item.score}}</td>
            <td><input type="checkbox" ng-model=item.selected ng-click="list.addOrRemoveFromRatedItems(item)"></td>
          </tr>
        </table>
      </div>
      <div class="col-md-4 selected-list">
        <ol class="selected-item-list">
          <li ng-repeat="selItem in list.ratedItems" class="selected-item-rated">
            <span >{{selItem.name}}- {{selItem.$$hashKey}}</span>
            <div class="list-making-tools pull-right" >
              <span class="glyphicon glyphicon-remove-circle text-danger" ng-click="list.removeFromRatedItems(selItem)"></span>
              <span class="glyphicon glyphicon-arrow-down text-warning " ng-click="list.moveDownRatedItems(selItem)"></span>
              <span class="glyphicon glyphicon-arrow-up text-success" ng-click="list.moveUpRatedItems(selItem)"></span>
            </div>
          </li>
        </ol>
      </div>
      </div> <!-- end RatingListController -->
      <div class="col-md-5 demo-explanation " ng-controller="PhotoDisplayController as photos">
        <div class="photo-box">
          <img ng-src="{{photos.chosenPhoto.photo_url}}" alt="{{photos.chosenPhoto.title}}">
        </div>
        <table id="photo-table" class="table table-bordered table-striped">
          <thead ng-init="sortType='score'; sortReverse=true ">
            <tr>
              <td>
                <a href="#"  ng-click="sortType = '-name'; sortReverse = !sortReverse">
                  Select From Below
                  <span ng-show="sortType == '-name' && sortReverse" class="fa fa-caret-down"></span>
                  <span ng-show="sortType == '-name' && !sortReverse" class="fa fa-caret-up"></span>
                </a>
              </td>
              <td>
                <a href="#" ng-click="sortType = 'score'; sortReverse = !sortReverse">
                Year 
                  <span ng-show="sortType == 'score' && !sortReverse" class="fa fa-caret-down"></span>
                  <span ng-show="sortType == 'score' && sortReverse" class="fa fa-caret-up"></span>
                </a>
              </td>
              <td class="check-box-label">  
               +<span style="color: red;"> -</span>
              </td>
            </tr>
          </thead>  
          <tr ng-repeat="item in photos.itemsInSet | orderBy:sortType:sortReverse">
            <td ng-class="{limeHighlight: item.selected}" >{{item.name}}</td>
            <td>{{item.year}}</td>
            <td><input type="checkbox" ng-model=item.selected ng-click="list.addOrRemoveFromRatedItems(item)"></td>
          </tr>
        </table>
      </div>
    </div>
  </div>
  

That sort of mess of logic in the view is why AngularJS got a bad rap… but! … that was never how it was supposed to be!

Here is how that HTML page looks with the content broken out into directives:


<div class="col-md-12"> 
  <div class="first-two-columns" ng-controller="RatingListController as list ">
    <div class="col-md-3 master-list">
      <tn-sortable-table 
        list-data='list' cols-displayed="name,score" cols-labels="Character,Rating" check-action='addOrRemoveFromRatedItems' sort-col='score' sort-desc='true'>
      </tn-sortable-table>
    </div>
    <div class="col-md-4 selected-list">
      <user-selected-list></user-selected-list>
    </div>
  </div> <!-- end RatingListCotnroller -->
  <div class="col-md-5 photo-display-list " ng-controller="PhotoDisplayController as photos">
    <div class="photo-box">
      <img ng-src="{{photos.chosenPhoto.photo_url}}" alt="{{photos.chosenPhoto.title}}">
    </div>
    <tn-sortable-table 
      list-data='photos' cols-displayed="title,category,year" cols-labels="Photos of Tom,Activity,Year" check-action="pickPhoto" sort-col='title' sort-desc='false'>
    </tn-sortable-table>
  </div> <!-- end PhotoDisplayController -->
</div>

 

See? The main page segments are called by custom tag with their specific code factored out.

The key element is the creation of the DSL like <sortable-table> tags.

You can use data from mutiple controllers, name the data columns you want to see, give custom column labels, and specify which columns and direction to sort by.

AND, you can dictate what action will be performed after checking…all from one directive line.

I will indent and comment for easier reading:

<tn-sortable-table 
  list-data='list' <!--we actually pass all the functions down to the directive to enable action-->
                   <!--for more than a demo controllers another param would be added here for custom data-->
  cols-displayed="name,score"<!--These Two columns of the data table will be displayed in this order-->
  cols-labels="Character Name,Rating"<!--Label the headers in a user friendly way-->
  sort-col='score' sort-desc='true'<!--On load, show table sorted by it's 'score' column descending-->
  check-action='addOrRemoveFromRatedItems'><!--this is the Tricky Part : pass a function name-->
</tn-sortable-table>

The first above lets a visitor choose names from a master list and put them into their own order.

You can see my project where you can use something similar to make Top Ten Lists and have your votes added to the public score as well as make image of your own opinions to share via social media.

And my second use of the directive, this time as a control feature for a photo display as you saw in the linked demo.

<tn-sortable-table 
  list-data='photos' <!-- same directive, different controller -->
  cols-displayed="title,category,year"<!--Three columns this time - any number of cols works-->
  cols-labels="Photos of Tom,Activity,Year"<!--user can click on any table header label to resort-->
  sort-col='title' sort-desc='false'
  check-action="pickPhoto" > 
</tn-sortable-table>

The parameters: list-data , passes all the list data downward as well as the scope needed to execute your action.

The ‘cols-displayed’ and ‘cols-labels’ do what you think.. the first is used as a key and the second as text in the table headers. Note, AngularJS will not take arrays. I chose comma separated strings as the easiest alternative to type.

It is easy to allow a content creator to choose what column the tables should be ranked on when landing with sort-col (name of the data key column) and sort-desc (true or false).

The checkAction is a string name of the function on the controller that will be called.

You can see the directive’s JS file below:

Here is the directive’s JS file that handles those the <tn-sortable-table> tags above.

// js/directives/sortable-table.js

angular
  .module('demo-directive-app')
  .directive('tnSortableTable', sortableTable);

  function sortableTable(){
    return {
      restrict: 'E',
      scope:{
        listData : "=listData"
      },
      templateUrl: 'templates/directives/sortable-table.html',
      link: function(scope, element, attrs){
        scope.colsDisplayed = attrs.colsDisplayed.split(',');
        scope.colsLabels = attrs.colsLabels.split(',');
        scope.sortCol = attrs.sortCol;
        scope.sortDesc = (attrs.sortDesc == "true");
        scope.itemsInSet = scope.listData.itemsInSet;
        scope.checkAction = function(item){
          scope.listData[attrs.checkAction](item);
        }
      }  
    };
  };

There are a few tricky things there. The kebab - separated names get ‘normalized’ into camel case in the JS file. (i.e. cols-displayed must be changed to colsDisplayed).

I use the setter format “=listData” to expand the page scope into the directive scope. An alternative way that you need when you’ll manipulate the data is the use of the ‘link: function() ‘ syntax.

I am going to cop-out on a true explanation of how this all works. That would be longer than this post all together.

Here are a few links Stack overflow essay on AngularJS inheritance and the main AngularJS developer guide on directives

I worry somewhat about the elegance of passing the entire scope via ‘listData’ however it’s important to realize …. and you might pound your head against the table for a few hours … that you can’t have ng-click’s given action build a regex or function via interpolated elements of expressions!

from their site:

No function declarations or RegExp creation with literal notation

You can’t declare functions or create regular expressions from within AngularJS expressions. This is to avoid complex model transformation logic inside templates. Such logic is better placed in a controller or in a dedicated filter where it can be tested properly.

So, that explains why I was sure that the parent scope of the controller was available to the directive via the “listData” param.

You CAN use a directive parameter to -be executed- . Ng-click looks at what is between the “quotes” and first tries to execute it rather than interpolating {{}} first then executing. Many work-arounds attempts will stop the entire page from executing.

What I do below is one working approach:

<!-- templates/sortable-table.html -->

<table id="all-performer-table" class="table table-bordered table-striped">
  <thead ng-init="sortType=colTwo; sortReverse=true ">
    <tr>
      <td sortable-table-header inner-type='sortType' inner-reverse ='sortReverse' this-col='colOne'></td>
      <td sortable-table-header inner-type='sortType' inner-reverse ='sortReverse' this-col='colTwo'></td >
      <td class="check-box-label">  
       +<span style="color: red;"> -</span>
     </td>
   </tr>
  </thead>  
  <tr ng-repeat="item in listData.itemsInSet | orderBy:sortType:sortReverse" > 
    <td ng-class="{limeHighlight: item.selected}" >
      {{item[colOne]}}
    </td>
    <td>
      {{item[colTwo]}}
    </td>
    <td>
      <input type="checkbox" ng-model=item.selected ng-click="listData[checkAction](item)">
    </td>
  </tr>
</table>
 

You’ll see that I have yet another directive, the directive, nested withing the directives.

That inner hidden directive is passed a column name to label the column from the parent parameter.

  • the single quotes around ‘colOne’ passes teh value of ‘colOne’, not the string “colOne”.
  • to retrieve the column information using that dynamic parameter, use bracket notation.

**By breaking out the header elements further, we cleans out the repeated glyphicon references. Even more importantly it is a step towards being able to iterate over an array of column names to accommodate a variable number of columns using a single directive. **

A developer won’t need to know the internal hand-off as it is encapsulated within the outer tags like a DSL.

For good measure though, here is the internal working:

// js/sortable-2col-header.js

angular
  .module('demo-directive-app')
  .directive('sortableTableHeader', sortableTableHeader);

  function sortableTableHeader(){

    return {
      scope: {
        thisCol : "=thisCol",
        innerReverse : "=innerReverse",
        innerType : "=innerType"
      },
      templateUrl: 'templates/sortable-table-header-cell.html'
    };
  };

<!-- templates/sortable-table-header-cell.html -->


<table id="all-performer-table" class="table table-bordered table-striped">
  <thead >
    <tr>
      <td ng-repeat="label in colsLabels">
        <a href=""  ng-click="
          $parent.sortCol == $parent.colsDisplayed[$index]
          ? $parent.sortDesc = !$parent.sortDesc
          : $parent.sortCol = $parent.colsDisplayed[$index]"
        >
          {{label}}
          <span ng-show="$parent.sortCol == $parent.colsDisplayed[$index] && $parent.sortDesc" class="fa fa-caret-down"></span>
          <span ng-show="$parent.sortCol == $parent.colsDisplayed[$index] && !$parent.sortDesc" class="fa fa-caret-up"></span>
        </a>
      </td >
      <td class="check-box-label">  
       + 
       <span style="color: red;"> -</span>
     </td>
   </tr>
  </thead>  
  <tr ng-repeat="item in itemsInSet | orderBy:sortCol:sortDesc" > 
    <td ng-repeat="col in colsDisplayed" ng-class="{limeHighlight: item.selected}" >
      {{$parent.item[col]}}
    </td>
    <td>
      <input type="checkbox" ng-model=item.selected ng-click="$parent.checkAction(item)" >
    </td>
  </tr>
</table>
 

I hope these examples might help someone toying with the same issues out.

Note that last line!

      <input type="checkbox" ng-model=item.selected ng-click="$parent.checkAction(item)" >

That might have been worth reading this blog entry alone if you’d been in a pinch. (LOL).

I could point out more in the template above, but most of it is basic sorting and filtering mechanisms that make AngularJS so easy to use for state centric displays.

I will -not- share the two controllers code with you. They’re very basic scaffolds to let the demo actually do something when you click.

Again, check out the live demo And/Or you can check out the entire little sandbox in this angularjs-custom-directive-demo GitHub repository.

Within that Repo you can check out a few earlier iterations, AND check out my more extensive and ambitious handlebars solution to the same types of displays but with more robust and reusable controllers and interesting approaches to making the pages maintain display state without watches and two way binding.

Post on that will be linked here soon!.