Sunday, December 28, 2014

ui-grid / ng-grid 3.0 and double click row feature...

To my sad experience the comment that double click was not provided because of touch devices was a mistery. So after many hours with my limited knowledge found a way to add the double click with uiGrid v3.0.0. First your app.js file add the following directive:

directive('dblClickRow', [
    '$compile',
    function($compile, $parse, $log) {
      return {
        priority : -190, // run after default uiGridCell directive
        restrict : 'A',
        scope : false,
        link : function($scope, $elm, $attrs, $log) {
          if ($scope.grid.options.enableRowSelection && !$scope.grid.options.enableRowHeaderSelection) {
            $elm.addClass('ui-grid-disable-selection');
            registerRowSelectionEvents();
          }
          function registerRowSelectionEvents() {
            $elm.on('dblclick', function(evt) {
              $log.log("double click on row", evt);
            });
          }
        } };
    } ])    ;
Next you setup your grid with the following rowTemplate in your grid controller...
$templateCache.put('ui-grid/ui-grid-row',  
     + "<div " //
     + " ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.colDef.name\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\"" + " ui-grid-cell dbl-click-row></div>");
Next in your grid setup .... register the event and run with it ...
var selectedRow;
$scope.gridOptions.onRegisterApi = function(gridApi) {
  $scope.gridApi = gridApi;
  gridApi.selection.on.rowSelectionChanged($scope, function(row) {
    var msg = 'row selected ' + row.isSelected;
    if (row.isSelected) {
      selectedRow = row;
    }
    else {
      selectedRow = undefined;
    }
    $log.log(selectedRow);
  });
  gridApi.registerEvent('selection', 'dblClick');
  gridApi.selection.on.dblClick($scope, function() {
    if (selectedRow) {
      $scope.open(selectedRow.entity);
    }
  });
};

Wednesday, October 22, 2014

List All Perl Modules That Are Installed On My Current System

One liner:
perl -MFile::Find=find -MFile::Spec::Functions -Tlwe 'find { wanted => sub { print canonpath $_ if /\.pm\z/ }, no_chdir => 1 }, @INC'

Sunday, October 19, 2014

Refresh npm, bower, grunt and wiredep.

Somehow I feel that I messed up something in the course of development... so let's start over... :-)

Removal of NPM


rm -fr ~/.npm
sudo su - 
cd /usr/local/lib/node_modules
rm -fr *

Node Package Manager (npm)

http://nodejs.org/ Select the link to install npm and installation will commence based on your operating system.

Yeoman Scaffolding (yo)


$ sudo npm install --global yo
/usr/local/bin/yo -> /usr/local/lib/node_modules/yo/cli.js

> yo@1.3.2 postinstall /usr/local/lib/node_modules/yo
> yodoctor

[Yeoman Doctor] Everything looks all right!

npm WARN unmet dependency /usr/local/lib/node_modules/npm/node_modules/npm-package-arg requires semver@'4' but will load
npm WARN unmet dependency /usr/local/lib/node_modules/npm/node_modules/semver,
npm WARN unmet dependency which is version 2.3.0
yo@1.3.2 /usr/local/lib/node_modules/yo
├── is-root@1.0.0
├── sudo-block@1.0.0
├── yeoman-doctor@1.0.0
├── fullname@1.0.0
├── opn@1.0.0
├── async@0.9.0
├── shelljs@0.3.0
├── underscore.string@2.3.3
├── lodash@2.4.1
├── string-length@1.0.0 (strip-ansi@2.0.0)
├── yeoman-character@1.0.0 (supports-color@1.2.0)
├── nopt@3.0.1 (abbrev@1.0.5)
├── findup@0.1.5 (colors@0.6.2, commander@2.1.0)
├── multiline@1.0.1 (strip-indent@1.0.0)
├── chalk@0.5.1 (escape-string-regexp@1.0.2, ansi-styles@1.1.0, supports-color@0.2.0, strip-ansi@0.3.0, has-ansi@0.1.0)
├── yosay@1.0.0 (ansi-regex@1.1.0, ansi-styles@1.1.0, strip-ansi@2.0.0, pad-component@0.0.1, word-wrap@0.1.3, minimist@1.1.0, taketalk@0.1.1)
├── update-notifier@0.2.2 (is-npm@1.0.0, semver-diff@2.0.0, latest-version@1.0.0)
├── configstore@0.3.1 (object-assign@0.3.1, osenv@0.1.0, graceful-fs@3.0.4, uuid@1.4.2, mkdirp@0.5.0, js-yaml@3.0.2)
├── insight@0.4.3 (object-assign@1.0.0, tough-cookie@0.12.1, os-name@1.0.1, lodash.debounce@2.4.1, request@2.45.0, inquirer@0.6.0)
├── yeoman-generator@0.17.7 (dargs@2.0.3, diff@1.0.8, class-extend@0.1.1, rimraf@2.2.8, text-table@0.2.0, github-username@1.0.0, mime@1.2.11, debug@1.0.4, isbinaryfile@2.0.1, grouped-queue@0.3.0, cross-spawn@0.2.3, run-async@0.1.0, mkdirp@0.5.0, iconv-lite@0.4.4, findup-sync@0.1.3, request@2.45.0, file-utils@0.2.1, gruntfile-editor@0.2.0, glob@4.0.6, cheerio@0.17.0, download@1.0.7, inquirer@0.7.3)
└── yeoman-environment@1.0.2 (log-symbols@1.0.1, diff@1.0.8, text-table@0.2.0, debug@2.1.0, grouped-queue@0.3.0, untildify@1.0.0, glob@4.0.6, inquirer@0.8.0)

Installing the AngularJS generator

$ sudo npm install -g generator-angularjs
Password:
npm WARN unmet dependency /usr/local/lib/node_modules/npm/node_modules/npm-package-arg requires semver@'4' but will load
npm WARN unmet dependency /usr/local/lib/node_modules/npm/node_modules/semver,
npm WARN unmet dependency which is version 2.3.0
generator-angularjs@1.0.0 /usr/local/lib/node_modules/generator-angularjs
└── yeoman-generator@0.14.2 (dargs@0.1.0, diff@1.0.8, debug@0.7.4, rimraf@2.2.8, text-table@0.2.0, mime@1.2.11, async@0.2.10, mkdirp@0.3.5, isbinaryfile@0.1.9, shelljs@0.2.6, chalk@0.3.0, underscore.string@2.3.3, iconv-lite@0.2.11, lodash@2.2.1, findup-sync@0.1.3, glob@3.2.11, file-utils@0.1.5, request@2.27.0, cheerio@0.12.4, inquirer@0.3.5, download@0.1.19)

Installing Grunt

$ sudo npm install -g grunt-cli
Password:
/usr/local/bin/grunt -> /usr/local/lib/node_modules/grunt-cli/bin/grunt
npm WARN unmet dependency /usr/local/lib/node_modules/npm/node_modules/npm-package-arg requires semver@'4' but will load
npm WARN unmet dependency /usr/local/lib/node_modules/npm/node_modules/semver,
npm WARN unmet dependency which is version 2.3.0
grunt-cli@0.1.13 /usr/local/lib/node_modules/grunt-cli
├── resolve@0.3.1
├── nopt@1.0.10 (abbrev@1.0.5)
└── findup-sync@0.1.3 (lodash@2.4.1, glob@3.2.11)

Wiredep (Re)Install

$ sudo npm install -g wiredep
Password:
wiredep@1.8.6 node_modules/wiredep
├── propprop@0.3.0
├── minimist@1.1.0
├── lodash@2.4.1
├── through2@0.6.3 (xtend@4.0.0, readable-stream@1.0.33-1)
├── chalk@0.5.1 (escape-string-regexp@1.0.2, ansi-styles@1.1.0, supports-color@0.2.0, strip-ansi@0.3.0, has-ansi@0.1.0)
├── glob@4.0.6 (inherits@2.0.1, graceful-fs@3.0.4, once@1.3.1, minimatch@1.0.0)
└── bower-config@0.5.2 (osenv@0.0.3, graceful-fs@2.0.3, optimist@0.6.1, mout@0.9.1)

Bower (Re)Install

http://bower.io/
$ sudo npm install -g bower
Password:
/usr/local/bin/bower -> /usr/local/lib/node_modules/bower/bin/bower
npm WARN unmet dependency /usr/local/lib/node_modules/npm/node_modules/npm-package-arg requires semver@'4' but will load
npm WARN unmet dependency /usr/local/lib/node_modules/npm/node_modules/semver,
npm WARN unmet dependency which is version 2.3.0
bower@1.3.12 /usr/local/lib/node_modules/bower
├── is-root@1.0.0
├── junk@1.0.0
├── stringify-object@1.0.0
├── abbrev@1.0.5
├── which@1.0.5
├── chmodr@0.1.0
├── osenv@0.1.0
├── opn@1.0.0
├── archy@0.0.2
├── rimraf@2.2.8
├── lru-cache@2.5.0
├── bower-logger@0.2.2
├── bower-endpoint-parser@0.2.2
├── graceful-fs@3.0.4
├── lockfile@1.0.0
├── nopt@3.0.1
├── retry@0.6.0
├── tmp@0.0.23
├── q@1.0.1
├── semver@2.3.2
├── p-throttler@0.1.0 (q@0.9.7)
├── request-progress@0.3.0 (throttleit@0.0.2)
├── shell-quote@1.4.2 (array-filter@0.0.1, array-reduce@0.0.0, array-map@0.0.0, jsonify@0.0.0)
├── fstream@1.0.2 (inherits@2.0.1)
├── mkdirp@0.5.0 (minimist@0.0.8)
├── bower-json@0.4.0 (intersect@0.0.3, deep-extend@0.2.11, graceful-fs@2.0.3)
├── promptly@0.2.0 (read@1.0.5)
├── chalk@0.5.0 (escape-string-regexp@1.0.2, ansi-styles@1.1.0, supports-color@0.2.0, has-ansi@0.1.0, strip-ansi@0.3.0)
├── fstream-ignore@1.0.1 (inherits@2.0.1, minimatch@1.0.0)
├── glob@4.0.6 (inherits@2.0.1, once@1.3.1, minimatch@1.0.0)
├── bower-config@0.5.2 (osenv@0.0.3, graceful-fs@2.0.3, optimist@0.6.1)
├── tar-fs@0.5.2 (pump@0.3.5, tar-stream@0.4.7)
├── mout@0.9.1
├── cardinal@0.4.0 (redeyed@0.4.4)
├── decompress-zip@0.0.8 (nopt@2.2.1, mkpath@0.1.0, touch@0.0.2, readable-stream@1.1.13, binary@0.3.0)
├── request@2.42.0 (caseless@0.6.0, json-stringify-safe@5.0.0, aws-sign2@0.5.0, forever-agent@0.5.2, stringstream@0.0.4, oauth-sign@0.4.0, tunnel-agent@0.4.0, node-uuid@1.4.1, qs@1.2.2, mime-types@1.0.2, bl@0.9.3, form-data@0.1.4, http-signature@0.10.0, tough-cookie@0.12.1, hawk@1.1.1)
├── bower-registry-client@0.2.1 (request-replay@0.2.0, lru-cache@2.3.1, async@0.2.10, mkdirp@0.3.5, graceful-fs@2.0.3, request@2.27.0)
├── update-notifier@0.2.0 (semver-diff@0.1.0, string-length@0.1.2, configstore@0.3.1, latest-version@0.2.0)
├── handlebars@2.0.0 (optimist@0.3.7, uglify-js@2.3.6)
├── inquirer@0.7.1 (figures@1.3.3, mute-stream@0.0.4, through@2.3.6, readline2@0.1.0, lodash@2.4.1, cli-color@0.3.2, rx@2.3.13)
└── insight@0.4.3 (object-assign@1.0.0, async@0.9.0, chalk@0.5.1, os-name@1.0.1, lodash.debounce@2.4.1, tough-cookie@0.12.1, configstore@0.3.1, inquirer@0.6.0)

Sunday, August 3, 2014

Perl: Exporting Static Methods with Moose and Sub::Exporter

Simple how to on exporting static metods with Moose and Sub::Exporter.
package Utils;

=head
   UTILS
=cut

use Moose;
use Carp qw/carp/;
use Sub::Exporter -setup => {
  exports => [
    qw(normalize bounds min max)
  ]
};

sub max {
  my ($value, $compare) = @_;
}
sub min {
  my ($value, $compare) = @_;
}
sub normalize {
  my ($value, $compare) = @_;
}

Saturday, July 19, 2014

Dancer2 Session & Authentication and AngularJS

This took me too much to figure out from different sources hence jotting down my recipe. My application is called ADA ... here the main part with login / logout
package Ada;
use Dancer2;
use Dancer2::Session::Simple;
use Dancer2::Plugin::Auth::Tiny;

our $VERSION = '0.1';

# Authentication Requires Session, here the in memory
set session => "Simple";
#set session => 'YAML';

hook before => sub {
  printf "logged in? %s\n", session('user') ? session('user') : '-';
  if ( !session('user') && request->dispatch_path !~ m{^/login} ) {
    forward '/login', { requested_path => request->dispatch_path };
  }
};

get '/' => sub {
  template 'index';
};

get '/login' => sub {

  # Display a login page; the original URL they requested is available as
  # param('requested_path'), so could be put in a hidden field in the form
  template 'login', { path => param('requested_path') };
};

post '/login' => sub {
  if ( _is_valid( param('user'), param('pass') ) ) {
    print "logged on success!\n";
    session user => param('user');
    redirect param('path') || '/';
  }
  else {
    redirect '/login?failed=1';
  }
};

sub _is_valid {
  my ( $u, $p ) = @_;
  my $store = schema 'default';
  foreach my $r ( $store->resultset('PmiUser')->all ) {
    printf "%10s = %s\n", $r->fedex_id, $r->fedex_pw;
  }
  my $rec =
    $store->resultset('PmiUser')
    ->find( { 'id' => $u, 'pwd' => $p } );
  printf $rec->fedex_id;
  return $rec and $rec->fedex_id ? 0 : 1;
}

get '/logout' => sub {
  printf( "logging out %s\n", session('user') ? session('user') : '-' );
  context->destroy_session;
  redirect '/';
};

true;

project layout

.
├── Gruntfile.js
├── MANIFEST
├── MANIFEST.SKIP
├── Makefile.PL
├── app
│   ├── 404.html
│   ├── favicon.ico
│   ├── fonts
│   ├── images
│   │   └── yeoman.png
│   ├── index.html
│   ├── robots.txt
│   ├── scripts
│   │   ├── app.js
│   │   ├── controllers
│   │   │   ├── main.js
│   │   └── services
│   │       ├── lookups.js
│   ├── styles
│   │   └── main.css
│   ├── test
│   │   └── spec
│   │       └── services
│   │           └── lookups.js
│   └── views
│       └── main.html
├── bin
│   └── app.pl
├── bower.json
├── config.yml
├── environments
│   ├── development.yml
│   └── production.yml
├── karma-e2e.conf.js
├── karma.conf.js
├── lib
│   └── Ada.pm
├── out
├── package.json
├── run.sh
├── t
│   ├── 001_base.t
│   └── 002_index_route.t
├── test
│   ├── runner.html
│   └── spec
│       ├── controllers
│       │   ├── main.js
│       └── services
│           ├── lookups.js
│           └── wls-domain-service.js
└── views
    ├── index.tt -> ../app/index.html
    ├── layouts
    │   ├── main.tt -> ../../app/index.html
    │   └── navbar.tt
    └── login.tt

config.yml

# This is the main configuration file of your Dancer2 app
# env-related settings should go to environments/$env.yml
# all the settings in this file will be loaded at Dancer's startup.

# Your application's name
appname: "ada"

# when the charset is set to UTF-8 Dancer2 will handle for you
# all the magic of encoding and decoding. You should not care
# about unicode within your app when this setting is set (recommended).
charset: "UTF-8"

# template engine
# simple: default and very basic template engine
# template_toolkit: TT
serializer: JSON
session: YAML
session_dir: /tmp/dancer-sessions

template: "template_toolkit"
engines:
  JSON:
    allow_blessed:   '1'
    canonical:       '1'
    convert_blessed: '1' 
  template_toolkit:
    encoding:  'utf8'
    start_tag: '[%'
    end_tag:   '%]'
    WRAPPER: layouts/main.tt
    EVAL_PERL: '1'
  Auth::RBAC:
    credentials:
        class: Config
        options:
            accounts:
                  user01:
                      password: foobar
                      roles:
                          - guest
                          - user
                  weblogic:
                      password: wladmin1
                      roles:
                          - admin

angularjs - index.tt

Add the Logout function to your navigation pills.... make sure its a href.
  <!-- Add your site or application content here -->
  <div class="header">
    <ul class="nav nav-pills pull-right">
      <li class="active"><a ng-href="#">Home</a></li>
      <li><a href="./logout">Logout</a></li>
    </ul>
    <h3 class="text-muted">AD Assistant</h3>
  </div>

login.tt

A very simple ugly login screen.
<html>
      <head>
        <title>Session and logging in</title>
      </head>
      <body>
        <form action='/login' method='POST'>
            User Name : <input type='text' name='user'/>
            Password: <input type='password' name='pass' />

            <!-- Put the original path requested into a hidden
                       field so it's sent back in the POST and can be
                       used to redirect to the right page after login -->
            <input type='hidden' name='path' value='[% path %]'/>

            <input type='submit' value='Login' />
        </form>
      </body>
</html>

Friday, July 4, 2014

Angular ng-enter Directive

Just copied it from somewhere to keep for myself .. I mostly put it in the app.js at the bottom so everyone is able to use this function.
.directive('ngEnter', function() {
 return function(scope, element, attrs) {
  element.bind("keydown keypress", function(event) {
   if (event.which === 13) {
    scope.$apply(function() {
     scope.$eval(attrs.ngEnter, {
      'event' : event
     });
    });

    event.preventDefault();
   }
  });
 };
});

Thursday, July 3, 2014

Dancer2 and changing server port

Well the --port does not work... keeps it on 3000 also set port does not work...
#!/usr/bin/env perl
use Dancer2;
set port => '3010';
use lib './lib';
use Data::Dumper;
dance;
does not work... What to do ??? Here the magic environment variables...
sub _build_default_config {
    my $self = shift;

    $ENV{PLACK_ENV}
      and $ENV{DANCER_APPHANDLER} = 'PSGI';

    return {
        apphandler   => ( $ENV{DANCER_APPHANDLER}   || 'Standalone' ),
        content_type => ( $ENV{DANCER_CONTENT_TYPE} || 'text/html' ),
        charset      => ( $ENV{DANCER_CHARSET}      || '' ),
        warnings     => ( $ENV{DANCER_WARNINGS}     || 0 ),
        startup_info => ( $ENV{DANCER_STARTUP_INFO} || 1 ),
        traces       => ( $ENV{DANCER_TRACES}       || 0 ),
        logger       => ( $ENV{DANCER_LOGGER}       || 'console' ),
        host         => ( $ENV{DANCER_SERVER}       || '0.0.0.0' ),
        port         => ( $ENV{DANCER_PORT}         || '3000' ),
        views        => ( $ENV{DANCER_VIEWS}
              || path( $self->config_location, 'views' ) ),
        appdir        => $self->location,
    };
}
So specify your bin/app.pl like this
#!/usr/bin/env perl

BEGIN {
  $ENV{'DANCER_PUBLIC'}            = './app';
  $ENV{'DBIC_TRACE'}               = 1;
  $ENV{'DANCER_PORT'}              = 3010;
  $ENV{'DBIC_NULLABLE_KEY_NOWARN'} = 1; # allowing nulls in uniq constraints
}
use Dancer2;
set port => '3010';
use lib './lib';
use myapp;
dance;

Thursday, June 26, 2014

Dancer2 and Yeoman integrating nicely

Dancer2 and Angular changes to
bin/app.pl

Generate the backend components...

  651  dancer2 gen -a CMA 
  652  cd CMA
  652  yo angular cma
Rearranging the public / dancer web directories.

  709  vi bin/app.psgi
    insert
BEGIN {
  $ENV{'DBIC_TRACE'}    = 1;
  $ENV{'RELATIVE'} = 1;
}
  710  cd app
  711  ln -s ../bower_components .
  712  cd ../lib && vi CMA.pm
    edit
use Cwd;
set auto_page => 1;
# set the new views directory to absolute path
set views => getcwd . "/app";


get '/' => sub {
 template 'index';
};

# catch anything that cannot be served html extension 
# with template, else just serve the files requested based on the absolute
# path of the files
any qr{.*} => sub {
  my $name = request->path;
  print "request for end of chain: $name\n";
  #print Dumper( config->{appdir} );
  if ( $name !~ /\.html$/ ) {
    send_file( config->{appdir} . "/app/$name", system_path => 1 );
  }
  else {
    template $name;
  }
};

  
  712  cd ..
  719  plackup -r bin/app.psgi 
After this you could remove the public and views directory since they have no purpose. The 'app' directory generated by Yeoman contains all the application files.

Sunday, June 15, 2014

Dancer::Plugin::Pagination, AngularJS Pagination Abstraction and Integration

Out of the box pagination with Dancer or Dancer2 using AngularJS as front-end.... I just got sick of copy and paste these functionalities... Ingredients:
  1. Dancer::Plugin::Pagination; class for implementing the pager() function in dancer with any DBIC class.
  2. Object to JSON mapping; decoupling the DBIC ResultSet to a portable JSON struct.
  3. Writing/Configuring the backend service for querying
At this point you should be able to get results back in the browser as a JSON resultset. Next is the AngularJS integration:
  1. yo angular:route mytable; the controller and base class for ng-grid
  2. yo angular:factory mytable; the service communicating with the Dancer backend
  3. defining the pagination controller; abstract class with basic functions

1. Dancer::Plugin::Pagination

package Dancer::Plugin::Pagination;

use strict;
use warnings;
use Dancer2;
use Dancer2::Plugin;
use Dancer2::Logger::Console;
use Data::Dumper;

our $AUTHORITY         = 'KAAN';
our $VERSION           = '0.01';
our $DEFAULT_PAGE_SIZE = 5;
our $logger            = Dancer2::Logger::Console->new;

sub mapper {
  my ($self, $defs, @rs) = @_;
  return &map_fields ( $defs, @rs);
}

sub map_fields {
  my ( $field_defs, @rs ) = @_;

  #print "MAPPING:" . Dumper($field_defs);
  my @result = ();
  foreach my $row (@rs) {
    my $rec = {};
    while ( my ( $k, $v ) = each %$field_defs ) {
      if ( ref($v) eq 'ARRAY' ) {
        my $base = $row;
        foreach my $method (@$v) {

     #printf "%s ->[%s] %s\n", join('->', @$v) , $method, Dumper($row->columns);
          $base = $base->$method if $base;
        }
        $rec->{$k} = $base;
      }
      else {
        $rec->{$k} = $row->$v;
      }
    }
    push @result, $rec;
  }
  return \@result;

}

sub pagination {
  my ( $self, $store, $table, $mapping, $subselect ) = @_;
  unless ($mapping) {
    die( "missing mapping for " . $table );
    return 1;
  }

  # deserialize the filter, page and sort options.
  my $pager  = $self->from_json( $self->params->{'pager'} );
  my $filter = $self->from_json( $self->params->{'filter'} );
  my $sort   = $self->from_json( $self->params->{'sort'} );

  my $limit  = $pager  && $pager->{'pageSize'}    || $DEFAULT_PAGE_SIZE;
  my $pagenr = $pager  && $pager->{'currentPage'} || 1;
  my $type   = $filter && $filter->{'matchType'}  || 'any';
  my $text   = $filter && $filter->{'filterText'} || '';
  my $field         = $filter && $filter->{'matchFields'};
  my $sortFields    = $sort   && $sort->{'fields'};
  my $sortDirection = $sort   && $sort->{'directions'} || ['ASC'];
  # establish default filtering
  $field = { 'name' => 1 } unless keys %$field;
  #
  my %where = ();
  if ( $text ne '' ) {
    if ( $type =~ /all/ ) {
      foreach my $f ( keys %$field ) {
        $where{ $mapping->{$f} }{'-like'} = $text . '%';
      }
    }
    else {
      if ( scalar keys %$field > 1 ) {
        foreach my $f ( keys %$field ) {
          push @{ $where{'-or'} },
            { $mapping->{$f} => { '-like' => $text . '%' } };
        }
      }
      else {
        foreach my $f ( keys %$field ) {
          $where{ $mapping->{$f} }{'-like'} = $text . '%';
        }
      }
    }
  }
  my @order = ();
  @order = map { $mapping->{$_} . ' ASC' } @$sortFields if $sortFields;
  my @rs = $store->resultset(
                             $table)->search(
                                              \%where,
                                              {
                                                page     => $pagenr,
                                                rows     => $limit,
                                                order_by => \@order
                                              }
                             );
  my $count = $store->resultset($table)->search( \%where )->count;

  my $result = &map_fields( $mapping, @rs );
  return { 'totalItems' => $count, 'items' => $result };
}

register get_lookups => \&get_lookups;
register pager       => \&pagination;
register mapper      => \&mapper;
register_plugin for_versions => [ 1, 2 ];

1;

2. Mapping Object to JSON

In my case I want to map address information to a JSON object ... in my application package I add the following:
our %mapping = (
  'address' => {
    'id' => 'crc',
    'street' => 'street',
    'city' => 'city',
    'state' => 'state',
    'zip' => 'postal_code',
    'lname' => 'last_name',
    'fname' => 'first_name',
    'acc' => 'accuracy',
    'status' => [qw/status id/],  # many-to-one fetch status->id
    'ministry' => [qw/ministry_record id/] # many-to-one fetch ministry_record->id
  }
);

3. Adding the request with the pager functionality

The base request would be a simple definition of the schema, table name/resultset in DBIC and supplying the mapping.
=head
  ################################
=cut
get '/address/list' => sub {
  my $store = schema 'default';
  return pager($store, 'TerritoryAddress', $mapping{'address'})
};

Result

When we start the module we and enter http://localhost:3000/address/list we get the JSON result back. The default result set is a number of 5 with the total count of items in a separate field.
{"totalItems":"370","items":[{"street":"136 Morse Rd.","status":"ARC","fname":"Andy","state":"CT","acc":8,"city":"East Montpelier","zip":"XXX","lname":"Goodman","id":"1","ministry":"ARC"},{"street":"89 Maple St","status":"D","fname":"Diane","state":"VA","acc":8,"city":"SomewhereElse","zip":"XXX","lname":"ABCDE","id":"4","ministry":"RD"},{"street":"1409 North Ave","status":"H","fname":"Tim & Tina","state":"NL","acc":8,"city":"OceanDeep","zip":"XXX","lname":"ABCDE","id":"5","ministry":"RD"},{"street":"54 Roseade Parkway","status":"ARC","fname":null,"state":"NB","acc":8,"city":"AnotherPlace","zip":"XXX","lname":"ABCDE","id":"6","ministry":"ARC"},{"street":"714 Riverside Ave Apt 2","status":"HH","fname":"Dennis","state":"NC","acc":9,"city":"Somewhere","zip":"XXX","lname":null,"id":"7","ministry":"MVD"}]}

4. and 5. Adding the service and controller for AngularJS

Now essential are the pageOptions, filterOptions and sortOptions. These options will control the backend pager. If you notice we are extending from a base controller 'paginationControler' which houses the common setup of the pagination, thus avoiding duplication when more screens need the same functionality.
'use strict';

angular
  .module('MyApp')
  .controller(
    'AddressMgtCtrl',
    function($scope, $controller, $modal, $http, $timeout, addressMgt) {
        // Initialize the super class and extend it.
        $.extend(this, $controller('paginationControler', {$scope: $scope}));

     $scope.filterOptions.fields = [ {
      id : 'street',
      desc : 'Street'
     }, {
      id : 'city',
      desc : 'City'
     }, {
      id : 'fname',
      desc : 'First Name'
     } ];

     $scope.sortOptions = {
      fields : [ "street" ],
      directions : [ "ASC" ]
     };

     $scope.pageSync = function() {
      setTimeout(function() {
       addressMgt.search($scope.pagingOptions,
         $scope.filterOptions, $scope.sortOptions)
         .success($scope.responseFn);
      }, 100);
     };

     $scope.gridOptions = {
      data : 'data',
      enableRowSelection : true,
      enablePaging : true,
      enableCellEditOnFocus : true,
      showFilter : true,
      showGroupPanel : true,
      showFooter : true,
      filterOptions : $scope.filterOptions,
      pagingOptions : $scope.pagingOptions,
      sortInfo : $scope.sortOptions,
      multiSelect : false,
      selectedItems : $scope.mySelections,
      totalServerItems : 'totalItems',
      columnDefs : [ {
       field : 'street',
       displayName : 'Street',
       enableCellEdit : false
      }, {
       field : 'city',
       displayName : 'City',
       enableCellEdit : false
      }, {
       field : 'state',
       displayName : 'State',
       enableCellEdit : false
      }, {
       field : 'zip',
       displayName : 'Zip',
       enableCellEdit : false
      }, {
       field : 'lname',
       displayName : 'Last Name',
       enableCellEdit : false
      }, {
       field : 'fname',
       displayName : 'First Name',
       enableCellEdit : false
      } ]
     };

     $scope.pageSync();
    });

5. The Service definition

Our 'addressMgt' service will handle all of our communication for this screen and thus the pagination as well. You notice the page, filter and sort options are send as parameters to the pager service on the backend.
'use strict';
angular.module('MyApp').factory('addressMgt', function($http) {
 var addressAPI = {};
 addressAPI.search = function(pageOpts, filterOpts, sortOpts) {
  return $http.get('/address/list', {
   params : {
    'filter' : filterOpts && angular.toJson(filterOpts) || '',
    'pager' : pageOpts && angular.toJson(pageOpts) || '',
    'sort' : sortOpts && angular.toJson(sortOpts) || ''
   }
  });
 };

 return addressAPI;

});

6. Putting it all together

The 'views/address_mgt.html' now would be a one liner...

7. The Abstract Pagination Controller in Angular

The abstract 'paginationController' is the last piece that would make pagination somewhat abstract. I choose to define the controller in the 'app.js' layer as a central top layer function.
'use strict';

angular.module(
  'myApp',
  [ 'ngCookies', 'ngResource', 'ngSanitize', 'ngRoute', 'ngGrid',
    'ui.bootstrap' ]).config(function($routeProvider) {
 $routeProvider.when('/', {
  templateUrl : 'views/main.html',
  controller : 'MainCtrl'
 }).when('/address_mgt', {
  templateUrl : 'views/address_mgt.html',
  controller : 'AddressMgtCtrl'
 }).when('/address_dtl', {
...
...
... ADD THIS BELOW  ....
}).controller('paginationControler', function($scope) {
 /**
  * PAGINATION CONTROLLER
  */
 $scope.filterOptions = {
  useExternalFilter : true,
  filterText : "",
  show : false,
  matchType : 'any',
  matchFields : {
   'street' : true,
   'city' : true
  },
  fields : []
 };

 $scope.totalItems = 0;

 $scope.pagingOptions = {
  pageSizes : [ 25, 50, 100 ],
  pageSize : 5,
  currentPage : 1
 };

 $scope.responseFn = function(response) {
  console.log("%o", response);
  if (response) {
   $scope.data = response['items'];
   $scope.totalItems = response['totalItems'];
  } else {
   $scope.list = [];
   $scope.totalItems = 0;
  }
  if (!$scope.$$phase) {
   $scope.$apply();
  }
 };
 $scope.watchAttribute = function(newVal, oldVal) {
  if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
   $scope.pageSync();
  }
 };
 $scope.$watch('pagingOptions.pageSize', function(oldVal, newVal) {
  if (newVal !== oldVal) {
   $scope.pagingOptions.currentPage = 1;
   $scope.pageSync();
  }
 }, true);

 $scope.$watch('pagingOptions', $scope.watchAttribute, true);

});

Monday, May 26, 2014

Dancer & Yeoman scaffolding... what needs changed

So here a quick one for integrating dancer and yeoman angular generation.
$ dancer -a myapp
$ cd myapp
$ yo angular myapp
$ rm public
# This is the main configuration file of your Dancer app
# env-related settings should go to environments/$env.yml
# all the settings in this file will be loaded at Dancer's startup.

# Your application's name
appname: "myapp"

# The default layout to use for your application (located in
# views/layouts/main.tt)
layout: "main"

# when the charset is set to UTF-8 Dancer will handle for you
# all the magic of encoding and decoding. You should not care
# about unicode within your app when this setting is set (recommended).
charset: "UTF-8"

# template engine
# simple: default and very basic template engine
# template_toolkit: 
serializer: JSON

template: "template_toolkit"
engines:
  JSON:
    allow_blessed:   '1'
    canonical:       '1'
    convert_blessed: '1' 
  template_toolkit:
    encoding:  'utf8'
    start_tag: '[%'
    end_tag:   '%]'
    WRAPPER: layouts/main.tt
    EVAL_PERL: '1'
 
public: app
auto_reload : true

# For session support enable the following line and see Dancer::Session
# session: "YAML"
plugins:
  DBIC:
    default:
      schema_class: Schema::PMI
      dsn: 'dbi:Oracle:sid=DDB00334;host=ddb00334.com;port=1526'
      user: 'XXXX'
      password: 'xxxx'
      on_connect_call: 'datetime_setup'

Wednesday, May 21, 2014

Mac OSX : VPN Checkpoint E75.01 - Connectivity lost with VPN Service

I spend several hours on this and found out the following.... it might be helpful... After a power failure I was unable to reconnect in fact the "connectivity with the VPN service is lost" popped up, and my settings under DockIcon->Connect To were not prompting me back anything. Then I took a look at:
$ cd "/Library/Application Support/Checkpoint/Endpoint Connect"
$ ls -lrt Tr*
-rwxr-xr-x 1 root wheel 9454272 Apr 5 04:51 TracSrvWrapper
-rw-r--r-- 1 root wheel 25766 Sep 5 11:10 Trac.config.bak
-rw-r--r-- 1 root wheel 0 Sep 5 13:10 Trac.config
$ 
Why was the active Trac.conf zero length? ... dunno why... so after shutting down the client from the DockIcon and executing
$ cd "/Library/Application Support/Checkpoint/Endpoint Connect"
$ sudo cp Trac.config.bak Trac.config
Restarted the EndPoint Security Client and problem solved! Greetz Bananaman! RESTART the Checkpoint Daemon
$ sudo launchctl start /Library/LaunchDaemons/com.checkpoint.epc.service.plist

Saturday, May 17, 2014

Cienega to JW Signlanguage App upload

Manual uploading all the contents for the JW Signlanguage App. For example I currently have 9 chapters downloaded thru the application, however I have the whole library on disk already.
 $ cd Cienega/Media/Bible/Chapter/
 $ cd 02\ Exodus
 $ ls -1 | perl -ne '@x=split/[\.\s]/; chomp;printf "mkdir -p ~/tmp/bi12_ASL_%s;ln  \"$_\" ~/tmp/bi12_ASL_%s/bi12_%s_%s_ASL_%02d_r240P.m4v\n",$x[0],$x[0],$x[0],$x[2] =~ m/(\w\w).+/,$x[3]; ' > x.sh
 $ . ./x.sh
The output looks like a staging area for the upload
host: 02 Exodus root$ cat x.sh 
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.001 Exodus 1.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_01_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.002 Exodus 2.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_02_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.003 Exodus 3.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_03_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.004 Exodus 4.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_04_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.005 Exodus 5.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_05_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.006 Exodus 6.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_06_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.007 Exodus 7.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_07_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.008 Exodus 8.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_08_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.009 Exodus 9.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_09_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.010 Exodus 10.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_10_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.011 Exodus 11.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_11_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.012 Exodus 12.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_12_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.013 Exodus 13.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_13_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.014 Exodus 14.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_14_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.015 Exodus 15.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_15_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.016 Exodus 16.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_16_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.017 Exodus 17.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_17_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.018 Exodus 18.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_18_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.019 Exodus 19.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_19_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.020 Exodus 20.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_20_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.021 Exodus 21.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_21_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.022 Exodus 22.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_22_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.023 Exodus 23.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_23_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.024 Exodus 24.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_24_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.025 Exodus 25.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_25_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.026 Exodus 26.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_26_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.027 Exodus 27.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_27_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.028 Exodus 28.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_28_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.029 Exodus 29.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_29_r240P.m4v
mkdir -p ~/tmp/bi12_ASL_02;ln  "02.030 Exodus 30.m4v" ~/tmp/bi12_ASL_02/bi12_02_Ex_ASL_30_r240P.m4v
Now use iFunBox to locate the User Application 'Signlanguage' and navigate to the Video's Section...

Next copy the ~/tmp/bi12_ASL_02 "folder" to your device...

After that close the *JW Sign Language App* and restart the app. Click on the book you transferred and all are there now.

Dancer / Template Toolkit : auto include javascript sources

Playing with Dancer and Angular I found out spliting up controllers in separate js files seems a clean thing to do... however I kept on missing js includes... let's automate this in your 'layout/main.tt' insert the following to include the js pieces automagically
....

[% PERL %]
my @files = ();
use File::Find;
File::Find::find({ wanted=>sub {   push @files, $File::Find::name if /^.*\.js\z/s; }}, 'public/javascripts', 'public/partials');
foreach my $js (@files) {
    $js =~ s/public\///g;
    print "\n";
}
[% END %]


...
..
Then make sure you configure the template engine in your 'config.yml' for TT.

template: "template_toolkit"
engines:
  template_toolkit:
    encoding:  'utf8'
    start_tag: '[%'
    end_tag:   '%]'
    WRAPPER: layouts/main.tt
    EVAL_PERL: '1'

Then my source gets generated like this (lines 35-43) :

Wednesday, January 15, 2014

JAD on Mac OSX

:base-war $ cat readme 
  657  cd class/
  658  find ../../target/base-war-2.2.0.0130/WEB-INF/lib/ -name '[MGvJ]*.jar' -exec jar xfv {} \; -print
  659  /Applications/jad158g.mac.intel/jad -v -o -r -d ../src *.class
  660  find ../src -type d -exec perl -e 'foreach my $r (glob("{}/*.jad")) { $x=$r;$r=~ s/jad/java/;system("mv $x $r") unless(-f $r)}' \;
  661  find ../src
  662  find .
  663  /Applications/jad158g.mac.intel/jad -v -o -r -d ../src **/*.class
  664  find .

  114  find ../../target/base-war-2.2.0.0130/WEB-INF/lib/ -name 'ms*.jar' -exec jar xfv {} \; -print 
  127  /Applications/jad158g.mac.intel/jad -o -r -s .java -d ~/workspace-3.7/zzzz/src/ system/_*.class 

Friday, January 10, 2014

ExtJs: more vtypes for validation .... vtype on a roll..

Ext.form.VTypes["hostnameVal1"] = /^[a-zA-Z][-.a-zA-Z0-9]{0,254}$/;
Ext.form.VTypes["hostnameVal2"] = /^[a-zA-Z]([-a-zA-Z0-9]{0,61}[a-zA-Z0-9]){0,1}([.][a-zA-Z]([-a-zA-Z0-9]{0,61}[a-zA-Z0-9]){0,1}){0,}$/;
Ext.form.VTypes["ipVal"] = /^([1-9][0-9]{0,1}|1[013-9][0-9]|12[0-689]|2[01][0-9]|22[0-3])([.]([1-9]{0,1}[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){2}[.]([1-9][0-9]{0,1}|1[0-9]{2}|2[0-4][0-9]|25[0-4])$/;
Ext.form.VTypes["netmaskVal"] = /^(128|192|224|24[08]|25[245].0.0.0)|(255.(0|128|192|224|24[08]|25[245]).0.0)|(255.255.(0|128|192|224|24[08]|25[245]).0)|(255.255.255.(0|128|192|224|24[08]|252))$/;
Ext.form.VTypes["portVal"] = /^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/;
Ext.form.VTypes["multicastVal"] = /^((22[5-9]|23[0-9])([.](0|[1-9][0-9]{0,1}|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3})|(224[.]([1-9][0-9]{0,1}|1[0-9]{2}|2[0-4][0-9]|25[0-5])([.](0|[1-9][0-9]{0,1}|1[0-9]{2}|2[0-4][0-9]|25[0-5])){2})|(224[.]0[.]([1-9][0-9]{0,1}|1[0-9]{2}|2[0-4][0-9]|25[0-5])([.](0|[1-9][0-9]{0,1}|1[0-9]{2}|2[0-4][0-9]|25[0-5])))$/;
Ext.form.VTypes["usernameVal"] = /^[a-zA-Z][-_.a-zA-Z0-9]{0,30}$/;
Ext.form.VTypes["passwordVal1"] = /^.{6,31}$/;
Ext.form.VTypes["passwordVal2"] = /[^a-zA-Z].*[^a-zA-Z]/;


Ext.form.VTypes["hostname"]=function(v){
 if(!Ext.form.VTypes["hostnameVal1"].test(v)){
  Ext.form.VTypes["hostnameText"]="Must begin with a letter and not exceed 255 characters"
  return false;
 }
 Ext.form.VTypes["hostnameText"]="L[.L][.L][.L][...] where L begins with a letter, ends with a letter or number, and does not exceed 63 characters";
 return Ext.form.VTypes["hostnameVal2"].test(v);
}
Ext.form.VTypes["hostnameText"]="Invalid Hostname"
Ext.form.VTypes["hostnameMask"]=/[-.a-zA-Z0-9]/;


Ext.form.VTypes["ip"]=function(v){
 return Ext.form.VTypes["ipVal"].test(v);
}
Ext.form.VTypes["ipText"]="1.0.0.1 - 223.255.255.254 excluding 127.x.x.x"
Ext.form.VTypes["ipMask"]=/[.0-9]/;


Ext.form.VTypes["netmask"]=function(v){
 return Ext.form.VTypes["netmaskVal"].test(v);
}
Ext.form.VTypes["netmaskText"]="128.0.0.0 - 255.255.255.252"
Ext.form.VTypes["netmaskMask"]=/[.0-9]/;


Ext.form.VTypes["port"]=function(v){
 return Ext.form.VTypes["portVal"].test(v);
}
Ext.form.VTypes["portText"]="0 - 65535"
Ext.form.VTypes["portMask"]=/[0-9]/;

Ext.form.VTypes["multicast"]=function(v){
 return Ext.form.VTypes["multicastVal"].test(v);
}
Ext.form.VTypes["multicastText"]="224.0.1.0 - 239.255.255.255"
Ext.form.VTypes["multicastMask"]=/[.0-9]/;
Ext.form.VTypes["username"]=function(v){
 return Ext.form.VTypes["usernameVal"].test(v);
}
Ext.form.VTypes["usernameText"]="Username must begin with a letter and cannot exceed 255 characters"
Ext.form.VTypes["usernameMask"]=/[-_.a-zA-Z0-9]/;
Ext.form.VTypes["password"]=function(v){
 if(!Ext.form.VTypes["passwordVal1"].test(v)){
  Ext.form.VTypes["passwordText"]="Password length must be 6 to 31 characters long";
  return false;
 }
 Ext.form.VTypes["passwordText"]="Password must include atleast 2 numbers or symbols";
 return Ext.form.VTypes["passwordVal2"].test(v);
}
Ext.form.VTypes["passwordText"]="Invalid Password"
Ext.form.VTypes["passwordMask"]=/./;

Monday, January 6, 2014

perl: Data::Dumper dyld: lazy symbol binding failed: Symbol not found: _isWORDCHAR Referenced from: /perl5/Library/Perl/Updates/5.12.3/darwin-thread-multi-2level/auto/Data/Dumper/Dumper.bundle Expected in: flat namespace dyld: Symbol not found: _isWORDCHAR

When installing Data::Dumper package 2.143 on MacOSX with perl 5.12.3 as runtime I received test results that were bad on the Dumper.xs tests. SO I forced the installation.... bad idea... got these errors on some of the Dumper calls when debugging DBIx result sets.
dyld: Symbol not found: _isWORDCHAR
  Referenced from: /Users/me/perl5/Library/Perl/Updates/5.12.3/darwin-thread-multi-2level/auto/Data/Dumper/Dumper.bundle
  Expected in: flat namespace
Solution: manually get out there on CPAN and get 2.145
$ cd .cpan/build
$ wget http://search.cpan.org/CPAN/authors/id/S/SM/SMUELLER/Data-Dumper-2.145.tar.gz
$ tar pfvx Data-Dumper-2.145.tar.gz
$ cd Data-Dumper-2.145
$ perl Makefile.PL
$ make && make test && make install
And it starts working ... here context of my Dumper calls that failed.... hope this helps anyone!
Can't set DBI::db=HASH(0x7f819f7c4108)->{HASH(0x7f819d3eab58)}: unrecognised attribute name or invalid value at /System/Library/Perl/Extras/5.12/darwin-thread-multi-2level/DBI.pm line 720.
Can't get DBI::db=HASH(0x7f819f7c4108)->{HASH(0x7f819d3eab58)}: unrecognised attribute name at /System/Library/Perl/Extras/5.12/darwin-thread-multi-2level/DBI.pm line 720.
SELECT me.server_os_type_map_seq, me.created_by_nm, me.created_dt, me.updated_by_nm, me.updated_dt, me.server_type_seq, me.os_version_seq FROM (
  SELECT me.server_os_type_map_seq, me.created_by_nm, me.created_dt, me.updated_by_nm, me.updated_dt, me.server_type_seq, me.os_version_seq  FROM PMI_SCHEMA.SERVER_OS_TYPE_MAP me 
) me WHERE ROWNUM <= ?
: '100'
SELECT me.os_version_seq, me.created_by_nm, me.created_dt, me.updated_by_nm, me.updated_dt, me.os_version_desc FROM PMI_SCHEMA.OS_VERSION me WHERE ( me.os_version_seq = ? ) : '32'
dyld: lazy symbol binding failed: Symbol not found: _isWORDCHAR
  Referenced from: /Users/me/perl5/Library/Perl/Updates/5.12.3/darwin-thread-multi-2level/auto/Data/Dumper/Dumper.bundle
  Expected in: flat namespace

dyld: Symbol not found: _isWORDCHAR
  Referenced from: /Users/me/perl5/Library/Perl/Updates/5.12.3/darwin-thread-multi-2level/auto/Data/Dumper/Dumper.bundle
  Expected in: flat namespace