@whsmith/infrastructure

3.5.8 • Public • Published

Infrastructure

Infrastructure is our core library for all SPA applications build within WHSmiths. It provides a wide range of helper methods and tools as well as styles, directives and filters.

Also infrastructure has a range of extensions which you can read more about below.

Quick links

Installation

Installation is easy as we have followed standards for all our plugin development.

$ npm install @whsmith/infrastructure --save

This will install WHS Infrastructure to your node_modules.

However it's likely infrastructure is already in your package.json file:

"@whsmith/infrastructure": "^3.0.0",

Version numbers use a sym versioning system so major.minor.build.

Breaking changes will cause the major version update.

New features will cause a minor version update (unless the new feature causes a breaking change).

Bug fixes will cause a build version update.

It's important to take all this into account when selecting or changing your version It's also a good idea to check the CHANGELOG.md file for information on what has changed with breaking change information.

Setup

Now you can import into your application (within your app.js):

import { infrastructure } from '@whsmith/infrastructure';

angular.module('WHSApp', [infrastructure]);

You will also need to make sure that your WHS.App.start has the following line to load the config

WHS.App.constant('loaderConfig', WHS.Loader);

CORE

Directives

Error Box

Used to display error messages

<error-box error="{errorObj}" onDismiss="function()"></error-box>
const errorObj = {
  ErrorMessage: 'Main message for error',
  Errors: ['Multi pointed error', 'Listed below main message'],
  HelpText: 'Error help text',
  HelpLink: 'http://google.co.uk',
  FullException: 'Full Stack Trace Exception',
};

ErrorSrc

Loads a src if the original src fails to load.

<img src="" error-src="" />

Focus

Focus element on load

<input focus />
<input focus-on />
<input focus-me />

Last Modified

<last-modifed model="{obj}"></last-modified>
const obj = {
  LastModifiedBy: 'User', // Display Name
  LastModifiedOn: moment(), // moment date
};

Loading Wrapper

Used to wrap content loaded from a promise

  • Attributes
    • busy - Instance of the busy library.
    • content - Model referance containing text or html to render while loading.
    • text - Raw text to display while loading (content will overright this).
    • once - If provided loading wrapper will load once and then ignore changes to the busy instance.
<loading-wrapper busy="{busyInstance}" content="htmlContentModel" text="Loading Text" once>
  <!-- CONTENT -->
</loading-wrapper>

ngEnter

Attaches an enter keybinding action to element that will trigger the referenced function or will remove enter actions if no function is provided.

numberInput

Restrict input key press of anything but numbers.

Allows: backspace, enter, escape, arrows, Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X.

ngPatternRestrict

Restrict ng-model value to follow the pattern provided

<input type="text" ng-model="name" pattern="([A-Z])\w+" ng-pattern-restrict>

Spinner

Displays a simple spinner based on the expression or referenced value.

<spin active="true/false"></spin>

Forms

The following directives are available for building and validating forms.

Control Group

Renders a simple form group with label and required flag.

<control-group label="Your Input Label" required>
  <input type="text"
  name="someInput"
  class="form-control" />
</control-group>

Validation

Implements advanced validation on input model value. (see validation rules for more information)

<input type="text"
  name="someinput"
  class="form-control"
  ng-model="myModel"
  validation="min_len:2|required" />

Form Submit

Simple replace to a standard ng-click on a button to trigger a function IF the wrapping form is valid.

<ng-form name="testForm" novalidate>
  <button type="button" class="btn btn-success" form-submit="Submit(testForm)" disable-on-invalid>Submit</button>
</ng-form>

The function will only be triggered if the wrapping form is $valid.

If the disable-on-invalid attr is used the the button will be disabled when the form is invalid.

Filters

checkOngoing

Displays Ongoing, From {date}, Until {date} or Expired.

<span>{{date | checkOngoing:start:end</span>

clearZero

This filter will clear any Zero (0) value.

company

T = Travel, H = High Street, B = Both

<span>{{data | company}}</span>

dateUntil

Returns date or No End Date if the date if same or after the extreme date 2999-12-31.

groupBy

Best used in the controller will group data by property with key and data mapping.

const grouped = $filter('groupBy')(data, 'myGroupProperty');
// grouped.key = myGroupProperty
// grouped.data = Array of grouped objects.

htmlEscape

Escape any html in value.

moment

Parse and return data in set format

{{ '2017-04-01': moment:'DD/MM/YYYY':'YYYY/MM/DD' }}

moment:format:parseFormat

percent

Return the percent value from a decimal (defaults 1 decimal place & precision 1)

percent:decimals:precison

{{ 0.254242: percent:1 }}

textReplace

Find and replace text in value

textReplace:search:replace

{{ 'Some Text': textReplace:'Text':'Cake' }}

titleCase

Title case input based on abbreviationsList

titleCase:abbreviationsList

trim

Trim value removing any whitespace

uniqueUrl

Adds a unique queryString to the end of provided url value to prevent caching

Services

whsHelper

The helper is a must include for most application controllers as it inherits a lot of core services and useful functions.

// Properties
root // Inherits $rootScope
busy // Inherits the busy service
logger // Inherits the whsLog service
auth // Inherits the whsAuth service
dialog // Inherits the whsDialog service
util // Inherits the whsUtil service
config // Inherits the whsConfig service which inherits loaderConfig
service // Inherits the CommonService

// Functions
sendError(message, ...objs); // Will log error objs to console in DEBUG MODE otherwise will send email to developers with objs.
getSettings() // Returns all the application settings (DNN Settings)
getSetting(key) // Returns the selected application setting by key
getUser() // Returns current logged user details or null
showInfo(message) // Display information sweetalert with message
showQuestion(title, message) // Display question sweetalert with title and message
showSuccess(message) // Display success sweetalert with message
showSuccessToast(message, timer) // Display a successful toast with message and close after timer (ms) (Default: 3000)
showWarning(message) // Display warning (opps) sweetalert with message
showError(message, ...objs) // Display error sweetalert with message (first parameter), any additional parameters will be logged to console, an option to report will also be presented
showErrorWithoutReport(message, ...objs) // Display error sweetalert with message (first parameter), any additional parameters will be logged to console
showErrorToast(message, timer, ...objs) // Display a error toast with message and close after timer (ms) (Default: 3000)
showConfirm(options, promise) // Display a confirmation sweetalert giving the user Yes/No Options and executing a promise on yes.
debug(...) // Log all params to console
addDebugVariable(name, value) // Add a variable to the debug page '/debug'
logError(...) // Log all params to console as error
saveAudit(ref, message, oldData, newData) // Adds a new audit event for the current application (each property can be null)

whsConfig

// Properties
. // Inherits the properties of loaderConfig
appSettings[] // appSettings array of [key] = value (DNN Settings)
DNNAccess // true/false based on if application running in DNN

whsLog

Log helper handles most console logging

log(...); // Prints all args to console as log/debug
info(...); // Prints all args to console as info
warn(...); // Prints all args to console as warning
error(...); // Prints all args to console as error
table(...); // Exec console.table(data, [, columns])
trace(); // Print console trace to this point
clear(); // Clear console
timeStart(key); // Start timer with key
timeStop(key); // End timer with key

pageTitle

pageTitle(title, header, includeAppName) 
  // title = page name to display in window/tab title
  // header = page header to display at top
  // includeAppName = true/false - should application name be displayed after title in window/tab title.

whsDialog

confirmModel(options) // Display a confirm model popup
  // OPTIONS
  //   - title: model title
  //   - body: model body
  //   - confirmButtonText: confirm button text, defaults : 'Confirm'
  //   - cancelButtonText: cancel button text, defaults: 'Cancel' 
  //   - execute: promise function to run on confirm.
alertMessageBox(options) // Display a alert model popup
  // OPTIONS
  //    - title: model title
  //    - message: model message (Allowed HTML)
successMessageBox(options) // Display a success model popup
  // OPTIONS
  //    - title: model title
  //    - message: model message (Allowed HTML)
errorMessageBox(options) // Display a error model popup
  // OPTIONS
  //    - title: model title
  //    - message: model message (Allowed HTML)
messageBox(caption, message, buttons) // Display a model popup message box
  // caption: title of the model
  // message: body of the model
  // buttons: array of buttons
  //   - cssClass: button classes
  //   - result: button return result
  //   - label: button text
showConfirm(options, promise) // Display a confirming SWAL (Sweetalert)
  // OPTIONS
  //   - title: title of the swal (defaults: 'Are You Sure')
  //   - html: body of the swal (defaults: 'Please confirm you wish to complete this action')
  //   - confirmButtonText (defaults: 'Yes')
  //   - cancelButtonText (defaults: 'No')
  // promise: promise function to run on confirm
getSwal()                 // Get an instance of `swal`
showSwal(options)         // Open a swal with the povided options
closeSwal()               // Close any open swal dialogs
getMixin(options)         // Create a swal mixin
runSwalQueue(items)       // Run array of swal options
showSwalError(message)    // Display validation error on swal
triggerSwal(options)      // Trigger a swal with defaults
showWarning(options)      // Display a 'Opps!' warning swal
showError(options)        // Display a 'Error!' error swal
showSuccess(options)      // Display a 'Success!' success swal
showInfo(options)         // Display a `Info` info swal
showQuestion(options)     // Display a question swal.

whsUtil

escapeHTML(string) // Escaped any html found in string
toBoolean(string)  // returns true/false based on string value
goBack() // Return to previous state or `app.default`
calculateWeekNumber(dateString) // Return the week number of date
calculateMonthNumber(dateString) // Return the month number of date
calculateYearNumber(dateString) // Return the year number of date
checkCalender(dateString) // Returns moment date plus the week, month and year numbers of the provided date.

whsGateway

// To initialize the gateway you must first create a new instance
const Gateway = whsGateway.create(clientName, version); // Client name is required, version will default to 'v1'.

// Using the newly created instance you have access to the following methods.
setupUrl(url, client, version) // Return gateway url based on the url, client and version.  (client and version will use the instance values if not provided here).

get(url, config)          // Trigger a HTTP GET Request
post(url, data, config)   // Trigger a HTTP POST Request
put(url, data, config)    // Trigger a HTTP PUT Request
delete(url, config)       // Trigger a HTTP DELETE Request

whsAuth

getUser()  // Get current user object or null.
getUserIpAddress() // Return current user IP or ''.
getPermissions() // Return current application permissions
directToLogin(target, targetParams) // Redirect to login page with the set return target & params.
checkAuth(required) // Check if current user is authenticated and redirect to login if not authenticated.

busy

// First initialize a busy instance
const isLoading = busy.create;

// With your new instance you can add a promise(s)
isLoading.promise(myPromiseFunction)
isLoading.promises([promiseFunction1, promiseFunction2]);
// When a promise is running the `isLoading.busy` state will update.

// You can also force a loading error with:
isLoading.showError(message) // Display message as error
isLoading.showErrors(message, errors) // Display error message and errors array
isLoading.clearError() // Will clear any errors

pollLoop

// First initialize a pollLoop instance
const loop = pollLoop.create($scope, () => functionThatReturnsAPromise(), 4000);
//  Instance of angular scope
//  Function that returns a promise to trigger on polling
//  Time between polls (optional defaults to 4000/4secs)

// PoolLoop Methods
start() // Start polling after the timer
startImmediately() // Start now
stop() // Stop current instance

CommonService

GetAudit(ref)  // Get audit event list for current application
GetAuditForApplication(application, ref) // Get audit event list for application
AddAudit(data) // Add new audit event data
AddAuditEvent(ref, message, oldData, newData) // Add new audit with current user, application and state url.
getTitleCaseDictionary() // Return `Common/TitleCaseDictionary` API method
sendEmail(data) // Send email using `Common/SendEmail` API method
sendErrorEmail(body, fromUser) // Sends error email to developers
  // body: HTML Enabled email body
  // fromUser: user object.

Validation Rules

Validation rules can be used within the validation directive.

For example the validation rule of required has no params and is passed simpily:

<input type="text"
  validation="required" />

You can pass multiple validation rules using a | (pipe) and params are added to rules using : semi-colon.

<!--- Required field with min length of 2 -->
<input type="text"
  validation="required|min_len:2" />

Lastly a custom error message can be provided with 'alt='.

<input type="text"
  validation="required:alt=You forgot this field.|min_len:2:alt=Need atleast 2 characters.">

Available Rules

// All rules are written as snake_case but camelCase is also possible.

accepted            // The field under validation must be yes, on, 1, or true.
alpha               // Only alpha characters (including latin) are allowed (a-z, A-Z)
alpha_spaces        // Same as `alpha` but includes spaces
alpha_num           // Same as `alpha` but includes (0-9)
alpha_num_spaces    // Same as `alpha_num` but includes spaces
alpha_dash          // Same as `alpha_num` but includes dashed (a-z,A-Z,0-9,_-)
alpha_dash_spaces   // Same as `alpha_dash` but includes spaces
between_num:min,max // Number between min and max
between_len:min,max // Length between min and max
between_date_iso:d1,d2 // Alias of `date_iso_between`
between_date_euro_long:d1,d2 // Alias of `date_euro_long_between`
between_date_euro_short:d1,d2 // Alias of `date_euro_short_between`
boolean             // Ensures value is true or false (or 0 or 1)
compare             // Alias of `match`
credit_card         // Valid credit card number
custom:f            // Calls a custom function (f) for validation  (see custom rule details below)
date_iso            // Ensure date follows ISO format (yyyy-mm-dd)
date_iso_between:d1,d2 // Ensure date follows ISO format and is between (d1) & (d2)
date_iso_max:d      // Date must follow ISO format and lower or equal than date (d)
date_iso_min:d      // Date must follow ISO format and is higher or equal than date (d)
date_euro_long      // Date must follow the european long format (dd-mm-yyyy) or (dd/mm/yyyy)
date_euro_long_between:d1,d2 // Date must follow european long format and is between (d1) & (d2)
date_euro_long_max:d  // Date must follow european long format and is lower or equal than date
date_euro_long_min:d // Date must follow european long format and is lower or equal than date
date_euro_short     // Date must follow the Euro short format (dd-mm-yy) or (dd/mm/yy)
date_euro_short_between:d1:d2 // Date must follow euro short format and is between (d1) & (d2)
date_euro_short_max:d // Date must follow euro short format and is lower or equal than date
date_euro_short_min:d // Date must follow euro short format and is higher or equal than date
different           // Alias of `different_input`
different_input:f   // Must be fidderent from another input (f) where (f) must be the exact ngModel attribute to compare to.  The error message will use the input name or the `friendly-name` if it was provided.
digits:n            // Ensures the field only has integer numbers and length precisely matches (n)
digits_between:min,max // Ensures the field only has numbers and is between (min) & (max)
email               // Checks for a valid email
email_address       // Alias of `email`
enum                // Alias of `in_list`
exact_len:n         // Ensures that field length precisely matches the length (n)
float               // Has to be a floating value
float_signed        // Has to be a floating value, could be signed (-/+).
in                  // Alias of `in_list`
in_list:foo,bar,..  // Ensures the value is included inside the given list of values (separated by ,)
int                 // Alias to `integer`
integer             // Only positive numbers
int_signed          // Alias to `integer_signed`
integer_signed      // Only numbers, can be signed (-/+)
ip                  // Alias of `ipv4`
ipv4                // Check for valid IPv4 IP.
ipv6                // Check for valid IPv6 IP.
match:f             // Match another input field (f) where (f) must be the ngModel attribute of the field to compare.  The error message will use the `friendly-name` if it was provided.
match_input         // Alias of `match`
max_date_iso        // Alias of `date_iso_max`
max_date_euro_long  // Alias of `date_euro_long_max`
max_date_euro_short // Alias of `date_euro_short_max`
max_len:n           // Checks field length no longer than (n)
max_length:n        // Alias of `max_len`
max_num:n           // Checks numeric value to be lower or equal than (n)
min_date_iso        // Alias of `date_iso_min`
min_date_euro_long  // Alias of `date_euro_long_min`
min_date_euro_short // Alias of `date_euro_short_min`
min_len:n           // Checks field length is no shorter than (n)
min_length:n        // Alias of `min_len`
min_num:n           // Checks numeric value to be higher or equal than (n)
not_in              // Alias of `no_in_list`
no_in_list:foo,bar,.. // Ensures the value is not within the given list
numeric             // Only positive numbers
numeric_signed      // Only numbers, can be signed (-/+)
pattern             // Ensures it follows a regular expression (pattern=/^(A-Z)$/)
remote:f            // Calls function (f) that returns promise for validation (see remote rule below for more details)
required            // Ensures the field exists and is not empty
same                // Alias of `match`
string_len          // Alias of 'between_len`
string_length       // Alias of `between_len`
time                // Ensures time follows the format (hh:mm) or (hh:mm:sss)
url                 // Check for valid URL or subdomain
Custom Rule

When using a custom rule your validation function can either return just a true/false bool or the bool and a error message. If only the bool is returns any error messages should be handles with alt message defined in the validator.

myCustomValidation(value) {
  let isValid = false;
  if (value === 'abc') {
    isValid = true;
  }

  // If valid is true or error message is handled with `alt` inside validator
  return isValid;

  // If valid is false and error message is not handled inside validator
  return { isValid, message: 'custom error' };
}
<input type="text"
  ng-model="myValue"
  validation="custom:myCustomValidation(myValue)">

<input type="text"
  ng-model="myValue2"
  validation="custom:myCustomValidation(myValue2):alt=There was an issue validating this field.">
Remote Rule

When usign a remote rule your validation function will need to return a promise which returns the same as the custom rule.

myRemoteValidation(value) {
  const def = $q.defer();

  // Using a timeout to simulate an API call
  setTimeout(() => {
    const isValid = (value === 'acb') ? true : false;

    // Option #1
    def.resolve(isValid);

    // Option #2
    def.resolve({ isValid, message: 'Returned error from promise.' });
  }, 1000);

  return def.promise;
}
<input type="text"
  ng-model="myValue"
  validation="remote:myRemoteValidation(myValue)">

Routing And Permissions

When adding states you are giving a bunch of additional options you can add to extend your route.

$stateProvider.state('app.sample', {
  url: '/',
  redirectTo: 'app.default', // Redirect to the desired state
  defaultSubRoute: 'app.sample.page', // Redirect to the desired sub route (will retain parameters)
  layout: {
    title: 'Infrastructure', // Browser title to display for page
    header: 'Infrastructure' // Page header to display for page
  },
  allowAnonymous: true // Flags that the page doesn't require authentication
});

In regards to permission it will closely relate to roles and inheritance:

[
  {
      "Name": "User",
      "Description": "Groups that have default access to use and view the application",
      "Roles": ["All"],
      "Inherit": "Admin",
      "AllowedAccess": true
  },
  {
      "Name": "Admin",
      "Description": "",
      "Roles": ["Administrator"],
      "Inherit": "",
      "AllowedAccess": true
  },
  {
    "Name": "StoresAdmin",
    "Description": "Roles that have access to change there store",
    "Roles": ["IntranetDeveloper"],
    "Inherit": "Admin",
    "AllowedAccess": true
  }
]

In the sample above:

  • All & Administrator will get the role User
  • Administrator will get the role User
  • IntranetDeveloper & Administrator will get the role StoresAdmin

Extensions

The Infrastructure plugin has an extendable feature, by default whs-infrastructure will use the core component this contains all the components we feel are required / commonly used in all our applications.

You then have the options of adding extra components to your application.

By using this method instead of just adding everything to the core plugin it means we can only include (and build) components that we require which will lower the size of our final bundled application.

Also when implementing extensions you can either attach them in your app.js so the extension is available app wide, or you can attach them to a specific area so only that area can use a specific extension.


ESLint Config

Our custom ESLint config is simple to implement when using infrastructure and will allow for easily following our coding standards.

Installation

Open your .eslintrc file and replace content with the following:

{
  "extends": "./node_modules/@whsmith/infrastructure/lint.js"
}

And your done.


WHS Table

Usage

Implementing the table extension is very simple by updating your current implementation of your infrastructure to the following:

import { infrastructure, whsTable } from '@whsmith/infrastructure';

angular.module('WHSApp', [infrastructure, whsTable]);

Once loaded you have access to all the directives avaliable for WHS Table.

Basic Usage

<table whs-table="rowCollection">
  <tr ng-repeat="row in rowCollection">
    <td>{{ row.number }}</td>
    <td>{{ row.name }}</td>
  </tr>
</table>
rowCollection = [
  {name: 'Test', number: 1},
  {name: 'Test 2', number: 2},
];

It is recommended however to use safe-src to load your data and then loop from a separate generated array:

<table whs-table="gridData" safe-src="rowCollection">
  <tr ng-repeat="row in gridData"></tr>
</table>

Removing Row

function removeRow(row) {
  var index = rowCollection.indexOf(row);
  if (index !== -1) {
    rowCollection.splice(index, 1);
  }
}

Sorting Data

<table whs-table="rowCollection">
  <tr>
    <th whs-table-sort="searchNameFunc">Name</th>
    <th whs-table-sort="number">Number</th>
  </tr>
  <tr ng-repeat="row in rowCollection">
    <td>{{ row.number }}</td>
    <td>{{ row.name }}</td>
  </tr>
</table>
function searchNameFunc(value) {
  // Value will equal the row object
  // this will sort by the length of the name string
  return value.name.length;
}

Searching / Filtering Data

<table whs-table="rowCollection" ng-enter="">
  <tr>
    <th whs-table-sort="searchNameFunc">Name</th>
    <th whs-table-sort="number">Number</th>
  </tr>
  <tr>
    <th><input class="input-sm form-control" type="search" placeholder="search for number" whs-table-search="number" /></th>
    <th><input class="input-sm form-control" type="search" placeholder="global search" whs-table-search /></th>
  </tr>
  <tr ng-repeat="row in rowCollection">
    <td>{{ row.number }}</td>
    <td>{{ row.name }}</td>
  </tr>
</table>

As shown above you can add a search for both single column searching and a global table search.

Selecting Rows

<table whs-table="rowCollection">
  <tr>
    <th>Name</th>
    <th>Number</th>
  </tr>
  <tr ng-repeat="row in rowCollection" whs-table-select-row="row" select-mode="single/multiple">
    <td>{{ row.number }}</td>
    <td>{{ row.name }}</td>
  </tr>
</table>

When a row is selected the row object with have a isSelected property set to true.

Pagination

<table whs-table="rowCollection">
  <tr>
    <th>Name</th>
    <th>Number</th>
  </tr>
  <tr ng-repeat="row in rowCollection">
    <td>{{ row.number }}</td>
    <td>{{ row.name }}</td>
  </tr>
  <tfoot>
    <tr>
      <td colspan="2">
        <div whs-pagination items-per-page="10" displayed-pages="5" items-options="[5,10,20]" current-page="1" page-change="changeTriggerFunc()"></div>
      </td>
    </tr>
  </tfoot>
</table>

Sticky Header

You can now fix your table headers allowing you to scroll large tables while keeping the header at the top of the screen.

<div class="table-box" whs-table="rowCollection">
  <div class="table-responsive">
    <table sticky-header="true/false">
      <thead>
        <tr>
          <th>Name</th>
          <th>Number</th>
        </tr>
      </thead>
      <tbody>
        <tr ng-repeat="row in rowCollection">
          <td>{{ row.number }}</td>
          <td>{{ row.name }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

NOTE There are some factors to take note of when using the fixed header.

  1. If you plan to use a filter on the table you must move the whs-table directive above the table element (like in the example above). If you don't plan to use filtering then it can be added to the table element as before.

  2. Your notice you have pass true/false into the directive this is so you can disable and enable the fixed header. For example when you open search boxes you should disable to fixed header, then you can re-enable when you hide them again.

xyScroll (Fixed Columns)

Using the xy-scroll directive you can build a fixed column table.

It does this by using a total of 4 tables (1 for left headers, 1 for left rows, 1 for right headers and 1 for right rows).

It is important to use fixed widths and heights when using fixed columns because without this your rows and headers will not match up correctly.

HTML

<div class="table-box">
  <div id="fixedSample" xy-scroll x="sampleFixedTable.x" y="sampleFixedTable.y" drag-x="true" drag-y="false">
    <!-- LEFT COLUMN HEADER -->
    <div class="xy-corner">
      <table class="table fixed">
        <colgroup>
          <col class="col-sm" />
        </colgroup>
        <tr>
          <th class="text-center">Item Name</th>
        </tr>
      </table>
    </div>
    <!-- FIXED LEFT COLUMN -->
    <div class="xy-header-left">
      <table class="table fixed">
        <colgroup>
          <col class="col-sm" />
        </colgroup>
        <tbody>
          <tr ng-repeat="h in $ctrl.sampleData track by $index">
            <td class="text-center" ng-bind="h.name"></td>
          </tr>
        </tbody>
      </table>
    </div>
    <!-- TABLE HEADER -->
    <div class="xy-header-top">
      <table class="table fixed">
        <colgroup>
          <col ng-repeat="col in $ctrl.fields" class="col-sm" />
        </colgroup>
        <tr>
          <th ng-repeat="col in $ctrl.fields">
            <div class="text-center" ng-bind="col"></div>
          </th>
        </tr>
      </table>
    </div>
    <!-- TABLE CONTENT -->
    <div class="xy-content">
      <table class="table fixed">
        <colgroup>
          <col ng-repeat="col in $ctrl.fields" class="col-sm" />
        </colgroup>
        <tr ng-repeat="item in $ctrl.sampleData track by $index">
          <td ng-bind="item['0']"></td>
          <td ng-bind="item['1']"></td>
          <td ng-bind="item['2']"></td>
          <td ng-bind="item['3']"></td>
          <td ng-bind="item['4']"></td>
          <td><input type="text" class="form-control" ng-model="item['5']"></td>
          <td ng-bind="item['6']"></td>
          <td ng-bind="item['7']"></td>
          <td ng-bind="item['8']"></td>
          <td ng-bind="item['9']"></td>
          <td ng-bind="item['10']"></td>
          <td ng-bind="item['11']"></td>
          <td ng-bind="item['12']"></td>
          <td ng-bind="item['13']"></td>
          <td ng-bind="item['14']"></td>
        </tr>
      </table>
    </div>
  </div>
</div>

JS

// Variables
var fields = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'];
var sampleData = mockData();

mockData(total) {
  total = total || 20;
  const data = [];
  for (let i = 0; i < total; i++) {
    const item = {
      name: `Item ${i}`
    };
    for (let j = 0; j < fields.length; j++) {
      item[j] = `column ${fields[j] + i}`;
    }
    data.push(item);
  }
  return data;
}

SCSS

#fixedSample {
  .fixed {
    table-layout: fixed;
  }

  .fixed .col-sm {
    width: 100px;
  }

  .fixed td,
  .fixed th {
    width: 100px;
  }

  .fixed td { height: 55px; }

  .fixed th {
    border-bottom: 0 none;
  }
}

WHS Datepicker

import { infrastructure, whsDatepicker } from '@whsmith/infrastructure';

angular.module('WHSApp', [infrastructure, whsDatepicker]);

Using Datepicker

<div class="input-group">
  <input type="text"
    name="dateFieldName"
    id="dateFieldId"
    class="form-control"
    ng-model="modelProperty"
    whs-datepicker
    format="DD/MM/YYYY"
    min-date="00/00/0000"
    max-date="00/00/0000"
    disabled="true/false"
    icon-btn="#dateField_btn"
    required />
  <div class="input-group-btn">
    <button type="button" class="btn btn-secondary" id="dateField_btn"><i class="fa fa-calendar"></i></button>
  </div>
</div>

Filters

Moment Parse

{{ date | momentParse : 'DD/MM/YYYY' }}

Moment UTC

{{ date | momentUtc }}

Date Differance

{{ fromDate | dateDifference : toDate : unit : usePrecision }}

Date Format

{{ date | dateFormat : 'DD/MM/YYYY' }}

WHS Print

Installing WHS Print

To load whs-print you can extend your current loading instance of infrastructure.

import { infrastructure, whsPrint } from '@whsmith/infrastructure';

angular.module('WHSApp', [infrastructure, whsPrint]);

Using WHS Print

printSection

  • Directive type: Attribute
  • Description: Makes element and its children visible for printing
<div>
    <div print-section>
      I'll print
      <p>Me, too!</p>
    </div>
    <div>I won't</div>
</div>

printOnly

  • Directive type: Attribute
  • Description: Makes element and its children only visible for printing
<div print-section>
    <div print-only>
      I'll print, but until then nobody wants me
      <p>Me, too!</p>
    </div>
    <div>Me, too! Except that people still want to look at me in the meantime...</div>
</div>

printHide

  • Directive type: Attribute
  • Description: Makes element invisible during printing, but it is replaced by blank space
<div print-section>
    <div print-hide>
      I won't print
      <p>Me, either</p>
    </div>
    <div>I'll print, but those bozos upstairs are still taking up space</div>
</div>

printRemove

  • Directive type: Attribute
  • Description: Makes element invisible during printing
<div print-section>
    <div print-remove>
      I won't print
      <p>Me, either</p>
    </div>
    <div>I'll print, and those bozos upstairs will finally stop making such a ruckus</div>
</div>

printIf

  • Directive type: Attribute
  • Description: Toggles print-visibility based on expression
<!--Pigs do not yet fly, so this div, despite having print-section, will not print-->
<div print-section print-if="pigsFly"></div>
<!--ET IS the best, so this div will print, despite not having print-section-->
<div print-if="ETIsTheBest"></div>

printBtn

  • Directive type: Attribute
  • Description: Adds onClick callback to element that will trigger printing
<button print-btn>Doesn't matter where you put me</button>
<span print-btn>I will make anything cause a print</span>
<p print-btn>to happen if you click me</p>

printLandscape

  • Directive type: Attribute
  • Description: Will cause printing to be landscape instead of portrait
<button print-landscape>Doesn't matter where you put me</button>
<span print-landscape>I will cause any print</span>
<p print-landscape>to be landscape</p>

printAvoidBreak

  • Directive type: Attribute
  • Description: Prevents page breaks on element
<button print-avoid-break>This element won't get split by page breaks</button>

printBreakBefore

  • Directive type: Attribute
  • Description: Causes a page break before rendering the current element
<div print-break-before>Print page will break before this element</div>

pageBreak

  • Directive type: Element
  • Description: Element to create a fixed point of a page break, dosn't render anything.
<div>Page 1</div>
<page-break></page-break>
<div>Page 2</div>

printTable

  • Directive type: Attribute
  • Description:
    • Optimizes table for printing. This includes keeping 'td' cell content from being cut-off by page breaks.
    • Must be passed an array scope object representing the data presented by the table.
    • Column headers will persist between pages only if the <thead> and <tbody> tags are used correctly.

This example shows adjustments to an already-visible table in order to tailor it for printing

<table print-table="people">
  <thead>
    <tr>
      <td print-remove>Unwanted field</td>
      <td>Name</td>
      <td>Address</td>
      <td>Phone</td>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="person in people">
      <td print-remove>{{person.unwantedInfo}}</td>
      <td>{{person.name}}</td>
      <td>{{person.address}}</td>
      <td>{{person.phone}}</td>
    </tr>
  </tbody>
</table>      

This example shows a table made to only be visible during printing

<table print-table="people" print-only>
  <thead>
    <tr>
      <td>Name</td>
      <td>Address</td>
      <td>Phone</td>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="person in people">
      <td>{{person.name}}</td>
      <td>{{person.address}}</td>
      <td>{{person.phone}}</td>
    </tr>
  </tbody>
</table>      

PDFPrinter Service

The PDFPrinter service is a great tool for building and creating pdfs either to open, print or download.

You can either provide the document definitions yourself or load the default and use inbuild functions to build your pdf.

Implementation

class SomeClass {
  constructor(PDFPrinter) {
    this.myPDF = PDFPrinter.create(); // Default

    this.customPDF = PDFPrinter.create(documentDef); // Custom
  }
}

documentDef is a object using pdfmake, more information about properties you can use are on their website: http://pdfmake.org/playground.html

Service Methods

From your created PDF instance you will have access to the following methods which will effect the instance you are running from.

print
this.myPDF.print(); // This will print the current PDF.
open
this.myPDF.open(); // This will open the current PDF.
download
this.myPDF.download(); // This will trigger a download for the current PDF.
AddElement
  • Parameters:
    • element - String or Object to add to the documentDef content.
this.myPdf.AddElement({
  text: 'sub header text element with page break',
  pageBreak: 'before',
  style: 'subheader'
});
AddPageHeader
  • Parameters:
    • text - Header text
this.myPdf.AddPageHeader('My PDF');
AddFooterPageCount

Add a page count at the footer of the PDF pages.

this.myPdf.AddFooterPageCount();
AddStyle

Adds a style object to the pdf document.

this.myPdf.AddStyle('myStyle', {
  bold: true;
  alignment: 'center'
});
AddHeader
  • Parameters:
    • text - Text to display as a header
this.myPDF.AddHeader('A Header'); // Add a header to pdf content.
AddSubHeader
  • Parameters:
    • text - Text to display as a sub header
this.myPDF.AddSubHeader('A Sub Header'); // Add a sub header to pdf content.
AddText
  • Parameters:
    • text - Text to display
this.myPDF.AddText('I want to write some text on my pdf.'); // Add text to pdf content.
AddList
  • Parameters:
    • text - Text to display as a header
    • bold - Should the list items be bold
this.myPDF.AddList(['item 1', 'item 2', 'item 3'], false);
AddSimpleTable
  • Parameters:
    • tableBody - Body array for the table object.
var data = [
  ['Header1', 'Header2', 'Header3'], // First colum is the header
  ['row1col1', 'row1col2', 'row2col3']
];
this.myPDF.AddSimpleTable(data); // Creates a simple table based on the data provided
AddRawTable
  • Parameters:
    • tableObj - Table object.
var table = {
  widths: ['*', 200, 50],
  body: [
    [
      { text: 'Auto Width Column', style: 'tableHeader', alignment: 'left' },
      { text: 'Smaller Column', style: 'tableHeader' },
      { text: 'Tiny Column', style: 'tableHeader' }
    ],
    ['row1col1', 'row1col2', 'row2col3']
  ]
};
this.myPDF.AddRawTable(table); // Creates a table from the raw table object
CreateTableObj

By creating a table object you will gain a bunch of extensions methods to help build your table obj. All methods can be chained and then ending with Build() to return the finished table obj use getTable().

  • Parameters:
    • tableObj - Core table object, default will be provided if undefined
const tableBuilder = this.myPDF.CreateTableObj({
  headerRows: 1,
  style: 'table'
});

// Add methods here to build your table (see below)
tableBuilder
  .SetWidths([100, 200])
  .AddHeader(['Header 1', 'Header 2'])
  .AddRow(['Row1 Col1', 'Row1 Col2'])
  .Build();

// Add table to PDF
this.myPDF.AddRawTable(tableBuilder.getTable());

Methods available within the tableBuilder are as follows:

SetWidths
  • Parameters:
    • widths - array of widths for each column, pass '*' for auto width.
const tableBuilder = this.myPDF.CreateTableObj();
tableBuilder.SetWidths([100, 200, '*']);
AddHeader
  • Parameters:
    • header - header objects / string
const tableBuilder = this.myPDF.CreateTableObj();
tableBuilder.AddHeader({ text: 'Header 1', style: 'tableHeader' });
AddHeaders
  • Parameters:
    • headers - array of header objects / string
const tableBuilder = this.myPDF.CreateTableObj();
tableBuilder.AddHeaders([
  'Header 1',
  { text: 'Header 2', alignment: 'center' }
]);
AddRow
  • Parameters:
    • row - array of row objects / string
const tableBuilder = this.myPDF.CreateTableObj();
tableBuilder.AddRow([
  { text: 'Col1 Text', style: 'someStyle' },
  { text: 'Col2 Text', bold: true },
]);
AddRowSmart

Adding a smart row will allow you to pass the raw object and then define how each column should be handled.

  • Parameters:
    • data - data objects (model)
    • columnOptions - array of column options
      • Object used the core pdfmake properties and also the following extension properties:
        • property - property name from data object to use for column
        • run - function to run property value through and display function response for column.
const tableBuilder = this.myPDF.CreateTableObj();
tableBuilder.AddRowSmart(obj, [
  { property: 'id', bold: true }, // Column 1, load obj.id
  { property: 'name' }, // Column 2, load obj.name
  { text: 'NEW', style: 'statusGreen' } // Column 3 forced text value
]);
AddRows
  • Parameters:
    • rows - array of array of row objects / string
const tableBuilder = this.myPDF.CreateTableObj();
tableBuilder.AddRows([
  ['Row1 Col1', 'Row1 Col2'],
  [{ text: 'Row2 Col1', bold: true }, 'Row2 Col2' ]
])
AddRowsSmart

Like the AddRowSmart however it will allow you to pass an entire array of data with and single set of column options and it will add all the rows.

  • Parameters:
    • data - Array of objects
    • columnOptions - array of column options
AddImage
  • Parameters:
    • imageUrl - Url / Location of the image you want to display
    • width - [Optional] Width to scale the image to.
this.myPDF.AddImage('myimage.jpg', 200);

Readme

Keywords

none

Package Sidebar

Install

npm i @whsmith/infrastructure

Weekly Downloads

1

Version

3.5.8

License

MIT

Unpacked Size

695 kB

Total Files

133

Last publish

Collaborators

  • aaron.barnaby
  • whsmith.jenkins