├── .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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
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 |
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 | `,
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
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 |
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 |
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 |
2 |
3 |
11 |
12 |
{{message}}
13 |
14 |
15 |
20 |
21 |
--------------------------------------------------------------------------------
/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 |
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 |
66 |
67 |
72 |
73 |
74 |
75 |
96 |
97 | [[errorMessage]]
98 |
99 |
100 |
101 |
102 |
186 |
187 |
--------------------------------------------------------------------------------
/client/app/polymerElements/select/select.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
56 |
57 |
58 |
59 |
93 |
94 |
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 | G v key _id name _id_ ns playground.users h v unique key username name username_1 ns playground.users background H v key _id name _id_ ns playground.models M v key _id name _id_ ns playground.manufacters F v key _id name _id_ ns playground.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 | }
--------------------------------------------------------------------------------