Wednesday, January 23, 2019

My Latest Learn-By-Doing Project: A Vue.js Application Using Firebase Cloud Firestore

A few weeks ago, I decided to take my very basic knowledge of Vue.js and apply it towards learning to do basic operations against a Firebase Cloud Firestore NoSQL database. The result of that project can be found on GitHub at https://github.com/bcswartz/fcf-vue-users. It’s a web application that allows you to create and authenticate user accounts and documents via Firebase's email/password-based authentication API and the Cloud Firestore document and document collection API.

Credit where credit is due: many of the techniques used in the app were borrowed from a very well-written post about using Vue.js with Firestore - https://savvyapps.com/blog/definitive-guide-building-web-app-vuejs-firebase. My project would have taken a lot longer to develop without the information and example code in that post.

Writing the app gave me some basic experience with popular Vue.js tools: the Vue Router, the Vuex data store, and the Vue CLI. Even though it’s a small application, it includes two reusable components - an error message display and a reauthentication modal - and contains an example of using Vue's event broadcasting system.

That reauthentication modal component wasn’t part of my original design plan. While the Cloud Firestore API calls for retrieving and managing documents and documents collections are pretty straightforward, the authentication API calls for performing operations related to user accounts only work if you have a fresh set of credentials (in this case, email address and password) for the user account you want to modify.

That can be quite a restriction. The reauthentication modal allows the current user to update their own email address and password even if their authentication session has grown stale, but I didn’t see any way for the current user to make email/password changes to another user’s account. So that’s something to keep in mind if you want user management features in your application.

Anyway, if you’re interested in getting your feet wet with Vue.js, or Firebase, or both, and are looking for a sample application, check out the repo. More details about what’s in the application can be found in the README file and in the src/views/Notes.vue file within the app.

Friday, June 8, 2018

My Impressions of the Bulma CSS Framework

In my last blog post, I said I was going to refactor my vadacl demo Angular application using the Bulma CSS framework.  I recently finished that task.  Here are some screenshots of the new look:



Having now used Bulma in a project, here are some of my thoughts and some of the things I learned:

  • The ability to change core style settings using Sass variables is a convenient feature.  I was able to implement the color scheme for my app by simply changing three of the primary theme variables.  I also made two minor spacing and sizing adjustments.

    $primary: #694979;
    $info: #474e52;
    $danger: #d01e42;
    $gap: 16px;
    $help-size: 1.65ex;
    

  • I like the clarity of the class names Bulma uses. "has-text-black" and "has-background-primary" are pretty self-explanatory and easy to remember.  The grid layout is implemented with a "columns" container and "column" blocks, with size modifiers like "is-2" or "is-half".

  • Most CSS frameworks remove the natural top and bottom margins of the HTML <p> paragraph element, but Bulma also removes the style and margins of the header elements (<h1>, <h2>, etc.).  You can re-apply the expected styles by adding the "title" class and one of the "is-size-x" classes (where "x" is a number between 1 and 7).

  • Related to the previous bullet, Bulma provides a "content" class that, when applied to a container element, restores the normal HTML styles to elements like paragraphs and titles.  So if you want to display a block of several normal paragraphs, you can enclose them in a "content" container and not have to create and apply your own CSS class to those paragraphs.

  • I like the variety of the different container classes Bulma provides.  Initially I tried to lay out the main menu page using tiles but couldn't achieve the look that I wanted, and I ended up using "box" elements inside of "column" blocks.

  • Some of the layout implementations can end up being very deep in terms of nesting.  Unless I'm designing for mobile, I tend to favor horizontal form layouts with the label and form control on the same line.  Bulma allows for this (and in a way that lets the labels and inputs still stack vertically when the screen becomes narrow, which is great), but each "row" of the form ends up using a lot of elements.  In the forms in the demo, the element tree containing each input ends up being:

    div.field.is-horizontal -> div.field-body -> div.field -> div.control -> input 

    Not the prettiest HTML to look at.

  • I also ran into a problem unique to horizontal form layout where the label (to the left of the input) only reaches a certain width before is starts to wrap the label text to the next line (see https://github.com/jgthms/bulma/issues/1852).  There's no obvious workaround for it using the Bulma classes.  To get around it, I ended up creating some custom CSS styles that widen the label to certain widths and maintain the default spacing between the input "rows".

    .hz-label-150 div.field-label {
      min-width: 150px;
    }
    
    .hz-label-200 div.field-label {
      min-width: 200px;
    }
    
    .hz-label-250 div.field-label {
      min-width: 250px;
    }
    
    .hz-label-300 div.field-label {
      min-width: 300px;
    }
    
    .hz-label-150 div.field.is-horizontal,
    .hz-label-200 div.field.is-horizontal,
    .hz-label-250 div.field.is-horizontal,
    .hz-label-300 div.field.is-horizontal
    {
      margin-bottom: 0.75rem;
    }
    
    <form [formGroup]="profileForm" class="hz-label-200">...
    


Overall, I liked Bulma enough to consider using it for future projects.

Sunday, May 13, 2018

Implementing the Bulma CSS Framework Using Sass Compilation in an Angular CLI Project

I recently stumbled across the Bulma CSS framework. I'd never heard of it before, but it's been out now for over two years, and is generated considered as a pure CSS (no JavaScript) alternative to Bootstrap.

After looking at the documentation, I decided I wanted to try styling my Angular 5.x vadacl demo app with Bulma, and that I wanted to be able to take advantage of the fact that it could be customized via Sass (even though I've never worked with Sass before).

After some missteps and some research, I discovered that integrating Bulma with the Angular CLI is pretty easy.  Here's what I ended up doing:

  1. In my vadacl demo project, I ran the CLI command that updates the CLI configuration to use SCSS ("SCSS" is the latest syntax for Sass) files to generate the styles for the project:  "ng set defaults.styleExt scss".
    • When creating a new Angular CLI project, you can use the flag "--style=scss" on the "ng new" command to do the same thing.

  2. I then installed Bulma using npm: "npm install bulma --save-dev".

  3. Next, I created a "sass" folder in the assets folder of my project and created a "variables.scss" file.  In that file, I added the line:

    $primary: #694979;

    ...to change the color value of the $primary Sass variable used in Bulma.

  4. Then I created the "styles.scss" file in the src folder of my project, and in that file I added the following lines:

    @import "assets/sass/variables.scss";
    @import "../node_modules/bulma/bulma.sass";

    Initially it seemed counterintuitive that I would set my own values for the Bulma variables ahead of the import for Bulma, but Bulma is designed to accept those variable values if they've already been declared, and otherwise fall back to using the Bulma defaults.

  5. Then I opened the "angular-cli.json" file in my project and made sure the "styles" property array included "styles.scss".

That's all I needed to do.  I confirmed that everything worked by adding a few buttons to the home view of my app with Bulma styles:
<p>Bulma test</p>
<a class="button is-primary">Primary</a>
<a class="button is-link">Link</a>
<a class="button is-info">Info</a>
<a class="button is-success">Success</a>
<a class="button is-warning">Warning</a>
<a class="button is-danger">Danger</a>
...and I could see the effect of my change to the $primary color (which is normally a light sea green.



Resources:


Friday, March 30, 2018

Added a generateForm() Method to vadacl

Today I published a new version of my vadacl library that includes a new convenience method:  generateForm().  The generateForm() method accepts a data object and returns a FormGroup with FormControl instances generated from the properties and any vadacl-based validation configurations associated with the data object.

So instead of declaring the FormGroup and FormControl instances manually:

export class CruiseShipComponent extends Vadacl implements OnInit {
  cruiseShip: CruiseShip;
  shipForm: FormGroup;

  ngOnInit() {
    this.cruiseShip = new CruiseShip();
    this.shipForm = new FormGroup({
      'name': new FormControl( this.cruiseShip.name, this.applyRules( this.cruiseShip, 'name' ) ),
      'cruiseLine': new FormControl( this.cruiseShip.cruiseLine, this.applyRules( this.cruiseShip, 'cruiseLine' ) ),
      'yearBuilt': new FormControl( this.cruiseShip.yearBuilt, this.applyRules( this.cruiseShip, 'yearBuilt' ) )
    });
  }

...you can simply invoke generateForm() with the data object:

...
ngOnInit() {
  this.cruiseShip = new CruiseShip();
  this.shipForm = this.generateForm( this.cruiseShip );
}

generateForm() also accepts a second arguments:  a "mods" object that allows you to customize the FormGroup returned by the method.  The possible modification properties are:
  • exclude:  an array of data object properties you don't want to create a FormControl for.
  • only:  an array of the only data object properties you want to create FormControls for (overrides the "exclude" property if you mistakenly use both).
  • rename: an object literal for remapping a data object property name (key) to a different FormControl name (value).
  • validations: an object literal of additional validations to apply to particular data object properties.

An example of using the mods argument:
this.shipForm = this.generateForm( this.cruiseShip, {
  exclude: [ 'yearBuilt' ],
  rename: { cruiseLine: 'company' },
  validations: {
     name: { maxLength: { maxLength: 100, message: 'Ship name cannot be longer than 100 characters' } }
  }
});

The Angular application I maintain on GitHub to demonstrate vadacl has been updated with a demo that uses generateForm(). 

Thursday, December 28, 2017

Angular Update Guide:

Earlier today when I wanted to retest my vadacl library against the latest version of Angular (5.1.2), I skimmed through the Angular blog post regarding the 5.0 release, and at the bottom of the post was a mention of a new upgrade tool:  the Angular Update Guide.



The guide is a Firebase-backed Angular web application styled with Angular Material.  You enter the Angular version you're currently running, the version you want to upgrade to, answer some general questions and press the button.  What you get is a set of brief (at least in my case) instructions for how to perform the upgrade, and what kind of changes you may need to make to your code before and after the update.

I'm glad the Angular team made this tool.  Updating the semver version of the Angular packages in the package.json file is usually simple:  it's knowing what the versions of other dependencies like rxjs or TypeScript need to be in order to make it work that's trickier.  And this takes care of that.

Inadvertent Example of the Benefit of vadacl's Class-Based Validation Rules

While I was verifying that my vadacl validation library wasn't adversely affected ("broken") by any changes in the latest version of Angular (5.1.2), I discovered that one of my validations wasn't working correctly.

In two of the forms in the vadacl demo I have up on GitHub, there is a "Gender" form field where the user is only allowed to enter "M" or "F".  As I was testing the validation on that field, I was surprised to see that while the "F" validation was being enforced, the "M" validation was allowing any word that started with the letter "M".

The validation rule was defined within the UserProfile object class in the demo:  one of the main features of vadacl is that you can define your validations in a class, like so...
validations: { [ index: string ] : PropertyValidations } = {
    firstName: {
      maxLength: { maxLength: 25 },
      required: {}
    },
    lastName: {
      maxLength: { maxLength: 25, message: 'Your last name cannot be longer than 25 characters.'},
      required: { message: 'Your last name is required.' }
    },
    username: {
      maxLength: { maxLength: 30, message: 'Your username cannot be longer than 30 characters.'},
      required: { message: 'You must have a username.' }
    },
    password: {
      minLength: { minLength: 6, message: 'Your password must be at least 6 characters long.' },
      required: { message: 'You must provide a password.' },
    },
    age: {
      pattern: { pattern: '[0-9]*', message: 'Enter your age as an integer.' }
    },
    gender: {
      pattern: { pattern: 'M|F', message: 'Enter your gender as "M" or "F".' }
    }
};

...and then utilize them in your components. The rule invoked the Pattern Validator method using the following pattern:

M|F

I thought that the either/or pipe would limit the valid value to either "M" or "F", but apparently not, and by virtue of being in front of the pipe the male validation allowed for characters beyond the "M".  So I fixed the validation by changing the pattern to:

[MF]{1}

If this same issue had come up in a different application that used normal Angular reactive form validation, I would have had to correct the pattern in the form controls in both of the components, rather than just once.

 

Thursday, September 14, 2017

New Version of vadacl Released as an NPM Package

It's been a LONG time since my last blog post.  Work and life simply kept me from doing a lot of personal coding.  But I'm making an effort to get back into the swing of things, and I decided to start that effort by moving forward with my vadacl Angular validation library.

For anyone not familiar with what vadacl is about, here's the current synopsis:

vadacl is a library that extends and enhances reactive form validation in Angular 4.x. It provides:

  • A mechanism for declaring validation logic within domain classes / data objects that can be reused in multiple components.
  • The ability to configure the text of validation failure messages as part of the domain class validation logic or within a global validation message object.
  • Helper methods for triggering validation and displaying validation results.
  • Additional validation methods beyond those provided by Angular 4.x, and the ability to extend the vadacl validation methods with custom methods in your project.
  • The new version of vadacl (currently version 1.0.10) is available as an NPM package at:

    https://www.npmjs.com/package/vadacl

    There is also a separate GitHub repo containing an Angular CLI-powered Angular 4.x app that demonstrates the use of vadacl in different scenarios:

    https://github.com/bcswartz/vadacl-demo

    As part of the transition to an NPM package, vadacl was refactored to allow you to extend or override the validation methods and messages without touching the library files themselves, using files specific to your project.  That will let you update to future versions of vadacl via npm without losing any of your custom code.  The new version was written for Angular 4.x and includes vadacl versions of the newest methods in Angular's Validators class:  min, max, and email.

    The new demonstration application is an updated version of the one still hosted on the old vadacl GitHub repo, and contains not only examples of the new validation methods, but also provides documentation about how to go about extending vadacl and an example of how you can swap out the global messages file with a different one (designed for a different audience or different language) during your build/deployment process.