├── .bowerrc
├── .gitignore
├── .jscsrc
├── .vscode
└── settings.json
├── README.md
├── appveyor.yml
├── assets
└── css
│ └── styles.css
├── bower.json
├── favicon.ico
├── gulp.config.js
├── gulpfile.js
├── index.html
├── licence
├── package.json
├── src
├── app
│ ├── app.component.html
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── app.routes.ts
│ ├── home
│ │ ├── home.component.html
│ │ └── home.component.ts
│ ├── main.ts
│ ├── rxjs-operators.ts
│ ├── schedules
│ │ ├── schedule-edit.component.html
│ │ ├── schedule-edit.component.ts
│ │ ├── schedule-list.component.html
│ │ └── schedule-list.component.ts
│ ├── shared
│ │ ├── directives
│ │ │ ├── highlight.directive.ts
│ │ │ └── mobile-hide.directive.ts
│ │ ├── interfaces.ts
│ │ ├── pipes
│ │ │ └── date-format.pipe.ts
│ │ ├── services
│ │ │ └── data.service.ts
│ │ └── utils
│ │ │ ├── config.service.ts
│ │ │ ├── items.service.ts
│ │ │ ├── lower-case-url-serializer.ts
│ │ │ ├── mapping.service.ts
│ │ │ └── notification.service.ts
│ └── users
│ │ ├── user-card.component.html
│ │ ├── user-card.component.ts
│ │ ├── user-list.component.html
│ │ └── user-list.component.ts
└── server
│ ├── index.js
│ └── package.json
├── systemjs.config.js
└── tsconfig.json
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | src/app/**/*.js
2 | src/app/**/*.map
3 | node_modules
4 | bower_components
5 | typings
6 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "excludeFiles": [
3 | "node_modules/**",
4 | "bower_components/**",
5 | ".jscsrc",
6 | "build/js/*.js"],
7 |
8 | "requireCurlyBraces": [
9 | "if",
10 | "else",
11 | "for",
12 | "while",
13 | "do",
14 | "try",
15 | "catch"
16 | ],
17 | "requireOperatorBeforeLineBreak": true,
18 | "requireCamelCaseOrUpperCaseIdentifiers": true,
19 | "maximumLineLength": {
20 | "value": 100,
21 | "allowComments": true,
22 | "allowRegex": true
23 | },
24 | "validateIndentation": 4,
25 | "validateQuoteMarks": "'",
26 |
27 | "disallowMultipleLineStrings": true,
28 | "disallowMixedSpacesAndTabs": true,
29 | "disallowTrailingWhitespace": true,
30 | "disallowSpaceAfterPrefixUnaryOperators": true,
31 | "disallowMultipleVarDecl": null,
32 |
33 | "requireSpaceAfterKeywords": [
34 | "if",
35 | "else",
36 | "for",
37 | "while",
38 | "do",
39 | "switch",
40 | "return",
41 | "try",
42 | "catch"
43 | ],
44 | "requireSpaceBeforeBinaryOperators": [
45 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
46 | "&=", "|=", "^=", "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
47 | "|", "^", "&&", "||", "===", "==", ">=",
48 | "<=", "<", ">", "!=", "!=="
49 | ],
50 | "requireSpaceAfterBinaryOperators": true,
51 | "requireSpacesInConditionalExpression": true,
52 | "requireSpaceBeforeBlockStatements": true,
53 | "requireLineFeedAtFileEnd": true,
54 | "disallowSpacesInsideObjectBrackets": false,
55 | "disallowSpacesInsideArrayBrackets": "all",
56 | "disallowSpacesInsideParentheses": true,
57 |
58 | "jsDoc": {
59 | "checkParamNames": true,
60 | "requireParamTypes": true
61 | },
62 |
63 | "disallowMultipleLineBreaks": true,
64 |
65 | "disallowCommaBeforeLineBreak": null,
66 | "disallowDanglingUnderscores": null,
67 | "disallowEmptyBlocks": null,
68 | "disallowTrailingComma": null,
69 | "requireCommaBeforeLineBreak": null,
70 | "requireDotNotation": null,
71 | "requireMultipleVarDecl": null,
72 | "requireParenthesesAroundIIFE": true
73 | }
74 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "**/.git": true,
5 | "**/.DS_Store": true,
6 | "**/app/**/*.js": true,
7 | "**/*.map": true
8 | },
9 | "typescript.tsdk": "node_modules/typescript/lib"
10 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular CRUD ops, Modals, Animations, Pagination, DateTimePicker, Directives and much more..
2 | [](https://ci.appveyor.com/project/chsakell/angular2-features/branch/master)
3 |
4 | Read blog post
5 |
6 | Single Page Application built with Angular 4 and TypeScript
7 | Back-end API built with ASP.NET Core
8 |
9 | It shows how to use several controls such as Modals , DateTimePicker , Pagination in Angular 4 applications.
10 |
11 | Installation Instructions
12 |
13 | Setup the API from here or read the accosiated post
14 | Clone the Scheduler.SPA app and open it in your favorite text editor
15 | Open a command prompt and run the following commands
16 |
17 | npm install
18 | bower install
19 |
20 |
21 | Start the API and set the _apiURI inside the utils/config.service.ts to point it
22 | Start the SPA by typing npm start
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Microsoft Azure Deployment
31 | Learn how to deploy an Angular 4 app on Microsoft Azure, here .
32 | Donations
33 | For being part of open source projects and documenting my work here and on chsakell's blog I really do not charge anything. I try to avoid any type of ads also.
34 |
35 | If you think that any information you obtained here is worth of some money and are willing to pay for it, feel free to send any amount through paypal.
36 |
37 |
38 | Paypal
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Follow chsakell's Blog
49 |
50 |
51 |
52 | Facebook
53 | Twitter
54 |
55 |
56 |
57 |
58 | Microsoft Web Application Development
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | License
73 | Code released under the MIT license .
74 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # Test against the latest version of this Node.js version
2 | environment:
3 | nodejs_version: "6"
4 |
5 | # Install scripts. (runs after repo cloning)
6 | install:
7 | # Get the latest stable version of Node.js or io.js
8 | - ps: Install-Product node $env:nodejs_version
9 | # install modules
10 | - npm install
11 |
12 | # Don't actually build.
13 | build: off
14 |
--------------------------------------------------------------------------------
/assets/css/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 70px; /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */
3 | background-color: whitesmoke;
4 | }
5 |
6 | .hero-spacer {
7 | margin-top: 50px;
8 | }
9 |
10 | .hero-feature {
11 | margin-bottom: 30px;
12 | }
13 |
14 | footer {
15 | margin: 50px 0;
16 | }
17 |
18 | .img-avatar {
19 | height: 150px;
20 | width: 150px;
21 | }
22 |
23 | .img-small {
24 | height: 50px;
25 | width: 50px;
26 | }
27 |
28 | .panel-heading h3 {
29 | white-space: nowrap;
30 | overflow: hidden;
31 | text-overflow: ellipsis;
32 | line-height: normal;
33 | width: 75%;
34 | padding-top: 8px;
35 | }
36 |
37 | .navbar-inverse {
38 | background-color: #4765a0;
39 | border-color: whitesmoke;
40 | border: 2px solid whitesmoke;
41 | }
42 |
43 | .shadowCard {
44 | box-shadow: 10px 10px 5px #888888;
45 | }
46 |
47 | .ng-valid[required] {
48 | border-left: 5px solid #42A948; /* green */
49 | }
50 |
51 | .ng-invalid:not(form) {
52 | border-left: 5px solid #a94442; /* red */
53 | }
54 |
55 | .table>tbody>tr>td {
56 | vertical-align: middle;
57 | }
58 |
59 | .fa {
60 | padding-right: 5px;
61 | }
62 |
63 | .navbar-fixed-bottom {
64 | background-color: #1d2140;
65 | color: whitesmoke;
66 | }
67 |
68 | .carousel-indicators .active {
69 | background: #31708f;
70 | }
71 | .content {
72 | margin-top: 20px;
73 | }
74 | .adjust1 {
75 | float: left;
76 | width: 100%;
77 | margin-bottom: 0;
78 | }
79 | .adjust2 {
80 | margin: 0;
81 | }
82 | .carousel-indicators li {
83 | border: 1px solid #ccc;
84 | }
85 | .carousel-control {
86 | color: #31708f;
87 | width: 5%;
88 | }
89 | .carousel-control:hover,
90 | .carousel-control:focus {
91 | color: #31708f;
92 | }
93 | .carousel-control.left,
94 | .carousel-control.right {
95 | background-image: none;
96 | }
97 | .media-object {
98 | margin: auto;
99 | margin-top: 15%;
100 | }
101 | @media screen and (max-width: 768px) {
102 | .media-object {
103 | margin-top: 0;
104 | }
105 | }
106 |
107 | .loader {
108 | border: 16px solid #f3f3f3;
109 | border-radius: 50%;
110 | border-top: 16px solid #3498db;
111 | width: 120px;
112 | height: 120px;
113 | -webkit-animation: spin 2s linear infinite;
114 | animation: spin 2s linear infinite;
115 | margin: auto;
116 | position: absolute;
117 | top: 0; left: 0; bottom: 0; right: 0;
118 | }
119 |
120 | @-webkit-keyframes spin {
121 | 0% { -webkit-transform: rotate(0deg); }
122 | 100% { -webkit-transform: rotate(360deg); }
123 | }
124 |
125 | @keyframes spin {
126 | 0% { transform: rotate(0deg); }
127 | 100% { transform: rotate(360deg); }
128 | }
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scheduler.spa",
3 | "private": true,
4 | "dependencies": {
5 | "alertify.js" : "0.3.11",
6 | "bootstrap": "^3.3.6",
7 | "font-awesome": "latest"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chsakell/angular2-features/69d4819e973ed8053e3fd8831ae015e19c72d4cf/favicon.ico
--------------------------------------------------------------------------------
/gulp.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | var base = 'wwwroot/';
3 |
4 | var config = {
5 | base: base,
6 | compiledTs: 'app/**/*.js',
7 | dist: 'dist',
8 | index: base + 'index.html',
9 | ngTemplates: 'app/**/*.html',
10 | sourceMaps: 'app/**/*.map',
11 | tsFiles: 'app/**/*.ts',
12 | tsConfig: 'tsconfig.json',
13 | vendors: base + 'vendors'
14 | };
15 |
16 | return config;
17 | };
18 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var gulp = require("gulp");
3 | var del = require("del");
4 | var replace = require("gulp-replace");
5 | var sourcemaps = require('gulp-sourcemaps');
6 |
7 | /**
8 | * Remove build directory.
9 | */
10 | gulp.task('clean', function (cb) {
11 | return del(["build"], cb);
12 | });
13 |
14 | /**
15 | * Copy all resources that are not TypeScript files into build directory.
16 | */
17 | gulp.task("resources", ["server", "app", "assets","systemjs", "bower"], function () {
18 | console.log("Building resources...");
19 | });
20 | /* copy the app core files to the build folder */
21 | gulp.task("app", ['index'], function(){
22 | return gulp.src(["src/app/**", "!src/app/**/*.ts"])
23 | .pipe(gulp.dest("build/app"));
24 | });
25 | /* get the index file to the root of the build */
26 | gulp.task("index", function(){
27 | return gulp.src(["index.html"])
28 | .pipe(replace('node_modules', 'lib'))
29 | .pipe(gulp.dest("build"));
30 | });
31 | /* copy node server to build folder */
32 | gulp.task("server", function () {
33 | return gulp.src(["index.js", "package.json"], { cwd: "src/server/**" })
34 | .pipe(gulp.dest("build"));
35 | });
36 | /* styles and other assets */
37 | gulp.task("assets", function(){
38 | return gulp.src(["assets/**/*"], { "base" : "." })
39 | .pipe(gulp.dest("build"));
40 | });
41 |
42 | /* styles and other assets */
43 | gulp.task("systemjs", function(){
44 | return gulp.src(["systemjs.config.js"])
45 | .pipe(replace('node_modules', 'lib'))
46 | .pipe(replace('src/app', 'app'))
47 | .pipe(gulp.dest("build"));
48 | });
49 |
50 | gulp.task("bower", function(){
51 | return gulp.src(["bower.json"])
52 | .pipe(gulp.dest("build"));
53 | });
54 |
55 | /**
56 | * Copy all required libraries into build directory.
57 | */
58 | gulp.task("libs", function () {
59 | var lib = "build/lib/";
60 |
61 | gulp.src('node_modules/' + "@angular/**/*.js",
62 | { base: 'node_modules/' + "@angular/" })
63 | .pipe(gulp.dest(lib + "@angular/"));
64 |
65 | gulp.src('node_modules/' + "angular2-in-memory-web-api/*.js",
66 | { base: 'node_modules/' })
67 | .pipe(gulp.dest(lib));
68 |
69 | gulp.src('node_modules/' + "core-js/client/shim*.js",
70 | { base: 'node_modules/' })
71 | .pipe(gulp.dest(lib));
72 |
73 | gulp.src('node_modules/' + "zone.js/dist/zone*.js",
74 | { base: 'node_modules/' })
75 | .pipe(gulp.dest(lib));
76 |
77 | gulp.src('node_modules/' + "reflect-metadata/Reflect*.js",
78 | { base: 'node_modules/' })
79 | .pipe(gulp.dest(lib));
80 |
81 | gulp.src('node_modules/' + "systemjs/dist/*.js",
82 | { base: 'node_modules/' })
83 | .pipe(gulp.dest(lib));
84 |
85 | gulp.src('node_modules/' + "rxjs/**/*.js",
86 | { base: 'node_modules/' })
87 | .pipe(gulp.dest(lib));
88 |
89 | gulp.src('node_modules/' + "bootstrap/dist/**/*",
90 | { base: 'node_modules/' })
91 | .pipe(gulp.dest(lib));
92 |
93 | gulp.src('node_modules/' + "ngx-bootstrap/**/*",
94 | { base: 'node_modules/' })
95 | .pipe(gulp.dest(lib));
96 |
97 | gulp.src('node_modules/' + "ng2-slim-loading-bar/**/*",
98 | { base: 'node_modules/' })
99 | .pipe(gulp.dest(lib));
100 |
101 | gulp.src('node_modules/' + "lodash/**/*",
102 | { base: 'node_modules/' })
103 | .pipe(gulp.dest(lib));
104 |
105 | gulp.src('node_modules/' + "moment/**/*",
106 | { base: 'node_modules/' })
107 | .pipe(gulp.dest(lib));
108 | });
109 | /**
110 | * Build the project.
111 | */
112 | gulp.task("default", ['resources', 'libs'], function () {
113 | console.log("Building the project ...");
114 | });
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Scheduler
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/licence:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Christos Sakellarios
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "name": "scheduler",
4 | "author": "Chris Sakellarios",
5 | "license": "MIT",
6 | "repository": "https://github.com/chsakell/angular2-features",
7 | "private": true,
8 | "dependencies": {
9 | "@angular/animations": "4.0.2",
10 | "@angular/common": "4.0.2",
11 | "@angular/compiler": "4.0.2",
12 | "@angular/core": "4.0.2",
13 | "@angular/forms": "4.0.2",
14 | "@angular/http": "4.0.2",
15 | "@angular/platform-browser": "4.0.2",
16 | "@angular/platform-browser-dynamic": "4.0.2",
17 | "@angular/router": "4.0.2",
18 | "angular-in-memory-web-api": "~0.2.4",
19 | "bootstrap": "^3.3.6",
20 | "core-js": "^2.4.1",
21 | "jquery": "^3.0.0",
22 | "lodash": "^4.17.4",
23 | "@types/lodash": "^4.14.68",
24 | "moment": "^2.13.0",
25 | "ngx-bootstrap": "^1.7.1",
26 | "ng2-slim-loading-bar": "1.5.1",
27 | "reflect-metadata": "^0.1.8",
28 | "rxjs": "5.0.1",
29 | "systemjs": "0.19.40",
30 | "zone.js": "^0.8.5"
31 | },
32 | "devDependencies": {
33 | "concurrently": "^3.0.0",
34 | "gulp": "3.9.1",
35 | "gulp-replace": "0.5.4",
36 | "del": "2.2.2",
37 | "gulp-sourcemaps": "2.2.0",
38 | "lite-server": "^2.2.0",
39 | "typescript": "2.2.2",
40 | "typings": "^1.3.2",
41 | "tslint": "^3.10.2",
42 | "@types/node": "~6.0.60"
43 | },
44 | "scripts": {
45 | "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite --baseDir ./app --port 8000\" ",
46 | "lite": "lite-server",
47 | "tsc": "tsc",
48 | "tsc:w": "tsc -w",
49 | "typings": "typings"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | chsakell's Blog
66 | Anything around ASP.NET MVC,Web API, WCF, Entity Framework & Angular
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewContainerRef } from '@angular/core';
2 |
3 | // Add the RxJS Observable operators we need in this app.
4 | import './rxjs-operators';
5 |
6 | @Component({
7 | moduleId: module.id,
8 | selector: 'scheduler',
9 | templateUrl: 'app.component.html'
10 | })
11 | export class AppComponent {
12 |
13 | constructor(private viewContainerRef: ViewContainerRef) {
14 | // You need this small hack in order to catch application root view container ref
15 | this.viewContainerRef = viewContainerRef;
16 | }
17 | }
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import './rxjs-operators';
2 |
3 | import { NgModule } from '@angular/core';
4 | import { BrowserModule } from '@angular/platform-browser';
5 | import { FormsModule } from '@angular/forms';
6 | import { HttpModule } from '@angular/http';
7 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
8 |
9 | import { PaginationModule } from 'ngx-bootstrap';
10 | import { DatepickerModule } from 'ngx-bootstrap';
11 | import { ModalModule } from 'ngx-bootstrap';
12 | import { ProgressbarModule } from 'ngx-bootstrap';
13 | import { SlimLoadingBarService, SlimLoadingBarComponent } from 'ng2-slim-loading-bar';
14 | import { TimepickerModule } from 'ngx-bootstrap';
15 |
16 | import { AppComponent } from './app.component';
17 | import { DateFormatPipe } from './shared/pipes/date-format.pipe';
18 | import { HighlightDirective } from './shared/directives/highlight.directive';
19 | import { HomeComponent } from './home/home.component';
20 | import { MobileHideDirective } from './shared/directives/mobile-hide.directive';
21 | import { ScheduleEditComponent } from './schedules/schedule-edit.component';
22 | import { ScheduleListComponent } from './schedules/schedule-list.component';
23 | import { UserCardComponent } from './users/user-card.component';
24 | import { UserListComponent } from './users/user-list.component';
25 | import { routing } from './app.routes';
26 |
27 | import { DataService } from './shared/services/data.service';
28 | import { ConfigService } from './shared/utils/config.service';
29 | import { ItemsService } from './shared/utils/items.service';
30 | import { MappingService } from './shared/utils/mapping.service';
31 | import { NotificationService } from './shared/utils/notification.service';
32 | import { UrlSerializer } from '@angular/router';
33 | import { LowerCaseUrlSerializer } from './shared/utils/lower-case-url-serializer';
34 |
35 | @NgModule({
36 | imports: [
37 | BrowserModule,
38 | BrowserAnimationsModule,
39 | DatepickerModule.forRoot(),
40 | FormsModule,
41 | HttpModule,
42 | ModalModule.forRoot(),
43 | ProgressbarModule.forRoot(),
44 | PaginationModule.forRoot(),
45 | routing,
46 | TimepickerModule.forRoot()
47 | ],
48 | declarations: [
49 | AppComponent,
50 | DateFormatPipe,
51 | HighlightDirective,
52 | HomeComponent,
53 | MobileHideDirective,
54 | ScheduleEditComponent,
55 | ScheduleListComponent,
56 | SlimLoadingBarComponent,
57 | UserCardComponent,
58 | UserListComponent
59 | ],
60 | providers: [
61 | ConfigService,
62 | DataService,
63 | ItemsService,
64 | MappingService,
65 | NotificationService,
66 | SlimLoadingBarService,
67 | {
68 | provide: UrlSerializer,
69 | useClass: LowerCaseUrlSerializer
70 | }
71 | ],
72 | bootstrap: [AppComponent]
73 | })
74 | export class AppModule { }
75 |
--------------------------------------------------------------------------------
/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { HomeComponent } from './home/home.component';
5 | import { UserListComponent } from './users/user-list.component';
6 | import { ScheduleListComponent } from './schedules/schedule-list.component';
7 | import { ScheduleEditComponent } from './schedules/schedule-edit.component';
8 |
9 | const appRoutes: Routes = [
10 | { path: 'users', component: UserListComponent },
11 | { path: 'schedules', component: ScheduleListComponent },
12 | { path: 'schedules/:id/edit', component: ScheduleEditComponent },
13 | { path: '', component: HomeComponent }
14 | ];
15 |
16 | export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);
--------------------------------------------------------------------------------
/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
ASP.NET Core
26 |
ASP.NET Core is a new open-source
27 | and cross-platform framework for building modern cloud based internet connected
28 | applications, such as web apps, IoT apps and mobile backends.
29 |
30 | Microsoft Corp.
https://docs.asp.net/en/latest/
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
Angular 2
50 |
Learn one way to build applications
51 | with Angular and reuse your code and abilities to build apps for any deployment
52 | target. For web, mobile web, native mobile and native desktop.
53 |
54 | Google
https://angular.io/
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
chsakell's Blog
74 |
Anything around ASP.NET MVC,Web
75 | API, WCF, Entity Framework & Angular.
76 |
77 | Chris Sakellarios
https://chsakell.com
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
Latest Features
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
ASP.NET Core
109 |
ASP.NET Core is a significant redesign of ASP.NET.
110 |
111 | More..
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
EF Core
124 |
A cross-platform version of Entity Framework.
125 |
126 | More..
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
Angular
139 |
Angular is a platform for building mobile and desktop web apps.
140 |
141 | More..
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
TypeScript
154 |
A free and open source programming language.
155 |
156 | More..
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
172 |
--------------------------------------------------------------------------------
/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, trigger, state, style, animate, transition } from '@angular/core';
2 |
3 | declare let componentHandler: any;
4 |
5 | @Component({
6 | moduleId: module.id,
7 | templateUrl: 'home.component.html',
8 | animations: [
9 | trigger('flyInOut', [
10 | state('in', style({ opacity: 1, transform: 'translateX(0)' })),
11 | transition('void => *', [
12 | style({
13 | opacity: 0,
14 | transform: 'translateX(-100%)'
15 | }),
16 | animate('0.6s ease-in')
17 | ]),
18 | transition('* => void', [
19 | animate('0.2s 10 ease-out', style({
20 | opacity: 0,
21 | transform: 'translateX(100%)'
22 | }))
23 | ])
24 | ])
25 | ]
26 | })
27 | export class HomeComponent {
28 |
29 | constructor() {
30 |
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/src/app/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 | import { AppModule } from './app.module';
3 |
4 | platformBrowserDynamic().bootstrapModule(AppModule);
--------------------------------------------------------------------------------
/src/app/rxjs-operators.ts:
--------------------------------------------------------------------------------
1 | // Statics
2 | import 'rxjs/add/observable/throw';
3 |
4 | // Operators
5 | import 'rxjs/add/operator/catch';
6 | import 'rxjs/add/operator/debounceTime';
7 | import 'rxjs/add/operator/distinctUntilChanged';
8 | import 'rxjs/add/operator/map';
9 | import 'rxjs/add/operator/switchMap';
10 | import 'rxjs/add/operator/toPromise';
--------------------------------------------------------------------------------
/src/app/schedules/schedule-edit.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/schedules/schedule-edit.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 | import { NgForm } from '@angular/forms';
4 |
5 | import { SlimLoadingBarService } from 'ng2-slim-loading-bar';
6 |
7 | import { DataService } from '../shared/services/data.service';
8 | import { ItemsService } from '../shared/utils/items.service';
9 | import { NotificationService } from '../shared/utils/notification.service';
10 | import { ConfigService } from '../shared/utils/config.service';
11 | import { MappingService } from '../shared/utils/mapping.service';
12 | import { ISchedule, IScheduleDetails, IUser } from '../shared/interfaces';
13 | import { DateFormatPipe } from '../shared/pipes/date-format.pipe';
14 |
15 | @Component({
16 | moduleId: module.id,
17 | selector: 'app-schedule-edit',
18 | templateUrl: 'schedule-edit.component.html'
19 | })
20 | export class ScheduleEditComponent implements OnInit {
21 | apiHost: string;
22 | id: number;
23 | schedule: IScheduleDetails;
24 | scheduleLoaded: boolean = false;
25 | statuses: string[];
26 | types: string[];
27 | private sub: any;
28 |
29 | constructor(private route: ActivatedRoute,
30 | private router: Router,
31 | private dataService: DataService,
32 | private itemsService: ItemsService,
33 | private notificationService: NotificationService,
34 | private configService: ConfigService,
35 | private mappingService: MappingService,
36 | private loadingBarService:SlimLoadingBarService) { }
37 |
38 | ngOnInit() {
39 | // (+) converts string 'id' to a number
40 | this.id = +this.route.snapshot.params['id'];
41 | this.apiHost = this.configService.getApiHost();
42 | this.loadScheduleDetails();
43 | }
44 |
45 | loadScheduleDetails() {
46 | this.loadingBarService.start();
47 | this.dataService.getScheduleDetails(this.id)
48 | .subscribe((schedule: IScheduleDetails) => {
49 | this.schedule = this.itemsService.getSerialized(schedule);
50 | this.scheduleLoaded = true;
51 | // Convert date times to readable format
52 | this.schedule.timeStart = new Date(this.schedule.timeStart.toString()); // new DateFormatPipe().transform(schedule.timeStart, ['local']);
53 | this.schedule.timeEnd = new Date(this.schedule.timeEnd.toString()); //new DateFormatPipe().transform(schedule.timeEnd, ['local']);
54 | this.statuses = this.schedule.statuses;
55 | this.types = this.schedule.types;
56 |
57 | this.loadingBarService.complete();
58 | },
59 | error => {
60 | this.loadingBarService.complete();
61 | this.notificationService.printErrorMessage('Failed to load schedule. ' + error);
62 | });
63 | }
64 |
65 | updateSchedule(editScheduleForm: NgForm) {
66 | console.log(editScheduleForm.value);
67 |
68 | var scheduleMapped = this.mappingService.mapScheduleDetailsToSchedule(this.schedule);
69 |
70 | this.loadingBarService.start();
71 | this.dataService.updateSchedule(scheduleMapped)
72 | .subscribe(() => {
73 | this.notificationService.printSuccessMessage('Schedule has been updated');
74 | this.loadingBarService.complete();
75 | },
76 | error => {
77 | this.loadingBarService.complete();
78 | this.notificationService.printErrorMessage('Failed to update schedule. ' + error);
79 | });
80 | }
81 |
82 | removeAttendee(attendee: IUser) {
83 | this.notificationService.openConfirmationDialog('Are you sure you want to remove '
84 | + attendee.name + ' from this schedule?',
85 | () => {
86 | this.loadingBarService.start();
87 | this.dataService.deleteScheduleAttendee(this.schedule.id, attendee.id)
88 | .subscribe(() => {
89 | this.itemsService.removeItemFromArray(this.schedule.attendees, attendee);
90 | this.notificationService.printSuccessMessage(attendee.name + ' will not attend the schedule.');
91 | this.loadingBarService.complete();
92 | },
93 | error => {
94 | this.loadingBarService.complete();
95 | this.notificationService.printErrorMessage('Failed to remove ' + attendee.name + ' ' + error);
96 | });
97 | });
98 | }
99 |
100 | back() {
101 | this.router.navigate(['/schedules']);
102 | }
103 |
104 | }
--------------------------------------------------------------------------------
/src/app/schedules/schedule-list.component.html:
--------------------------------------------------------------------------------
1 |
2 | Schedules
3 | {{totalItems}}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Title
13 | Creator
14 | Description
15 |
16 | Time Start
17 | Time End
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{schedule.title}}
26 | {{schedule.creator}}
27 | {{schedule.description}}
28 | {{schedule.location}}
29 | {{schedule.timeStart | dateFormat | date:'medium'}}
30 | {{schedule.timeEnd | dateFormat | date:'medium'}}
31 |
32 | Details
33 |
34 | Edit
35 |
36 | Delete
37 |
38 |
39 |
40 |
41 |
42 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/app/schedules/schedule-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild, Input, Output,
2 | trigger,
3 | state,
4 | style,
5 | animate,
6 | transition } from '@angular/core';
7 |
8 | import { ModalDirective } from 'ngx-bootstrap';
9 | import { SlimLoadingBarService } from 'ng2-slim-loading-bar';
10 |
11 | import { DataService } from '../shared/services/data.service';
12 | import { DateFormatPipe } from '../shared/pipes/date-format.pipe';
13 | import { ItemsService } from '../shared/utils/items.service';
14 | import { NotificationService } from '../shared/utils/notification.service';
15 | import { ConfigService } from '../shared/utils/config.service';
16 | import { ISchedule, IScheduleDetails, Pagination, PaginatedResult } from '../shared/interfaces';
17 |
18 | @Component({
19 | moduleId: module.id,
20 | selector: 'app-schedules',
21 | templateUrl: 'schedule-list.component.html',
22 | animations: [
23 | trigger('flyInOut', [
24 | state('in', style({ opacity: 1, transform: 'translateX(0)' })),
25 | transition('void => *', [
26 | style({
27 | opacity: 0,
28 | transform: 'translateX(-100%)'
29 | }),
30 | animate('0.5s ease-in')
31 | ]),
32 | transition('* => void', [
33 | animate('0.2s 10 ease-out', style({
34 | opacity: 0,
35 | transform: 'translateX(100%)'
36 | }))
37 | ])
38 | ])
39 | ]
40 | })
41 | export class ScheduleListComponent implements OnInit {
42 | @ViewChild('childModal') public childModal: ModalDirective;
43 | schedules: ISchedule[];
44 | apiHost: string;
45 |
46 | public itemsPerPage: number = 2;
47 | public totalItems: number = 0;
48 | public currentPage: number = 1;
49 |
50 | // Modal properties
51 | @ViewChild('modal')
52 | modal: any;
53 | items: string[] = ['item1', 'item2', 'item3'];
54 | selected: string;
55 | output: string;
56 | selectedScheduleId: number;
57 | scheduleDetails: IScheduleDetails;
58 | selectedScheduleLoaded: boolean = false;
59 | index: number = 0;
60 | backdropOptions = [true, false, 'static'];
61 | animation: boolean = true;
62 | keyboard: boolean = true;
63 | backdrop: string | boolean = true;
64 |
65 | constructor(
66 | private dataService: DataService,
67 | private itemsService: ItemsService,
68 | private notificationService: NotificationService,
69 | private configService: ConfigService,
70 | private loadingBarService:SlimLoadingBarService) { }
71 |
72 | ngOnInit() {
73 | this.apiHost = this.configService.getApiHost();
74 | this.loadSchedules();
75 | }
76 |
77 | loadSchedules() {
78 | this.loadingBarService.start();
79 |
80 | this.dataService.getSchedules(this.currentPage, this.itemsPerPage)
81 | .subscribe((res: PaginatedResult) => {
82 | this.schedules = res.result;// schedules;
83 | this.totalItems = res.pagination.TotalItems;
84 | this.loadingBarService.complete();
85 | },
86 | error => {
87 | this.loadingBarService.complete();
88 | this.notificationService.printErrorMessage('Failed to load schedules. ' + error);
89 | });
90 | }
91 |
92 | pageChanged(event: any): void {
93 | this.currentPage = event.page;
94 | this.loadSchedules();
95 | //console.log('Page changed to: ' + event.page);
96 | //console.log('Number items per page: ' + event.itemsPerPage);
97 | };
98 |
99 | removeSchedule(schedule: ISchedule) {
100 | this.notificationService.openConfirmationDialog('Are you sure you want to delete this schedule?',
101 | () => {
102 | this.loadingBarService.start();
103 | this.dataService.deleteSchedule(schedule.id)
104 | .subscribe(() => {
105 | this.itemsService.removeItemFromArray(this.schedules, schedule);
106 | this.notificationService.printSuccessMessage(schedule.title + ' has been deleted.');
107 | this.loadingBarService.complete();
108 | },
109 | error => {
110 | this.loadingBarService.complete();
111 | this.notificationService.printErrorMessage('Failed to delete ' + schedule.title + ' ' + error);
112 | });
113 | });
114 | }
115 |
116 | viewScheduleDetails(id: number) {
117 | this.selectedScheduleId = id;
118 |
119 | this.dataService.getScheduleDetails(this.selectedScheduleId)
120 | .subscribe((schedule: IScheduleDetails) => {
121 | this.scheduleDetails = this.itemsService.getSerialized(schedule);
122 | // Convert date times to readable format
123 | this.scheduleDetails.timeStart = new DateFormatPipe().transform(schedule.timeStart, ['local']);
124 | this.scheduleDetails.timeEnd = new DateFormatPipe().transform(schedule.timeEnd, ['local']);
125 | this.loadingBarService.complete();
126 | this.selectedScheduleLoaded = true;
127 | this.childModal.show();//.open('lg');
128 | },
129 | error => {
130 | this.loadingBarService.complete();
131 | this.notificationService.printErrorMessage('Failed to load schedule. ' + error);
132 | });
133 | }
134 |
135 | public hideChildModal(): void {
136 | this.childModal.hide();
137 | }
138 | }
--------------------------------------------------------------------------------
/src/app/shared/directives/highlight.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, HostListener, Input } from '@angular/core';
2 | @Directive({
3 | selector: '[highlight]'
4 | })
5 | export class HighlightDirective {
6 | private _defaultColor = 'beige';
7 | private el: HTMLElement;
8 |
9 | constructor(el: ElementRef) {
10 | this.el = el.nativeElement;
11 | }
12 |
13 | @Input('highlight') highlightColor: string;
14 |
15 | @HostListener('mouseenter') onMouseEnter() {
16 | this.highlight(this.highlightColor || this._defaultColor);
17 | }
18 | @HostListener('mouseleave') onMouseLeave() {
19 | this.highlight(null);
20 | }
21 |
22 | private highlight(color: string) {
23 | this.el.style.backgroundColor = color;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/shared/directives/mobile-hide.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, HostListener, Input } from '@angular/core';
2 | @Directive({
3 | selector: '[mobileHide]',
4 | host: {
5 | '(window:resize)': 'onResize($event)'
6 | }
7 | })
8 | export class MobileHideDirective {
9 | private _defaultMaxWidth: number = 768;
10 | private el: HTMLElement;
11 |
12 | constructor(el: ElementRef) {
13 | this.el = el.nativeElement;
14 | }
15 |
16 | @Input('mobileHide') mobileHide: number;
17 |
18 | onResize(event:Event) {
19 | var window : any = event.target;
20 | var currentWidth = window.innerWidth;
21 | if(currentWidth < (this.mobileHide || this._defaultMaxWidth))
22 | {
23 | this.el.style.display = 'none';
24 | }
25 | else
26 | {
27 | this.el.style.display = 'block';
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/shared/interfaces.ts:
--------------------------------------------------------------------------------
1 | export interface IUser {
2 | id: number;
3 | name: string;
4 | avatar: string;
5 | profession: string;
6 | schedulesCreated: number;
7 | }
8 |
9 | export interface ISchedule {
10 | id: number;
11 | title: string;
12 | description: string;
13 | timeStart: Date;
14 | timeEnd: Date;
15 | location: string;
16 | type: string;
17 | status: string;
18 | dateCreated: Date;
19 | dateUpdated: Date;
20 | creator: string;
21 | creatorId: number;
22 | attendees: number[];
23 | }
24 |
25 | export interface IScheduleDetails {
26 | id: number;
27 | title: string;
28 | description: string;
29 | timeStart: Date;
30 | timeEnd: Date;
31 | location: string;
32 | type: string;
33 | status: string;
34 | dateCreated: Date;
35 | dateUpdated: Date;
36 | creator: string;
37 | creatorId: number;
38 | attendees: IUser[];
39 | statuses: string[];
40 | types: string[];
41 | }
42 |
43 | export interface Pagination {
44 | CurrentPage : number;
45 | ItemsPerPage : number;
46 | TotalItems : number;
47 | TotalPages: number;
48 | }
49 |
50 | export class PaginatedResult {
51 | result : T;
52 | pagination : Pagination;
53 | }
54 |
55 | export interface Predicate {
56 | (item: T): boolean
57 | }
--------------------------------------------------------------------------------
/src/app/shared/pipes/date-format.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({
4 | name: 'dateFormat'
5 | })
6 |
7 | export class DateFormatPipe implements PipeTransform {
8 | transform(value: any, args: any[]): any {
9 |
10 | if (args && args[0] === 'local') {
11 | return new Date(value).toLocaleString();
12 | }
13 | else if (value) {
14 | return new Date(value);
15 | }
16 | return value;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/app/shared/services/data.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Http, Response, Headers } from '@angular/http';
3 | //Grab everything with import 'rxjs/Rx';
4 | import { Observable } from 'rxjs/Observable';
5 | import {Observer} from 'rxjs/Observer';
6 | import 'rxjs/add/operator/map';
7 | import 'rxjs/add/operator/catch';
8 |
9 | import { IUser, ISchedule, IScheduleDetails, Pagination, PaginatedResult } from '../interfaces';
10 | import { ItemsService } from '../utils/items.service';
11 | import { ConfigService } from '../utils/config.service';
12 |
13 | @Injectable()
14 | export class DataService {
15 |
16 | _baseUrl: string = '';
17 |
18 | constructor(private http: Http,
19 | private itemsService: ItemsService,
20 | private configService: ConfigService) {
21 | this._baseUrl = configService.getApiURI();
22 | }
23 |
24 | getUsers(): Observable {
25 | return this.http.get(this._baseUrl + 'users')
26 | .map((res: Response) => {
27 | return res.json();
28 | })
29 | .catch(this.handleError);
30 | }
31 |
32 | getUserSchedules(id: number): Observable {
33 | return this.http.get(this._baseUrl + 'users/' + id + '/schedules')
34 | .map((res: Response) => {
35 | return res.json();
36 | })
37 | .catch(this.handleError);
38 | }
39 |
40 | createUser(user: IUser): Observable {
41 |
42 | let headers = new Headers();
43 | headers.append('Content-Type', 'application/json');
44 |
45 | return this.http.post(this._baseUrl + 'users/', JSON.stringify(user), {
46 | headers: headers
47 | })
48 | .map((res: Response) => {
49 | return res.json();
50 | })
51 | .catch(this.handleError);
52 | }
53 |
54 | updateUser(user: IUser): Observable {
55 |
56 | let headers = new Headers();
57 | headers.append('Content-Type', 'application/json');
58 |
59 | return this.http.put(this._baseUrl + 'users/' + user.id, JSON.stringify(user), {
60 | headers: headers
61 | })
62 | .map((res: Response) => {
63 | return;
64 | })
65 | .catch(this.handleError);
66 | }
67 |
68 | deleteUser(id: number): Observable {
69 | return this.http.delete(this._baseUrl + 'users/' + id)
70 | .map((res: Response) => {
71 | return;
72 | })
73 | .catch(this.handleError);
74 | }
75 | /*
76 | getSchedules(page?: number, itemsPerPage?: number): Observable {
77 | let headers = new Headers();
78 | if (page != null && itemsPerPage != null) {
79 | headers.append('Pagination', page + ',' + itemsPerPage);
80 | }
81 |
82 | return this.http.get(this._baseUrl + 'schedules', {
83 | headers: headers
84 | })
85 | .map((res: Response) => {
86 | return res.json();
87 | })
88 | .catch(this.handleError);
89 | }
90 | */
91 |
92 | getSchedules(page?: number, itemsPerPage?: number): Observable> {
93 | var peginatedResult: PaginatedResult = new PaginatedResult();
94 |
95 | let headers = new Headers();
96 | if (page != null && itemsPerPage != null) {
97 | headers.append('Pagination', page + ',' + itemsPerPage);
98 | }
99 |
100 | return this.http.get(this._baseUrl + 'schedules', {
101 | headers: headers
102 | })
103 | .map((res: Response) => {
104 | console.log(res.headers.keys());
105 | peginatedResult.result = res.json();
106 |
107 | if (res.headers.get("Pagination") != null) {
108 | //var pagination = JSON.parse(res.headers.get("Pagination"));
109 | var paginationHeader: Pagination = this.itemsService.getSerialized(JSON.parse(res.headers.get("Pagination")));
110 | console.log(paginationHeader);
111 | peginatedResult.pagination = paginationHeader;
112 | }
113 | return peginatedResult;
114 | })
115 | .catch(this.handleError);
116 | }
117 |
118 | getSchedule(id: number): Observable {
119 | return this.http.get(this._baseUrl + 'schedules/' + id)
120 | .map((res: Response) => {
121 | return res.json();
122 | })
123 | .catch(this.handleError);
124 | }
125 |
126 | getScheduleDetails(id: number): Observable {
127 | return this.http.get(this._baseUrl + 'schedules/' + id + '/details')
128 | .map((res: Response) => {
129 | return res.json();
130 | })
131 | .catch(this.handleError);
132 | }
133 |
134 | updateSchedule(schedule: ISchedule): Observable {
135 |
136 | let headers = new Headers();
137 | headers.append('Content-Type', 'application/json');
138 |
139 | return this.http.put(this._baseUrl + 'schedules/' + schedule.id, JSON.stringify(schedule), {
140 | headers: headers
141 | })
142 | .map((res: Response) => {
143 | return;
144 | })
145 | .catch(this.handleError);
146 | }
147 |
148 | deleteSchedule(id: number): Observable {
149 | return this.http.delete(this._baseUrl + 'schedules/' + id)
150 | .map((res: Response) => {
151 | return;
152 | })
153 | .catch(this.handleError);
154 | }
155 |
156 | deleteScheduleAttendee(id: number, attendee: number) {
157 |
158 | return this.http.delete(this._baseUrl + 'schedules/' + id + '/removeattendee/' + attendee)
159 | .map((res: Response) => {
160 | return;
161 | })
162 | .catch(this.handleError);
163 | }
164 |
165 | private handleError(error: any) {
166 | var applicationError = error.headers.get('Application-Error');
167 | var serverError = error.json();
168 | var modelStateErrors: string = '';
169 |
170 | if (!serverError.type) {
171 | console.log(serverError);
172 | for (var key in serverError) {
173 | if (serverError[key])
174 | modelStateErrors += serverError[key] + '\n';
175 | }
176 | }
177 |
178 | modelStateErrors = modelStateErrors = '' ? null : modelStateErrors;
179 |
180 | return Observable.throw(applicationError || modelStateErrors || 'Server error');
181 | }
182 | }
--------------------------------------------------------------------------------
/src/app/shared/utils/config.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable()
4 | export class ConfigService {
5 |
6 | _apiURI : string;
7 |
8 | constructor() {
9 | this._apiURI = 'http://localhost:5000/api/';
10 | }
11 |
12 | getApiURI() {
13 | return this._apiURI;
14 | }
15 |
16 | getApiHost() {
17 | return this._apiURI.replace('api/','');
18 | }
19 | }
--------------------------------------------------------------------------------
/src/app/shared/utils/items.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Predicate } from '../interfaces'
3 |
4 | import * as _ from 'lodash';
5 |
6 | @Injectable()
7 | export class ItemsService {
8 |
9 | constructor() { }
10 |
11 | /*
12 | Removes an item from an array using the lodash library
13 | */
14 | removeItemFromArray(array: Array, item: any) {
15 | _.remove(array, function (current) {
16 | //console.log(current);
17 | return JSON.stringify(current) === JSON.stringify(item);
18 | });
19 | }
20 |
21 | removeItems(array: Array, predicate: Predicate) {
22 | _.remove(array, predicate);
23 | }
24 |
25 | /*
26 | Finds a specific item in an array using a predicate and repsaces it
27 | */
28 | setItem(array: Array, predicate: Predicate, item: T) {
29 | var _oldItem = _.find(array, predicate);
30 | if(_oldItem){
31 | var index = _.indexOf(array, _oldItem);
32 | array.splice(index, 1, item);
33 | } else {
34 | array.push(item);
35 | }
36 | }
37 |
38 | /*
39 | Adds an item to zero index
40 | */
41 | addItemToStart(array: Array, item: any) {
42 | array.splice(0, 0, item);
43 | }
44 |
45 | /*
46 | From an array of type T, select all values of type R for property
47 | */
48 | getPropertyValues(array: Array, property : string) : R
49 | {
50 | var result = _.map(array, property);
51 | return result;
52 | }
53 |
54 | /*
55 | Util method to serialize a string to a specific Type
56 | */
57 | getSerialized(arg: any): T {
58 | return JSON.parse(JSON.stringify(arg));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/app/shared/utils/lower-case-url-serializer.ts:
--------------------------------------------------------------------------------
1 | import { DefaultUrlSerializer, UrlTree } from '@angular/router';
2 | // /ref: https://stackoverflow.com/questions/42065409/angular-2-routes-3-0-case-sensitive
3 |
4 | export class LowerCaseUrlSerializer extends DefaultUrlSerializer {
5 | parse(url: string): UrlTree {
6 | return super.parse(url.toLowerCase());
7 | }
8 | }
--------------------------------------------------------------------------------
/src/app/shared/utils/mapping.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { ISchedule, IScheduleDetails, IUser } from '../interfaces';
4 | import { ItemsService } from './items.service'
5 |
6 | @Injectable()
7 | export class MappingService {
8 |
9 | constructor(private itemsService : ItemsService) { }
10 |
11 | mapScheduleDetailsToSchedule(scheduleDetails: IScheduleDetails): ISchedule {
12 | var schedule: ISchedule = {
13 | id: scheduleDetails.id,
14 | title: scheduleDetails.title,
15 | description: scheduleDetails.description,
16 | timeStart: scheduleDetails.timeStart,
17 | timeEnd: scheduleDetails.timeEnd,
18 | location: scheduleDetails.location,
19 | type: scheduleDetails.type,
20 | status: scheduleDetails.status,
21 | dateCreated: scheduleDetails.dateCreated,
22 | dateUpdated: scheduleDetails.dateUpdated,
23 | creator: scheduleDetails.creator,
24 | creatorId: scheduleDetails.creatorId,
25 | attendees: this.itemsService.getPropertyValues(scheduleDetails.attendees, 'id')
26 | }
27 |
28 | return schedule;
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/src/app/shared/utils/notification.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Predicate } from '../interfaces'
3 |
4 | declare var alertify: any;
5 |
6 | @Injectable()
7 | export class NotificationService {
8 | private _notifier: any = alertify;
9 |
10 | constructor() { }
11 |
12 | /*
13 | Opens a confirmation dialog using the alertify.js lib
14 | */
15 | openConfirmationDialog(message: string, okCallback: () => any) {
16 | this._notifier.confirm(message, function (e) {
17 | if (e) {
18 | okCallback();
19 | } else {
20 | }
21 | });
22 | }
23 |
24 | /*
25 | Prints a success message using the alertify.js lib
26 | */
27 | printSuccessMessage(message: string) {
28 |
29 | this._notifier.success(message);
30 | }
31 |
32 | /*
33 | Prints an error message using the alertify.js lib
34 | */
35 | printErrorMessage(message: string) {
36 | this._notifier.error(message);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/users/user-card.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{edittedUser.name}}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{edittedUser.profession}}
14 |
15 |
16 |
17 |
18 |
19 |
20 | Schedules
21 | {{edittedUser.schedulesCreated}}
22 |
23 |
24 |
25 |
26 |
27 |
43 |
44 |
45 |
46 |
47 |
48 |
54 |
55 |
56 |
57 |
58 | Title
59 | Description
60 | Place
61 | Time Start
62 | Time End
63 |
64 |
65 |
66 |
67 | {{schedule.title}}
68 | {{schedule.description}}
69 | {{schedule.location}}
70 | {{schedule.timeStart | dateFormat | date:'medium'}}
71 | {{schedule.timeEnd | dateFormat | date:'medium'}}
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/app/users/user-card.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Output, OnInit, ViewContainerRef, EventEmitter, ViewChild,
2 | trigger,
3 | state,
4 | style,
5 | animate,
6 | transition } from '@angular/core';
7 |
8 | import { IUser, ISchedule } from '../shared/interfaces';
9 | import { DataService } from '../shared/services/data.service';
10 | import { ItemsService } from '../shared/utils/items.service';
11 | import { NotificationService } from '../shared/utils/notification.service';
12 | import { ConfigService } from '../shared/utils/config.service';
13 | import { HighlightDirective } from '../shared/directives/highlight.directive';
14 |
15 | import { ModalDirective } from 'ngx-bootstrap';
16 |
17 | @Component({
18 | moduleId: module.id,
19 | selector: 'user-card',
20 | templateUrl: 'user-card.component.html',
21 | animations: [
22 | trigger('flyInOut', [
23 | state('in', style({ opacity: 1, transform: 'translateX(0)' })),
24 | transition('void => *', [
25 | style({
26 | opacity: 0,
27 | transform: 'translateX(-100%)'
28 | }),
29 | animate('0.5s ease-in')
30 | ]),
31 | transition('* => void', [
32 | animate('0.2s 10 ease-out', style({
33 | opacity: 0,
34 | transform: 'translateX(100%)'
35 | }))
36 | ])
37 | ])
38 | ]
39 | })
40 | export class UserCardComponent implements OnInit {
41 | @ViewChild('childModal') public childModal: ModalDirective;
42 | @Input() user: IUser;
43 | @Output() removeUser = new EventEmitter();
44 | @Output() userCreated = new EventEmitter();
45 |
46 | edittedUser: IUser;
47 | onEdit: boolean = false;
48 | apiHost: string;
49 | // Modal properties
50 | @ViewChild('modal')
51 | modal: any;
52 | items: string[] = ['item1', 'item2', 'item3'];
53 | selected: string;
54 | output: string;
55 | userSchedules: ISchedule[];
56 | userSchedulesLoaded: boolean = false;
57 | index: number = 0;
58 | backdropOptions = [true, false, 'static'];
59 | animation: boolean = true;
60 | keyboard: boolean = true;
61 | backdrop: string | boolean = true;
62 |
63 | constructor(private itemsService: ItemsService,
64 | private notificationService: NotificationService,
65 | private dataService: DataService,
66 | private configService: ConfigService) { }
67 |
68 | ngOnInit() {
69 | this.apiHost = this.configService.getApiHost();
70 | this.edittedUser = this.itemsService.getSerialized(this.user);
71 | if (this.user.id < 0)
72 | this.editUser();
73 | }
74 |
75 | editUser() {
76 | this.onEdit = !this.onEdit;
77 | this.edittedUser = this.itemsService.getSerialized(this.user);
78 | // JSON.parse(JSON.stringify(this.user)); // todo Utils..
79 | }
80 |
81 | createUser() {
82 | //this.slimLoader.start();
83 | this.dataService.createUser(this.edittedUser)
84 | .subscribe((userCreated) => {
85 | this.user = this.itemsService.getSerialized(userCreated);
86 | this.edittedUser = this.itemsService.getSerialized(this.user);
87 | this.onEdit = false;
88 |
89 | this.userCreated.emit({ value: userCreated });
90 | //this.slimLoader.complete();
91 | },
92 | error => {
93 | this.notificationService.printErrorMessage('Failed to created user');
94 | this.notificationService.printErrorMessage(error);
95 | //this.slimLoader.complete();
96 | });
97 | }
98 |
99 | updateUser() {
100 | //this.slimLoader.start();
101 | this.dataService.updateUser(this.edittedUser)
102 | .subscribe(() => {
103 | this.user = this.edittedUser;
104 | this.onEdit = !this.onEdit;
105 | this.notificationService.printSuccessMessage(this.user.name + ' has been updated');
106 | //this.slimLoader.complete();
107 | },
108 | error => {
109 | this.notificationService.printErrorMessage('Failed to edit user');
110 | this.notificationService.printErrorMessage(error);
111 | //this.slimLoader.complete();
112 | });
113 | }
114 |
115 | openRemoveModal() {
116 | this.notificationService.openConfirmationDialog('Are you sure you want to remove '
117 | + this.user.name + '?',
118 | () => {
119 | //this.slimLoader.start();
120 | this.dataService.deleteUser(this.user.id)
121 | .subscribe(
122 | res => {
123 | this.removeUser.emit({
124 | value: this.user
125 | });
126 | //this.slimLoader.complete();
127 | //this.slimLoader.complete();
128 | }, error => {
129 | this.notificationService.printErrorMessage(error);
130 | //this.slimLoader.complete();
131 | })
132 | });
133 | }
134 |
135 | viewSchedules(user: IUser) {
136 | console.log(user);
137 | this.dataService.getUserSchedules(this.edittedUser.id)
138 | .subscribe((schedules: ISchedule[]) => {
139 | this.userSchedules = schedules;
140 | console.log(this.userSchedules);
141 | this.userSchedulesLoaded = true;
142 | this.childModal.show();
143 | //this.slimLoader.complete();
144 | },
145 | error => {
146 | //this.slimLoader.complete();
147 | this.notificationService.printErrorMessage('Failed to load users. ' + error);
148 | });
149 |
150 | }
151 |
152 | public hideChildModal(): void {
153 | this.childModal.hide();
154 | }
155 |
156 | opened() {
157 | //this.slimLoader.start();
158 | this.dataService.getUserSchedules(this.edittedUser.id)
159 | .subscribe((schedules: ISchedule[]) => {
160 | this.userSchedules = schedules;
161 | console.log(this.userSchedules);
162 | this.userSchedulesLoaded = true;
163 | //this.slimLoader.complete();
164 | },
165 | error => {
166 | //this.slimLoader.complete();
167 | this.notificationService.printErrorMessage('Failed to load users. ' + error);
168 | });
169 | this.output = '(opened)';
170 | }
171 |
172 | isUserValid(): boolean {
173 | return !(this.edittedUser.name.trim() === "")
174 | && !(this.edittedUser.profession.trim() === "");
175 | }
176 |
177 | }
--------------------------------------------------------------------------------
/src/app/users/user-list.component.html:
--------------------------------------------------------------------------------
1 |
2 | Add
3 |
4 | Cancel
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/app/users/user-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | import { DataService } from '../shared/services/data.service';
4 | import { ItemsService } from '../shared/utils/items.service';
5 | import { NotificationService } from '../shared/utils/notification.service';
6 | import { IUser } from '../shared/interfaces';
7 | import { UserCardComponent } from './user-card.component';
8 |
9 | @Component({
10 | moduleId: module.id,
11 | selector: 'users',
12 | templateUrl: 'user-list.component.html'
13 | })
14 | export class UserListComponent implements OnInit {
15 |
16 | users: IUser[];
17 | addingUser: boolean = false;
18 |
19 | constructor(private dataService: DataService,
20 | private itemsService: ItemsService,
21 | private notificationService: NotificationService) { }
22 |
23 | ngOnInit() {
24 | this.dataService.getUsers()
25 | .subscribe((users: IUser[]) => {
26 | this.users = users;
27 | },
28 | error => {
29 | this.notificationService.printErrorMessage('Failed to load users. ' + error);
30 | });
31 | }
32 |
33 | removeUser(user: any) {
34 | var _user: IUser = this.itemsService.getSerialized(user.value);
35 | this.itemsService.removeItemFromArray(this.users, _user);
36 | // inform user
37 | this.notificationService.printSuccessMessage(_user.name + ' has been removed');
38 | }
39 |
40 | userCreated(user: any) {
41 | var _user: IUser = this.itemsService.getSerialized(user.value);
42 | this.addingUser = false;
43 | // inform user
44 | this.notificationService.printSuccessMessage(_user.name + ' has been created');
45 | console.log(_user.id);
46 | this.itemsService.setItem(this.users, (u) => u.id == -1, _user);
47 | // todo fix user with id:-1
48 | }
49 |
50 | addUser() {
51 | this.addingUser = true;
52 | var newUser = { id: -1, name: '', avatar: 'avatar_05.png', profession: '', schedulesCreated: 0 };
53 | this.itemsService.addItemToStart(this.users, newUser);
54 | //this.users.splice(0, 0, newUser);
55 | }
56 |
57 | cancelAddUser() {
58 | this.addingUser = false;
59 | this.itemsService.removeItems(this.users, x => x.id < 0);
60 | }
61 | }
--------------------------------------------------------------------------------
/src/server/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express'),
2 | path = require('path'),
3 | fs = require('fs');
4 |
5 | var app = express();
6 | var staticRoot = __dirname + '/';
7 |
8 | app.set('port', (process.env.PORT || 3000));
9 |
10 | app.use(express.static(staticRoot));
11 |
12 | app.use(function(req, res, next){
13 |
14 | // if the request is not html then move along
15 | var accept = req.accepts('html', 'json', 'xml');
16 | if(accept !== 'html'){
17 | return next();
18 | }
19 |
20 | // if the request has a '.' assume that it's for a file, move along
21 | var ext = path.extname(req.path);
22 | if (ext !== ''){
23 | return next();
24 | }
25 |
26 | fs.createReadStream(staticRoot + 'index.html').pipe(res);
27 |
28 | });
29 |
30 | app.listen(app.get('port'), function() {
31 | console.log('app running on port', app.get('port'));
32 | });
--------------------------------------------------------------------------------
/src/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scheduler-web-ui",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "express": "4.13.4"
6 | },
7 | "scripts": {
8 | "postinstall": "bower install"
9 | }
10 | }
--------------------------------------------------------------------------------
/systemjs.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * System configuration for Angular 2 samples
3 | * Adjust as necessary for your application needs.
4 | */
5 | (function (global) {
6 | System.config({
7 | paths: {
8 | // paths serve as alias
9 | 'npm:': 'node_modules/'
10 | },
11 | // map tells the System loader where to look for things
12 | map: {
13 | // our app is within the app folder
14 | app: 'src/app',
15 | // angular bundles
16 | '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
17 | '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
18 | '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
19 | '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
20 | '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
21 | '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
22 | '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
23 | '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
24 | '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
25 | '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
26 | '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
27 | // other libraries
28 | 'rxjs': 'npm:rxjs',
29 | 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
30 | 'jquery': 'npm:jquery/',
31 | 'lodash': 'npm:lodash/lodash.js',
32 | 'moment': 'npm:moment',
33 | 'ngx-bootstrap': 'npm:ngx-bootstrap',
34 | 'ng2-slim-loading-bar': 'npm:ng2-slim-loading-bar',
35 | 'symbol-observable': 'npm:symbol-observable'
36 | },
37 | // packages tells the System loader how to load when no filename and/or no extension
38 | packages: {
39 | app: {
40 | main: './main.js',
41 | defaultExtension: 'js'
42 | },
43 | rxjs: {
44 | defaultExtension: 'js'
45 | },
46 | 'angular-in-memory-web-api': {
47 | main: './index.js',
48 | defaultExtension: 'js'
49 | },
50 | 'moment': { main: 'moment.js', defaultExtension: 'js' },
51 | 'ngx-bootstrap': { format: 'cjs', main: 'bundles/ngx-bootstrap.umd.js', defaultExtension: 'js' },
52 | 'ng2-slim-loading-bar': { main: 'index.js', defaultExtension: 'js' },
53 | 'symbol-observable': { main: 'index.js', defaultExtension: 'js' }
54 | }
55 | });
56 | })(this);
57 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "removeComments": false,
10 | "noImplicitAny": false,
11 | "skipLibCheck": true
12 | }
13 | }
--------------------------------------------------------------------------------