├── .bowerrc ├── .gitignore ├── README.md ├── bower.json ├── client ├── app │ ├── bootstrap.ts │ ├── components │ │ ├── app.ts │ │ ├── common │ │ │ ├── common.components.ts │ │ │ ├── header │ │ │ │ ├── header.component.html │ │ │ │ └── header.component.ts │ │ │ └── pagination │ │ │ │ └── pagination.component.ts │ │ ├── custom │ │ │ ├── alert │ │ │ │ ├── alert.component.html │ │ │ │ └── alert.component.ts │ │ │ └── custom.components.ts │ │ └── pages │ │ │ └── home │ │ │ ├── home.component.html │ │ │ └── home.component.ts │ ├── config │ │ └── routes.config.ts │ ├── directives │ │ └── directives.ts │ ├── modules │ │ ├── all_in_one │ │ │ ├── carDetails.component.html │ │ │ ├── carDetails.component.ts │ │ │ ├── carFilterPanel.component.ts │ │ │ ├── carHome.component.html │ │ │ ├── carHome.component.ts │ │ │ ├── carList.component.ts │ │ │ ├── carListItem.component.html │ │ │ ├── carListItem.component.ts │ │ │ ├── carSearchPanel.component.ts │ │ │ ├── cars.component.ts │ │ │ ├── cars.module.ts │ │ │ ├── carsSearch.component.ts │ │ │ ├── config │ │ │ │ └── carColors.ts │ │ │ ├── controls │ │ │ │ └── colorSelector.component.ts │ │ │ ├── converters │ │ │ │ ├── color.ts │ │ │ │ ├── converters.ts │ │ │ │ ├── manufacter.ts │ │ │ │ ├── milage.ts │ │ │ │ ├── price.ts │ │ │ │ └── year.ts │ │ │ ├── decorators │ │ │ │ ├── converter.decorator.ts │ │ │ │ ├── decorators.ts │ │ │ │ └── filter.decorator.ts │ │ │ ├── filterController.service.ts │ │ │ ├── filters │ │ │ │ ├── filterItems │ │ │ │ │ ├── colorFilter.component.ts │ │ │ │ │ ├── filters.ts │ │ │ │ │ ├── manufacterFilter.component.ts │ │ │ │ │ ├── milageFilter.component.ts │ │ │ │ │ ├── priceFilter.component.ts │ │ │ │ │ └── yearFilter.component.ts │ │ │ │ └── filterWrapper.component.ts │ │ │ ├── pageController.service.ts │ │ │ ├── searchPage.component.ts │ │ │ └── searchParamsController.service.ts │ │ ├── communication │ │ │ ├── communication.component.ts │ │ │ ├── queryCom.component.ts │ │ │ ├── serviceCom.component.ts │ │ │ └── subjectsCom.component.ts │ │ ├── forms_explore │ │ │ ├── children.form.component.html │ │ │ ├── children.form.component.ts │ │ │ ├── customValidators.ts │ │ │ ├── date.form.component.html │ │ │ ├── date.form.component.ts │ │ │ ├── forms.component.html │ │ │ └── forms.component.ts │ │ ├── http_explore │ │ │ ├── extHttp.ts │ │ │ ├── http.component.ts │ │ │ ├── loader.component.ts │ │ │ └── some.component.ts │ │ ├── polymer │ │ │ └── components │ │ │ │ ├── polymer.component.ts │ │ │ │ ├── polymerD3.component.ts │ │ │ │ └── polymerForm.component.ts │ │ ├── router_explore │ │ │ ├── hooks.component.ts │ │ │ └── router.component.ts │ │ └── rx_explore │ │ │ ├── api.service.ts │ │ │ └── components │ │ │ ├── autocomplete.component.html │ │ │ ├── autocomplete.component.ts │ │ │ ├── rxExplore.component.html │ │ │ └── rxExplore.component.ts │ ├── polymerElements │ │ ├── chart │ │ │ └── chart.html │ │ ├── custom.html │ │ ├── inputs │ │ │ ├── confirm-password-input.html │ │ │ ├── email-input.html │ │ │ └── password-input.html │ │ └── select │ │ │ └── select.html │ ├── services │ │ ├── api.service.ts │ │ └── services.ts │ └── utils │ │ └── formValidators.ts └── assets │ ├── animate.css │ ├── app.less │ ├── images │ ├── 27.JPG │ ├── audi.jpg │ ├── bmw.jpg │ ├── car.png │ ├── check.png │ ├── github.png │ ├── tesla.jpg │ └── volkswagen.jpg │ ├── index.less │ └── seachBox.less ├── dump └── playground │ ├── cars.bson │ ├── cars.metadata.json │ ├── manufacters.bson │ ├── manufacters.metadata.json │ ├── models.bson │ ├── models.metadata.json │ ├── system.indexes.bson │ ├── users.bson │ └── users.metadata.json ├── gulpfile.js ├── jsconfig.json ├── nodemon.json ├── package.json ├── server ├── config │ ├── config.js │ ├── defaults.js │ ├── env │ │ ├── development.js │ │ ├── production.js │ │ └── test.js │ ├── express.js │ ├── mongoose.js │ └── webpack.js ├── controllers │ ├── api.controller.js │ └── test.controller.js ├── models │ ├── car.js │ ├── manufacter.js │ ├── models.js │ └── user.js ├── routes │ ├── api.routes.js │ └── routes.js ├── server.js └── views │ └── index.jade ├── tsconfig.json └── typings.json /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory":"client/vendor" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | build 4 | client/vendor 5 | npm-debug.log 6 | typings -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## angular2 playground app (beta 8) 2 | An example Angular 2 learning project. 3 | 4 | #### Themes covered 5 | - Components communication 6 | - Http service extending 7 | - Rx.js in terms of angular 2 8 | - Dive into forms 9 | - Awesome router 10 | - Bonus sample market app 11 | - More to come.. 12 | 13 | #### Requirements 14 | - [mongodb](https://docs.mongodb.org/manual/tutorial/install-mongodb-on-windows/) 15 | - nodemon 16 | - gulp 17 | 18 | #### Installation 19 | ```bash 20 | npm install 21 | bower install 22 | typings install 23 | ``` 24 | **!!! Important !!!** 25 | Sample app uses big db with almost 10000 records. Project contains db dump, so just run 26 | ```bash 27 | mongorestore --db playground dump/playground 28 | ``` 29 | #### Start 30 | ```bash 31 | gulp 32 | ``` 33 | Open [http://localhost:3030](http://localhost:3030) -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-playground", 3 | "description": "Angular2 playground project", 4 | "main": "server/server.js", 5 | "authors": [ 6 | "byavv" 7 | ], 8 | "license": "ISC", 9 | "keywords": [ 10 | "angular2", 11 | "typescript", 12 | "playground" 13 | ], 14 | "homepage": "https://github.com/byavv/ng2-playground", 15 | "moduleType": [], 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "client/vendor", 21 | "test", 22 | "tests" 23 | ], 24 | "dependencies": { 25 | "bootstrap": "~3.3.5", 26 | "font-awesome": "~4.4.0", 27 | "iron-ajax": "PolymerElements/iron-ajax#~1.1.1", 28 | "iron-input": "PolymerElements/iron-input#~1.0.8", 29 | "paper-button": "PolymerElements/paper-button#~1.0.11", 30 | "paper-dropdown-menu": "PolymerElements/paper-dropdown-menu#~1.1.1", 31 | "paper-icon-button": "PolymerElements/paper-icon-button#~1.0.6", 32 | "paper-input": "PolymerElements/paper-input#~1.1.3", 33 | "paper-item": "PolymerElements/paper-item#~1.1.3", 34 | "paper-listbox": "polymerelements/paper-listbox#~1.1.0", 35 | "paper-menu": "PolymerElements/paper-menu#~1.2.1", 36 | "polymer": "~1.2.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/app/bootstrap.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * App Dependencies 3 | */ 4 | require('jquery'); 5 | require('bootstrap'); 6 | 7 | import {provide} from 'angular2/core'; 8 | import {bootstrap} from 'angular2/platform/browser'; 9 | 10 | 11 | /** 12 | * Angular Modules 13 | */ 14 | import {FORM_PROVIDERS} from 'angular2/common'; 15 | import {HTTP_PROVIDERS} from 'angular2/http'; 16 | import {ROUTER_PROVIDERS, APP_BASE_HREF} from 'angular2/router'; 17 | 18 | /** 19 | * Global styles 20 | */ 21 | 22 | require('font-awesome'); 23 | require('../assets/index.less'); 24 | require('../assets/animate.css'); 25 | 26 | require('d3'); 27 | 28 | /** 29 | * App Services 30 | * our collection of injectables services 31 | */ 32 | import {APP_SERVICES_BINDINGS} from './services/services'; 33 | 34 | /** 35 | * App Root Component 36 | */ 37 | import {App} from './components/app'; 38 | 39 | /** 40 | * Angular and app custom providers 41 | */ 42 | const PROVIDERS = [ 43 | HTTP_PROVIDERS, 44 | FORM_PROVIDERS, 45 | ROUTER_PROVIDERS, 46 | 47 | provide(APP_BASE_HREF, { useValue: '/' }), 48 | APP_SERVICES_BINDINGS 49 | ]; 50 | 51 | /** 52 | * Bootstrap Angular app 53 | */ 54 | 55 | // start angular after all polymer html imports are resolved and elements are registered 56 | window.addEventListener('WebComponentsReady', (e) => { 57 | bootstrap( 58 | App, 59 | PROVIDERS 60 | ).then((app) => { 61 | // do smth with app 62 | // app.injector.get... 63 | // app.instance.someValue... 64 | }, error => console.log(error)) 65 | }) -------------------------------------------------------------------------------- /client/app/components/app.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Angular 2 decorators and services 3 | */ 4 | import {Component} from 'angular2/core'; 5 | import {RouteConfig} from 'angular2/router'; 6 | import {Header} from './common/header/header.component' 7 | import {HomeComponent} from "./pages/home/home.component" 8 | 9 | /* 10 | * Angular Directives 11 | */ 12 | import {CORE_DIRECTIVES} from 'angular2/common'; 13 | import {ROUTER_DIRECTIVES} from 'angular2/router'; 14 | 15 | import {COMMON_COMPONENTS} from './common/common.components'; 16 | /** 17 | * Root Component 18 | */ 19 | @Component({ 20 | selector: 'app', 21 | directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES, COMMON_COMPONENTS], 22 | 23 | template: ` 24 |
25 | 26 | 27 |
28 | 29 | 30 |
31 |
32 | ` 33 | }) 34 | @RouteConfig( 35 | [ 36 | { path: '/pg/...', as: 'Home', component: HomeComponent, useAsDefault: true } 37 | ] 38 | ) 39 | export class App { 40 | constructor() { 41 | } 42 | ngAfterViewInit() { 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/app/components/common/common.components.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Header} from './header/header.component'; 3 | 4 | export * from './header/header.component'; 5 | 6 | export var COMMON_COMPONENTS: Array = [ 7 | Header, /* Footer */ 8 | ]; -------------------------------------------------------------------------------- /client/app/components/common/header/header.component.html: -------------------------------------------------------------------------------- 1 |
2 | 15 |
-------------------------------------------------------------------------------- /client/app/components/common/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, View} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/common'; 3 | import {ROUTER_DIRECTIVES, Router} from 'angular2/router'; 4 | import {Routes} from '../../../config/routes.config'; 5 | 6 | 7 | @Component({ 8 | selector: 'app-header' 9 | }) 10 | @View({ 11 | template: require('./header.component.html'), 12 | directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES], 13 | styles: [` 14 | .headerButton{ 15 | font-size: 1.1em; 16 | } 17 | `] 18 | }) 19 | export class Header { 20 | routes = Routes; 21 | isAuthenticated: boolean = false; 22 | constructor(private router: Router) { 23 | 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /client/app/components/common/pagination/pagination.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Output, EventEmitter} from 'angular2/core' 2 | import {CORE_DIRECTIVES} from 'angular2/common'; 3 | 4 | 5 | export interface IPage { 6 | label: string; 7 | value: any; 8 | } 9 | 10 | @Component({ 11 | selector: 'pagination', 12 | template: ` 13 | 30 | `, 31 | directives: [CORE_DIRECTIVES], 32 | styles: [` 33 | .pagination span{ 34 | cursor: pointer; 35 | } 36 | .page-prev span.disabled, 37 | .page-next span.disabled{ 38 | pointer-events: none; 39 | cursor: default; 40 | opacity:0.5; 41 | } 42 | .pagination i { 43 | font-size:0.8em; 44 | } 45 | .pagination{ 46 | margin: 2px 0; 47 | } 48 | `], 49 | host: { 50 | '[style.display]': 'isVisible()' 51 | } 52 | }) 53 | export class PaginationControlsCmp { 54 | private _currentPage: number = 0; 55 | private _totalItems; 56 | private _currentMin; 57 | private _currentMax; 58 | private _itemsPerPage; 59 | 60 | 61 | public shownPages: IPage[] = []; 62 | public allPages: IPage[] = []; 63 | 64 | @Input() maxSize: number = 6; 65 | @Input() 66 | set totalItems(value) { 67 | this._totalItems = value; 68 | this._createPageArray(); 69 | }; 70 | get totalItems() { 71 | return this._totalItems; 72 | }; 73 | @Input() 74 | set itemsPerPage(value) { 75 | this._itemsPerPage = +value; 76 | this._createPageArray(); 77 | 78 | }; 79 | get itemsPerPage() { 80 | return this._itemsPerPage; 81 | }; 82 | @Input() 83 | set currentPage(value) { 84 | this._currentPage = value; 85 | this._createPageArray(); 86 | }; 87 | get currentPage() { 88 | return this._currentPage; 89 | }; 90 | @Output() pageChange: EventEmitter = new EventEmitter(); 91 | 92 | constructor() { 93 | this._currentMax = this.maxSize; 94 | this._currentMin = 0; 95 | } 96 | 97 | isFirstPage(): boolean { 98 | return this.currentPage === 0; 99 | } 100 | 101 | isLastPage(): boolean { 102 | return Math.ceil(this.totalItems / this.itemsPerPage) === this.currentPage + 1; 103 | } 104 | 105 | isVisible() { 106 | return this.allPages.length > 1 ? "block" : "none" 107 | } 108 | 109 | setPage(value) { 110 | if (this.currentPage != value) { 111 | this.currentPage = value; 112 | this.pageChange.next(value); 113 | } 114 | } 115 | 116 | _createPageArray() { 117 | this.allPages = []; 118 | if (!!this.totalItems && !!this.itemsPerPage) { 119 | let totalPages = Math.ceil(this.totalItems / this.itemsPerPage); 120 | for (let i = 0; i < totalPages; i++) { 121 | this.allPages.push({ 122 | label: i + 1 + "", 123 | value: i 124 | }); 125 | } 126 | // Happens, we need to correct range, because currentPage has impossible value 127 | if (this.currentPage > totalPages - 1) { 128 | this._currentMax = totalPages - 1; 129 | this._currentMin = this._currentMax - this.maxSize; 130 | return this.setPage(totalPages - 1); 131 | } 132 | // works like window, we move it depending on page value 133 | // if current page is out of boundaries, correct window position accordingly 134 | if (this.currentPage + 1 > this._currentMax || this.currentPage + 1 < this._currentMin) { 135 | this._currentMin = this.currentPage - Math.ceil(this.maxSize / 2); 136 | this._currentMax = this.currentPage + Math.floor(this.maxSize / 2); 137 | if (this._currentMax > this.allPages.length) { 138 | this._currentMax = this.allPages.length; 139 | this._currentMin = this._currentMax - this.maxSize; 140 | } 141 | if (this._currentMin < 0) { 142 | this._currentMin = 0; 143 | this._currentMax = this.maxSize; 144 | } 145 | } 146 | // click on the right boundary - move window to the right 147 | if (this.currentPage + 1 == this._currentMax) { 148 | this._currentMin += Math.ceil(this.maxSize / 2); 149 | this._currentMax += Math.ceil(this.maxSize / 2); 150 | if (this._currentMax > this.allPages.length) { 151 | this._currentMax = this.allPages.length; 152 | this._currentMin = this._currentMax - this.maxSize; 153 | } 154 | } 155 | // click on the left boundary - move window to the left 156 | if (this.currentPage == this._currentMin) { 157 | this._currentMin -= Math.ceil(this.maxSize / 2); 158 | this._currentMax -= Math.ceil(this.maxSize / 2); 159 | if (this._currentMin < 0) { 160 | this._currentMin = 0; 161 | this._currentMax = this.maxSize; 162 | } 163 | } 164 | this.shownPages = this.allPages.slice(this._currentMin, this._currentMax); 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /client/app/components/custom/alert/alert.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
-------------------------------------------------------------------------------- /client/app/components/custom/alert/alert.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Output, EventEmitter} from 'angular2/core'; 2 | import {CORE_DIRECTIVES} from 'angular2/common'; 3 | 4 | import {Routes} from '../../../config/routes.config'; 5 | 6 | @Component({ 7 | selector: 'alert', 8 | directives: [CORE_DIRECTIVES], 9 | template: require("./alert.component.html"), 10 | inputs: ["type"], 11 | outputs: ["onclose"], 12 | styles: [` 13 | .alert{ 14 | font-size:12px; 15 | padding: 11px 7px; 16 | } 17 | ` 18 | ] 19 | }) 20 | 21 | export class Alert { 22 | visible: boolean = true; 23 | onclose: EventEmitter = new EventEmitter(); 24 | close() { 25 | this.onclose.emit(null); 26 | } 27 | } -------------------------------------------------------------------------------- /client/app/components/custom/custom.components.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Alert} from './alert/alert.component'; 3 | 4 | export * from './alert/alert.component'; 5 | 6 | export var CUSTOM_COMPONENTS: Array = [ 7 | Alert, 8 | ]; -------------------------------------------------------------------------------- /client/app/components/pages/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Basic 4 | 11 |
12 | Experimental 13 | 16 |
17 | Complex 18 | 21 |
22 |
23 | 24 | 25 |
26 |
-------------------------------------------------------------------------------- /client/app/components/pages/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from 'angular2/core'; 2 | import {ROUTER_DIRECTIVES, RouteConfig} from 'angular2/router'; 3 | import {RxExploreComponent} from '../../../modules/rx_explore/components/rxExplore.component'; 4 | import {PolymerExplore} from '../../../modules/polymer/components/polymer.component'; 5 | import {CarSearchComponent} from '../../../modules/all_in_one/cars.component'; 6 | 7 | import {CommunicationExplore} from '../../../modules/communication/communication.component'; 8 | import {FormsExplore} from '../../../modules/forms_explore/forms.component'; 9 | import {HttpExplore} from '../../../modules/http_explore/http.component'; 10 | import {RouterExplore} from '../../../modules/router_explore/router.component'; 11 | @Component({ 12 | selector: 'home', 13 | template: require("./home.component.html"), 14 | directives: [ROUTER_DIRECTIVES], 15 | styles: [ 16 | `.router-link-active { background:cornsilk !important; }` 17 | ] 18 | }) 19 | @RouteConfig([ 20 | { path: '/rxexplore', as: 'Rx', component: RxExploreComponent }, 21 | { path: '/polymer/...', as: 'Polymer', component: PolymerExplore }, 22 | { path: '/communication/...', as: 'Communication', component: CommunicationExplore }, 23 | { path: '/forms', as: 'Forms', useAsDefault: true, component: FormsExplore }, 24 | { path: '/http', as: 'Http', component: HttpExplore }, 25 | { path: '/router/...', as: 'Router', component: RouterExplore }, 26 | 27 | { path: '/all/...', as: 'All', component: CarSearchComponent }, 28 | ]) 29 | export class HomeComponent { 30 | constructor() { } 31 | 32 | } -------------------------------------------------------------------------------- /client/app/config/routes.config.ts: -------------------------------------------------------------------------------- 1 | import {HomeComponent} from '../components/pages/home/home.component'; 2 | import {RouteDefinition} from 'angular2/router'; 3 | 4 | export const Routes = { 5 | home: { 6 | path: '/', 7 | as: 'Home', 8 | component: HomeComponent, 9 | link: ['/Home'] 10 | } 11 | }; 12 | 13 | export const APP_ROUTES: RouteDefinition[] = 14 | Object.keys(Routes).map((name) => Routes[name]); -------------------------------------------------------------------------------- /client/app/directives/directives.ts: -------------------------------------------------------------------------------- 1 | export var APP_DIRECTIVES: Array = [ 2 | 3 | ]; -------------------------------------------------------------------------------- /client/app/modules/all_in_one/carDetails.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Car 5 |
6 |
7 |

{{car.name}}

8 |

{{car.description}}

9 |

{{car.price | currency:'EUR' }}

10 |
11 |
12 |
13 |
14 |

Milage: {{car.milage}} km

15 |

First registration: {{car.year}}

16 |
17 |
18 |

Gear: {{car.gear}}

19 |

Engine: {{car.engine}}

20 |
21 |
22 |
-------------------------------------------------------------------------------- /client/app/modules/all_in_one/carDetails.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from 'angular2/core'; 2 | import {CORE_DIRECTIVES} from 'angular2/common'; 3 | import {ApiService} from '../../services/api.service.ts'; 4 | import {ROUTER_DIRECTIVES, RouteParams} from 'angular2/router'; 5 | 6 | @Component({ 7 | selector: 'carDetails', 8 | template: require("./carDetails.component.html"), 9 | 10 | directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES] 11 | }) 12 | export class CarDetailsComponent implements OnInit { 13 | car: any = { 14 | images: [] 15 | }; 16 | constructor(private apiService: ApiService, private params: RouteParams) { 17 | } 18 | 19 | ngOnInit() { 20 | this.apiService 21 | .getCar(this.params.get('id')) 22 | .subscribe((result: any) => { 23 | this.car = result.car; 24 | }) 25 | } 26 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/carFilterPanel.component.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Component, OnInit, EventEmitter} from 'angular2/core'; 3 | import {CORE_DIRECTIVES} from 'angular2/common'; 4 | 5 | import {ROUTER_DIRECTIVES, RouteParams, Router} from 'angular2/router'; 6 | import {FilterService, FilterModel} from './filterController.service'; 7 | import {FilterWrapperComponent} from './filters/filterWrapper.component' 8 | import * as filters from './filters/filterItems/filters'; 9 | import {ApiService} from '../../services/api.service.ts'; 10 | 11 | @Component({ 12 | selector: 'carFilterPanel', 13 | template: ` 14 |
15 |
16 | Detailed Seach 17 | 18 | 19 |
20 |
21 | 24 | 25 |
26 | 29 |
30 |
31 |
32 | `, 33 | inputs: ["car"], 34 | outputs: ['changed'], 35 | directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES, FilterWrapperComponent], 36 | styles: [` 37 | .sch-button{ 38 | width: 100%; 39 | color: #fff; 40 | outline: none; 41 | background-color: #337ab7; 42 | border-color: #006DCC #005CAB #00559E;; 43 | -webkit-box-shadow: 0 1px .5px 0 rgba(0,0,0,.25); 44 | box-shadow: 0 1px .5px 0 rgba(0,0,0,.25); 45 | } 46 | .link{ 47 | color: #337ab7; 48 | text-decoration: underline; 49 | cursor: pointer; 50 | font-size:16px; 51 | } 52 | `] 53 | 54 | }) 55 | /* 56 | * Left filter panel, Loads all filters dynamically end emits event when value of any of them is changed 57 | */ 58 | export class CarFilterPanelComponent { 59 | filters: any[] = []; 60 | changed: EventEmitter = new EventEmitter(); 61 | count: number; 62 | 63 | constructor(private filterState: FilterService, private apiService: ApiService) { 64 | filterState.filtersSubject.subscribe((value) => { 65 | this.filters = value; 66 | }) 67 | filterState.filterStateSubject 68 | .flatMap(flterModel=> { 69 | return this.apiService 70 | .getCarsCount(flterModel) 71 | }) 72 | .subscribe((result: any) => { 73 | this.count = +result.count; 74 | }) 75 | } 76 | 77 | onFilterValueChanged(newValue, filterName) { 78 | console.debug(`The ${filterName} filter's value changed to: `, newValue); 79 | this.filterState.filter = newValue; 80 | } 81 | showAll() { 82 | this.changed.next(this.filterState.filter); 83 | } 84 | detailedSeach() { 85 | console.warn("This feature has not been released yet"); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /client/app/modules/all_in_one/carHome.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 | 8 | 13 |
14 |
15 |
16 |
17 | 18 | 23 |
24 |
25 |
26 |
27 | 28 | 32 |
33 |
34 |
35 |
36 | 37 | 39 |
40 |
41 |
42 | 43 | 44 | 47 |
48 |
49 |
50 |
-------------------------------------------------------------------------------- /client/app/modules/all_in_one/carHome.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, EventEmitter} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES, Control, FormBuilder, ControlGroup} from 'angular2/common'; 3 | 4 | import {ROUTER_DIRECTIVES, Location, Router, RouteParams} from 'angular2/router'; 5 | import {ApiService} from '../../services/api.service.ts'; 6 | import * as rx from 'rxjs'; 7 | import {FilterService} from './filterController.service' 8 | 9 | @Component({ 10 | selector: 'carForm', 11 | template: require('./carHome.component.html'), 12 | directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES, FORM_DIRECTIVES], 13 | outputs: ["onSeach"], 14 | }) 15 | export class CarsHomeComponent { 16 | carFormModel: any; 17 | carForm: ControlGroup; 18 | carManufacters: Array = []; 19 | carModels: Array = []; 20 | carCount: number; 21 | yearFroms: Array = []; 22 | loading: boolean = true; 23 | onSeach: EventEmitter = new EventEmitter(); 24 | 25 | manufacter: Control; 26 | model: Control; 27 | yearFrom: Control; 28 | priceUp: Control; 29 | 30 | constructor(builder: FormBuilder, private params: RouteParams, 31 | private apiService: ApiService, private location: Location, private router: Router, seachState: FilterService) { 32 | this.carFormModel = {} 33 | this.manufacter = new Control(); 34 | this.model = new Control(); 35 | this.yearFrom = new Control(); 36 | this.priceUp = new Control(); 37 | 38 | this.apiService 39 | .initCarsDefaults() 40 | .subscribe((res: any) => { 41 | this.carManufacters = res.manufacters; 42 | let currentYearFrom = new Date().getFullYear(); 43 | let startYearFrom = res.minYearFrom || 1980; 44 | for (let i = startYearFrom; i <= currentYearFrom; i++) { 45 | this.yearFroms.push(i); 46 | } 47 | this.loading = false; 48 | }, (err) => { 49 | console.error(err); 50 | }) 51 | 52 | this.manufacter 53 | .valueChanges 54 | .do(() => { this.loading = true }) 55 | .map(newValue=> Object.assign({}, this.carFormModel, { manufacter: newValue })) 56 | .flatMap((form) => { 57 | return this.apiService.getCarsByManufacter(form); 58 | }) 59 | .subscribe((val: any) => { 60 | this.carModels = val.models; 61 | this.carCount = val.count; 62 | this.loading = false; 63 | }) 64 | 65 | this.model 66 | .valueChanges 67 | .map(newValue=> Object.assign({}, this.carFormModel, { model: newValue })) 68 | .flatMap((form) => { 69 | return this.apiService.getCarsCount(form); 70 | }) 71 | .subscribe((val: any) => { 72 | this.carCount = val.count; 73 | this.loading = false; 74 | }) 75 | 76 | this.yearFrom 77 | .valueChanges 78 | .map(newValue=> Object.assign({}, this.carFormModel, { yearFrom: newValue })) 79 | .flatMap((form) => { 80 | return this.apiService.getCarsCount(form); 81 | }) 82 | .subscribe((val: any) => { 83 | this.carCount = val.count; 84 | this.loading = false; 85 | }) 86 | 87 | this.priceUp 88 | .valueChanges 89 | .do(() => { this.loading = true }) 90 | .debounceTime(500) 91 | .map(newValue=> Object.assign({}, this.carFormModel, { priceUp: newValue })) 92 | .flatMap((form) => { 93 | return this.apiService.getCarsCount(form); 94 | }) 95 | .subscribe((val: any) => { 96 | this.carCount = val.count; 97 | this.loading = false; 98 | }) 99 | } 100 | 101 | submit() { 102 | console.log(this.carFormModel.model) 103 | this.router.navigate(['SearchList', { 104 | manufacter: `${this.carFormModel.manufacter || 'any'},${this.carFormModel.model || ''}`, 105 | price: `any..${this.carFormModel.priceUp || ''}`, 106 | year: `${this.carFormModel.yearFrom || 'any'}..`, 107 | }]) 108 | } 109 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/carList.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/common'; 3 | import {ROUTER_DIRECTIVES, RouteParams, OnReuse, ComponentInstruction} from 'angular2/router'; 4 | import {CarItemComponent} from './carListItem.component' 5 | 6 | @Component({ 7 | selector: 'carsList', 8 | template: ` 9 |
10 |
11 |
    12 |
  • 13 | 14 |
  • 15 |
16 |
17 |
18 | `, 19 | directives: [CORE_DIRECTIVES, CarItemComponent], 20 | styles: [` 21 | .cars-list{ 22 | list-style: none; 23 | margin: 0; 24 | padding: 0; 25 | } 26 | .cars-list li { 27 | border-bottom: 1px solid #ebebeb; 28 | padding:5px 0; 29 | } 30 | `] 31 | }) 32 | // Root component to contain filters and finded cars list 33 | export class CarsListComponent { 34 | @Input() 35 | cars: Array = []; 36 | constructor() { } 37 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/carListItem.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/modules/all_in_one/carListItem.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from 'angular2/core'; 2 | import {CORE_DIRECTIVES} from 'angular2/common'; 3 | import {ROUTER_DIRECTIVES} from 'angular2/router'; 4 | 5 | @Component({ 6 | selector: 'carItem', 7 | template: require("./carListItem.component.html"), 8 | inputs: ["car"], 9 | directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES] 10 | }) 11 | export class CarItemComponent { 12 | constructor() { } 13 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/carSearchPanel.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Output, EventEmitter, OnInit, AfterViewInit} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES, ControlGroup, Control} from 'angular2/common'; 3 | import {ROUTER_DIRECTIVES, RouteParams, OnReuse, ComponentInstruction} from 'angular2/router'; 4 | import {CarItemComponent} from './carListItem.component' 5 | import {FilterService} from './filterController.service'; 6 | import {SearchParamsController} from './searchParamsController.service'; 7 | import {PaginationControlsCmp} from '../../components/common/pagination/pagination.component'; 8 | 9 | @Component({ 10 | selector: 'carsSearchPanel', 11 | template: ` 12 | 13 |
14 |
15 | 16 | 27 |
28 |
29 | 30 | 37 |
38 |
39 | 40 | `, 41 | styles: [` 42 | 43 | `], 44 | directives: [CORE_DIRECTIVES] 45 | }) 46 | 47 | 48 | // Root component to contain filters and finded cars list 49 | export class CarsSearchPanelComponent implements AfterViewInit { 50 | private _totalPages: number; 51 | @Input() 52 | set totalPages(value) { 53 | this._totalPages = value; 54 | this.count = Math.ceil(value / this.limit); 55 | }; 56 | get totalPages() { 57 | return this._totalPages; 58 | } 59 | 60 | @Output() 61 | changed: EventEmitter = new EventEmitter(); 62 | @Output() 63 | limitChanged: EventEmitter = new EventEmitter(); 64 | 65 | count: number; 66 | limit: number; 67 | sort: string; 68 | form: ControlGroup; 69 | sortControl: Control; 70 | limitControl: Control; 71 | constructor(private searchController: SearchParamsController) { 72 | this.searchController 73 | .searchParamsSubject 74 | .subscribe((filter) => { 75 | this.sort = filter.sort; 76 | this.limit = filter.limit; 77 | this.sortControl = new Control(this.sort); 78 | this.limitControl = new Control(this.limit); 79 | this.form = new ControlGroup({ 80 | sort: this.sortControl, 81 | limit: this.limitControl 82 | }); 83 | this.searchController.params = { 84 | sort: this.sort, 85 | limit: this.limit 86 | } 87 | }) 88 | } 89 | 90 | ngAfterViewInit() { 91 | this.form 92 | .valueChanges 93 | .subscribe((value) => { 94 | this.onSeachParamsChanged(value) 95 | }); 96 | this.limitControl.valueChanges.subscribe((value)=>{ 97 | this.limitChanged.next(value); 98 | }) 99 | } 100 | 101 | onSeachParamsChanged(newValue) { 102 | this.searchController.params = newValue; 103 | this.count = Math.ceil(this.totalPages / this.limit); 104 | this.changed.next(this.searchController.params); 105 | } 106 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/cars.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, provide} from 'angular2/core'; 2 | import {CORE_DIRECTIVES} from 'angular2/common'; 3 | 4 | import {ROUTER_DIRECTIVES, RouteConfig} from 'angular2/router'; 5 | import {CarsHomeComponent} from './carHome.component'; 6 | import {CarsSeachComponent} from './carsSearch.component'; 7 | import {CarDetailsComponent} from './carDetails.component'; 8 | import {CARS_MODULE_PROVIDERS} from './cars.module'; 9 | import {Loader} from '../http_explore/loader.component'; 10 | import {API_SERVICE_PROVIDERS} from "../../services/api.service.ts" 11 | import {Http, ConnectionBackend, XHRBackend, BaseRequestOptions,HTTP_PROVIDERS} from 'angular2/http'; 12 | 13 | import {ExtHttp} from "../http_explore/extHttp"; 14 | 15 | @Component({ 16 | selector: 'carseach', 17 | template: ` 18 |
19 |
20 | 21 | 22 |
23 | `, 24 | directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES, CarsHomeComponent, CarsSeachComponent, Loader], 25 | providers:[ 26 | HTTP_PROVIDERS, XHRBackend, BaseRequestOptions, 27 | provide(Http, {useFactory: (backend, defaultOptions) => { 28 | return new ExtHttp(backend, defaultOptions); 29 | }, deps: [XHRBackend, BaseRequestOptions]}), 30 | CARS_MODULE_PROVIDERS, API_SERVICE_PROVIDERS //todo: rename 31 | ], 32 | styles:[` 33 | .cmn-t-underline { 34 | position: relative; 35 | color: #ff3296; 36 | height: 5px; 37 | } 38 | .cmn-t-underline:after { 39 | display: block; 40 | position: absolute; 41 | left: 0px; 42 | top: 0px; 43 | width: 0; 44 | height: 3px; 45 | background-color: #3B678E; 46 | content: ""; 47 | width: 100%; 48 | } 49 | `], 50 | 51 | 52 | }) 53 | @RouteConfig([ 54 | { path: '/', as: 'HomeSearch', component: CarsHomeComponent, useAsDefault: true }, 55 | { path: '/seach/:manufacter/:year/:price/', as: 'SearchList', component: CarsSeachComponent }, 56 | { path: '/details/:id', as: 'Details', component: CarDetailsComponent }, 57 | ]) 58 | export class CarSearchComponent { 59 | constructor() { } 60 | } 61 | -------------------------------------------------------------------------------- /client/app/modules/all_in_one/cars.module.ts: -------------------------------------------------------------------------------- 1 | import {FilterService} from "./filterController.service"; 2 | import {SearchParamsController} from "./searchParamsController.service"; 3 | import {PageController} from "./pageController.service"; 4 | 5 | 6 | export * from "./filterController.service"; 7 | export * from "./searchParamsController.service"; 8 | export * from "./pageController.service"; 9 | 10 | export var CARS_MODULE_PROVIDERS: Array = [ 11 | FilterService, SearchParamsController, PageController 12 | ]; -------------------------------------------------------------------------------- /client/app/modules/all_in_one/carsSearch.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter, AfterViewInit, ViewQuery, QueryList, OnInit} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/common'; 3 | import {ApiService} from '../../services/api.service.ts'; 4 | import {ROUTER_DIRECTIVES, RouteParams, Router, OnReuse, ComponentInstruction} from 'angular2/router'; 5 | import * as filters from './filters/filterItems/filters'; 6 | 7 | import {CarFilterPanelComponent} from './carFilterPanel.component' 8 | import {CarsListComponent} from './carList.component'; 9 | import {CarsSearchPanelComponent} from './carSearchPanel.component'; 10 | import {PagePanelComponent} from './searchPage.component'; 11 | import {PageController} from './pageController.service'; 12 | // 13 | import * as Rx from 'rxjs'; 14 | 15 | import {FilterService} from './filterController.service'; 16 | import {SearchParamsController} from './searchParamsController.service' 17 | @Component({ 18 | selector: 'carSeach', 19 | template: ` 20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 | 31 |
32 | 40 |
41 |
42 |
43 | `, 44 | directives: [ 45 | CORE_DIRECTIVES, 46 | ROUTER_DIRECTIVES, 47 | CarsListComponent, 48 | CarFilterPanelComponent, 49 | CarsSearchPanelComponent, 50 | PagePanelComponent 51 | ] 52 | }) 53 | // Root component to contain filters and finded cars list 54 | export class CarsSeachComponent implements OnReuse, OnInit { 55 | cars: Array = []; 56 | totalCount: any; 57 | limit: any; 58 | page: any; 59 | constructor( 60 | private apiService: ApiService, 61 | private router: Router, 62 | private params: RouteParams, 63 | private filterService: FilterService, 64 | private searchParamsContr: SearchParamsController, 65 | private pageContr: PageController) { } 66 | 67 | routerCanReuse() { 68 | return true; 69 | } 70 | 71 | routerOnReuse(instruction: ComponentInstruction) { 72 | var queryObject = Object.assign({}, 73 | this.filterService.createFromRouteParams(instruction.params), 74 | this.searchParamsContr.createFromRouteParams(instruction.params)); 75 | console.info('Search object: ', queryObject); 76 | this.seach(queryObject); 77 | this.limit = queryObject.limit; 78 | this.page = queryObject.page; 79 | } 80 | ngOnInit() { 81 | var queryObject = Object.assign({}, 82 | this.filterService.createFromRouteParams(this.params.params), 83 | this.searchParamsContr.createFromRouteParams(this.params.params)); 84 | this.seach(queryObject); 85 | this.limit = queryObject.limit; 86 | this.page = queryObject.page; 87 | } 88 | 89 | seach(filter) { 90 | this.apiService 91 | .seachCars(filter) 92 | .subscribe((result: any) => { 93 | this.cars = result.cars; 94 | this.totalCount = +result.count; 95 | }, (err) => { console.error(err) }) 96 | } 97 | 98 | 99 | filterChanged() { 100 | var route = Object.assign({}, 101 | this.filterService.convertToRouteParams(), 102 | this.searchParamsContr.convertToRouteParams(), 103 | this.pageContr.convertToRouteParams(), 104 | { page: 0 }); // return to the first page, when filter changing 105 | this.router.navigate(['SearchList', route]) 106 | } 107 | 108 | searchViewChanged() { 109 | var route = Object.assign({}, 110 | this.filterService.convertToRouteParams(), 111 | this.searchParamsContr.convertToRouteParams(), 112 | this.pageContr.convertToRouteParams()); 113 | this.router.navigate(['SearchList', route]) 114 | } 115 | 116 | pageChanged() { 117 | var route = Object.assign({}, 118 | this.filterService.convertToRouteParams(), 119 | this.searchParamsContr.convertToRouteParams(), 120 | this.pageContr.convertToRouteParams()); 121 | this.router.navigate(['SearchList', route]) 122 | } 123 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/config/carColors.ts: -------------------------------------------------------------------------------- 1 | export function allColors() { 2 | return ["red", "green", "white", "silver", "yellow", "black", "purple", "blue", "brown", "orange"] 3 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/controls/colorSelector.component.ts: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import {Component, EventEmitter} from 'angular2/core'; 4 | import { 5 | CORE_DIRECTIVES, 6 | FORM_DIRECTIVES, 7 | Validators, 8 | Control, 9 | ControlGroup, 10 | NgControl, 11 | ControlValueAccessor} from 'angular2/common'; 12 | import {allColors} from "../config/carColors"; 13 | 14 | @Component({ 15 | selector: 'colorPicker[ngControl]', 16 | directives: [CORE_DIRECTIVES, FORM_DIRECTIVES], 17 | template: ` 18 |
19 |
20 |
24 |
25 |
26 |
27 | `, 28 | styles: [` 29 | .color-picker{ 30 | width: 33px; 31 | height: 33px; 32 | float: left; 33 | margin: 0 5px 5px 0; 34 | border-radius: 4px; 35 | border: 1px solid rgba(0,0,0,.2); 36 | cursor: pointer; 37 | } 38 | .color-picker.active{ 39 | background-image: url('/build/images/check.png'); 40 | background-repeat: no-repeat; 41 | background-position: center; 42 | } 43 | `] 44 | }) 45 | 46 | export class ColorPickerControl implements ControlValueAccessor { 47 | 48 | colors: Array = []; 49 | onChange: EventEmitter = new EventEmitter(); 50 | onTouched: any; 51 | constructor(private cd: NgControl) { 52 | cd.valueAccessor = this; 53 | } 54 | selectColor(index) { 55 | this.colors[index].active = !this.colors[index].active; 56 | var activeColors = this.colors.filter((color) => color.active); 57 | this.onChange.next(activeColors.map((color)=>color.color)); 58 | } 59 | 60 | /** 61 | * ControlValueAccessor 62 | */ 63 | writeValue(value: Array) { 64 | if (value) { 65 | this.colors = allColors().map((color) => { 66 | return value.indexOf(color) > -1 67 | ? { active: true, color: color } 68 | : { active: false, color: color } 69 | }) 70 | } else { 71 | this.colors = allColors().map((color) => { 72 | return { 73 | active: false, 74 | color: color 75 | } 76 | }); 77 | } 78 | 79 | } 80 | registerOnChange(fn): void { 81 | this.onChange.subscribe(fn); 82 | } 83 | registerOnTouched(fn): void { 84 | this.onTouched = fn; 85 | } 86 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/converters/color.ts: -------------------------------------------------------------------------------- 1 | import {Converter} from "../decorators/converter.decorator"; 2 | import {allColors} from "../config/carColors"; 3 | @Converter({ converterName: `color` }) 4 | export class ColorConverter { 5 | // convert from route to filter value 6 | // "red,green" --> ["red", "green"] 7 | static convert(value): any { 8 | value = value[0]; 9 | if (!value) { 10 | return { 11 | colors: [] 12 | } 13 | } else { 14 | var params: Array = value.split(','); 15 | return { colors: params }; 16 | } 17 | } 18 | 19 | // ["red", "green"] --> "red,green" 20 | static convertBack(value): any { 21 | value = value.colors; 22 | 23 | var res = (value.length>0) 24 | ? { colors: 25 | 26 | value.join()} 27 | : { colors: "" }; 28 | console.log(res) 29 | return res; 30 | } 31 | 32 | 33 | 34 | // how to present filter value to user 35 | static convertToView(value) { 36 | return `color`; 37 | } 38 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/converters/converters.ts: -------------------------------------------------------------------------------- 1 | export * from "./manufacter.ts"; 2 | export * from "./year.ts"; 3 | export * from "./price.ts"; 4 | export * from "./milage.ts"; 5 | export * from "./color.ts"; -------------------------------------------------------------------------------- /client/app/modules/all_in_one/converters/manufacter.ts: -------------------------------------------------------------------------------- 1 | import {Converter} from "../decorators/converter.decorator"; 2 | 3 | @Converter({ converterName: `manufacter` }) 4 | export class ManufacterConverter { 5 | static convert(value) { 6 | value = value[0]; 7 | if (!value) return { 8 | manufacter: null, 9 | model: null 10 | } 11 | var params = value.split(','); 12 | if (params[0] === "any" || !params[0]) 13 | return { 14 | manufacter: '', 15 | model: '' 16 | } 17 | if (params[1] === "any" || !params[1]) { 18 | return { 19 | manufacter: params[0], 20 | model: '' 21 | } 22 | } else { 23 | return { 24 | manufacter: params[0], 25 | model: params[1] 26 | } 27 | } 28 | } 29 | 30 | static convertBack(value): any { 31 | if (value.manufacter && value.model) { 32 | return { manufacter: `${value.manufacter},${value.model}` }; 33 | } 34 | if (!value.manufacter) 35 | return { manufacter: `any` }; 36 | if (!value.model) 37 | return { manufacter: `${value.manufacter},any` }; 38 | } 39 | 40 | static convertToView(value) { 41 | if (value.manufacter && value.model) 42 | return `${value.manufacter},${value.model}`; 43 | if (!value.manufacter) 44 | return `any`; 45 | if (!value.model) 46 | return `${value.manufacter},any`; 47 | } 48 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/converters/milage.ts: -------------------------------------------------------------------------------- 1 | import {Converter} from "../decorators/converter.decorator"; 2 | 3 | @Converter({ converterName: `milage` }) 4 | export class MilageConverter { 5 | // convert from route to filter value 6 | static convert(value) { 7 | return { 8 | milageFrom: value[0], 9 | milageUp: value[1] 10 | } 11 | } 12 | //convert filter value to route params 13 | static convertBack(value): any { 14 | return { 15 | milageFrom: value.milageFrom || '', 16 | milageUp: value.milageUp || '' 17 | }; 18 | } 19 | // how to present filter value to user 20 | static convertToView(value) { 21 | var from, up; 22 | value.milageFrom ? from = value.milageFrom : from = "any"; 23 | value.milageUp ? up = value.milageUp : up = "any"; 24 | return `milage: ${from}..${up}`; 25 | } 26 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/converters/price.ts: -------------------------------------------------------------------------------- 1 | import {Converter} from "../decorators/converter.decorator"; 2 | 3 | @Converter({ converterName: `price` }) 4 | export class PriceConverter { 5 | //convert from route to filter value 6 | static convert(value) { 7 | value = value[0]; 8 | if (!value) return { 9 | priceFrom: null, 10 | priceUp: null 11 | } 12 | var params = value.split('..'); 13 | var up, from; 14 | params[0] === "any" || !params[0] ? from = null : from = parseInt(params[0]); 15 | params[1] === "any" || !params[1] ? up = null : up = parseInt(params[1]); 16 | return { 17 | priceFrom: from, 18 | priceUp: up 19 | } 20 | } 21 | //convert filter value to route 22 | static convertBack(value): any { 23 | var from = value.priceFrom || "any"; 24 | var up = value.priceUp || "any"; 25 | return { price: `${from}..${up}` }; 26 | } 27 | // how to present filter value to user 28 | static convertToView(value) { 29 | var from, up; 30 | value.priceFrom ? from = value.priceFrom : from = "any"; 31 | value.priceUp ? up = value.priceUp : up = "any"; 32 | return `price: ${from}..${up}`; 33 | } 34 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/converters/year.ts: -------------------------------------------------------------------------------- 1 | import {Converter} from "../decorators/converter.decorator"; 2 | 3 | @Converter({ converterName: `year` }) 4 | export class YearConverter { 5 | static convert(value) { 6 | value = value[0]; 7 | if (!value) return { 8 | yearFrom: null, 9 | yearUp: null 10 | } 11 | var params = value.split('..'); 12 | var up, from; 13 | params[0] === "any" || !params[0] ? from = "" : from = parseInt(params[0]); 14 | params[1] === "any" || !params[1] ? up = "" : up = parseInt(params[1]); 15 | return { 16 | yearFrom: from, 17 | yearUp: up 18 | } 19 | } 20 | 21 | static convertBack(value): any { 22 | var from = value.yearFrom || "any"; 23 | var up = value.yearUp || "any"; 24 | return { year: `${from}..${up}` }; 25 | } 26 | 27 | static convertToView(value) { 28 | var from, up; 29 | value.yearFrom ? from = value.yearFrom : from = "any"; 30 | value.yearUp ? up = value.yearUp : up = "any"; 31 | return `year: ${from}..${up}`; 32 | } 33 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/decorators/converter.decorator.ts: -------------------------------------------------------------------------------- 1 | import * as converters from '../converters/converters'; 2 | 3 | /** 4 | * Class decorator to assign decorated converter for the appropriate filter 5 | */ 6 | export function Converter(converter: any) { 7 | return (target: any) => { 8 | target.converterName = converter.converterName; 9 | } 10 | } 11 | /** 12 | * Function or property decorator used to convert filter value to "presentable" string 13 | * ex. {manufacter: "BMW", model: "3-series"} --> "BMW,3-series" 14 | * conversion method should be defined in appropriate converter's "convertToView" function 15 | */ 16 | export function convert(target: any, propertyName: string, descriptor: TypedPropertyDescriptor) { 17 | 18 | if (descriptor.value != null) { 19 | _convertFilterValue_func(descriptor, target); 20 | } 21 | else if (descriptor.get != null) { 22 | _convertFilterValue_acc(descriptor, target); 23 | } 24 | else { 25 | throw "Only put a convert decorator on a method or get accessor."; 26 | } 27 | } 28 | 29 | function _convertFilterValue_func(descriptor: TypedPropertyDescriptor, target) { 30 | const originalValue = descriptor.value; 31 | let convertedValue: string = `Converter not found`; 32 | 33 | descriptor.value = function(...args: any[]) { 34 | var filterValue = originalValue.apply(this, args); 35 | let converter = _findConverterByName(target.filterName); 36 | if (!!converter) { 37 | convertedValue = converter.convertToView(filterValue); 38 | } 39 | return convertedValue; 40 | }; 41 | } 42 | 43 | function _convertFilterValue_acc(descriptor: TypedPropertyDescriptor, target) { 44 | const originalGet = descriptor.get; 45 | const originalSet = descriptor.set; 46 | let convertedValue: string = `Converter not found`; 47 | 48 | descriptor.get = function(...args: any[]) { 49 | var filterValue = originalGet.apply(this, args); 50 | let converter = _findConverterByName(target.filterName); 51 | if (!!converter) { 52 | convertedValue = converter.convertToView(filterValue); 53 | } 54 | return convertedValue; 55 | }; 56 | 57 | if (descriptor.set != null) { 58 | descriptor.set = function(...args: any[]) { 59 | return originalSet.apply(this, args); 60 | }; 61 | } 62 | } 63 | 64 | function _findConverterByName(name) { 65 | var converter = null; 66 | var keys = Object.keys(converters) 67 | keys.forEach((converterName) => { 68 | var converterFunc = converters[converterName]; 69 | if (converterFunc.converterName === name) { 70 | converter = converterFunc 71 | } 72 | }) 73 | return converter; 74 | } 75 | -------------------------------------------------------------------------------- /client/app/modules/all_in_one/decorators/decorators.ts: -------------------------------------------------------------------------------- 1 | export * from "./filter.decorator.ts"; 2 | export * from "./converter.decorator.ts"; 3 | 4 | -------------------------------------------------------------------------------- /client/app/modules/all_in_one/decorators/filter.decorator.ts: -------------------------------------------------------------------------------- 1 | import * as converters from '../converters/converters'; 2 | 3 | /** 4 | * Class decorator, adds static methods 'convert' and 'convertBack' and requiredParams property to any class, 5 | * takes this functions from the appropriate converter. 6 | * 7 | * params - required router params to get value(s) from, 8 | * convert - how to convert this params into filter's working value(s) 9 | * convertBack - accordingly, turn filter value into route parameter. 10 | * 11 | * ### Example 12 | * 13 | * @Filter({ filterName: "my-filter-name", params: ['my-param'] }) 14 | * 15 | */ 16 | export function Filter(filter: any) { 17 | return (target: any) => { 18 | target.filterName = filter.filterName; 19 | target.requiredParams = filter.params || []; 20 | target.prototype.filterName = filter.filterName; 21 | let converter = _findConverterByName(filter.filterName); 22 | if (converter) { 23 | target.convert = converter.convert; 24 | target.convertBack = converter.convertBack; 25 | } 26 | } 27 | } 28 | 29 | function _findConverterByName(name) { 30 | var converter = null; 31 | var keys = Object.keys(converters) 32 | keys.forEach((converterName) => { 33 | var converterFunc = converters[converterName]; 34 | if (converterFunc.converterName === name) { 35 | converter = converterFunc 36 | } 37 | }) 38 | return converter; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /client/app/modules/all_in_one/filterController.service.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Injectable} from 'angular2/core'; 3 | import * as Rx from 'rxjs'; 4 | import * as filters from './filters/filterItems/filters'; 5 | 6 | export interface IFilterModel { 7 | manufacter?: string; 8 | model?: string; 9 | year?: string; 10 | price?: number; 11 | yearUp?: number; 12 | yearFrom?: number; 13 | priceUp?: number; 14 | priceFrom?: number; 15 | limit?: number; 16 | sort?: string; 17 | page?: number; 18 | colors?: Array; 19 | milageFrom?: number; 20 | milageUp?: number; 21 | } 22 | export class FilterModel implements IFilterModel { 23 | manufacter: string; 24 | model: string; 25 | year: string; 26 | price: number; 27 | yearUp: number; 28 | yearFrom: number; 29 | priceUp: number; 30 | priceFrom: number; 31 | colors: Array; 32 | milageFrom: number; 33 | milageUp: number; 34 | 35 | constructor() { 36 | this.manufacter = ""; 37 | this.priceUp = 0; 38 | this.priceFrom = 0; 39 | this.model = ''; 40 | this.yearUp = 0; 41 | this.yearFrom = 0; 42 | this.colors = []; 43 | this.milageFrom = 0; 44 | this.milageUp = 0; 45 | } 46 | } 47 | /** 48 | * Manages filter value, creates this value gaining all values from filters 49 | */ 50 | @Injectable() 51 | export class FilterService { 52 | private _currentState: FilterModel = new FilterModel(); 53 | filtersSubject: Rx.Subject> = new Rx.Subject(); 54 | filterStateSubject: Rx.Subject = new Rx.Subject(); 55 | filterDefaults: any; 56 | _sortState: string; 57 | _limit: number; 58 | _page: number; 59 | _resultsCountState: string; 60 | _filters: Array = []; 61 | constructor() { } 62 | 63 | public set filter(value: IFilterModel) { 64 | Object.assign(this._currentState, value); 65 | this.filterStateSubject.next(this._currentState); 66 | } 67 | public get filter(): IFilterModel { 68 | return this._currentState; 69 | } 70 | /** 71 | * Converts route params to the filter state passing them through all the filters, 72 | * which perform conversion to working values. 73 | */ 74 | public createFromRouteParams(params): any { 75 | this._filters = []; 76 | let filter = {}; 77 | for (let type in filters) { 78 | // filterName is static property, added by decorator, 79 | // as like as 'convert' and 'convertBack' functions, 80 | // so applied filter acts like component and the value converter at the same time. 81 | if (!!filters[type].filterName) { 82 | let filterName = filters[type].filterName; 83 | // if filter was decorated properly, it contains static memebers (convert, convertBack, requiredParams) 84 | if (typeof filters[type].convert != 'function' || !filters[type].requiredParams) { 85 | console.error(`There is no converter for filter ${type}, or it has wrong config`); 86 | } else { 87 | var filterParams = []; 88 | filters[type].requiredParams.forEach((paramName) => { 89 | //get params from route 90 | filterParams.push(params[paramName]); 91 | }); 92 | // convert them to filter value 93 | // ex. "2000..2005" --> {yearFrom: 2000, yearUp: 2005}, 94 | // "red, green" --> ["red", "green"] 95 | let filterValue = filters[type].convert(filterParams); 96 | // init filter with given values !! 97 | Object.assign(filter, filterValue); 98 | this._filters.push({ name: filterName, value: filterValue }); 99 | } 100 | } else { 101 | console.error(`Filter ${type} should be decorated with @Filter({filterName:'myFilterName'})`); 102 | } 103 | } 104 | this.filter = filter; 105 | this.filtersSubject.next(this._filters); 106 | return this.filter; 107 | } 108 | /** 109 | * Backward operation. Converts filter state to route params 110 | */ 111 | public convertToRouteParams(): any { 112 | var route = {}; 113 | // filters defined 114 | for (let type in filters) { 115 | if (!!filters[type].filterName) { 116 | let filterName = filters[type].filterName; 117 | Object.assign(route, filters[type].convertBack(this.filter)); 118 | } else { 119 | console.error(`Filter ${type} should be decorated with @Filter({filterName:'myFilterName'})`); 120 | } 121 | } 122 | return route; 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /client/app/modules/all_in_one/filters/filterItems/colorFilter.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter, Input, Output, AfterViewInit} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, Control, ControlGroup} from 'angular2/common'; 3 | import {Filter, convert} from "../../decorators/decorators"; 4 | 5 | import {ColorPickerControl} from "../../controls/colorSelector.component"; 6 | 7 | @Component({ 8 | selector: 'color-filter', 9 | template: ` 10 |
11 |
12 |
13 |
14 | 15 | 19 | 20 |
21 |
22 |
23 |
24 | 25 | 26 | `, 27 | directives: [CORE_DIRECTIVES, ColorPickerControl] 28 | }) 29 | @Filter({ filterName: "color", params: ['colors'] }) 30 | export class ColorFilterComponent implements AfterViewInit { 31 | @Input() 32 | active: boolean; 33 | @Input() 34 | filterValue: any = {}; 35 | @Output() 36 | changed: EventEmitter = new EventEmitter(); 37 | colors: Control; 38 | form: ControlGroup; 39 | 40 | constructor() { 41 | this.colors = new Control(); 42 | this.form = new ControlGroup({ 43 | colors: this.colors, 44 | }); 45 | } 46 | ngAfterViewInit() { 47 | this.form.valueChanges 48 | .distinctUntilChanged() 49 | .subscribe(value=> { 50 | this.changed.next(value); 51 | }) 52 | } 53 | @convert 54 | viewValue() { 55 | return this.filterValue; 56 | } 57 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/filters/filterItems/filters.ts: -------------------------------------------------------------------------------- 1 | export * from "./manufacterFilter.component.ts"; 2 | export * from "./yearFilter.component.ts"; 3 | export * from "./priceFilter.component.ts"; 4 | export * from "./milageFilter.component.ts"; 5 | export * from "./colorFilter.component.ts"; 6 | -------------------------------------------------------------------------------- /client/app/modules/all_in_one/filters/filterItems/manufacterFilter.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter, Input, Output, AfterViewInit} from 'angular2/core'; 2 | import {CORE_DIRECTIVES} from 'angular2/common'; 3 | import {FORM_DIRECTIVES, Control} from 'angular2/common'; 4 | import {FilterService, FilterModel} from '../../filterController.service'; 5 | import {ROUTER_DIRECTIVES, RouteParams} from 'angular2/router'; 6 | import {ApiService} from '../../../../services/api.service.ts'; 7 | import * as rx from 'rxjs'; 8 | import {Filter, convert} from "../../decorators/decorators"; 9 | 10 | @Component({ 11 | selector: 'manufacterWrapper', 12 | template: ` 13 |
14 |
15 |
16 |
{{viewValue}}
17 | Reset 18 | Change 19 |
20 |
21 |
22 |
23 |
24 | 25 | 29 |
30 |
31 | 32 | 36 |
37 | 38 | 39 |
40 |
41 |
42 |
43 |
`, 44 | styles: [` 45 | .link{ 46 | color: #337ab7; 47 | text-decoration: underline; 48 | cursor: pointer; 49 | font-size:14px; 50 | } 51 | `], 52 | directives: [CORE_DIRECTIVES, FORM_DIRECTIVES] 53 | }) 54 | 55 | @Filter({ filterName: "manufacter", params: ['manufacter'] }) 56 | export class ManufacterFilterComponent implements AfterViewInit { 57 | @Input() 58 | active: boolean; 59 | @Input() 60 | filterValue: any = { 61 | manufacter: '', 62 | model: '' 63 | }; 64 | @Output() 65 | changed: EventEmitter = new EventEmitter(); 66 | manufacter: Control; 67 | model: Control; 68 | 69 | carManufacters: any[]; 70 | models: any[]; 71 | loading: boolean = false; 72 | opened: boolean = false; 73 | valueView: string; 74 | oldValue: any; 75 | subscription: rx.Subscription; 76 | 77 | constructor(private apiService: ApiService) { 78 | this.manufacter = new Control(this.filterValue.manufacter); 79 | this.model = new Control(this.filterValue.model); 80 | } 81 | 82 | ngAfterViewInit() { 83 | this.manufacter 84 | .valueChanges 85 | .filter((value) => !!value) 86 | .do((value) => { 87 | this.loading = true; 88 | }) 89 | .flatMap(value=> { // and require new model to fill select 90 | return this.apiService 91 | .getCarsByManufacter({ manufacter: value }); 92 | }) 93 | .subscribe((value: any) => { 94 | this.loading = false; 95 | if (value && value.models) { 96 | this.models = value.models; // set manufacter models taken from server 97 | if (!this.models.some(model=> model.name === this.filterValue.model)) { 98 | this.model.updateValue(null); // if manufacter changed, reset current value 99 | this.filterValue.model = null; 100 | } 101 | } 102 | }); 103 | } 104 | ngOnDestroy() { 105 | if (this.subscription) { 106 | this.subscription.unsubscribe(); 107 | } 108 | } 109 | 110 | @convert 111 | get viewValue() { 112 | return this.filterValue; 113 | } 114 | 115 | openFilter() { 116 | this.manufacter.updateValue(this.filterValue.manufacter); 117 | this.model.updateValue(this.filterValue.model); 118 | this.opened = true; 119 | this.oldValue = Object.assign({}, this.filterValue); 120 | if (!this.carManufacters) { 121 | this.subscription = this.apiService 122 | .initCarsDefaults() 123 | .subscribe((value: any) => { 124 | if (value && value.manufacters) 125 | this.carManufacters = value.manufacters; 126 | }) 127 | } 128 | } 129 | 130 | applyFilter() { 131 | this.changed.next({ 132 | model: this.filterValue.model, 133 | manufacter: this.filterValue.manufacter 134 | }); 135 | this.opened = false; 136 | } 137 | 138 | closeFilter() { 139 | this.filterValue = this.oldValue; 140 | this.opened = false; 141 | } 142 | 143 | resetFilter() { 144 | this.filterValue.model = null; 145 | this.filterValue.manufacter = null; 146 | this.changed.next({ 147 | model: this.filterValue.model, 148 | manufacter: this.filterValue.manufacter 149 | }) 150 | } 151 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/filters/filterItems/milageFilter.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter, Input, Output, AfterViewInit, ViewEncapsulation} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, Control, ControlGroup} from 'angular2/common'; 3 | import {Filter, convert} from "../../decorators/decorators"; 4 | 5 | @Component({ 6 | selector: 'milage-filter', 7 | template: ` 8 | 9 |
10 |
11 |
12 | 13 |
14 |
15 | 23 |
24 | 25 |
26 | 34 |
35 |
36 |
37 | 38 | 39 | 40 | `, 41 | styles: [` 42 | .moved-left{ 43 | padding-right:7px; 44 | } 45 | .moved-right{ 46 | padding-left:7px; 47 | } 48 | @media (max-width: 992px) { 49 | .moved-right, 50 | .moved-left{ 51 | padding-left:15px; 52 | padding-right:15px; 53 | } 54 | } 55 | 56 | `], 57 | directives: [CORE_DIRECTIVES], 58 | encapsulation: ViewEncapsulation.None 59 | }) 60 | @Filter({ filterName: "milage", params: ['milageFrom', 'milageUp'] }) 61 | export class MilageFilterComponent implements AfterViewInit { 62 | @Input() 63 | active: boolean; 64 | @Input() 65 | filterValue: any = {}; 66 | @Output() 67 | changed: EventEmitter = new EventEmitter(); 68 | milageFrom: Control; 69 | milageUp: Control; 70 | form: ControlGroup; 71 | milagesUp: Array = []; 72 | milagesFrom: Array = []; 73 | constructor() { 74 | this.milageFrom = new Control(); 75 | this.milageUp = new Control(); 76 | this.form = new ControlGroup({ 77 | milageFrom: this.milageFrom, 78 | milageUp: this.milageUp 79 | }); 80 | 81 | } 82 | ngAfterViewInit() { 83 | this.form.valueChanges 84 | .distinctUntilChanged() 85 | .debounceTime(500) 86 | .subscribe(value=> { 87 | this.changed.next(value); 88 | }) 89 | } 90 | @convert 91 | viewValue() { 92 | return this.filterValue; 93 | } 94 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/filters/filterItems/priceFilter.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter, Input, Output, AfterViewInit} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, Control, ControlGroup} from 'angular2/common'; 3 | import {Filter, convert} from "../../decorators/decorators"; 4 | 5 | @Component({ 6 | selector: 'priceWrapper', 7 | template: ` 8 |
9 |
10 |
11 | 12 |
13 |
14 | 22 |
23 |
24 | 32 |
33 |
34 |
35 | `, 36 | directives: [CORE_DIRECTIVES] 37 | }) 38 | @Filter({ filterName: "price", params: ['price'] }) 39 | export class PriceFilterComponent implements AfterViewInit { 40 | @Input() 41 | active: boolean; 42 | @Input() 43 | filterValue: any = {}; 44 | @Output() 45 | changed: EventEmitter = new EventEmitter(); 46 | priceFrom: Control; 47 | priceUp: Control; 48 | form: ControlGroup; 49 | pricesUp: Array = []; 50 | pricesFrom: Array = []; 51 | constructor() { 52 | this.priceFrom = new Control(); 53 | this.priceUp = new Control(); 54 | this.form = new ControlGroup({ 55 | priceFrom: this.priceFrom, 56 | priceUp: this.priceUp 57 | }); 58 | 59 | } 60 | ngAfterViewInit() { 61 | this.form.valueChanges 62 | .distinctUntilChanged() 63 | .debounceTime(500) 64 | .subscribe(value=> { 65 | this.changed.next(value); 66 | }) 67 | } 68 | @convert 69 | viewValue() { 70 | return this.filterValue; 71 | } 72 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/filters/filterItems/yearFilter.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter, Output, Input, AfterViewInit} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, Control, FORM_DIRECTIVES, ControlGroup} from 'angular2/common'; 3 | import {Filter, convert} from "../../decorators/decorators"; 4 | 5 | 6 | @Component({ 7 | selector: 'yearWrapper', 8 | template: ` 9 |
10 |
11 |
12 | 13 |
14 |
15 | 26 |
27 |
28 | 39 |
40 |
41 |
42 | `, 43 | inputs: ["active"], 44 | directives: [CORE_DIRECTIVES, FORM_DIRECTIVES] 45 | }) 46 | @Filter({ filterName: "year", params: ['year'] }) 47 | export class YearFilterComponent implements AfterViewInit { 48 | @Input() 49 | active: boolean; 50 | @Input() 51 | filterValue: any = { 52 | yearUp: '', 53 | yearFrom: '' 54 | }; 55 | @Output() 56 | changed: EventEmitter = new EventEmitter(); 57 | yearFrom: Control; 58 | yearUp: Control; 59 | form: ControlGroup; 60 | yearsUp: Array = []; 61 | yearsFrom: Array = []; 62 | constructor() { 63 | this.yearFrom = new Control(""); 64 | this.yearUp = new Control(""); 65 | this.form = new ControlGroup({ 66 | yearFrom: this.yearFrom, 67 | yearUp: this.yearUp 68 | }); 69 | for (let i = 1980; i <= new Date().getFullYear(); i++) { 70 | this.yearsUp.push(i); 71 | this.yearsFrom.push(i); 72 | } 73 | } 74 | 75 | ngAfterViewInit() { 76 | this.form.valueChanges 77 | .distinctUntilChanged() 78 | .subscribe(value=> { 79 | this.changed.next(value); 80 | }) 81 | } 82 | 83 | @convert 84 | viewValue() { 85 | return this.filterValue; 86 | } 87 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/filters/filterWrapper.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter} from 'angular2/core'; 2 | import {CORE_DIRECTIVES} from 'angular2/common'; 3 | import {DynamicComponentLoader, ElementRef, OnInit} from 'angular2/core'; 4 | import * as filters from './filterItems/filters'; 5 | 6 | @Component({ 7 | selector: 'filterWrapper', 8 | template: ` 9 |
10 |
11 |
12 | `, 13 | inputs: ["filterValue", "filterName"], 14 | outputs: ["changed"], 15 | directives: [CORE_DIRECTIVES] 16 | }) 17 | export class FilterWrapperComponent implements OnInit { 18 | filterValue: any; 19 | filterName: string; 20 | changed: EventEmitter = new EventEmitter(); 21 | constructor(private dcl: DynamicComponentLoader, private elementRef: ElementRef) {} 22 | ngOnInit() { 23 | for (let type in filters) { 24 | if (filters[type].filterName === this.filterName) { 25 | this.dcl 26 | .loadIntoLocation(filters[type], this.elementRef, 'filterContainer') 27 | .then((component) => { 28 | component.instance.filterValue = this.filterValue; 29 | //todo experiment with params in html code 30 | component.instance.changed.subscribe((value) => { 31 | this.changed.next(value); 32 | }); 33 | }); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/pageController.service.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Injectable} from 'angular2/core'; 3 | import * as Rx from 'rxjs'; 4 | import * as filters from './filters/filterItems/filters'; 5 | 6 | export interface IPage { 7 | page?: number; 8 | } 9 | export class Page implements IPage { 10 | page: number; 11 | constructor() { 12 | this.page = 0; 13 | } 14 | } 15 | 16 | @Injectable() 17 | export class PageController { 18 | private _page: Page = new Page(); 19 | PageSubject: Rx.Subject = new Rx.Subject(); 20 | 21 | constructor() {} 22 | 23 | public set page(value: IPage) { 24 | Object.assign(this._page, value); 25 | } 26 | public get page(): IPage { 27 | return this._page; 28 | } 29 | public createFromRouteParams(params): any { 30 | this.page = { 31 | page: +params["page"] || 0 32 | } 33 | this.PageSubject.next(this.page); 34 | return this.page; 35 | } 36 | public convertToRouteParams(): any { 37 | return this.page; 38 | } 39 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/searchPage.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Output, EventEmitter, OnInit, AfterViewInit} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES, ControlGroup, Control} from 'angular2/common'; 3 | import {ROUTER_DIRECTIVES, RouteParams, OnReuse, ComponentInstruction} from 'angular2/router'; 4 | import {CarItemComponent} from './carListItem.component' 5 | import {FilterService} from './filterController.service'; 6 | import {PageController} from './pageController.service'; 7 | import {PaginationControlsCmp} from '../../components/common/pagination/pagination.component'; 8 | 9 | @Component({ 10 | selector: 'searchPage', 11 | template: ` 12 | 13 |
14 | 19 | 20 |
21 | `, 22 | styles: [` 23 | 24 | `], 25 | directives: [CORE_DIRECTIVES, PaginationControlsCmp] 26 | }) 27 | 28 | 29 | // Root component to contain filters and finded cars list 30 | export class PagePanelComponent implements AfterViewInit { 31 | private _totalPages: number; 32 | private _limit: number; 33 | private _currentPage: number; 34 | @Input() 35 | set totalPages(value) { 36 | this._totalPages = value; 37 | }; 38 | get totalPages() { 39 | return this._totalPages; 40 | } 41 | @Input() 42 | set limit(value) { 43 | this._limit = value; 44 | }; 45 | get limit() { 46 | return this._limit; 47 | } 48 | @Input() 49 | set currentPage(value) { 50 | this._currentPage = value; 51 | }; 52 | get currentPage() { 53 | return this._currentPage; 54 | } 55 | 56 | @Output() 57 | changed: EventEmitter = new EventEmitter(); 58 | 59 | 60 | constructor(private pageController: PageController) { 61 | this.pageController 62 | .PageSubject 63 | .subscribe((page) => { 64 | this.currentPage = page.page; 65 | }) 66 | } 67 | ngAfterViewInit() { 68 | } 69 | 70 | 71 | pageChanged(page) { 72 | this.currentPage = page; 73 | this.onSeachParamsChanged({ page: this.currentPage }); 74 | } 75 | onSeachParamsChanged(newValue) { 76 | this.pageController.page = newValue; 77 | this.changed.next(this.pageController.page); 78 | } 79 | } -------------------------------------------------------------------------------- /client/app/modules/all_in_one/searchParamsController.service.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Injectable} from 'angular2/core'; 3 | import * as Rx from 'rxjs'; 4 | import * as filters from './filters/filterItems/filters'; 5 | 6 | export interface ISearchParams { 7 | limit?: number; 8 | sort?: string; 9 | page?: number; 10 | } 11 | export class SearchParams implements ISearchParams { 12 | limit: number; 13 | sort: string; 14 | page: number; 15 | 16 | constructor() { 17 | this.limit = 20; 18 | this.sort = "price"; 19 | this.page = 0; 20 | } 21 | } 22 | 23 | @Injectable() 24 | export class SearchParamsController { 25 | private _currentParams: SearchParams = new SearchParams(); 26 | searchParamsSubject: Rx.Subject = new Rx.Subject(); 27 | 28 | constructor() { } 29 | 30 | public set params(value: ISearchParams) { 31 | Object.assign(this._currentParams, value); 32 | } 33 | public get params(): ISearchParams { 34 | return this._currentParams; 35 | } 36 | public createFromRouteParams(params): any { 37 | this.params = { 38 | sort: params["sort"] || "price", 39 | limit: +params["limit"] || 20, 40 | page: +params["page"] || 0 41 | } 42 | this.searchParamsSubject.next(this.params); 43 | return this.params; 44 | } 45 | public convertToRouteParams(): any { 46 | return this.params; 47 | } 48 | } -------------------------------------------------------------------------------- /client/app/modules/communication/communication.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgZone} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES, Control, FormBuilder, ControlGroup, Validators} from 'angular2/common'; 3 | import {ROUTER_DIRECTIVES, RouteConfig} from 'angular2/router'; 4 | 5 | import {ServiceComComponent, SimpleCommunicationService} from './serviceCom.component'; 6 | import {QueryComComponent} from './queryCom.component'; 7 | import {SubjectsComponent, CommunicationService} from './subjectsCom.component'; 8 | 9 | @Component({ 10 | selector: 'communication', 11 | template: ` 12 | 13 |
14 | 19 |
20 |
21 | 22 | 23 |
24 | 25 | `, 26 | directives: [FORM_DIRECTIVES, CORE_DIRECTIVES, ROUTER_DIRECTIVES], 27 | styles: [ 28 | `.router-link-active { background:cornsilk !important; }` 29 | ], 30 | providers: [SimpleCommunicationService, CommunicationService] 31 | }) 32 | @RouteConfig([ 33 | { path: '/comp', as: 'Comp', component: ServiceComComponent}, 34 | { path: '/subj', as: 'Subj', component: SubjectsComponent }, 35 | { path: '/quer', as: 'Quer', component: QueryComComponent, useAsDefault: true }, 36 | ]) 37 | export class CommunicationExplore {} 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /client/app/modules/communication/queryCom.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, 2 | ViewQuery, 3 | QueryList, 4 | ElementRef, 5 | Input, 6 | EventEmitter, 7 | ViewChildren, 8 | AfterViewInit, 9 | OnInit, 10 | DynamicComponentLoader 11 | } from 'angular2/core'; 12 | import {CORE_DIRECTIVES} from 'angular2/common'; 13 | 14 | @Component({ 15 | selector: 'component3', 16 | template: ` 17 |
18 | COMPONENT 3 19 | 20 | 21 |
22 | `, 23 | directives: [CORE_DIRECTIVES], 24 | outputs: ["changed"] 25 | }) 26 | export class Component3 { 27 | objValue: string; 28 | changed: EventEmitter = new EventEmitter(); 29 | constructor() { } 30 | setValue(value) { 31 | this.objValue = value; 32 | this.changed.next(value); 33 | } 34 | click() { 35 | this.changed.next(""); 36 | } 37 | } 38 | 39 | @Component({ 40 | selector: 'component4', 41 | template: ` 42 |
43 | COMPONENT 4 44 | {{value}} 45 |
46 | `, 47 | directives: [CORE_DIRECTIVES], 48 | 49 | }) 50 | export class Component4 { 51 | // property can't be binded if it's not decorated by @Input decarator 52 | // or defined in ComponentMetadata in "inputs" section 53 | @Input() value: string; 54 | constructor() { } 55 | } 56 | 57 | //************************************* 58 | //--------OPTION 1.1 (One-way binding)--- 59 | //************************************* 60 | /*@Component({ 61 | selector: 'quer-communication', 62 | template: ` 63 |
64 | 65 | 66 |
67 | `, 68 | directives: [CORE_DIRECTIVES, Component3, Component4], 69 | 70 | }) 71 | 72 | export class QueryComComponent { 73 | constructor() { 74 | } 75 | }*/ 76 | 77 | //************************************* 78 | //--------OPTION 1.2 (Events binding)--- 79 | //************************************* 80 | /* 81 | @Component({ 82 | selector: 'quer-communication', 83 | template: ` 84 |
85 | 86 | 87 |
88 | `, 89 | directives: [CORE_DIRECTIVES, Component3, Component4], 90 | 91 | }) 92 | 93 | export class QueryComComponent { 94 | constructor() { 95 | } 96 | } 97 | */ 98 | //**************************************************** 99 | //-OPTION 2.1 (Manually, via query components in view)-- 100 | //**************************************************** 101 | 102 | @Component({ 103 | selector: 'quer-communication', 104 | template: ` 105 |
106 | 107 |
108 | 109 |
110 |
111 | `, 112 | directives: [CORE_DIRECTIVES, Component3, Component4], 113 | 114 | }) 115 | 116 | export class QueryComComponent implements AfterViewInit{ 117 | constructor( 118 | @ViewQuery("comp3") private comp3: QueryList, 119 | @ViewQuery("comp4") private comp4: QueryList) { 120 | } 121 | 122 | ngAfterViewInit(){ 123 | this.comp3.first.changed.subscribe((value)=>{ 124 | this.comp4.toArray().forEach((comp)=>{ 125 | comp.value = value; 126 | }) 127 | }) 128 | } 129 | } 130 | 131 | 132 | 133 | //******************************************************************* 134 | //------OPTION 2.2 Query components with @ViewChildren--------------- 135 | //******************************************************************* 136 | /* 137 | @Component({ 138 | selector: 'quer-communication', 139 | template: ` 140 |
141 | 142 |
143 | 144 |
145 |
146 | `, 147 | directives: [CORE_DIRECTIVES, Component3, Component4], 148 | }) 149 | 150 | export class QueryComComponent implements AfterViewInit { 151 | @ViewChildren(Component4) components: QueryList; 152 | constructor() {} 153 | 154 | ngAfterViewInit() { 155 | // components available here 156 | } 157 | setValue(value) { 158 | console.log(value); 159 | this.components.toArray().forEach((comp) => { 160 | comp.value = value 161 | }); 162 | } 163 | } 164 | */ 165 | //******************************************************************* 166 | //------OPTION 3 Load components dynamically (You just can)---------- 167 | //******************************************************************* 168 | /* 169 | @Component({ 170 | selector: 'quer-communication', 171 | template: ` 172 |
173 | 174 | Dynamically loaded components: 175 |
176 |
177 | `, 178 | directives: [CORE_DIRECTIVES, Component3, Component4], 179 | }) 180 | 181 | export class QueryComComponent implements OnInit { 182 | components: Array = []; 183 | constructor(private dcl: DynamicComponentLoader, private elementRef: ElementRef) { 184 | 185 | } 186 | ngOnInit() { 187 | for (let i = 0; i <= 3; i++) { 188 | this.dcl.loadIntoLocation(Component4, this.elementRef, 'host') 189 | .then((component) => { 190 | this.components.push(component.instance); 191 | }); 192 | } 193 | } 194 | 195 | setValue(value) { 196 | this.components.forEach((comp) => { 197 | comp.value = value; 198 | }) 199 | } 200 | }*/ -------------------------------------------------------------------------------- /client/app/modules/communication/serviceCom.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, ViewQuery, QueryList, ElementRef, forwardRef, Inject, Injectable, Input} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/common'; 3 | import * as Rx from 'rxjs'; 4 | 5 | @Component({ 6 | selector: 'component1', 7 | template: ` 8 |
9 | COMPONENT 1 10 | 11 | 12 |
13 | `, 14 | directives: [CORE_DIRECTIVES] 15 | }) 16 | 17 | export class Component1 { 18 | objValue: string; 19 | constructor( 20 | @Inject(forwardRef(() => SimpleCommunicationService)) private comp: SimpleCommunicationService) {} 21 | 22 | setValue(value) { 23 | this.comp.myObj = value; 24 | } 25 | click() { 26 | this.comp.myObj = ""; 27 | } 28 | } 29 | 30 | 31 | @Component({ 32 | selector: 'component2', 33 | template: ` 34 |
35 | COMPONENT 2 36 | {{value}} 37 |
38 | `, 39 | directives: [CORE_DIRECTIVES] 40 | }) 41 | 42 | export class Component2 { 43 | @Input() value: string; 44 | constructor() {} 45 | } 46 | 47 | @Component({ 48 | selector: 'serv-communication', 49 | template: ` 50 |
51 | 52 | 53 |
54 | `, 55 | directives: [CORE_DIRECTIVES, Component1, Component2] 56 | }) 57 | 58 | export class ServiceComComponent { 59 | constructor(@Inject(forwardRef(() => SimpleCommunicationService)) private comp: SimpleCommunicationService) { } 60 | } 61 | 62 | 63 | 64 | ///////////////////////////////////////////// 65 | @Injectable() 66 | export class SimpleCommunicationService { 67 | _myObj: any; 68 | constructor() { 69 | this._myObj = null; 70 | } 71 | 72 | get myObj() { 73 | return this._myObj; 74 | } 75 | set myObj(value) { 76 | this._myObj = value; 77 | } 78 | } 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /client/app/modules/communication/subjectsCom.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, ViewQuery, QueryList, ElementRef, forwardRef, Inject, Injectable} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/common'; 3 | import * as Rx from 'rxjs'; 4 | 5 | @Component({ 6 | selector: 'component1', 7 | template: ` 8 |
9 | COMPONENT 1 10 | 11 | 12 |
13 | `, 14 | directives: [CORE_DIRECTIVES] 15 | }) 16 | 17 | export class Component1 { 18 | objValue: string; 19 | constructor( @Inject(forwardRef(() => CommunicationService)) private comp: CommunicationService) { } 20 | 21 | setValue(value) { 22 | this.comp.myObj = value; 23 | } 24 | click() { 25 | this.comp.myObj = ""; 26 | } 27 | } 28 | 29 | 30 | @Component({ 31 | selector: 'component2', 32 | template: ` 33 |
34 | COMPONENT 2 35 | {{val}} 36 |
37 | `, 38 | directives: [CORE_DIRECTIVES] 39 | }) 40 | 41 | export class Component2 { 42 | val: string; 43 | constructor( @Inject(forwardRef(() => CommunicationService)) private comp: CommunicationService) { 44 | comp.subject 45 | .debounceTime(500) 46 | .map(value=> value.toUpperCase()) 47 | .subscribe((val) => { 48 | this.val = val; 49 | }) 50 | } 51 | } 52 | 53 | @Component({ 54 | selector: 'subj-communication', 55 | template: ` 56 |
57 | 58 | 59 |
60 | `, 61 | directives: [CORE_DIRECTIVES, FORM_DIRECTIVES, Component1, Component2] 62 | }) 63 | 64 | export class SubjectsComponent { 65 | constructor() { } 66 | } 67 | 68 | @Injectable() 69 | export class CommunicationService { 70 | subject: Rx.Subject = new Rx.Subject(); 71 | _myObj: any; 72 | constructor() { 73 | this._myObj = null; 74 | this.subject.share() 75 | } 76 | 77 | get myObj() { 78 | return this._myObj; 79 | } 80 | set myObj(value) { 81 | this._myObj = value; 82 | this.subject.next(value); 83 | } 84 | } 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /client/app/modules/forms_explore/children.form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | {{child.name}}, {{child.age}} 6 | 7 | 10 |
11 |
12 |
13 | 16 |
17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 42 | 43 | 44 | 45 | 53 | 54 | 62 |
63 |
64 | 65 |
66 |
67 | 68 | 75 |
76 | Name field is required 77 |
78 |
79 |
80 |
81 | 82 |
83 | 84 | 92 |
93 | Age is wrong 94 |
95 |
96 |
97 |
98 |
99 | 100 | 101 | 102 |
103 |
104 |
105 |
106 |
-------------------------------------------------------------------------------- /client/app/modules/forms_explore/children.form.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter, Input} from 'angular2/core'; 2 | import { 3 | CORE_DIRECTIVES, 4 | FORM_DIRECTIVES, 5 | Validators, 6 | Control, 7 | ControlGroup, 8 | NgControl, 9 | ControlValueAccessor} from 'angular2/common'; 10 | import * as appValidators from './customValidators'; 11 | 12 | @Component({ 13 | selector: 'children[ngControl]', 14 | directives: [CORE_DIRECTIVES, FORM_DIRECTIVES], 15 | template: require("./children.form.component.html"), 16 | styles: [` 17 | .child{ 18 | display: inline-block; 19 | background: grey; 20 | padding: 5px; 21 | margin: 2px 5px 2px 0; 22 | color:white; 23 | border-radius: 5px; 24 | } 25 | .child span{ 26 | cursor: pointer; 27 | margin-left:5px; 28 | } 29 | .child.disabled{ 30 | pointer-events: none; 31 | cursor: default; 32 | opacity:0.5; 33 | } 34 | .control-container{ 35 | border: 1px solid #ccc; 36 | border-radius: 3px; 37 | padding: 5px 5px; 38 | } 39 | .form-container{ 40 | margin-top:10px; 41 | } 42 | `] 43 | }) 44 | 45 | export class ChildrenForm implements ControlValueAccessor { 46 | private _disabled: boolean; 47 | @Input() set disabled(value) { 48 | this._disabled = value; 49 | if (value) { 50 | this.onChange.emit([]); 51 | } else { 52 | this.onChange.emit(this.children); 53 | } 54 | } 55 | get disabled() { 56 | return this._disabled; 57 | } 58 | @Input() children: Array = []; 59 | editMode: boolean; 60 | opened: boolean; 61 | submitted: boolean; 62 | //currentChild 63 | child = { 64 | name: "", 65 | age: 1, 66 | gender: "male", 67 | index: null 68 | } 69 | 70 | 71 | childForm: ControlGroup; 72 | onChange: EventEmitter = new EventEmitter(); 73 | onTouched: any; 74 | nameControl: Control = new Control("", Validators.required); 75 | genderControl: Control = new Control("male", Validators.required); 76 | ageControl: Control = new Control(1, Validators.compose([Validators.required, appValidators.minValue(0)])); 77 | 78 | constructor(private cd: NgControl) { 79 | cd.valueAccessor = this; 80 | this.childForm = new ControlGroup({ 81 | name: this.nameControl, 82 | gender: this.genderControl, 83 | age: this.ageControl 84 | }); 85 | } 86 | /** 87 | * Add to list new child 88 | */ 89 | onAdd(value) { 90 | this.submitted = true; 91 | if (this.childForm.valid) { 92 | // Sould be so, but radio input does not reflect here 93 | // this.children.push(value); // <-------------------- 94 | // !!!!!!!!! Temporal until #4285 is solved 95 | this.children.push(this.child); 96 | // todo: remove op above and use form value, not model 97 | 98 | this.onChange.emit(this.children); 99 | this.opened = false; 100 | this._reset(); 101 | } else { 102 | this.cd.control.setErrors({ "smthwrong": true }); 103 | } 104 | } 105 | /** 106 | * Update child data 107 | */ 108 | onUpdate(value) { 109 | if (this.childForm.valid) { 110 | 111 | // Sould be so, but radio input does not reflect here 112 | // this.children[this.child.index] = value; 113 | 114 | //!!!!!!!!! Temporal until #4285 is solved 115 | this.children[this.child.index] = this.child; 116 | // todo: remove op above and use form value, not model 117 | 118 | this.onChange.emit(this.children); 119 | } else { 120 | this.cd.control.setErrors({ "smthwrong": true }); 121 | } 122 | this.opened = false; 123 | } 124 | /** 125 | * Delete child 126 | */ 127 | delete(index) { 128 | console.log(index); 129 | if (this.child.index == index) { 130 | this.opened = false; 131 | } 132 | this.children.splice(index, 1); 133 | this.onChange.emit(this.children); 134 | } 135 | edit(index) { 136 | this.editMode = true; 137 | this.opened = true; 138 | this.child = Object.assign({}, this.children[index], { index: index }); 139 | } 140 | add() { 141 | this.opened = true; 142 | this.editMode = false; 143 | this._reset(); 144 | } 145 | cancel() { 146 | this.opened = false; 147 | this._reset(); 148 | } 149 | 150 | 151 | /** 152 | * ControlValueAccessor 153 | */ 154 | writeValue(children: Array) { 155 | this.children = children; 156 | } 157 | registerOnChange(fn): void { 158 | this.onChange.subscribe(fn); 159 | } 160 | registerOnTouched(fn): void { 161 | this.onTouched = fn; 162 | } 163 | _reset() { 164 | this.child = { 165 | name: "", 166 | age: 0, 167 | gender: "male", 168 | index: null 169 | } 170 | this.submitted = false; 171 | } 172 | } -------------------------------------------------------------------------------- /client/app/modules/forms_explore/customValidators.ts: -------------------------------------------------------------------------------- 1 | export function emailValidator(control: any) { 2 | if (!/.+\@.+\..+/.test(control.value)) { 3 | return { email: true }; 4 | } 5 | } 6 | 7 | export function minValue(value: number) { 8 | return function(control) { 9 | if (control.value) { 10 | if ((control.value < value)) { 11 | return { minValue: true } 12 | } 13 | } 14 | } 15 | } 16 | export function maxValue(value: number) { 17 | return function(control) { 18 | if (control.value) { 19 | if ((control.value > value)) { 20 | return { maxValue: true } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /client/app/modules/forms_explore/date.form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 | 8 |
9 | Year format is wrong 10 |
11 |
12 |
13 |
14 |
15 | 16 | 30 |
31 |
32 |
33 | 34 |
35 | 36 | 37 |
38 | Year format is wrong 39 |
40 |
41 |
42 |
43 |
-------------------------------------------------------------------------------- /client/app/modules/forms_explore/date.form.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter} from 'angular2/core'; 2 | import { 3 | CORE_DIRECTIVES, 4 | FORM_DIRECTIVES, 5 | Validators, 6 | Control, 7 | ControlGroup, 8 | NgControl, 9 | ControlValueAccessor} from 'angular2/common'; 10 | import * as appValidators from './customValidators'; 11 | 12 | @Component({ 13 | selector: 'dateSelector[ngControl]', 14 | directives: [CORE_DIRECTIVES, FORM_DIRECTIVES], 15 | template: require("./date.form.component.html"), 16 | styles: [` 17 | .form-group{ 18 | margin-bottom: 0!important; 19 | } 20 | `] 21 | }) 22 | 23 | export class DateSelector implements ControlValueAccessor { 24 | date: any = { 25 | day: 1, 26 | month: 5, 27 | year: 1990 28 | }; 29 | dateForm: ControlGroup; 30 | onChange: EventEmitter = new EventEmitter(); 31 | onTouched: any; 32 | constructor(private cd: NgControl) { 33 | cd.valueAccessor = this; 34 | this.dateForm = new ControlGroup({ 35 | day: new Control(1, Validators.compose([Validators.required, appValidators.minValue(1), appValidators.maxValue(31)])), 36 | month: new Control(5, Validators.required), 37 | year: new Control(1990, Validators.compose([Validators.required, appValidators.minValue(1915), appValidators.maxValue(new Date().getFullYear())])) 38 | }); 39 | this.dateForm.valueChanges 40 | .subscribe((val) => { 41 | if (this.dateForm.valid) { 42 | this.onChange.emit(this._computeDate()); 43 | } else { 44 | this.cd.control.setErrors({ "wrongDate": true }); 45 | } 46 | }); 47 | } 48 | 49 | _computeDate() { 50 | if (!this.date.year || !this.date.month || !this.date.day) 51 | return null; 52 | var date = new Date(this.date.year, this.date.month, this.date.day); 53 | return date; 54 | } 55 | _updateValue(date) { 56 | let dat = new Date(date); 57 | this.date.day = dat.getDate(); 58 | this.date.month = dat.getMonth(); 59 | this.date.year = dat.getFullYear(); 60 | } 61 | /** 62 | * ControlValueAccessor 63 | */ 64 | writeValue(date) { 65 | this._updateValue(date); 66 | } 67 | registerOnChange(fn): void { 68 | this.onChange.subscribe(fn); 69 | } 70 | registerOnTouched(fn): void { 71 | this.onTouched = fn; 72 | } 73 | } -------------------------------------------------------------------------------- /client/app/modules/forms_explore/forms.component.html: -------------------------------------------------------------------------------- 1 | 85 | 86 | 87 | 88 | 89 | 90 |
91 |
92 |

REGISTRATION FORM

93 |
94 |
95 |
96 |
97 | 98 | 99 |
100 | Name field is required 101 |
102 |
103 |
104 | 105 | 106 |
107 | Address field is required 108 |
109 |
110 |
111 | 112 | 113 |
114 | Date required 115 |
116 |
117 |
118 | 119 | 123 |
124 | Country is required 125 |
126 |
127 |
128 |
129 | 132 |
133 |
134 |
135 | 136 | 137 | 138 |
139 | Add children 140 |
141 |
142 | 143 |
144 | Is valid: {{f.valid}} 145 |
146 |
-------------------------------------------------------------------------------- /client/app/modules/forms_explore/forms.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from 'angular2/core'; 2 | import {DateSelector} from './date.form.component'; 3 | import {ChildrenForm} from './children.form.component'; 4 | import {CORE_DIRECTIVES, FORM_DIRECTIVES, FORM_PROVIDERS, Control, FormBuilder, ControlGroup, Validators, ControlContainer} from 'angular2/common'; 5 | @Component({ 6 | selector: 'forms', 7 | template: require('./forms.component.html'), 8 | directives: [FORM_DIRECTIVES, CORE_DIRECTIVES, DateSelector, ChildrenForm], 9 | styles: [ 10 | `` 11 | ], 12 | providers: [FORM_PROVIDERS] 13 | }) 14 | 15 | export class FormsExplore { 16 | personal = { 17 | name: "", 18 | birthday: "", 19 | address: "", 20 | country: "", 21 | haschildren: false, 22 | children: [], 23 | } 24 | submitted: boolean = false; 25 | error: string = null; 26 | info: string = null; 27 | form: ControlGroup; 28 | countries: Array = ["USA", "Germany", "France", "Russia"]; 29 | 30 | constructor(fbuilder: FormBuilder) { 31 | this.form = fbuilder.group({ 32 | birthday: ['', Validators.compose([Validators.required])], 33 | name: ['', Validators.compose([Validators.required])], 34 | address: ['', Validators.compose([Validators.required])], 35 | country: ['', Validators.compose([Validators.required])], 36 | haschildren: [false], 37 | children: [[], Validators.compose([ifChildrenChecked(this.personal)])] 38 | }); 39 | } 40 | 41 | onSubmit(value) { 42 | if (!this.submitted) this.submitted = true; 43 | if (this.form.valid) { 44 | console.log(value); 45 | } 46 | } 47 | 48 | closeAlert() { 49 | this.info = null; 50 | this.error = null; 51 | } 52 | } 53 | 54 | function ifChildrenChecked(model: any) { 55 | return function(control) { 56 | if (!(control.value.length > 0) && model.haschildren) { 57 | return { required: true } 58 | } 59 | } 60 | } 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /client/app/modules/http_explore/extHttp.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from 'angular2/core'; 2 | import {Http, Headers, RequestOptions, RequestOptionsArgs, Response, RequestMethod, Request, Connection, ConnectionBackend} from 'angular2/http'; 3 | import * as Rx from 'rxjs'; 4 | 5 | 6 | export enum Action { QueryStart, QueryStop }; 7 | 8 | @Injectable() 9 | export class ExtHttp extends Http { 10 | process: Rx.Subject = new Rx.Subject(); 11 | error: Rx.Subject = new Rx.Subject(); 12 | constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions) { 13 | super(_backend, _defaultOptions); 14 | } 15 | 16 | private _createHeaders(): Headers { 17 | let authData = { token: `abracadabra` }; 18 | let headers = new Headers({ 19 | 'Accept': 'application/json', 20 | 'Content-Type': 'application/json' 21 | }); 22 | if (!!authData && authData.token) { 23 | headers.append('Authorization', `Bearer ${authData.token}`) 24 | } 25 | return headers; 26 | } 27 | 28 | 29 | public get(url: string, options?: RequestOptionsArgs) { 30 | return this._request(RequestMethod.Get, url, null, options); 31 | } 32 | 33 | public post(url: string, body: string, options?: RequestOptionsArgs) { 34 | return this._request(RequestMethod.Post, url, body, options); 35 | } 36 | 37 | public put(url: string, body: string, options?: RequestOptionsArgs) { 38 | return this._request(RequestMethod.Put, url, body, options); 39 | } 40 | 41 | public delete(url: string, options?: RequestOptionsArgs) { 42 | return this._request(RequestMethod.Delete, url, null, options); 43 | } 44 | 45 | private _request(method: RequestMethod, url: string, body?: string, options?: RequestOptionsArgs): Rx.Observable { 46 | let requestOptions = new RequestOptions(Object.assign({ 47 | method: method, 48 | url: url, 49 | body: body, 50 | headers: this._createHeaders() 51 | }, options)); 52 | 53 | return Rx.Observable.create((observer) => { 54 | this.process.next(Action.QueryStart); 55 | super.request(new Request(requestOptions)) 56 | .map(res=> res.json()) 57 | .finally(() => { 58 | this.process.next(Action.QueryStop); 59 | }) 60 | .subscribe( 61 | (res) => { 62 | observer.next(res); 63 | observer.complete(); 64 | }, 65 | (err) => { 66 | switch (err.status) { 67 | case 401: 68 | //intercept 401 69 | this.error.next(err); 70 | observer.error(err); 71 | break; 72 | case 500: 73 | //intercept 500 74 | this.error.next(err) 75 | observer.error(err); 76 | break; 77 | default: 78 | this.error.next(err) 79 | observer.error(err); 80 | break; 81 | } 82 | }) 83 | }) 84 | } 85 | //todo: add caching 86 | } 87 | 88 | export var EXTHTTPPROVIDERS: Array = [ 89 | Action, ExtHttp 90 | ]; -------------------------------------------------------------------------------- /client/app/modules/http_explore/http.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, provide, Host, OnInit} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES, Control, FormBuilder, ControlGroup, Validators} from 'angular2/common'; 3 | import {Http, ConnectionBackend, XHRBackend, BaseRequestOptions,HTTP_PROVIDERS} from 'angular2/http'; 4 | 5 | import {ExtHttp} from "./extHttp" 6 | import {Loader} from "./loader.component" 7 | import {Some} from "./some.component" 8 | 9 | 10 | @Component({ 11 | selector: 'http', 12 | template: ` 13 |
14 |
15 | 16 | 17 | 18 | {{info}} 19 |
20 | 21 |
22 |
23 | `, 24 | directives: [FORM_DIRECTIVES, CORE_DIRECTIVES, Loader, Some], 25 | styles:[` 26 | .cmn-t-underline { 27 | position: relative; 28 | color: #ff3296; 29 | height: 5px; 30 | } 31 | .cmn-t-underline:after { 32 | display: block; 33 | position: absolute; 34 | left: 0px; 35 | top: 0px; 36 | width: 0; 37 | height: 3px; 38 | background-color: #98004a; 39 | content: ""; 40 | width: 100%; 41 | } 42 | .some-container{ 43 | margine-top:5px; 44 | } 45 | 46 | `], 47 | // This is not very convince, you should do it in bootstrap 48 | // if you want this service to be injectable throughout all the app. 49 | providers:[HTTP_PROVIDERS, XHRBackend, BaseRequestOptions, provide(Http, {useFactory: 50 | function(backend, defaultOptions) { 51 | return new ExtHttp(backend, defaultOptions); 52 | }, 53 | deps: [XHRBackend, BaseRequestOptions]})] 54 | 55 | }) 56 | 57 | export class HttpExplore implements OnInit{ 58 | http: ExtHttp; 59 | someArray: Array = [42,42,42,42,42]; 60 | info: string; 61 | constructor(http:Http) { 62 | this.http = http; 63 | this.http.error.subscribe((value)=>{ 64 | //(1) catch it here or whereever you need error notification 65 | this.info = "Received " + value.status + " code" 66 | }) 67 | } 68 | ngOnInit(){ 69 | 70 | } 71 | onClick(){ 72 | this.someArray.push(42); 73 | } 74 | onErrClick(){ 75 | this.http.post("/test/errpost",JSON.stringify({myData:"test data"})).subscribe(()=>{ 76 | //do smth 77 | },(err)=>{ 78 | //(2) and catch it here 79 | console.log(err) 80 | }) 81 | } 82 | onPostClick(){ 83 | this.http.post("/test/post",JSON.stringify({query:"test query"})).subscribe((value)=>{ 84 | this.info = "Received: Your auth token is: " + value.authHeader; 85 | },(err)=>{ 86 | console.log(err) 87 | }) 88 | } 89 | } 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /client/app/modules/http_explore/loader.component.ts: -------------------------------------------------------------------------------- 1 | import {Directive, ElementRef, Renderer, View } from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES, Control, FormBuilder, ControlGroup, Validators} from 'angular2/common'; 3 | 4 | import {AnimationBuilder} from 'angular2/src/animate/animation_builder'; 5 | 6 | import {Http} from 'angular2/http'; 7 | import {ExtHttp, Action} from "./extHttp"; 8 | 9 | /** 10 | * Naive loader 11 | */ 12 | @Directive({ 13 | selector: '[loader]', 14 | 15 | }) 16 | export class Loader { 17 | http: ExtHttp; 18 | trackedRequests: Array = []; 19 | currentValue: number = 0; 20 | loadingTimer: any; 21 | constructor(private element: ElementRef, private renderer: Renderer, http: Http, private _ab: AnimationBuilder) { 22 | 23 | renderer.setElementClass(element.nativeElement, "cmn-t-underline", true) 24 | this.renderer.setElementStyle(this.element.nativeElement, "width", "0"); 25 | this.http = http; 26 | this.http.process.subscribe((value) => { 27 | if (value == Action.QueryStart) { 28 | this.trackedRequests.push(value); 29 | this._render(Action.QueryStart); 30 | } 31 | if (value == Action.QueryStop) { 32 | this.trackedRequests.pop(); 33 | this._render(Action.QueryStop); 34 | } 35 | }) 36 | } 37 | 38 | _render(action) { 39 | this._stopLoadingEmulation(); 40 | if (this.trackedRequests.length > 0) { 41 | let newValue; 42 | if (action == Action.QueryStart) { 43 | if(this.currentValue == 0){ 44 | newValue = 10; //start position 45 | }else{ 46 | newValue = this.currentValue - this.currentValue / 3; 47 | } 48 | } else { 49 | newValue = this.currentValue + ((100 - this.currentValue) / 3); 50 | if (newValue >= 100) { 51 | newValue = 95; 52 | } 53 | } 54 | this.animateTransitionToNewValue(this.currentValue, newValue); 55 | this._startLoadingEmulation(); 56 | this.currentValue = newValue; 57 | } else { 58 | this.currentValue = 100; 59 | this._stopLoadingEmulation(); 60 | this.animateTransitionToNewValue(this.currentValue, 100); 61 | setTimeout(() => { 62 | this.animateTransitionToNewValue(this.currentValue, 0); 63 | this.currentValue = 0; 64 | },200) 65 | } 66 | } 67 | 68 | animateTransitionToNewValue(from, to) { 69 | this.renderer.setElementStyle(this.element.nativeElement, "width", to + '%') 70 | } 71 | 72 | _startLoadingEmulation() { 73 | this.loadingTimer = setInterval(() => { 74 | let oldValue = this.currentValue; 75 | this.currentValue += this._inc(); 76 | this.animateTransitionToNewValue(oldValue, this.currentValue); 77 | }, 250); 78 | } 79 | 80 | _stopLoadingEmulation() { 81 | if (this.loadingTimer) { 82 | clearInterval(this.loadingTimer); 83 | } 84 | } 85 | 86 | // Inspired by https://github.com/chieffancypants/angular-loading-bar/blob/master/src/loading-bar.js 87 | _inc() { 88 | if (this.currentValue >= 100) { 89 | return; 90 | } 91 | if (this.currentValue >= 0 && this.currentValue < 25) { 92 | return (Math.random() * (5 - 3 + 1) + 3); 93 | } else if (this.currentValue >= 25 && this.currentValue < 60) { 94 | return (Math.random() * 3); 95 | } else if (this.currentValue >= 60 && this.currentValue < 90) { 96 | return (Math.random() * 2) / 100; 97 | } else if (this.currentValue >= 90 && this.currentValue < 99) { 98 | return 0.5; 99 | } else { 100 | return -0.5; 101 | } 102 | } 103 | } 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /client/app/modules/http_explore/some.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, provide, Host, OnInit} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES, Control, FormBuilder, ControlGroup, Validators} from 'angular2/common'; 3 | import {Http, ConnectionBackend, XHRBackend, BaseRequestOptions, HTTP_PROVIDERS} from 'angular2/http'; 4 | 5 | import {ExtHttp} from "./extHttp" 6 | 7 | @Component({ 8 | selector: 'some', 9 | template: ` 10 |
11 | State: {{state}} 12 |
13 | `, 14 | directives: [FORM_DIRECTIVES, CORE_DIRECTIVES], 15 | styles: [` 16 | .someCss { 17 | width: 120px; 18 | color: white; 19 | background: #6B4717; 20 | margin: 5px; 21 | display: inline-block; 22 | padding: 15px; 23 | min-height: 50px; 24 | } 25 | `] 26 | }) 27 | 28 | export class Some implements OnInit { 29 | http: ExtHttp; 30 | state: string; 31 | constructor(http: Http) { 32 | this.http = http; 33 | this.state = "Getting data"; 34 | } 35 | ngOnInit() { 36 | this.http.get('/test/get') 37 | .subscribe((value) => { 38 | this.state = value.data; 39 | }) 40 | } 41 | 42 | } 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /client/app/modules/polymer/components/polymer.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgZone} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES, Control, FormBuilder, ControlGroup, Validators} from 'angular2/common'; 3 | import {ROUTER_DIRECTIVES, RouteConfig} from 'angular2/router'; 4 | import {PolymerFormComponent} from './polymerForm.component'; 5 | import {PolymerD3} from './polymerD3.component'; 6 | 7 | @Component({ 8 | selector: 'polimer', 9 | template: ` 10 | 11 |
12 | 16 |
17 |
18 | 19 | 20 |
21 | 22 | `, 23 | directives: [FORM_DIRECTIVES, CORE_DIRECTIVES, ROUTER_DIRECTIVES], 24 | styles: [ 25 | `.router-link-active { background:cornsilk !important; }` 26 | ] 27 | }) 28 | @RouteConfig([ 29 | { path: '/form', as: 'Form', component: PolymerFormComponent }, 30 | { path: '/d3', as: 'D3', useAsDefault: true, component: PolymerD3 }, 31 | ]) 32 | export class PolymerExplore {} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /client/app/modules/polymer/components/polymerD3.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, ViewQuery, QueryList, ElementRef} from 'angular2/core'; 2 | import {CORE_DIRECTIVES} from 'angular2/common'; 3 | 4 | @Component({ 5 | selector: 'polimer', 6 | template: ` 7 |
8 |
9 | Update 10 |
11 |
12 |
13 | 14 |
15 |
16 |
17 | `, 18 | directives: [CORE_DIRECTIVES], 19 | styles: [` 20 | .chart-container { 21 | background: rgba(255, 255, 255, 0.68); 22 | opacity: 0.8; 23 | margin: 5px; 24 | padding: 0 15px; 25 | box-shadow: 0px 2px 5px 0 rgba(189, 189, 189, 0.68); 26 | } 27 | .button-bar{ 28 | padding-top:10px; 29 | padding-bottom:10px; 30 | } 31 | `] 32 | }) 33 | 34 | export class PolymerD3 { 35 | 36 | symbols: Array = ["GOOG", "ORCL", "MSFT", "YHOO", "AAPL", "CSCO"]; 37 | months: any = {}; 38 | test: Array = [6, 12]; 39 | charts: QueryList; 40 | 41 | constructor(@ViewQuery("chart") charts: QueryList) { 42 | this.charts = charts; 43 | this.symbols.forEach(symbol => { 44 | this.months[symbol] = 6; 45 | }); 46 | } 47 | 48 | updateCharts() { 49 | this.charts.toArray().forEach((chart) => { 50 | chart.nativeElement.updateChart(); 51 | }) 52 | } 53 | } 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /client/app/modules/polymer/components/polymerForm.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, AfterViewInit} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, FORM_DIRECTIVES, Control, FormBuilder, ControlGroup, Validators} from 'angular2/common'; 3 | import {ROUTER_DIRECTIVES, OnReuse, CanReuse, ComponentInstruction} from 'angular2/router'; 4 | 5 | @Component({ 6 | selector: 'polymerForm', 7 | template: ` 8 |
9 |
10 |
11 | 19 | 20 |
21 | 28 | 29 | 38 | 39 |
40 | 49 | 50 | Submit 51 |
52 |
53 |
54 | `, 55 | directives: [FORM_DIRECTIVES, CORE_DIRECTIVES], 56 | 57 | }) 58 | 59 | export class PolymerFormComponent { 60 | loginForm: ControlGroup; 61 | _mp: string; 62 | password: any = { 63 | password: "", 64 | confirm: "" 65 | }; 66 | genderOptions: Array = ["Male", "Female"]; 67 | gender: string; 68 | submitted: boolean; 69 | 70 | constructor(fbuilder: FormBuilder) { 71 | this.submitted = false; 72 | this.loginForm = fbuilder.group({ 73 | email: ['', Validators.compose([Validators.required, _emailValidator])], 74 | password: fbuilder.group({ 75 | password: ['', Validators.compose([Validators.required, Validators.minLength(6)])], 76 | passwordConfirmation: ['', Validators.required] 77 | }), 78 | gender: [""], 79 | }); 80 | } 81 | onSubmit(formValue) { 82 | console.log(formValue); 83 | this.submitted = true; 84 | } 85 | 86 | getError(controlName) { 87 | if (this.loginForm.controls['controlName'].hasError("required")) { 88 | console.log(this.loginForm); 89 | } 90 | } 91 | } 92 | 93 | function _emailValidator(control: any) { 94 | if (!/.+\@.+\..+/.test(control.value)) { 95 | return { email: true }; 96 | } 97 | } 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /client/app/modules/router_explore/hooks.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from 'angular2/core'; 2 | import {FORM_DIRECTIVES, CORE_DIRECTIVES, ControlGroup, Control} from 'angular2/common'; 3 | import {ROUTER_DIRECTIVES, CanReuse, OnReuse, RouteParams, Router, ComponentInstruction} from 'angular2/router'; 4 | import {URLSearchParams, JSONP_PROVIDERS, Jsonp, Http} from 'angular2/http'; 5 | 6 | @Component({ 7 | selector: 'hooks', 8 | template: ` 9 |
10 |
11 |
12 |
13 | 14 | 17 |
18 |
19 | 20 | 23 |
24 | 25 |
26 |
27 |
28 |
29 | 40 |
41 |
42 |
43 |
44 |
45 | Car 46 |
47 |
48 | `, 49 | directives: [ROUTER_DIRECTIVES, FORM_DIRECTIVES, CORE_DIRECTIVES], 50 | providers: [JSONP_PROVIDERS], 51 | styles: [` 52 | .flickr-img{ 53 | width: 150px; 54 | color: white; 55 | margin: 5px; 56 | display: inline-block; 57 | height: 100px; 58 | } 59 | .flickr-result-container{ 60 | 61 | } 62 | `] 63 | }) 64 | export class HooksComponent implements CanReuse, OnReuse, OnInit { 65 | used: number = 0; 66 | images: Array = []; 67 | imagesToShow: Array = []; 68 | currentPage: number = 0; 69 | searchTag: string; 70 | perPage: number; 71 | form: ControlGroup; 72 | pages: Array = []; // limited by 100 (20 for public api) for example's sake 73 | 74 | constructor(private _routeParams: RouteParams, private router: Router, private jsonp: Jsonp, private http: Http) { 75 | this.form = new ControlGroup({ 76 | perPage: new Control(), 77 | searchTag: new Control() 78 | }) 79 | this.currentPage = +this._routeParams.get("page") || 0; 80 | this.perPage = +this._routeParams.get("perPage") || 6; 81 | this.searchTag = this._routeParams.get("searchTag") || ''; 82 | } 83 | 84 | ngOnInit() { 85 | if (this.searchTag) { 86 | this.getNew(this.searchTag); 87 | } 88 | } 89 | 90 | onSubmit(value) { 91 | if (value.searchTag) 92 | this.getNew(value.searchTag); 93 | this.router.navigate(["Hooks", { 94 | page: this.currentPage, 95 | searchTag: value.searchTag, 96 | perPage: value.perPage 97 | }]) 98 | } 99 | 100 | routerCanReuse() { 101 | return true; 102 | } 103 | 104 | routerOnReuse(instruction: ComponentInstruction) { 105 | this.currentPage = +instruction.params["page"] || 0; 106 | this.perPage = +instruction.params["perPage"] || 20; 107 | this.searchTag = instruction.params["searchTag"] || ""; 108 | if (!!this.searchTag) 109 | this.updateView(); 110 | } 111 | 112 | getNew(tags) { 113 | var search = new URLSearchParams(); 114 | search.set('tags', tags); 115 | search.set('format', 'json'); 116 | return this.jsonp 117 | 118 | .get('http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=JSONP_CALLBACK', { search }) 119 | .map((response) => response.json()) 120 | .map(result=> result.items) 121 | .map(result=> { 122 | return result.map((item) => { 123 | return { 124 | url: item.media.m, 125 | title: item.title 126 | } 127 | }) 128 | }) 129 | 130 | // if you have a flickr api key, uncomment this code. It works much better. 131 | /* .get('https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=xxxxxxYOURAPIKEYxxxxxx&privacy_filter=1&content_type=1&extras=url_m&format=json&jsoncallback=JSONP_CALLBACK', { search }) 132 | .map((response) => response.json()) 133 | .map(result=> result.photos) 134 | .map(result=> { 135 | return result.photo.map((item) => { 136 | return { 137 | url: item.url_m, 138 | title: item.title 139 | } 140 | }) 141 | })*/ 142 | 143 | 144 | .subscribe((items) => { 145 | this.images = items; 146 | var total = items.length / this.perPage; 147 | this.pages = []; 148 | for (var i = 0; i < total; i++) { 149 | this.pages.push(i); 150 | } 151 | this.updateView(); 152 | }) 153 | } 154 | updateView() { 155 | this.imagesToShow = this.images 156 | .slice(this.perPage * this.currentPage, this.perPage * this.currentPage + this.perPage) 157 | } 158 | navigateOnPage(pageNumber) { 159 | this.router.navigate(["Hooks", { 160 | page: pageNumber, 161 | searchTag: this.searchTag, 162 | perPage: this.perPage 163 | }]) 164 | } 165 | } 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /client/app/modules/router_explore/router.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from 'angular2/core'; 2 | import {CORE_DIRECTIVES} from 'angular2/common'; 3 | import {ROUTER_DIRECTIVES, RouteConfig} from 'angular2/router'; 4 | 5 | import {HooksComponent} from './hooks.component'; 6 | 7 | @Component({ 8 | selector: 'router', 9 | template: ` 10 |
11 | 14 |
15 |
16 | 17 | 18 |
19 | `, 20 | directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES], 21 | styles: [ 22 | `.router-link-active { background:cornsilk !important; }` 23 | ] 24 | }) 25 | // todo: explore otherwise : #2965 26 | // https://github.com/angular/angular/issues/2965 27 | @RouteConfig([ 28 | { path: '/hooks/', as: 'Hooks', component: HooksComponent, useAsDefault: true }, 29 | ]) 30 | export class RouterExplore { } 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /client/app/modules/rx_explore/api.service.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Injectable} from 'angular2/core'; 3 | import {Http, Headers, Response} from 'angular2/http'; 4 | import * as rx from 'rxjs'; 5 | 6 | 7 | @Injectable() 8 | export class ApiService { 9 | headers: Headers; 10 | constructor(private http: Http) { 11 | this.headers = new Headers({ 12 | 'Accept': 'application/json', 13 | 'Content-Type': 'application/json' 14 | }); 15 | } 16 | 17 | public searchManufacters(value): rx.Observable { 18 | return this.http 19 | .post("/api/searchManufacters", JSON.stringify({ query: value }), { headers: this.headers }) 20 | .map((res: Response) => res.json()) 21 | } 22 | 23 | } 24 | 25 | export const API_SERVICE_BINDINGS = [ 26 | ApiService 27 | ]; 28 | -------------------------------------------------------------------------------- /client/app/modules/rx_explore/components/autocomplete.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/modules/rx_explore/components/autocomplete.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, ViewQuery, QueryList, ElementRef, EventEmitter, OnDestroy} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, Control, FORM_DIRECTIVES} from 'angular2/common'; 3 | import * as rx from 'rxjs'; 4 | import {AnimationBuilder} from 'angular2/src/animate/animation_builder'; 5 | import {ApiService} from '../api.service'; 6 | import {Http, Response, Headers} from 'angular2/http'; 7 | 8 | @Component({ 9 | selector: 'autocomplete', 10 | template: require('./autocomplete.component.html'), 11 | directives: [CORE_DIRECTIVES, FORM_DIRECTIVES], 12 | outputs: ['changed'] 13 | }) 14 | 15 | export class AutoComplete { 16 | visible = true; 17 | seachText: Control; 18 | seachTextModel: string; 19 | results: Array; 20 | message: string; 21 | opened: boolean = false; 22 | changed: EventEmitter = new EventEmitter(); 23 | 24 | 25 | constructor( 26 | private _ab: AnimationBuilder, 27 | @ViewQuery('sbSearch, sbSearchResults') private elList: QueryList, 28 | private api: ApiService) { 29 | 30 | this.seachText = new Control(); 31 | this.resetAutocomplete(); 32 | this.seachText 33 | .valueChanges 34 | .do(value => { 35 | if (value) { 36 | this.message = 'searching...'; 37 | } 38 | }) 39 | .debounceTime(500) 40 | .map((value: string) => value.toLowerCase()) 41 | .distinctUntilChanged() 42 | .flatMap((value: string) => this.api.searchManufacters(value)) 43 | .subscribe((value) => { 44 | this.toggleResults(value); 45 | }, (err) => { 46 | /* dont care in playground */ 47 | }) 48 | } 49 | resutSelected(result) { 50 | this.changed.next(result); 51 | } 52 | 53 | toggleResults(results = null) { 54 | if (results.length) { 55 | this.results = results; 56 | this.message = null; 57 | } else { 58 | this.results = []; 59 | this.message = 'no cars found' 60 | } 61 | } 62 | 63 | resetAutocomplete() { 64 | this.seachTextModel = ''; 65 | this.results = []; 66 | this.message = null; 67 | } 68 | 69 | toggleControlState(open: boolean = false) { 70 | if (!open) { 71 | this.resetAutocomplete(); 72 | this.animateClose() 73 | } else { 74 | this.animateOpen(); 75 | } 76 | } 77 | 78 | animateOpen() { 79 | let animation = this._ab.css(); 80 | animation 81 | .setDuration(700); 82 | animation.setFromStyles({ width: '45px' }) 83 | .setToStyles({ width: '400px' }) 84 | animation.start(this.elList.first.nativeElement); 85 | } 86 | 87 | animateClose() { 88 | let animation = this._ab.css(); 89 | animation 90 | .setDuration(700); 91 | animation.setFromStyles({ width: '400px' }) 92 | .setToStyles({ width: '45px' }); 93 | animation.start(this.elList.first.nativeElement); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /client/app/modules/rx_explore/components/rxExplore.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | Click seach button and type your favorite car maker 5 |

6 |
7 |
8 |
9 |
10 |
11 | 12 |
13 |
14 |
15 | SELECTED VALUE: 16 | {{selectedValue}} 17 |
18 |
-------------------------------------------------------------------------------- /client/app/modules/rx_explore/components/rxExplore.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, ViewQuery, QueryList, ElementRef, OnInit, EventEmitter} from 'angular2/core'; 2 | import {CORE_DIRECTIVES, Control, FORM_DIRECTIVES} from 'angular2/common'; 3 | import * as rx from 'rxjs'; 4 | import {AutoComplete} from "./autocomplete.component"; 5 | import {AnimationBuilder} from 'angular2/src/animate/animation_builder'; 6 | import {ApiService} from '../api.service'; 7 | import {Http, Response, Headers} from 'angular2/http'; 8 | 9 | @Component({ 10 | selector: 'rx', 11 | template: require("./rxExplore.component.html"), 12 | directives: [CORE_DIRECTIVES, FORM_DIRECTIVES, AutoComplete], 13 | providers: [ApiService] 14 | 15 | }) 16 | 17 | export class RxExploreComponent { 18 | selectedValue: string; 19 | constructor() { 20 | } 21 | autocompleteCanged(value) { 22 | this.selectedValue = value.name; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /client/app/polymerElements/custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /client/app/polymerElements/inputs/confirm-password-input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 64 | 65 | 101 | 102 | 186 | 187 | -------------------------------------------------------------------------------- /client/app/polymerElements/select/select.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 56 | 57 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /client/app/services/api.service.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Injectable} from 'angular2/core'; 3 | import {Http, Headers, Response} from 'angular2/http'; 4 | import * as Rx from 'rxjs'; 5 | 6 | 7 | @Injectable() 8 | export class ApiService { 9 | headers: Headers; 10 | constructor(private http: Http) { 11 | this.headers = new Headers({ 12 | 'Accept': 'application/json', 13 | 'Content-Type': 'application/json' 14 | }); 15 | } 16 | 17 | public initCarsDefaults(): Rx.Observable { 18 | return this.http 19 | .post("/api/initcarsdefaults", null, { headers: this.headers }) 20 | //.map(res => res.json()) // uncomment if you use regular http service 21 | 22 | } 23 | public getCarsCount(query): Rx.Observable { 24 | return this.http 25 | .post("/api/getcount", JSON.stringify(query), { headers: this.headers }) 26 | //.map(res => res.json()) 27 | 28 | } 29 | 30 | public getCarsByManufacter(query): Rx.Observable { 31 | return this.http 32 | .post("/api/getmanufactermodels", JSON.stringify(query), { headers: this.headers }) 33 | //.map(res => res.json()) 34 | 35 | } 36 | public seachCars(query): Rx.Observable { 37 | return this.http 38 | .post("/api/seachcars", JSON.stringify(query), { headers: this.headers }) 39 | //.map(res => res.json()) 40 | } 41 | 42 | public getCar(id): Rx.Observable { 43 | return this.http 44 | .get(`/api/getcar/${id}`, { headers: this.headers }) 45 | //.map(res => res.json()) 46 | } 47 | 48 | } 49 | 50 | export const API_SERVICE_PROVIDERS = [ 51 | ApiService 52 | ]; 53 | -------------------------------------------------------------------------------- /client/app/services/services.ts: -------------------------------------------------------------------------------- 1 | import {ApiService} from "./api.service"; 2 | 3 | export * from "./api.service"; 4 | 5 | export var APP_SERVICES_BINDINGS: Array = [ 6 | ApiService 7 | ]; -------------------------------------------------------------------------------- /client/app/utils/formValidators.ts: -------------------------------------------------------------------------------- 1 | // Expected community's set of ng2 validators 2 | export function emailValidator(control: any) { 3 | if (!/.+\@.+\..+/.test(control.value)) { 4 | return { email: true }; 5 | } 6 | } 7 | export function passwordLongerThen6IfExists(control: any) { 8 | if (control.value) { 9 | if (!(control.value.length >= 6)) { 10 | return { longerifexists: true } 11 | } 12 | } 13 | } 14 | export function minValue(value: number) { 15 | return function(control) { 16 | if (control.value) { 17 | if ((control.value < value)) { 18 | return { minValue: true } 19 | } 20 | } 21 | } 22 | } 23 | export function maxValue(value: number) { 24 | return function(control) { 25 | if (control.value) { 26 | if ((control.value > value)) { 27 | return { maxValue: true } 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /client/assets/app.less: -------------------------------------------------------------------------------- 1 | @mainBackground: #d5d4ca; 2 | @headerHeight: 65px; 3 | @defaultThemeColor: rgb(18, 42, 79); 4 | @alternativeColor: #d9534f; 5 | 6 | body{ 7 | margin-top: @headerHeight; 8 | background: #f5f5f5; 9 | padding: 10px; 10 | //min-height: 100vh; 11 | box-sizing: border-box; 12 | } 13 | 14 | paper-dropdown-menu { 15 | text-align: left; 16 | margin: auto; 17 | width: 100px; 18 | } 19 | 20 | .horizontal-section { 21 | text-align: center; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /client/assets/images/27.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/client/assets/images/27.JPG -------------------------------------------------------------------------------- /client/assets/images/audi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/client/assets/images/audi.jpg -------------------------------------------------------------------------------- /client/assets/images/bmw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/client/assets/images/bmw.jpg -------------------------------------------------------------------------------- /client/assets/images/car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/client/assets/images/car.png -------------------------------------------------------------------------------- /client/assets/images/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/client/assets/images/check.png -------------------------------------------------------------------------------- /client/assets/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/client/assets/images/github.png -------------------------------------------------------------------------------- /client/assets/images/tesla.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/client/assets/images/tesla.jpg -------------------------------------------------------------------------------- /client/assets/images/volkswagen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/client/assets/images/volkswagen.jpg -------------------------------------------------------------------------------- /client/assets/index.less: -------------------------------------------------------------------------------- 1 | @import "app"; 2 | @import "seachBox"; -------------------------------------------------------------------------------- /client/assets/seachBox.less: -------------------------------------------------------------------------------- 1 | @defaultthemecolor: cornsilk ; 2 | @seachheadermaxwidth: 400px; 3 | @seachheaderminwidth: 45px; 4 | 5 | 6 | .calculatedWidth{ 7 | 8 | width: calc(~"100% - 45px"); 9 | } 10 | 11 | .sb-search { 12 | position: relative; /*если это убрать, то элементы спозиционируются относительно самого заголовка*/ 13 | min-width: @seachheaderminwidth; 14 | height: 45px; 15 | float: right; 16 | } 17 | 18 | .sb-search-input { 19 | position: absolute; 20 | top: 0; 21 | right: 0; 22 | border: none; 23 | outline: none; 24 | background: white; 25 | width: 100%; 26 | height: 45px; 27 | margin: 0; 28 | z-index: 10; 29 | font-family: fontawesome-webfont; 30 | font-size: 20px; 31 | color: dimgrey; 32 | box-shadow: #eaeaea 1px 1px; 33 | } 34 | 35 | 36 | input[type="search"].sb-search-input { 37 | -webkit-appearance: none; 38 | -webkit-border-radius: 0px; 39 | border-radius: 0px; 40 | } 41 | 42 | /*цвета временной записи placeholder ЗАПОМНИТЬ КАК ЭТО ДЕЛАЕТСЯ*/ 43 | .sb-search-input::-webkit-input-placeholder { 44 | color: #eaeaea; 45 | } 46 | 47 | .sb-search-input:-moz-placeholder { 48 | color: #eaeaea; 49 | } 50 | 51 | .sb-search-input::-moz-placeholder { 52 | color: #eaeaea; 53 | } 54 | 55 | .sb-search-input:-ms-input-placeholder { 56 | color: #efefef; 57 | } 58 | 59 | .sb-search-icons { 60 | width: @seachheaderminwidth; 61 | height: 45px; 62 | display: block; 63 | position: absolute; 64 | right: 0; 65 | top: 0; 66 | padding: 0; 67 | margin: 0; 68 | line-height: 45px; 69 | text-align: center; 70 | cursor: pointer; 71 | color: #B3946E; 72 | } 73 | 74 | //set submit invisible 75 | .sb-search-submit { 76 | .sb-search-icons; 77 | opacity: 0; 78 | color: transparent; 79 | border: none; 80 | outline: none; 81 | z-index: -1; 82 | } 83 | 84 | .sb-icon-search { 85 | .sb-search-icons; 86 | background: darken(@defaultthemecolor,5%); 87 | z-index: 90; 88 | font-size: 20px; 89 | -webkit-font-smoothing: antialiased; 90 | } 91 | 92 | 93 | 94 | //////////////////////////////////////////// 95 | ////////////////RESULTS BLOCK/////////////// 96 | //////////////////////////////////////////// 97 | .sb-results-dropdown-menu { 98 | margin: 0; 99 | padding: 0; 100 | // box-shadow: #eaeaea 1px 1px; 101 | border-radius: 3px; 102 | box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.18); 103 | 104 | li { 105 | position: relative; 106 | border-bottom: 1px solid rgba(239, 239, 239, 0.25); 107 | height: 60px; 108 | padding: 15px; 109 | list-style: none; 110 | background: white; 111 | 112 | img { 113 | border-radius:50%; 114 | display:inline-block; 115 | width:40px; 116 | height:40px; 117 | } 118 | span { 119 | line-height:30px; 120 | font-family: fontawesome-webfont; 121 | color: #525252; 122 | font-size:20px; 123 | padding-left:5px; 124 | cursor:pointer; 125 | } 126 | } 127 | 128 | li:nth-child(odd) { 129 | background: #f7f7f7; 130 | } 131 | 132 | li:last-child { 133 | border-bottom: 0; 134 | } 135 | } 136 | .sb-seachresults { 137 | position: absolute; 138 | top: 46px; 139 | right: 45px; 140 | z-index: 5; 141 | width: calc(~"100% - 45px"); 142 | &.hidden{ 143 | visibility: 0; 144 | } 145 | } 146 | .sb-message{ 147 | position: absolute; 148 | top: 46px; 149 | right: 45px; 150 | .calculatedWidth(); 151 | background: darken(#f7f7f7,5%); 152 | height: 20px; 153 | span { 154 | color: dimgrey; 155 | text-align: right; 156 | line-height: 10px; 157 | padding: 5px; 158 | font-size: 12px; 159 | font-family: fontawesome-webfont; 160 | display: block; 161 | } 162 | box-shadow: #eaeaea 1px 1px; 163 | } 164 | /* 165 | .sb-seachresults .sb-seach-results-header, 166 | .sb-seachresults .sb-seach-results-footer { 167 | display: block; 168 | width: 100%; 169 | background: lighten(@defaultthemecolor,5%); 170 | height: 40px; 171 | a { 172 | span { 173 | color: rgba(235, 235, 235, 0.25); 174 | text-align: right; 175 | line-height: 20px; 176 | padding: 10px; 177 | font-size: 15px; 178 | font-family: fontawesome-webfont; 179 | display: block; 180 | } 181 | } 182 | }*/ 183 | -------------------------------------------------------------------------------- /dump/playground/cars.bson: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/dump/playground/cars.bson -------------------------------------------------------------------------------- /dump/playground/cars.metadata.json: -------------------------------------------------------------------------------- 1 | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"playground.cars"}]} -------------------------------------------------------------------------------- /dump/playground/manufacters.bson: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/dump/playground/manufacters.bson -------------------------------------------------------------------------------- /dump/playground/manufacters.metadata.json: -------------------------------------------------------------------------------- 1 | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"playground.manufacters"}]} -------------------------------------------------------------------------------- /dump/playground/models.bson: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/dump/playground/models.bson -------------------------------------------------------------------------------- /dump/playground/models.metadata.json: -------------------------------------------------------------------------------- 1 | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"playground.models"}]} -------------------------------------------------------------------------------- /dump/playground/system.indexes.bson: -------------------------------------------------------------------------------- 1 | Gvkey_idname_id_nsplayground.usershvuniquekeyusernamename username_1nsplayground.usersbackgroundHvkey_idname_id_nsplayground.modelsMvkey_idname_id_nsplayground.manufactersFvkey_idname_id_nsplayground.cars -------------------------------------------------------------------------------- /dump/playground/users.bson: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byavv/angular2-playground/da208b648be0685d68131d9e65c223dcce06994d/dump/playground/users.bson -------------------------------------------------------------------------------- /dump/playground/users.metadata.json: -------------------------------------------------------------------------------- 1 | {"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"playground.users"},{"v":1,"unique":true,"key":{"username":1},"name":"username_1","ns":"playground.users","background":true}]} -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'), 4 | gulp = require('gulp'), 5 | $ = require('gulp-load-plugins')(), 6 | webpack = require("webpack-stream"), 7 | path = require('path'), 8 | mongobackup = require('mongobackup'); 9 | ; 10 | 11 | 12 | gulp.task('clean:build', () => { 13 | return (require('del'))('./build'); 14 | }); 15 | gulp.task("images", ["clean:build"], () => { 16 | return gulp.src("client/assets/images/*") 17 | .pipe(gulp.dest("build/images")); 18 | }); 19 | // build for production 20 | gulp.task("build", ["images"], () => { 21 | var wpConfig = require("./server/config/webpack")("production"); 22 | return gulp.src(__dirname + '/client/app/bootstrap.ts') 23 | .pipe(webpack(wpConfig)) 24 | .pipe(gulp.dest('./build/')); 25 | }); 26 | 27 | // dev task 28 | gulp.task('default', ["images"], () => { 29 | // livereload Chrome extension 30 | // extension: https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei 31 | $.livereload.listen(); 32 | $.nodemon().on('restart', () => { 33 | gulp.src('server/server.js') 34 | .pipe($.livereload()) 35 | .pipe($.notify('Reloading page, please wait...')); 36 | }) 37 | }); 38 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "NODE_ENV": "development" 4 | }, 5 | "ext": "js ts less sass html", 6 | "restartable": "rs", 7 | "ignore": [ 8 | ".git", 9 | "node_modules/**/node_modules", 10 | "/build/*.*", 11 | "/.idea/*.*", 12 | "/test/*.*", 13 | "gulpfile.js", 14 | "*.json", 15 | "/coverage" 16 | ], 17 | "verbose": true, 18 | "execMap": { 19 | "js": "node" 20 | }, 21 | "watch": [ 22 | "client/**/*.ts", 23 | "client/**/*.less", 24 | "client/**/*.sass", 25 | "server/**/*.js", 26 | "*.js" 27 | ] 28 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-playground", 3 | "version": "1.0.0", 4 | "description": "Angular2 playground project", 5 | "main": "server/server.js", 6 | "scripts": {}, 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/byavv/ng2-playground.git" 10 | }, 11 | "keywords": [ 12 | "angular2", 13 | "typescript", 14 | "playground" 15 | ], 16 | "author": "byavv", 17 | "license": "ISC", 18 | "dependencies": { 19 | "angular2": "^2.0.0-beta.8", 20 | "baggage-loader": "^0.2.4", 21 | "body-parser": "^1.14.1", 22 | "chalk": "^1.1.1", 23 | "d3": "^3.5.12", 24 | "es6-promise": "^3.0.2", 25 | "es6-shim": "^0.33.3", 26 | "express": "^4.13.3", 27 | "express-webpack-assets": "0.0.2", 28 | "jade": "^1.11.0", 29 | "loader-utils": "^0.2.12", 30 | "lodash": "^3.10.1", 31 | "method-override": "^2.3.5", 32 | "moment": "^2.10.6", 33 | "mongoose": "^4.3.1", 34 | "morgan": "^1.6.1", 35 | "nconf": "^0.8.2", 36 | "reflect-metadata": "0.1.2", 37 | "request": "^2.67.0", 38 | "rxjs": "5.0.0-beta.2", 39 | "source-map": "^0.1.43", 40 | "zone.js": "0.5.15" 41 | }, 42 | "devDependencies": { 43 | "assets-webpack-plugin": "^3.2.0", 44 | "bower-webpack-plugin": "^0.1.9", 45 | "css-loader": "^0.23.0", 46 | "del": "^2.2.0", 47 | "file-loader": "^0.8.5", 48 | "gulp": "^3.9.0", 49 | "gulp-livereload": "^3.8.1", 50 | "gulp-load-plugins": "^1.1.0", 51 | "gulp-nodemon": "^2.0.6", 52 | "gulp-notify": "^2.2.0", 53 | "less": "^2.5.3", 54 | "less-loader": "^2.2.2", 55 | "mongobackup": "^0.1.8", 56 | "ngmin": "^0.5.0", 57 | "ngmin-webpack-plugin": "^0.1.3", 58 | "node-sass": "^3.4.2", 59 | "raw-loader": "^0.5.1", 60 | "sass-loader": "^3.1.2", 61 | "style-loader": "^0.13.0", 62 | "ts-loader": "^0.7.2", 63 | "typescript": "^1.7.3", 64 | "webpack": "^1.12.9", 65 | "webpack-dev-middleware": "^1.4.0", 66 | "webpack-stream": "^3.0.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /server/config/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"), 4 | fs = require("fs"), 5 | nconf = require("nconf"), 6 | chalk = require("chalk"), 7 | defaultConf = require("./defaults"); 8 | 9 | /** 10 | * Add environment specific configuration 11 | */ 12 | let config = { 13 | for: (env, done) => { 14 | let configPath = path.join(__dirname, './env'); 15 | env = env.toLowerCase().trim(); 16 | return new Promise((resolve, reject) => { 17 | fs.readdir(configPath, (err, files) => { 18 | if (err) reject(err); 19 | !!files && files.forEach((file) => { 20 | if (file.match(new RegExp(env))) { 21 | nconf.overrides(require(configPath + '/' + file)); 22 | console.info(chalk.underline( 23 | `Configuration for ${chalk.white.bgBlue(env.toUpperCase()) } mode was built` 24 | )); 25 | } 26 | }); 27 | resolve(); 28 | }); 29 | }); 30 | } 31 | }; 32 | /** 33 | * Default app configuration 34 | * Use: config.configure.for("development").then().. 35 | */ 36 | Object.defineProperty(config, "configure", { 37 | get: () => { 38 | nconf.argv().env(); 39 | nconf.use("memory"); 40 | nconf.defaults(defaultConf); 41 | return config; 42 | } 43 | }); 44 | module.exports = config; -------------------------------------------------------------------------------- /server/config/defaults.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | appName: 'playground app' 3 | }; 4 | -------------------------------------------------------------------------------- /server/config/env/development.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | db: "mongodb://localhost/playground", 3 | httpPort: process.env.PORT || 3030, 4 | httpsPort: process.env.HTTPS_PORT || 3443, 5 | 6 | }; -------------------------------------------------------------------------------- /server/config/env/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | db: "mongodb://xxxxxxxxxxxxx", 3 | httpPort: process.env.PORT || 3030, 4 | httpsPort: process.env.HTTPS_PORT || 3443 5 | } 6 | -------------------------------------------------------------------------------- /server/config/env/test.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | db: "mongodb://localhost/mea2n-ts-test", 3 | httpPort: process.env.PORT || 3030, 4 | httpsPort: process.env.HTTPS_PORT || 3443, 5 | } -------------------------------------------------------------------------------- /server/config/express.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require("express"), 4 | morgan = require("morgan"), 5 | bodyParser = require("body-parser"), 6 | methodOverride = require("method-override"), 7 | path = require("path"), 8 | webpackAssets = require('express-webpack-assets'); 9 | 10 | module.exports = function (app) { 11 | var rootPath = process.cwd(); 12 | app.set('views', rootPath + '/server/views'); 13 | app.set('view engine', 'jade'); 14 | if (process.env.NODE_ENV === "development") { 15 | app.use(webpackAssets('./build/webpack-assets.json', { devMode: true })); 16 | var webpack = require('webpack'); 17 | var webpackConfig = require("./webpack")("development"); 18 | var compiler = webpack(webpackConfig); 19 | app.use(require("webpack-dev-middleware")(compiler, { 20 | publicPath: '/build/', 21 | stats: { colors: true } 22 | })); 23 | app.use(morgan("dev")); 24 | } else { 25 | app.use(webpackAssets('./build/webpack-assets.json', { devMode: false })); 26 | } 27 | 28 | app.use(bodyParser.urlencoded({ extended: false })); 29 | app.use(bodyParser.json()); 30 | app.use(methodOverride("_method")); 31 | 32 | app.use('/build/', express.static(rootPath + '/build/')); 33 | app.use('/vendor/', express.static(rootPath + '/client/vendor')); 34 | app.use('/polymer/', express.static(rootPath + '/client/app/polymerElements')); 35 | 36 | app.use((err, req, res, next) => { 37 | var code = 500, 38 | msg = { message: "Internal Server Error" }; 39 | switch (err.name) { 40 | case "UnauthorizedError": 41 | code = err.status; 42 | msg = undefined; 43 | break; 44 | case "BadRequestError": 45 | case "UnauthorizedAccessError": 46 | case "NotFoundError": 47 | code = err.status; 48 | msg = err.inner; 49 | break; 50 | default: 51 | break; 52 | } 53 | return res.status(code).json(msg); 54 | }); 55 | }; 56 | 57 | -------------------------------------------------------------------------------- /server/config/mongoose.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var mongoose = require("mongoose"), 4 | path = require("path"), 5 | fs = require("fs"), 6 | chalk = require("chalk"), 7 | nconf = require("nconf"); 8 | 9 | /** 10 | * Configure mongoose 11 | */ 12 | exports.configure = function () { 13 | mongoose.connect(nconf.get("db")); 14 | let db = mongoose.connection; 15 | db.on('error', (err) => { 16 | throw err; 17 | }); 18 | db.once('open', () => { 19 | console.log(chalk.green("Database opened")); 20 | }); 21 | 22 | let modelsPath = path.join(__dirname, '../models'); 23 | return new Promise((resolve, reject) => { 24 | fs.readdir(modelsPath, (err, modelsFiles) => { 25 | if (err) reject(err); 26 | !!modelsFiles && modelsFiles.forEach((file) => { 27 | if (/(.*)\.(js$)/.test(file)) { 28 | require(path.join(modelsPath, file)); 29 | } 30 | }); 31 | console.info(`Models loaded.`); 32 | resolve(); 33 | }); 34 | }) 35 | }; 36 | exports.close = function (done) { 37 | mongoose.disconnect((err) => { 38 | console.info(chalk.yellow('Disconnected from MongoDB.')); 39 | done(err); 40 | }); 41 | } -------------------------------------------------------------------------------- /server/config/webpack.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | webpack = require('webpack'), 3 | SaveHashes = require('assets-webpack-plugin'), 4 | BowerWebpackPlugin = require("bower-webpack-plugin"), 5 | NgminPlugin = require("ngmin-webpack-plugin"); 6 | 7 | module.exports = function (mode) { 8 | mode = mode || "development"; 9 | var webpackConfig = { 10 | entry: { 11 | main: ['./client/app/bootstrap.ts'], 12 | vendors: [ 13 | "es6-shim", 14 | "es6-promise", 15 | 'reflect-metadata', 16 | "rxjs", 17 | // "zone.js", 18 | "zone.js/dist/zone-microtask", 19 | "zone.js/dist/long-stack-trace-zone", 20 | 21 | 'angular2/common', 22 | 'angular2/core', 23 | 'angular2/router', 24 | 'angular2/http', 25 | "angular2/platform/browser", 26 | 'angular2/platform/common_dom', 27 | 'lodash' 28 | ] 29 | }, 30 | output: { 31 | path: path.join(__dirname, './build'), 32 | filename: '[name].js', 33 | publicPath: '/build/', 34 | pathinfo: false 35 | }, 36 | noParse: [ 37 | path.join("node_modules", '/angular2'), 38 | path.join("node_modules", '/lodash') 39 | ], 40 | module: { 41 | loaders: [ 42 | { test: /\.html$/, loader: "raw" }, 43 | { test: /\.css$/, loader: "style!css" }, 44 | { test: /\.less$/, loader: "style!css!less" }, 45 | { test: /\.scss$/, loader: "style!css!sass" }, 46 | { test: /\.(png|jpg)$/, loader: "url?limit=25000" }, 47 | { test: /\.jpe?g$|\.gif$|\.png$|\.wav$|\.mp3$|\.otf$/, loader: "file" }, 48 | { test: /\.(ttf|eot|svg|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file" }, 49 | { 50 | test: /\.ts$/, 51 | loader: 'ts-loader', 52 | exclude: ['node_modules'], 53 | query: { 54 | ignoreDiagnostics: [ 55 | 2403, // 2403 -> Subsequent variable declarations 56 | 2300, // 2300 -> Duplicate identifier 57 | 2374, // 2374 -> Duplicate number index signature 58 | 2375, // 2375 -> Duplicate string index signature 59 | ] 60 | }, 61 | } 62 | ], 63 | postLoaders: [], 64 | }, 65 | resolve: { 66 | extensions: ["", ".ts", ".js", ".less", ".sass"], 67 | modulesDirectories: ["node_modules"], 68 | alias: { 69 | 'rx': 'rxjs', 70 | 'moment': path.join(__dirname, "../../node_modules/moment/moment.js") 71 | }, 72 | 73 | }, 74 | plugins: [ 75 | new webpack.DefinePlugin({ 76 | _PROD_MODE: JSON.stringify(mode === "production"), 77 | _DEV_MODE: JSON.stringify((mode === "development")), 78 | _TEST_MODE: JSON.stringify((mode === "test")) 79 | }), 80 | new webpack.optimize.DedupePlugin(), 81 | new webpack.optimize.OccurenceOrderPlugin(true), 82 | new webpack.ProvidePlugin({ 83 | _: "lodash", 84 | $: "jquery", 85 | jQuery: "jquery" 86 | }), 87 | new BowerWebpackPlugin({ 88 | modulesDirectories: ["client/vendor"], 89 | manifestFiles: ["bower.json", ".bower.json"], 90 | searchResolveModulesDirectories: false 91 | }), 92 | new SaveHashes({ path: "./build/" }) 93 | ] 94 | }; 95 | 96 | switch (mode) { 97 | case "test": 98 | webpackConfig.entry = { 99 | app: ["./client/app/app.module.ts"] 100 | }; 101 | webpackConfig.module.postLoaders.push({ 102 | test: /\.ts$/, 103 | exclude: /(tests|node_modules|.\client\vendor)\//, 104 | loader: 'istanbul-instrumenter' 105 | }); 106 | webpackConfig.devtool = "hidden"; 107 | break; 108 | case "production": 109 | webpackConfig.plugins.push(new webpack.optimize.CommonsChunkPlugin("vendors", "vendors.js", Infinity)); 110 | webpackConfig.plugins.push( 111 | new NgminPlugin(), 112 | new webpack.optimize.UglifyJsPlugin({ 113 | mangle: false, 114 | compress: { 115 | warnings: false 116 | } 117 | })); 118 | webpackConfig.debug = false; 119 | webpackConfig.devtool = "false"; 120 | break; 121 | case "development": 122 | webpackConfig.debug = true; 123 | webpackConfig.devtool = "source-map"; 124 | webpackConfig.plugins.push( 125 | new webpack.optimize.CommonsChunkPlugin("vendors", "vendors.js", Infinity), 126 | new webpack.HotModuleReplacementPlugin()); 127 | break; 128 | } 129 | return webpackConfig; 130 | }; -------------------------------------------------------------------------------- /server/controllers/api.controller.js: -------------------------------------------------------------------------------- 1 | var mongoose = require("mongoose"), 2 | _ = require("lodash"), 3 | Car = mongoose.model("Car"), 4 | Manufacter = mongoose.model("Manufacter"), 5 | request = require('request'), 6 | qs = require('querystring'), 7 | Model = mongoose.model("Model"); 8 | 9 | 10 | 11 | function _createFilterQuery(request) { 12 | var countQuery = Car.count(); 13 | if (request.model) { 14 | countQuery.where("model").equals(request.model); 15 | } 16 | if (request.manufacter) { 17 | countQuery.where("manufacter").equals(request.manufacter); 18 | } 19 | if (request.yearFrom) { 20 | countQuery.where("year").gte(parseInt(request.yearFrom)); 21 | } 22 | if (request.yearUp) { 23 | countQuery.where("year").lte(parseInt(request.yearUp)); 24 | } 25 | if (request.priceFrom) { 26 | countQuery.where("price").gte(parseInt(request.priceFrom)); 27 | } 28 | if (request.colors && request.colors.length > 0) { 29 | countQuery.where("color").in(request.colors); 30 | } 31 | if (request.priceUp) { 32 | countQuery.where("price").lte(parseInt(request.priceUp)); 33 | } 34 | if (request.milageUp) { 35 | countQuery.where("milage").lte(parseInt(request.milageUp)); 36 | } 37 | if (request.milageFrom) { 38 | countQuery.where("milage").gte(parseInt(request.milageFrom)); 39 | } 40 | return countQuery; 41 | } 42 | 43 | module.exports = { 44 | 45 | initCarsDefaults: function (req, res, next) { 46 | Manufacter.find() 47 | .exec() 48 | .then((manufacters) => { 49 | Car.aggregate([ 50 | { 51 | $group: { 52 | _id: {}, 53 | minYear: { $min: "$year" } 54 | } 55 | } 56 | ], (err, result) => { 57 | if (err) throw err; 58 | return res.status(200) 59 | .send({ 60 | manufacters: manufacters, 61 | minYear: result[0].minYear 62 | }); 63 | }) 64 | }, (err) => { 65 | return res.sendStatus(500); 66 | }) 67 | }, 68 | 69 | getCarsByManufacter: function (req, res, next) { 70 | var query = Car.count(); 71 | if (req.body.manufacter) { 72 | Manufacter.findOne({ name: req.body.manufacter }) 73 | .populate('models', '-manufacter') 74 | .exec() 75 | .then((manufacter) => { 76 | query.where("manufacter").equals(manufacter.name); 77 | 78 | if (req.body.year) { 79 | query.where("year").gte(parseInt(req.body.year)); 80 | } 81 | if (req.body.priceFrom) { 82 | query.where("priceFrom").lte(parseInt(req.body.priceFrom)); 83 | } 84 | query.exec((err, count) => { 85 | return res.status(200).send({ count: count, models: manufacter.models }); 86 | }) 87 | }) 88 | } else { 89 | query.exec((err, count) => { 90 | return res.status(200).send({ count: count }); 91 | }) 92 | } 93 | }, 94 | 95 | getCarsCount: function (req, res, next) { 96 | var countQuery = _createFilterQuery(req.body); 97 | countQuery.exec((err, count) => { 98 | return res.status(200).send({ count: count }); 99 | }) 100 | }, 101 | 102 | seachCars: function (req, res, next) { 103 | var query = _createFilterQuery(req.body); 104 | 105 | query.exec().then((count) => { 106 | if (req.body.sort) { 107 | query.sort(`${req.body.sort}`); 108 | } 109 | if (req.body.limit) { 110 | query.limit(parseInt(req.body.limit)); 111 | } 112 | if (req.body.limit && req.body.page) { 113 | query.skip(parseInt(req.body.limit) * parseInt(req.body.page)); 114 | } 115 | query.find().exec((err, cars) => { 116 | return res.status(200).send({ cars: cars, count: count }); 117 | }) 118 | }) 119 | }, 120 | getCarDetails: function (req, res, next) { 121 | if (req.params.id) { 122 | Car.findById(req.params.id) 123 | .exec() 124 | .then((car) => { 125 | return res.status(200).send({ car: car }); 126 | }, (err) => { 127 | return res.sendStatus(500); 128 | }) 129 | } 130 | }, 131 | 132 | searchManufacters: function (req, res, next) { 133 | if (req.body.query) { 134 | var query = qs.stringify({ cmd: "getMakes" }); 135 | request 136 | .get('http://www.carqueryapi.com/api/0.3/?' + query, (err, response, body) => { 137 | if (!err && response.statusCode == 200) { 138 | var makes = JSON.parse(body).Makes; 139 | var filtered = 140 | makes 141 | .filter((make) => { 142 | return make.make_id.match(new RegExp(`^${req.body.query}`)) 143 | }) 144 | .map((val) => { return { name: val.make_display } }) 145 | return res.status(200).send(filtered); 146 | } 147 | }) 148 | } 149 | else { 150 | return res.status(200).send([]); 151 | } 152 | }, 153 | 154 | checkEmailEquality: function (req, res) { 155 | console.log(req.body) 156 | if (req.body.email) { 157 | if (req.body.email === "my@email.com") { 158 | return res.status(200).send({ equal: false }); 159 | } 160 | return res.status(200).send({ equal: true }); 161 | } else { 162 | return res.status(500).send({ error: true }); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /server/controllers/test.controller.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | 4 | testGet: function (req, res, next) { 5 | setTimeout(() => { 6 | return res.status(200).send({ data: "MY TEST DATA" }); 7 | }, 1000) 8 | }, 9 | testPost: function (req, res, next) { 10 | 11 | var token; 12 | var parts = req.headers.authorization.split(' '); 13 | if (parts.length == 2) { 14 | var scheme = parts[0]; 15 | var credentials = parts[1]; 16 | 17 | if (/^Bearer$/i.test(scheme)) { 18 | token = credentials; 19 | 20 | if (req.body.query) { 21 | setTimeout(() => { 22 | return res.status(200).send({ query: req.body.query, authHeader: token }); 23 | }, 1000) 24 | } else { 25 | setTimeout(() => { 26 | return res.sendStatus(500); 27 | }, 1000) 28 | } 29 | } else { 30 | setTimeout(() => { 31 | return res.sendStatus(500); 32 | }, 1000) 33 | } 34 | } 35 | }, 36 | errtestPost: function (req, res, next) { 37 | setTimeout(() => { 38 | return res.sendStatus(500); 39 | }, 3000) 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /server/models/car.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema; 8 | 9 | /** 10 | * Article Schema 11 | */ 12 | var CarsSchema = new Schema({ 13 | name: { 14 | type: String, 15 | default: '', 16 | }, 17 | added: { 18 | type: Date, 19 | default: Date.now 20 | }, 21 | description: { 22 | type: String, 23 | default: '', 24 | trim: true 25 | }, 26 | color: { 27 | type: String, 28 | default: '', 29 | trim: true 30 | }, 31 | milage: { 32 | type: Number, 33 | default: 0, 34 | }, 35 | images: [{ 36 | type: String 37 | }], 38 | price: { 39 | type: Number, 40 | default: 0, 41 | }, 42 | year: { 43 | type: Number, 44 | default: 2015, 45 | }, 46 | manufacter: { 47 | type: String, 48 | default: '', 49 | trim: true 50 | }, 51 | gear:{ 52 | type: String, 53 | default: 'manual', 54 | trim: true 55 | }, 56 | engine:{ 57 | type: String, 58 | default: 'petrol', 59 | trim: true 60 | }, 61 | model: { 62 | type: String, 63 | default: '', 64 | trim: true 65 | }, 66 | owner: { 67 | type: Schema.ObjectId, 68 | ref: 'User' 69 | } 70 | }); 71 | 72 | module.exports = mongoose.model('Car', CarsSchema); -------------------------------------------------------------------------------- /server/models/manufacter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'), 4 | Schema = mongoose.Schema; 5 | 6 | var ManufacterSchema = new Schema({ 7 | name: { 8 | type: String, 9 | default: '', 10 | trim: true, 11 | required: 'Title cannot be blank' 12 | }, 13 | models: [{ 14 | type: Schema.ObjectId, 15 | ref: 'Model' 16 | }] 17 | }); 18 | 19 | module.exports = mongoose.model('Manufacter', ManufacterSchema); -------------------------------------------------------------------------------- /server/models/models.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'), 4 | Schema = mongoose.Schema; 5 | 6 | var ModelSchema = new Schema({ 7 | name: { 8 | type: String, 9 | default: '', 10 | trim: true, 11 | required: 'Name cannot be blank' 12 | }, 13 | manufacter: { 14 | type: Schema.ObjectId, 15 | ref: 'Manufacter' 16 | } 17 | }); 18 | 19 | module.exports = mongoose.model('Model', ModelSchema); -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema; 8 | 9 | /** 10 | * User Schema 11 | */ 12 | var UserSchema = new Schema({ 13 | username: { 14 | type: String, 15 | unique: 'Username already exists', 16 | required: 'Please fill in a username', 17 | trim: true 18 | }, 19 | cars: [{ 20 | type: Schema.ObjectId, 21 | ref: 'Car' 22 | }] 23 | }); 24 | 25 | 26 | module.exports = mongoose.model('User', UserSchema); -------------------------------------------------------------------------------- /server/routes/api.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var nconf = require("nconf"), 4 | api = require('../controllers/api.controller'), 5 | test = require('../controllers/test.controller'); 6 | 7 | module.exports = function (app) { 8 | app.route("/api/getcount").post(api.getCarsCount); 9 | app.route("/api/initcarsdefaults").post(api.initCarsDefaults); 10 | app.route("/api/getmanufactermodels").post(api.getCarsByManufacter); 11 | app.route("/api/seachcars").post(api.seachCars); 12 | app.route("/api/getcar/:id").get(api.getCarDetails); 13 | app.route("/api/searchManufacters").post(api.searchManufacters); 14 | app.route("/api/checkEmail").post(api.checkEmailEquality); 15 | 16 | app.route("/test/get").get(test.testGet); 17 | app.route("/test/post").post(test.testPost); 18 | app.route("/test/errpost").post(test.errtestPost); 19 | 20 | 21 | }; -------------------------------------------------------------------------------- /server/routes/routes.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app) { 2 | //api 3 | require("./api.routes")(app); 4 | //default 5 | app.route('*').get((req, res) => { res.render('index'); }); 6 | }; 7 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require("express"), 4 | chalk = require("chalk"), 5 | config = require("./config/config"), 6 | mongooseConfig = require("./config/mongoose"), 7 | nconf = require("nconf"); 8 | 9 | const env = process.env.NODE_ENV || "development"; 10 | 11 | config 12 | .configure.for(env) 13 | .then(() => { 14 | return mongooseConfig.configure(); 15 | }) 16 | .then(() => { 17 | //<-- 18 | let app = express(); 19 | require("./config/express")(app); 20 | require("./routes/routes")(app); 21 | let port = nconf.get("httpPort") || 3000; 22 | app.listen(port); 23 | console.info(chalk.green(`Server started on http port: ${port}`)); 24 | }) 25 | .catch((error) => { 26 | console.error(chalk.bgRed.white(`Crush ${error}`)); 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /server/views/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta(charset='utf-8') 5 | meta(http-equiv='X-UA-Compatible', content='IE=edge') 6 | title ng2 playground 7 | meta(name='description', content='') 8 | meta(name='viewport', content='width=device-width, initial-scale=1') 9 | script(src='/vendor/webcomponentsjs/webcomponents-lite.js') 10 | script(src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js') 11 | link(rel='import' href='/vendor/polymer/polymer.html') 12 | link(rel='import' href='/polymer/custom.html') 13 | 14 | body 15 | app Loading... 16 | 17 | script(src = "#{webpack_asset('vendors').js}") 18 | script(src = "#{webpack_asset('main').js}") 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "removeComments": false, 9 | "noImplicitAny": false 10 | }, 11 | "exclude": [ 12 | "node_modules" 13 | ] 14 | } -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ambientDependencies": { 3 | "es6-promise": "github:DefinitelyTyped/DefinitelyTyped/es6-promise/es6-promise.d.ts#830e8ebd9ef137d039d5c7ede24a421f08595f83", 4 | "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#6697d6f7dadbf5773cb40ecda35a76027e0783b2", 5 | "lodash": "github:DefinitelyTyped/DefinitelyTyped/lodash/lodash.d.ts#df712b17fa4737fdb3eb52118b1465cd3be81a1d", 6 | "node": "github:DefinitelyTyped/DefinitelyTyped/node/node.d.ts#1c56e368e17bb28ca57577250624ca5bd561aa81", 7 | "zone.js": "github:DefinitelyTyped/DefinitelyTyped/zone.js/zone.js.d.ts#b923a5aaf013ac84c566f27ba6b5843211981c7a" 8 | } 9 | } --------------------------------------------------------------------------------