Challenge Overview
IMPORTANT NOTE:
We found out that members from certain countries are restricted from working on this challenge, due to the client’s company policies. Challenge submissions cannot be accepted from members registered in a country with a score of 37 or less according to Transparency International's Corruption Perception Index 2015.
If your country is highlighted in red in the following sheet your submission WILL NOT be accepted, unfortunately. This is NOT a new Topcoder policy or something that will become a normal part of future challenges. This is an isolated policy and requirement for this specific client and their challenges. Thank you for your understanding.
https://drive.google.com/file/d/0B9lpcmurgZ35WlkyWUV2OW9IRjQ/view
Welcome to the Document Management Angular 2.0 Component Challenge!
The goal of this challenge is to implement the angular 2.0 component outlined below.
As part of this challenge you will need to document all HTML5/CSS3/JavaScript code. We need clear explanation of the code to help us figure all the HTML5/CSS3/JavaScript code functions and make it easier for future developers and the client to understand what you have built.
IMPORTANT!
- This application will eventually use a customised version of Bootstrap 3. Exact visual interpretation is less important than a functionally correct and elegant Angular component(s) and other code.
- Asking questions early and getting Copilot or PM's feedback is very important for the success of this challenge.
Source Code
https://gitlab.com/anemoi-document-management/anemoi-document-mangement-angular
Access to the repo is provided in challenge forums.
Challenge Overview
The main task of this competition is to develop a AngularJS 2 component based on the outlined story description outlined below. The component must work properly in all required browsers.
Page Requirements
The component should consider responsive design; however the application will be primarily used on a desktop and hence focus should be on implementing the best solution possible for higher screen sizes.
Challenge Description
Story Description
As a technically-proficient user, I would like to be able to construct my own Lucene queries in the application, to be able to search documents meeting detailed criteria. My existing search capability is limited - specifying queries by set form fields is insufficient for my requirements. Two examples of Lucene I want to specify are below:
(tlp?red OR tlp?amber OR tlp?green OR tlp?white OR fs-isac?red OR fs-isac?amber OR fs-isac?yellow OR fs-isac?green OR fs-isac?white) AND NOT labels:tlp*
text:(term1 term2 –term3 -"phrase term") AND title:(–term7)
A query builder is NOT required. I am comfortable with Lucene syntax. However I want to be able to validate my Lucene query prior to saving it, so I do not cause adverse effect on the system.
Story Wireframe
Functional Criteria
- A modal is shown when clicking on the button.
- The modal provides a text area for entering a Lucene query.
- The modal has three buttons:
- Cancel
- Validate and Run
- Save
- On clicking Cancel, the modal closes / hides.
- On clicking Validate and Run, the content in the text area is validated (according to Lucene core syntax – bonus - external REST call to list of schema fields). If the query is valid, the number of current results is displayed as an indicator.
- You can use https://github.com/mahnunchik/lucene-query-parser or any other open source parser.
- On clicking Save, the constructed query is passed to a stub service function, and the modal closes / hides.
- The user will be informed if:
- On clicking Save, the query is not saved (non-200 response code);
- The user clicks Cancel when content is in the text area.
- Bonus functionality:
- Auto-highlighting of syntax errors (as with Microsoft Office) on change of text area content.
- Autocomplete of commands (use Lucene core syntax and external REST call to list of schema fields).
Angular Setup provided
- Custom cut-down AngularJS 2 application with gulp workflow, Karma / Jasmine / Istanbul unit testing and coverage.
- Basic route / page and a plain modal component.
- A stub service function to call for getting the current results count, and adding the query.
Development Criteria
- Meet criteria specified in General Coding Requirements and Coding Standards at the end of this document.
- One (or more) component(s) created in the ‘apponents’ folder, using (extending) the modal component.
…app/apponents/<component-name>/
<component-name>.component.ts
<component-name>.component.spec.ts
<component-name>.component.html
<component-name>.component.styl
- Main controller file in Typescript.
- Spec file for unit testing (must meet 100% code coverage from Istanbul).
- Html file for the template.
- Style file for any styling required.
- Any required external libraries or assets placed in the assets folder, amending SystemJS, index.html or other relevant files.
- No external libraries (e.g. for Lucene query parsing) have been recommended – you are free to choose the most appropriate for this task (although follow the guidelines for use of non-licensed software, and use good judgement on the most appropriate library to use – e.g. based on support, popularity, author, compatibility, etc.).
- Error handling for responses from the service (REST) call, e.g. non-200 error response codes.
General Coding Requirements
All components (whether generic or route) will be in their own directory, containing html, ts, styl and spec files (see RULE 3 in Coding Standards).
.html file - template
- Provide comments on the page elements to give clear explanation of the code usage. The goal is to help future developers understand the code.
- Please use clean INDENTATION for all HTML code so future developers can follow the code.
- All HTML code naming should not have any conflicts or errors.
- Element and Attribute names should be in lowercase and use a "-" to separate multiple-word classes (e.g. "main-content")
- Use semantically correct tags- use H tags for headers, etc. Use strong and em tags instead of bold and italic tags.
- No inline CSS styles- all styles must be placed in the external css (Stylus) stylesheet.
- Validate your code - reviewers may accept minor validation errors, but please comment your reason for any validation errors. Use the validators listed in the scorecard.
.styl file - styling
- We use Stylus, which is transpiled to CSS within our gulp workflow.
- We also use a modified version of Bootstrap 3 for styling. Do not be too concerned about pixel perfect representation – this will be done by a separate User Experience team.
- Use CSS3 Media Queries to load different styles for each page and don't build different page for different device/layout.
- Provide comments on the CSS code. We need CSS comments to give a clear explanation of the code usage. The goal is to help future developers understand the code.
- Please use clean INDENTATION for all CSS so developers can follow the code.
- All CSS naming should not have any conflicts.
- As possible use CSS3 style when create all styling.
- You MUST use Stylus to make the CSS code clean, provide source files on your submission.
- Use CSS to space out objects, not clear/transparent images (GIFs or PNGs) and use proper structural CSS to lay out your page. Only use table tags for tables of data/information and not for page layout.
.ts file - Typescript
- We use TypeScript for our components / coding in AngularJS 2.
- A full list of coding standards is shown at the end of this document.
.spec file – Unit testing
- The spec file must provide full (100%) coverage as reported by Istanbul.
External Files – including Javascript libraries
- All external libraries must be loaded according to the instructions set out in DEVNOTES.md.
- All JavaScript, images or other must not have a copyright by a third party. You are encouraged to use your own scripts, or scripts that are free, publicly available and do not have copyright statements or author recognition requirements anywhere in the code.
Desktop Browsers Requirements
Windows & Mac OS
- Mandatory:
- IE11
- Chrome (version 50+)
- Optional:
- IE10
- Safari (version 7)
- Firefox (version 32)
Development Framework Requirements
- Bootstrap (version 3)
- AngularJS 2.0
Final Submission Guidelines
Below is an overview of the deliverables:
- - Fully Implemented functionality defined by the requirements.
- A complete and detailed deployment document explaining how to deploy the application including configuration information.
- UNIT Tests to verify your solution successfully meets the requirements of the challenge.
Final Submission
For each member, the final submission should be uploaded via the challenge detail page on topcoder.com.
Coding Standards:
RULE 1 - Listen to Paps
Where possible, adhere to https://angular.io/docs/ts/latest/guide/style-guide.html
If in any doubt of standards to follow, this style guide should provide guidance (unless a rule defined herein overrides it).
The following rules within this guide are mandatory:
- STYLE 03-01: Use upper camel case when naming classes;
- STYLE 03-03: Use upper camel case when naming interfaces;
- STYLE 03-04: Use lower camel case to name properties and methods;
- STYLE 05-12: Do use @Input and @Output instead of inputs and outputs properties.
RULE 2 - Organise imports alphabetically
import {addProviders, async, ComponentFixture, inject, TestComponentBuilder} from '@angular/core/testing';
import {HTTP_PROVIDERS, Response, ResponseOptions, XHRBackend} from '@angular/http';
import {MockBackend, MockConnection} from '@angular/http/testing';
import {By} from '@angular/platform-browser';
import {ActivatedRoute} from '@angular/router';
import {persons} from '../../../data/persons';
import {MockActivatedRoute} from '../../../mocks/mock-activated-route';
import {Route1DetailComponent} from './route1-detail.component';
import {PersonService} from '../../../services/person-service';
Order imports alphabetically by the string '@angular/core/testing' and ignore ../ when ordering.
Also order within the curlies {addProviders, async, ComponentFixture, inject, TestComponentBuilder}
RULE 3 - Follow our 5 tiered component structure
Given the following routing ..
export const routes:RouterConfig = [
{
path: 'route1',
component: Route1Component,
children: [
{path: ':id', component: Route1DetailComponent},
{path: '', component: Route1ListComponent}
]
},
{path: 'route2', component: Route2Component}
];
We require the following component folder structure.
- routes
- route1
- route1.component.ts|html|styl|spec (parent)
- detail
- route1-detail.component.ts|html|styl|spec (child)
- partial1
- route1-detail-partial1.component.ts|html|styl|spec (partial)
- partial2
- route1-detail-partial2.component.ts|html|styl|spec (partial)
- list
- route1-list.component.ts|html|styl|spec (child)
- route2
- route2.component.ts|html|styl|spec (parent)
We also employ the following component folder structure in respect of reusable components.
- apponents
- apponent1
- apponent1.component.ts|html|styl|spec (apponent)
- partial1
- apponent1-partial1.component.ts|html|styl|spec (partial)
- partial2
- apponent1-partial2.component.ts|html|styl|spec (partial)
- apponent2
- apponent2.component.ts|html|styl|spec (apponent)
- components
- my-component1
- my-component1.component.ts|html|styl|spec (reusable)
- partial1
- my-component1-partial1.component.ts|html|styl|spec (partial)
- partial2
- my-component1-partial2.component.ts|html|styl|spec (partial)
- my-component2
- my-component2.component.ts|html|styl|spec (reusable)
This structure reveals a 5 tiered component approach.
- parent - a non-reusable parent routing component.
- child - a non-reusable child routing component. These components are always nested within their parent.
- apponent - a component that offers application level reuse.
- reusable - a component that offers total reuse. Total reuse is defined as the level of reuse which would permit a component to form part of a public library.
- partial - a component that comprises part of another component. Used for organisational purposes. These components are always nested within their owning component.
RULE 4 - Data requests must be made in child components
Child components must make ALL data requests (eg HTTP requests) with 3 limited exceptions.
- A parent component may make a data request if it has no children.
- A partial component owned by a child component may make a data request.
- An apponent or reusable component should normally receive data as Angular2 @Input - this approach makes the component generic thereby extending reuse cases. There could however be a use case for making data requests here. For instance, consider a weather component. This may make a call to a public weather API and render the results accordingly. The API call here closely maps to the fundamental purpose of the component. A proposal for making a data request from an apponent or reusable component should be referred to project lead. One must consider whether public APIs are accessible in the production environment.
RULE 5 - Use RxJS Observables NOT promises
These support streaming and binding. For instance, you can use RxJS Observables to bind to a URL param.
When the URL param changes, the relevant parts of the view are updated without the need to completely recompile. This affords great speed rewards.
RxJS Observables are harder to get your head around but are worth the pain. An RxJS Observable can be thought of as a stream of promises. The .subscribe() method (equivalent to a promises .then() method) is potentially called many times, every time an event trickles down the stream.
export class Route1DetailComponent implements OnInit {
private DEFAULT_ID:number = 1;
private person:Person;
constructor(public route:ActivatedRoute, private personService:PersonService) {
}
ngOnInit() {
let paramsStream = this.route.params
.map((params) => parseInt(params['id'], 10))
.map((id) => !isNaN(id) ? id : this.DEFAULT_ID);
// We use flatMap instead of map to prevent this stream being a metastream - i.e. stream of streams
let responseStream = paramsStream.flatMap((id) => {
let personStream = this.personService.getPerson(id);
return personStream;
}).publish().refCount();
responseStream.subscribe((person:Person) => {
console.log(person);
this.person = person;
},
(err) => console.error('oops', err)
);
}
}
RxJS is reactive programming. Reactive programming means reacting to events emitted from streams.
To understand RxJS you must understand streams. A stream is a sequence just like an array. The one difference is that arrays are stored in memory. Streams are time based.
Please read this RxJS tutorial
FROM the heading Reactive programming is programming with asynchronous data streams
TO the text that says Since this feels so familiar already
In particular all developers must be conversant with ASCII stream diagrams from reading this tutorial.
Now discussing the above code ..
An RxJS Observable is a stream and has a .subscribe() method (just like promises have a .then() method)
Our first stream is paramsStream which is a stream of URL params and looks like ..
` paramsStream ------- {id:10} -------- {id:7} ------ {id:'x'} -------------- {id:20} ------->
We can .map() a stream exactly as we .map() an array. Our two .map() calls give ..
` .map() ------- 10 ------------- 7 ----------- NaN ------------------- 20 ------------>
` .map() ------- 10 ------------- 7 ----------- 1 --------------------- 20 ------------>
We now want to use the ID numbers being emitted from this stream to make API calls using personService.getPerson(id)
The problem is that personService.getPerson(id) returns a stream. Do we want responseStream to be a metastream viz a stream of streams? NO
If responseStream was a metastream it would emit streams but we want it to emit API responses.
To overcome this issue we simply use .flatMap() instead of .map()
Assuming that there is no person with ID 7 being served up by our API, we end up with ..
responseStream ------- person10 ------- undefined --- person1 --------------- person20 ------>
We can .subscribe() to any of our streams with ..
stream.subscribe(successFunc, errorFunc);
Compare that with promises.
promise.then(successFunc, errorFunc);
No wonder we liken RxJS Observables to streaming promises!
As a team we will refer to RxJS Observables as streams.
Please use RxJS wherever possible and try to think with a reactive mindset.
RULE 6 - Skinny controller, phat model
Controller code should be minimal and high level. A developer should be able to glance at controller code and know what it is doing immediately.
Data requests and heavy data processing should take place in an Angular2 service.
RULE 7 - Model transform
A models folder provides classes for converting, for example, API responses to TypeScript objects.
An overarching class should be used to store JSON responses. That class should then provide methods for returning refined / massaged results.
The preferred TypeScript approach here is use of the Interface Segregation Principle and Type Casting
Don't forget John Papa and his Rule of One!
RULE 8 - Use trackBy for iterators
When using *ngFor if you do not use trackBy then any changes to the list will cause all list DOM items to be reloaded. trackBy introduces intelligence, allowing Angular2 to only update those DOM elements bound to updated list items.
<div *ngFor="let person of persons; trackBy:myTrackingFunction"> // essential practice
<div *ngFor="let person of persons"> // extremely inefficient
For more see https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ngfortrackby
RULE 9 - Naming conventions
class names use .. PascalCase AKA UpperCamelCase
function names use .. camelCase
filenames use .. spinal-case AKA kebab-case in conjunction with Papa John's feature.type.ext
- my-component1-partial1.component.ts|html|styl|spec
- my-http.service.ts
These naming conventions can AND WILL BE enforced by the TypeScript compiler!
RULE 10 - Template and Styles
Components can have an inline or external template and / or styles.
External is preferred but inline may be used where there are 3 or less lines of HTML or CSS.
Inline CSS must be pure CSS, inline Stylus is not supported.
@Component({
template: `<section class="greeting">hello</section>`,
styles: [`section.greeting { color:red }`]
})
External is de facto. External stylesheets may be written in Stylus. The styleUrls property must reference the .css extension.
@Component({
templateUrl: 'route1-detail.component.html',
styleUrls: ['route1-detail.component.css']
})
The /templates folder provides two template components, one using an external template, the other uses inline.
Copy and paste these as appropriate when creating a new component.
RULE 11 - Unit testing
All components must have a .spec.ts file containing unit tests.
RULE 13 - NEVER import 'rxjs/Rx'
Importing 'rxjs/Rx' imports the entire RxJS library, which is massive, and kills performance.
A developer must never do this. Rather import only the RxJS operators you need with ..?
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/publish';
RULE 14 - Understand providers and make them available at the right hierarchical level
In Angular1 each service provider provides a singleton instance.
In Angular2 each service provider provides a singleton instance BUT there can be numerous service providers at different hierarchical levels for the same service.
The following diagrams depict this.
Here the parent component provides a singleton instance to itself and all descendant components.
All components share the same singleton instance.
Here the parent component, as before, provides a singleton instance.
However, child 2 component provides a different singleton instance to itself and its descendants.
Different components have access to a different singleton instance.
Because of this behavior within Angular2 we require that
- Providers for stateless services be defined at any hierarchical level.
- Providers for stateful services be defined in the top level parent component only.
- Stateful services be avoided wherever possible. Please consult a team lead before developing a stateful service.
RULE 15 - Minimal constructor logic use ngOnInit
Angular2 docs advise to put minimal logic in the constructor.
export class MyClass implements OnInit {
constructor(private myApiService:MyApiService) {
}
ngOnInit() {
// Do initialisation here
// For example, API calls needed to render the page via MyApiService
}
}
This, as well as complying with Angular2 advice, gives us more control when unit testing since we can call ngOnInit() when we want.
RULE 16 - Component to component comms
Where two components exist on the same page they should communicate via a service. The service should expose an Rx.Subject() to facilitate comms.