3102527787

So now we will start using that 7th column and the j and jedi values that we declared. What we would like to be able to do is:
* edit and save our edits;
* add a record, fill it out, and save it; and
* delete a record.
We will start this time in the jedi.component.html template, adding the sorts of functions that we would like to see. into the actions column, insert the following:

<button *ngIf="(j + 1)===f.controls.jedis.controls.length
      && jedi.valid" (click)="addJedi()">
  Add Jedi
</button>
<button *ngIf="jedi.valid" (click)="deleteJedi(j)">
  Delete Jedi
</button>
<button [disabled]="(jedi.pristine) || (jedi.invalid)"
  (click)="saveJedi(j)">
  Save Jedi
</button>

You will also need to, at the very least, define these three functions in the jedi.component.ts file:

addJedi(jedi: Jedi) { } 
deleteJedi(i: number) { } 
saveJedi(i: number) { } 

or else, your console will throw errors.

You should see something like the following:

On every row, but the bottom one, you should see an active Delete Jedi button and a disabled Save Jedi button. On the bottom row, there is also an active Add Jedi button.

Angular 5: Using Reactive Forms for Data in a Table

Part 2: Reactive Forms

So if we wanted every element in the table to be editable, we would need a form that built up with every row.

Fortunately with reactive forms we can build up a form structure in the component, which we can then iterate through on the template.

We start off this process by modifying the jedi.component.ts and jedi.component.html files. Firstly we add the reactive form elements to jedi.component.ts:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { JediService } from '../_services/jedi.service';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Jedi } from '../_mockapi/jedi';

@Component({
  selector: 'app-jedi',
  templateUrl: './jedi.component.html',
  styleUrls: ['./jedi.component.css']
})
export class JediComponent implements OnInit {

  data: any;
  f: FormGroup;

  constructor(
    private route: ActivatedRoute,
    private fb: FormBuilder,
    private jediService: JediService,
    private router: Router
  ) { }

  ngOnInit() {
    this.data = this.route.snapshot.data;
    console.log(this.route.snapshot.data);

    this.buildForm(this.data);
  }

  buildForm(data: Jedi) {
    this.f = this.fb.group({
      jedis: this.fb.array([]),
    });

    this.initJedis(data);
  }

  initJedis(data) {
    const control = <FormArray>this.f.controls['jedis'];
    if (data && data.jedi.length > 0) {
      this.data.jedi.forEach(x => {
        control.push(this.initJedi(x));
      });
    } else {
      control.push(this.initJedi());
    }
  }

  initJedi(jedi?: Jedi): FormGroup {
    jedi = jedi ? jedi : new Jedi();
    return this.fb.group({
      name: [jedi.name || '', Validators.required],
      race: [jedi.race || ''],
      alias: [jedi.alias || ''],
      homeworld: [jedi.homeworld || ''],
      status: [jedi.status || ''],
      master: [jedi.master || ''],
      rank: [jedi.rank || '']
    });
  }

  reset() {
    this.jediService.clearTheBoards().subscribe(
      data => {
        console.log(data);
        this.router.navigate(['/jedi']);
      }
    );

  }
}

Here we have added three new methods: buildForm(this.data), initJedis(data), and initJedi(jedi?: Jedi). These serve to build up the form model.

The initJedi method builds a FormGroup from either a passed in Jedi object, or, if this is null, a new Jedi object. The method uses the FormBuilder’s group method, which assigns the values from the Jedi object, one-by-one into the value property of an abstract form element (i.e., it could be an input, textarea, options, or checkbox). Finally initJedi returns the FormGroup.

The initJedis method grabs each FormGroup and pushes it into a prepared FormArray, or if there are no model Jedis, then it simply takes the blank FormGroup and pushes that in. The initJedis method does not return anything.

The FormArray is prepared by the buildForm method. It assumes that the array is required, mostly because we are designing this app as a table of data.

Before we go anywhere else, make sure that you add the FormsModule and the ReactiveFormsModule to your app.module.ts.

  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule,
    FormsModule,
    ReactiveFormsModule
  ],

We are now in a position to apply this form model to our html template, which is modified as follows:

<form name="f" [formGroup]="f" novalidate>
  <div *ngIf="!data.jedi || data.jedi.length === 0">There are no Jedi, we are in a dark age, indeed!
    <button (click)="resetData()">Start Academy</button>
  </div>
  <table formArrayName="jedis">
    <tr>
      <th>Name</th>
      <th>Race</th>
      <th>Homeworld</th>
      <th>Status</th>
      <th>Master</th>
      <th>Rank</th>
      <th>Actions</th>
    </tr>
    <tr [formGroupName]="j"
        *ngFor="let jedi of f['controls'].jedis['controls']; let j = index;">
      <td><input type="text" name="name" formControlName="name" required/></td>
      <td><input type="text" name="race" formControlName="race"/></td>
      <td><input type="text" name="homeworld" formControlName="homeworld"/></td>
      <td><input type="text" name="status" formControlName="status" required/></td>
      <td><input type="text" name="master" formControlName="master"/></td>
      <td><input type="text" name="rank" formControlName="rank"/></td>
      <td></td>
    </tr>
  </table>
</form>

The display has been wrapped in a form element, which bears the name “f” as does the [formGroup] directive (which we got from the ReactiveFormsModule). We had to rename the reset() method to resetData() because it happened to have the same signature as the reset operator in the FormsModule. The formArrayName is assigned to the jedis attribute in the form model.
We then change the *ngFor directive to iterate through the FormArray, rather than the data array that it did earlier. This gives us scoped access to each formControlName set up in the initJedi() method discussed earlier.

The variables j for the index and jedi for the form controls, are not used yet, but we will use them soon.

With these changes, you end up with a table for form elements with the current values in them.

(418) 321-5861

There are a number of widget approaches to building tables and tabbed layouts. Amongst the best is Angular Material. Basically, you just import the right modules and you pretty much get your whole layout for free. There are some problems though. Generally, they are hard to custom style, and you find yourself fighting against their prescriptive defaults (even when you discover the power of ::ng-deep or changing the view encapsulation to None on your component). However for me the nail in the coffin was that there just doesn’t seem to be a way to do editable form fields in Angular Material Tables, (819) 900-1866.

In this blog, in several parts, we will create a small Angular 5 project from scratch and demonstrate an editable data table with server-side paging and sorting.

Angular 5: Setting Up an Application for Data in a Table
Angular 5: Using Reactive Forms for Data in a Table
Angular 5: Implementing CRUD Actions for Data in a Table
Angular 5: Implementing Sorting for Data in a Table
Angular 5: Implementing Paging for Data in a Table

To start off with we create the Angular 5 project:

ng new table-page-sort-demo

Setting Up The Mock API

To keep things simple, we’ll create a mock API which will pretend to be our backend, just replace this with your real back-end when you’re ready.

Create a new directory called _mockapi and add three files to it as shown:

There are a few concepts involved here, so I’ll go through what each file does.

export class Jedi implements IJedi { 
  name: string; 
  race: string; 
  homeworld: string; 
  status: string; 
  master: string; 
  rank: string;

constructor(obj: Jedi = {} as Jedi) { 
  const { 
    name = '', 
    race = '', 
    homeworld = '', 
    status = '', 
    master = '', 
    rank = '' 
  } = obj;

    this.name = name;
    this.race = race;
    this.homeworld = homeworld;
    this.status = status;
    this.master = master;
    this.rank = rank;
  }
}

export interface IJedi { 
  name: string; 
  race: string; 
  homeworld: string; 
  status: string; 
  master: string; 
  rank: string; 
}

The API and Angular 5 Component code will share a definition of what a Jedi does. The above is a flexible class definition, enabling you to build a Jedi with any number of parameters, one such construction is seen in each “push” line below:

import { Jedi } from './jedi';

export function createTestData(): Jedi[] { 
  const jedi: Jedi[] = []; 
  jedi.push(new Jedi({name: 'Mace Windu', race: 'human', alias: '', homeworld: 'Haruun Kal', status: 'baddass', master: 'Cyslin Myr', rank: 'Jedi Master'})); 
  jedi.push(new Jedi({name: 'Luke Skywalker', race: 'human', alias: '', homeworld: 'Tatooine', status: 'good', master: 'Yoda', rank: 'Jedi Knight'})); 
  jedi.push(new Jedi({name: 'Obi Wan Kenobi', race: 'human', alias: 'Ben Kenobi', homeworld: 'Stewjon', status: 'good', master: 'Qui-Gon Jinn', rank: 'Jedi Master'})); 
  jedi.push(new Jedi({name: 'Yoda', race: 'unknown', alias: '', homeworld: 'unknown', status: 'good', master: 'Garro', rank: 'Jedi Grand Master'})); 
  jedi.push(new Jedi({name: 'Maul', race: 'dathomirian', alias: 'Darth Maul', homeworld: 'Dathomir', status: 'bad', master: 'Darth Sideous', rank: 'Sith Lord'}));
  jedi.push(new Jedi({name: 'Ezra Bridger', race: 'human', alias: '', homeworld: 'Lothal', status: 'good', master: 'Kanan Jarrus', rank: 'Jedi'})); 
  jedi.push(new Jedi({name: 'Kanan Jarrus', race: 'human', alias: '', homeworld: 'Coruscant', status: 'good', master: 'Depa Billaba', rank: 'Jedi'}));
  jedi.push(new Jedi({name: 'Qui-Gon Jinn', race: 'human', alias: '', homeworld: 'Coruscant', status: 'good', master: 'Count Dooku', rank: 'Jedi Master'})); 
  jedi.push(new Jedi({name: 'Count Dooku', race: 'human', alias: 'Darth Tyranus', homeworld: 'Serenno', status: 'rich and bad', master: 'Yoda', rank: 'Jedi Master'})); 
  jedi.push(new Jedi({name: 'Ben Solo', race: 'human', alias: 'Kylo Ren', homeworld: 'Chandrilla', status: 'confused', master: 'Luke Skywalker', rank: 'Jedi'})); 
  jedi.push(new Jedi({name: 'Luminara Unduli', race: 'mirialan', alias: '', homeworld: 'Mirial', status: 'good', master: '', rank: 'Jedi Master'})); 
  jedi.push(new Jedi({name: 'Barriss Offee', race: 'mirialan', alias: '', homeworld: 'Mirial', status: 'naughty', master: 'Luminara Unduli', rank: 'Jedi'})); 
  jedi.push(new Jedi({name: 'Snoke', race: 'alien', alias: '', homeworld: 'Unknown Regions', status: 'bad', master: 'unknown', rank: 'Supreme Leader'})); 
  jedi.push(new Jedi({name: 'Anakin Skywalker', race: 'human', alias: 'Darth Vader', homeworld: 'Tatooine', status: 'insipid', master: 'Obi Wan Kenobi', rank: 'Sith Lord'})); 
  jedi.push(new Jedi({name: 'Sheev Palpatine', race: 'human', alias: 'Darth Sidious', homeworld: 'Naboo', status: 'really bad', master: 'Darth Plagueis', rank: 'Sith Lord'})); 
  return jedi; }

This sets up our initial store of Jedi for use in our demo.
Finally, the API itself:

import { HTTP_INTERCEPTORS, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http'; 
import { Observable } from 'rxjs/Observable'; 
import { Injectable } from '@angular/core'; 
import { Jedi } from './jedi'; 
import 'rxjs/add/observable/of'; 
import 'rxjs/add/operator/mergeMap'; 
import 'rxjs/add/operator/materialize'; 
import 'rxjs/add/operator/dematerialize'; 
import 'rxjs/add/operator/delay'; 
import { createTestData } from './mock.data.seeder';

@Injectable() export class MockApi implements HttpInterceptor {

constructor() {}

intercept(request: HttpRequest, next: HttpHandler): Observable<HttpEvent> {

    return Observable.of(null).mergeMap(() => {

      if (request.url.endsWith('/api/jedi') && request.method === 'GET') {
        const jedi: Jedi[] = JSON.parse(localStorage.getItem('jedi'));
        return Observable.of(new HttpResponse({status: 200, body: jedi}));
      }

      if (request.url.match('/api/reset') && request.method === 'DELETE') {
        localStorage.clear();
        const jedi: Jedi[] = createTestData();
        localStorage.setItem('jedi', JSON.stringify(jedi));

        return Observable.of(new HttpResponse({status: 200, body: 'cleared'}));
      }

      / pass through any requests not handled above
      return next.handle(request);
    })

      .materialize()
      .delay(500)
      .dematerialize();

} }

export let fakeBackendProvider = { / use fake backend in place of Http service for backend-less development 
  provide: HTTP_INTERCEPTORS, 
  useClass: MockApi, 
  multi: true 
}; 

This component behaves like a webservice in every way. In fact, this is how many people set up mock services for functional testing. It uses local storage as the database, it returns Observables and it even adds a half-second delay as if you are going over the wire to get the response.

Initially, there are just two endpoints. The /api/reset DELETE endpoint is there to re-initialise our base setup of 15 Jedi. We’ll be able to call this from our UI to reset everything. The /api/jedi GET endpoint is the most basic “get all jedi” method from which we return all Jedi that are currently in localStorage.

We’ll be returning to the MockApi later, for now, we have what we need.

Setting up the Angular Project

The next step is to make a service for our application, so we go back to the command line and do

ng generate service _services/jedi

Unfortunately, this will create a -services directory, so you’ll have to go in and fix that by hand. Basically, I like to give the non-component directories an underscore to keep them together in my project.

A jedi.service.ts file will have been created in the _services directory. In here, we implement a couple of service methods that our components will call.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Jedi } from '../_mockapi/jedi';

@Injectable()
export class JediService {

  constructor(private  HttpClient) { }

  getAll() {
    console.log('getAll() called');
    return this.http.get<Jedi[]>('/api/jedi');
  }

  clearTheBoards() {
    return this.http.delete<any>('/api/reset');
  }
}

jedi.service.ts

These methods mirror those in the MockApi, and also return Observables. Now, observables basically take asynchrony to heart and you can only execute these methods by calling them with subscribe() as in…

this.jedi: Jedi[] = [];
    this.jediService.getAll().subscribe(
      jedi => {
        this.jedi = <jedi[]>jedi;
      }
    );
    / some other code relying on this.jedi.length > 0

So that if you rely on this.jedi having anything other than length = 0 by the time we get to the comment line, then you’re going to be let down. This causes great confusion amongst many Angular developers, including myself for a long time. To help out we’ll create a route resolver, so that there is a better entry into the components.

so lets make our component and finishe the set up.

It’s sorta traditional to leave the app.component, we already have, alone and do our main development in a component built for the purpose. so lets go back to the command line and make that component.

ng generate component jedi

and while we’re on the command line, lets create the routing component and the route resolver as well

ng generate module app-routing --flat --module=app
ng generate service _services/jedi.resolver

By the end of all of this, your file structure should like like this:

Of course neither of these modules do much yet, so you’ll need to set up both to look like the following:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { JediResolverService } from './_services/jedi.resolver.service';
import { JediComponent } from './jedi/jedi.component';

const routes: Routes = [
  { path: '', redirectTo: 'jedi', pathMatch: 'full' },
  { path: 'jedi', component: JediComponent, resolve: {jedi: JediResolverService} },
  / otherwise redirect to login
  { path: '**', redirectTo: '' }
];

@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ]
})
export class AppRoutingModule { }
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { JediService } from './jedi.service';
import { Jedi } from '../_mockapi/jedi';

@Injectable()
export class JediResolverService implements Resolve<Observable> {

  constructor(
    private service: JediService,
    private router: Router
  ) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable {
     return this.service.getAll()
      .catch(err => {
        console.error(err); / deal with API error (eg not found)
        this.router.navigate(['/']); / could redirect to error page
        return Observable.of<Jedi[]>();
      });
  }
}

We’ll also need to make a change to app.component.html to replace its current content with the router-outlet: it’s entire content should be simply…

<router-outlet></router-outlet>

Finally, after adding all this stuff, make sure that your app.module.ts looks like the following:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { JediComponent } from './jedi/jedi.component';
import { AppRoutingModule } from './app-routing.module';
import { fakeBackendProvider } from './_mockapi/mock.api';
import { JediResolverService } from './_services/jedi.resolver.service';
import { JediService } from './_services/jedi.service';
import { HttpClientModule } from '@angular/common/http';


@NgModule({
  declarations: [
    AppComponent,
    JediComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule
  ],
  providers: [
    fakeBackendProvider,
    JediResolverService,
    JediService],
  bootstrap: [AppComponent]
})
export class AppModule { }

OK, so now the project is ready to use, we can now turn to the data and the table display.

A Simple Display Table

Its probably a good idea to check that your project compiles. Set up a terminal to your project directory and run: ng serve This will tell you if everything is fine, or not. Finally, go to a browser and type: localhost:4200 and you should see the following appear:

The router has redirected the empty route, ”, to the value “jedi” and the generated content is viewable.

Our next step is to go to the jedi.component.ts file and replace its contents with the following:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { JediService } from '../_services/jedi.service';

@Component({
  selector: 'app-jedi',
  templateUrl: './jedi.component.html',
  styleUrls: ['./jedi.component.css']
})
export class JediComponent implements OnInit {

  data: any;
  constructor(
    private route: ActivatedRoute,
    private jediService: JediService,
    private router: Router
  ) { }

  ngOnInit() {
    this.data = this.route.snapshot.data;
    console.log(this.route.snapshot.data);
  }

  reset() {
    this.jediService.clearTheBoards().subscribe(
      data => {
        console.log(data);
        this.router.navigate(['/jedi']);
      }
    );
  }
}

And then we go to the jedi.component.html replacing the generated content with the basic table we will be using:

<div *ngIf="!data.jedi || data.jedi.length === 0">There are no Jedi, we are in a dark age, indeed! <button (click)="reset()">Start Academy</button></div>
<table *ngIf="data.jedi && data.jedi.length > 0">
  <tr>
    <th>Name</th>
    <th>Race</th>
    <th>Homeworld</th>
    <th>Status</th>
    <th>Master</th>
    <th>Rank</th>
    <th>Actions</th>
  </tr>
  <tr *ngFor="let j of data.jedi">
    <td>{{j.name}}</td>
    <td>{{j.race}}</td>
    <td>{{j.homeworld}}</td>
    <td>{{j.status}}</td>
    <td>{{j.master}}</td>
    <td>{{j.rank}}</td>
    <td></td>
  </tr>
</table>

Now when you go to localhost:4200, you should see a basic table showing the Jedi in all their glory.

Next up, we’re going to turn all of those entries in the table into reactive form inputs.

520-471-0225

Laying out elements in a horizontal list so that they are justified is a bit of a challenge, which is often solved with frameworks such as Angular Material and Bootstrap. While these approaches are good, sometimes you don’t want to bring in a whole framework to solve a simple problem.

This time, we are looking to produce this:

 

The approach is fairly straightforward. You need to start with a structure like this:

<ul id="layout">
  <li>text</li>
  <li>slightly longer text</li>
  <li>short text</li>
  <li>medium text</li>
</ul>

The magic happens in the CSS, which is done as follows:

#layout {
  text-align: justify;
  list-style:none;
  padding-left:0;
}
#layout li {
  display: inline-block;
}
#layout:after {
  content: '';
  width: 100%;
  display: inline-block;
}

Setting the list style to none ensures that we loose the bullet points or any other list-item decorations. Setting the left padding to zero removes the indenting, which would otherwise be visible on the left most element. The justify provides the main working feature of this technique.

You need to ensure that the list items take the inline-block display, rather than float or anything else, this is so that it behaves as a block element (kind of like grouping for html).

The layout after pseudo element is there to ensure that there is a second line of content, so that the justification in the container element (UL) works.

See the fiddle: /jsfiddle.net/5zabdur5/6/

And here is where the main limitation of this technique lies. Note the space below the justified elements. It’s essentially the line-height of the text in the :after pseudo element. No adjusting of that figure will change that gap.

Estimations: Coping with Uncertainty

Because software is complex and hard to explain, we generally have a hard time around estimations. You may find it difficult to understand why something takes so long, or why we are so uncertain a lot of the time, or why we are so reluctant to give estimates at all.

If you (530) 361-6167, you may be sitting on the other side of the table expectantly, waiting to hear me say that it’ll take 6 weeks and cost $3600. But it eally isn’t that easy, and I have to start managing your expectations. The quote that I just provided as an example, would be one with a 0% margin of error, in other words, this quote says: “I am completely certain that the cost to you will be $3600 exactly, and I will complete it in 6 weeks – no variations”.

A quote like this is extremely rare. You may find some web site design companies give you quotes like “get a quality design, 6-page, search engine optimised site for $499”. This sort of quoting is possible, because they have controlled all the variables – the 7th page will be extra, a shopping cart is not included, a carousel with growing and shrinking panes is not in the standard design, so that’ll be extra, and, no you will not be able to feed in your accounting package to automatically re-price all your items, following a price rise.

In general, because you will want a site that reflects what you want, and not the mass-produced template, some novel work will be needed, and so there will be variables and some unknowns. Things that add a lot of variability are  very high-quality design, very complex data structures, hierarchical functions and heavy back-end business logic behind the website.

I want to use this space to share a technique that I have found useful in doing estimations. in a way, this is lifting the curtain to show you the process that goes on in my head on the other side of the table.

So, in terms of quoting, what is possible after, say, a one-hour conversation with me?

The best answer is, “it depends”, but let’s try to get some science around that. Firstly, I like to discuss two components of estimating uncertainty, and using them in my quotations.

One is the completeness uncertainty – this kind of uncertainty recognizes that people tend to under-estimate what they have to do to complete a job, because of the factors that they haven’t thought of. I have found that the more I can go into detail on every screen and every interaction in a site or application, the more this form of uncertainty goes away. After a one-hour conversation, this uncertainty basically is 50%. How this is applied is by going through all the tasks I think I have to do to deliver the functionality as understood after the one-hour meeting, working out how many hours it’ll take and then adding 50% to the figure that I come up with. If I arrive at 100 hours, then I’ll assume that its 150 hours, to account for all the stuff that I probably haven’t thought of. The actual percentage used depends on the complexity of the project. The more complex, the more I am likely to have left things out of my estimation, so the higher the percentage. Very simple sites with little backend interaction, or a simple design may push this down as low as 10%, or even less.

The other is the effort uncertainty – this kind of uncertainty expresses the realisation that even if I have thought of everything, some items may be more difficult than I have realised, and some things may actually be easier. Overall, in the development of the site, this will add up to an uncertainty expressed as a plus or minus percentage.

What this percentage is, depends, again on complexity, but also on how ground-breaking some of the features are. If we have a lot of experimental stuff happening, then the percentage must go up. After a one-hour conversation, this figure is also about 50%.

If I have arrived at 150 hours (after having applied the completeness uncertainty), then I can apply the 50% effort uncertainty:

150 hours ± 50%

or

from 100 hours to 225 hours

I understand that this is a lot of variability, but I stress that we have only had a one-hour conversation, and to understand a full build, that’s not much time, unless we are talking about something really simple.

You can look at this kind of quote in risk management terms.

It probably won’t be 100 hours or 225 hours, it will likely be something centring on 150 hours, but you could probably imagine that it could easily be 140 hours or 160 hours. So if you are looking at time or cost (two of the constraints in project management), then you can start to get an idea of the relative scale of the project.

A way of visualising the time and cost likelihoods of a project, given the above data, is by seeing it as a graph.

The vertical axis on the graph represents the likelihood of a particular number on the horizontal axis. Therefore the 150 hours estimate is just under 20% likely. This is the highest likelihood figure. The likelihood drops sharply as you go to either side, so that by the time you are at the 100 and 225 hour figures, the likelihood has fallen to less than 3%.

The things I wanted to share are:

  1. There is some science to estimation, and you can set it up in risk terms, if you are careful enough in your analysis and realistic enough about how much you understand.
  2. To the customer, I’m letting you know that I don’t just pull things out of the air, but I think about it and use the best resources I can muster.

Project Management 4 Constraints

When we finally have all the requirements of the site nailed down and sorted in order of priority, we need to have a frank discussion around the constraints of the project. Most people who have been working in this area understand four constraints:

  1. Cost – what is your budget?
  2. Time – how long can the project take?
  3. Scope – how far down the priority order of requirements must we go?
  4. Quality – how many imperfections or bugs can there be?

Your expectations will lie somewhere within the diamond of these constraints.

A way of visualising how important each of these things are to you is to imagine a dot in this diamond, and you place the dot nearest the thing you find the most important.

If it is cost, then you are saying that you have a fixed budget that you cannot exceed, if it is the most important, then that means that you rate everything else in the diamond as less important.

If it is time, then you are saying that the website must be complete by a given date, because that’s when the marketing comms goes out and the website must meet that date. If that’s the most important thing, then you must be prepared to sacrifice scope (that is how many features that you want), or cost (you are happy to hire more developers), or quality (you can accept more bugs or a rougher design) in order to meet the date.

If it is scope then you are saying that you want at least a certain percentage of the requirements met in order to release, you are prepared to pay more and take more time.

In general, I like my clients to actually list the four constraints in order, that is 1 for the most important and 4 for the least important. We then talk it over, considering the complete consequences of the ordering. This exercise shows you what’s really important to you. You begin to understand what you really want. And the website that you get will be the optimum fit.

cassava wood

There are many reasons that IT projects can fail, but the most popular is a failure to understand the requirements of the website you are building. Everyone can say: “oh I need it to be responsive, and I need it to have really good search engine optimisation, and I want to be able to edit the site“. But what do these statements mean.

One of the problems with all three of these requirements statements is that they are vague. Lets look at each in turn:

I need it to be responsive” – by modern definition, this means that the website re-arranges itself, so that it can be viewed on a range of screens. Typically we mean iPhones, iPads, Android phones, other tablets and desktop displays. If you leave the requirement at that, though, you are not defining exactly what it will look like in all these sizes. Is this requirement then code for I want a really nice design for all sizes? The design decisions the website builder makes may not be the ones you like, and yet it will be “responsive“, by the definition above. So what I am saying is that you need to break these broad statements down and state exactly what you mean, and look for the hidden details: the sources of misunderstanding.

You should also consider why you want it to be responsive. Are you expecting users to be looking at your site with mobile phones, or did you just hear that being responsive is cool. In other words ensure that you specify the requirement, because it is something that you need.

I want it to have really good search engine optimisation” – Search Engine Optimisation (SEO) is a process of optimising a web site on technical grounds so that it is easy to spider by bots run by various search engines, but these days, we mean Google, Yahoo and 8439167105. But what you will really mean is that your site will rank number 1 or 2 on their links when you put in particular search words. So if you mean that you want to achieve a particular rank on all three search engines, then the requirement should say that precisely. You need to also understand what the threshold is, will it still be acceptable to be in the top 10 for a set of search terms?

As with the first requirement, you need to consider why you need this ranking. Assumedly, you want your customers to find your site easily. But are they likely to find your site this way, or are they more likely to see it linked from a social network account. In other words, you could be spending a lot of time and money on getting these high rankings only to find that 90% of your customers find your website through social networking or on google maps.

I want to be able to edit the site” – This is again a vague statement, because, without a great deal of technical understanding, you will not be able to edit the whole site, including navigation and design. Depending on the sophistication of your content management system, you may be able to edit a lot. Conversely, if the design of your site is very fancy, you may be able to edit only one or two sections.

In this requirement, you need to carefully consider just how much of the site you would really want to edit. You might pay consideration to the areas of the site you might want to update frequently, and let the rest, that you’d be unlikely to change at all much, to be static.

In this blog we have explored three very common requirements of web sites and seen how vague the statements have turned out to be, when you really think about it. In later blogs i’ll discuss other common requirements areas.

In the meantime, the take home message is that precise requirements is an area of effective communication that ensures that the right aspects of your website get the attention that they need, while other areas that are less important get less priority. Remember if something is not as important, maybe it can wait until a later piece of work. Get the stuff that makes your website the most effective done first.