diff --git a/.angular-cli.json b/.angular-cli.json
new file mode 100644
index 00000000000..3462b18888b
--- /dev/null
+++ b/.angular-cli.json
@@ -0,0 +1,68 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "project": {
+ "name": "primeng"
+ },
+ "apps": [
+ {
+ "root": "src",
+ "outDir": "dist",
+ "assets": [
+ "assets",
+ "favicon.png"
+ ],
+ "index": "index.html",
+ "main": "main.ts",
+ "polyfills": "polyfills.ts",
+ "test": "test.ts",
+ "tsconfig": "tsconfig.app.json",
+ "testTsconfig": "tsconfig.spec.json",
+ "prefix": "app",
+ "styles": [
+ "styles.css",
+ "../node_modules/fullcalendar/dist/fullcalendar.min.css",
+ "../node_modules/quill/dist/quill.snow.css",
+ "../node_modules/font-awesome/css/font-awesome.min.css"
+ ],
+ "scripts": [
+ "../node_modules/jquery/dist/jquery.js",
+ "../node_modules/moment/moment.js",
+ "../node_modules/chart.js/dist/Chart.js",
+ "../node_modules/fullcalendar/dist/fullcalendar.js",
+ "../node_modules/quill/dist/quill.js",
+ "../node_modules/prismjs/prism.js",
+ "../node_modules/prismjs/components/prism-typescript.js"
+ ],
+ "environmentSource": "environments/environment.ts",
+ "environments": {
+ "dev": "environments/environment.ts",
+ "prod": "environments/environment.prod.ts"
+ }
+ }
+ ],
+ "e2e": {
+ "protractor": {
+ "config": "./protractor.conf.js"
+ }
+ },
+ "lint": [
+ {
+ "project": "src/tsconfig.app.json"
+ },
+ {
+ "project": "src/tsconfig.spec.json"
+ },
+ {
+ "project": "e2e/tsconfig.e2e.json"
+ }
+ ],
+ "test": {
+ "karma": {
+ "config": "./karma.conf.js"
+ }
+ },
+ "defaults": {
+ "styleExt": "css",
+ "component": {}
+ }
+}
diff --git a/.editorconfig b/.editorconfig
index 58717cd0a17..6e87a003da8 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,11 +1,13 @@
-# http://editorconfig.org
-
+# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
-indent_size = 4
-end_of_line = lf
-insert_final_newline = false
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = off
trim_trailing_whitespace = false
diff --git a/.gitignore b/.gitignore
index 0b6047ebd15..825c75d0a42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,32 +1,50 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+/components
+/resources
+/aot
+
# dependencies
-node_modules
-
-# generated by compilation
-primeng.js
-components/**/*.js
-showcase/*.js
-showcase/demo/**/*.js
-showcase/setup/*.js
-showcase/theming/*.js
-*.map
-
-# production showcase bundle
-dist
-
-# distribution resources
-resources/**/*.css
-resources/images
-
-# type definition
-*.d.ts
-
-# aot
-aot
-components/**/*.metadata.json
-
-# misc
-*.log
-.DS_STORE
-.idea
-.vscode
-.sass-cache
\ No newline at end of file
+/node_modules
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+testem.log
+/typings
+
+# themes
+/src/assets/components/themes/**/*.css
+/src/assets/components/themes/**/*.map
+!/src/assets/components/themes/bootstrap/theme.css
+
+# e2e
+/e2e/*.js
+/e2e/*.map
+
+# System Files
+.DS_Store
+Thumbs.db
diff --git a/.npmignore b/.npmignore
index fcd8c372f72..4d1507c6435 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,35 +1,53 @@
+# compiled output
+/dist
+/tmp
+/out-tsc
+/aot
+
+# source
+src
+
+# test
+e2e
+karmaconf.js
+protractor.conf.js
+
# dependencies
node_modules
-# demo
-showcase
-index.html
-upload.php
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+.editor-config
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
-# typescript
-*.ts
-!*.d.ts
-
-# git
-.git
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+testem.log
+/typings
.gitignore
# config
+.angular-cli.json
tsconfig.json
-tsconfig-aot.json
-webpack.config.js
-config
-gulpfile.js
-
-# aot
-aot
-
-# misc
-.github
-.vscode
-*.log
-prod
-.editorconfig
-
-
+tsconfig-release.json
+tslint.json
+# System Files
+.DS_Store
+Thumbs.db
diff --git a/README.md b/README.md
new file mode 100644
index 00000000000..0b027e07bcb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+# Primeng
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.1.0.
+
+## Development server
+
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
+
+## Running unit tests
+
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+Before running the tests make sure you are serving the app via `ng serve`.
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
diff --git a/e2e/app.e2e-spec.ts b/e2e/app.e2e-spec.ts
new file mode 100644
index 00000000000..83f61bb6a73
--- /dev/null
+++ b/e2e/app.e2e-spec.ts
@@ -0,0 +1,16 @@
+import { PrimengPage } from './app.po';
+
+describe('primeng App', () => {
+ let page: PrimengPage;
+
+ beforeEach(() => {
+ page = new PrimengPage();
+ });
+
+ it('should display welcome message', done => {
+ page.navigateTo();
+ page.getPROText()
+ .then(msg => expect(msg).toEqual('PrimeNG PRO Support'))
+ .then(done, done.fail);
+ });
+});
diff --git a/e2e/app.po.ts b/e2e/app.po.ts
new file mode 100644
index 00000000000..7b1a2d7ee41
--- /dev/null
+++ b/e2e/app.po.ts
@@ -0,0 +1,11 @@
+import { browser, by, element } from 'protractor';
+
+export class PrimengPage {
+ navigateTo() {
+ return browser.get('/');
+ }
+
+ getPROText() {
+ return element(by.css('.pro-title')).getText();
+ }
+}
diff --git a/e2e/tsconfig.e2e.json b/e2e/tsconfig.e2e.json
new file mode 100644
index 00000000000..e2a9a2fc779
--- /dev/null
+++ b/e2e/tsconfig.e2e.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/e2e",
+ "module": "commonjs",
+ "target": "es5",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ }
+}
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 00000000000..b9db99a15d6
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,50 @@
+'use strict';
+
+var gulp = require('gulp'),
+ concat = require('gulp-concat'),
+ uglifycss = require('gulp-uglifycss'),
+ rename = require('gulp-rename'),
+ del = require('del'),
+ flatten = require('gulp-flatten');
+
+gulp.task('build-css', function() {
+ gulp.src([
+ 'src/app/components/common/common.css',
+ 'src/app/components/**/*.css'
+ ])
+ .pipe(concat('primeng.css'))
+ .pipe(gulp.dest('resources'));
+});
+
+gulp.task('build-css-prod', function() {
+ gulp.src([
+ 'src/app/components/common/common.css',
+ 'src/app/components/**/*.css'
+ ])
+ .pipe(concat('primeng.css'))
+ .pipe(gulp.dest('resources'))
+ .pipe(uglifycss({"uglyComments": true}))
+ .pipe(rename('primeng.min.css'))
+ .pipe(gulp.dest('resources'));
+});
+
+gulp.task('images', function() {
+ return gulp.src(['src/app/components/**/images/*.png', 'src/app/components/**/images/*.gif'])
+ .pipe(flatten())
+ .pipe(gulp.dest('resources/images'));
+});
+
+gulp.task('themes', function() {
+ return gulp.src(['src/assets/components/themes/**/*'])
+ .pipe(gulp.dest('resources/themes'));
+});
+
+//Cleaning previous gulp tasks from project
+gulp.task('clean', function() {
+ del(['resources']);
+});
+
+//Building project with run sequence
+gulp.task('build-assets', ['clean','build-css-prod', 'images', 'themes']);
+
+
\ No newline at end of file
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 00000000000..4d9ab9d9482
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,33 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/0.13/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular/cli'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular/cli/plugins/karma')
+ ],
+ client:{
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ reports: [ 'html', 'lcovonly' ],
+ fixWebpackSourcePaths: true
+ },
+ angularCli: {
+ environment: 'dev'
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false
+ });
+};
diff --git a/package.json b/package.json
new file mode 100644
index 00000000000..1096b13498c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,65 @@
+{
+ "name": "primeng",
+ "version": "0.0.0",
+ "license": "MIT",
+ "scripts": {
+ "ng": "ng",
+ "start": "ng serve",
+ "build": "ng build",
+ "test": "ng test",
+ "lint": "ng lint",
+ "e2e": "ng e2e"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^4.0.0",
+ "@angular/common": "^4.0.0",
+ "@angular/compiler": "^4.0.0",
+ "@angular/core": "^4.0.0",
+ "@angular/forms": "^4.0.0",
+ "@angular/http": "^4.0.0",
+ "@angular/platform-browser": "^4.0.0",
+ "@angular/platform-browser-dynamic": "^4.0.0",
+ "@angular/router": "^4.0.0",
+ "core-js": "^2.4.1",
+ "install": "^0.10.1",
+ "npm": "^5.0.1",
+ "rxjs": "^5.1.0",
+ "zone.js": "^0.8.4",
+ "chart.js": "2.1.3",
+ "fullcalendar": "^3.1.0",
+ "jquery": "^3.1.1",
+ "moment": "^2.17.1",
+ "font-awesome":"4.7.0",
+ "quill": "^1.1.8",
+ "web-animations-js": "^2.2.2",
+ "prismjs": "^1.6.0"
+ },
+ "devDependencies": {
+ "@angular/cli": "1.1.0",
+ "@angular/compiler-cli": "^4.0.0",
+ "@angular/language-service": "^4.0.0",
+ "@types/jasmine": "2.5.45",
+ "@types/node": "~6.0.60",
+ "codelyzer": "~3.0.1",
+ "jasmine-core": "~2.6.2",
+ "jasmine-spec-reporter": "~4.1.0",
+ "karma": "~1.7.0",
+ "karma-chrome-launcher": "~2.1.1",
+ "karma-cli": "~1.0.1",
+ "karma-jasmine": "~1.1.0",
+ "karma-jasmine-html-reporter": "^0.2.2",
+ "karma-coverage-istanbul-reporter": "^1.2.1",
+ "protractor": "~5.1.2",
+ "ts-node": "~3.0.4",
+ "tslint": "~5.3.2",
+ "typescript": "~2.3.3",
+ "del": "^2.2.0",
+ "gulp": "^3.9.1",
+ "gulp-concat": "^2.6.0",
+ "gulp-flatten": "^0.2.0",
+ "gulp-rename": "^1.2.2",
+ "gulp-uglify": "^1.5.3",
+ "gulp-uglifycss": "^1.0.6"
+ }
+}
diff --git a/primeng.js b/primeng.js
new file mode 100644
index 00000000000..bc92b8d376c
--- /dev/null
+++ b/primeng.js
@@ -0,0 +1,75 @@
+/* Shorthand */
+"use strict";
+function __export(m) {
+ for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
+}
+Object.defineProperty(exports, "__esModule", { value: true });
+__export(require("./components/common/api"));
+__export(require("./components/common/shared"));
+__export(require("./components/accordion/accordion"));
+__export(require("./components/autocomplete/autocomplete"));
+__export(require("./components/blockui/blockui"));
+__export(require("./components/breadcrumb/breadcrumb"));
+__export(require("./components/button/button"));
+__export(require("./components/captcha/captcha"));
+__export(require("./components/calendar/calendar"));
+__export(require("./components/carousel/carousel"));
+__export(require("./components/chart/chart"));
+__export(require("./components/checkbox/checkbox"));
+__export(require("./components/chips/chips"));
+__export(require("./components/codehighlighter/codehighlighter"));
+__export(require("./components/contextmenu/contextmenu"));
+__export(require("./components/datagrid/datagrid"));
+__export(require("./components/datalist/datalist"));
+__export(require("./components/datascroller/datascroller"));
+__export(require("./components/datatable/datatable"));
+__export(require("./components/defer/defer"));
+__export(require("./components/confirmdialog/confirmdialog"));
+__export(require("./components/dialog/dialog"));
+__export(require("./components/dragdrop/dragdrop"));
+__export(require("./components/dropdown/dropdown"));
+__export(require("./components/editor/editor"));
+__export(require("./components/fieldset/fieldset"));
+__export(require("./components/fileupload/fileupload"));
+__export(require("./components/galleria/galleria"));
+__export(require("./components/gmap/gmap"));
+__export(require("./components/growl/growl"));
+__export(require("./components/inplace/inplace"));
+__export(require("./components/inputmask/inputmask"));
+__export(require("./components/inputswitch/inputswitch"));
+__export(require("./components/inputtext/inputtext"));
+__export(require("./components/inputtextarea/inputtextarea"));
+__export(require("./components/lightbox/lightbox"));
+__export(require("./components/listbox/listbox"));
+__export(require("./components/megamenu/megamenu"));
+__export(require("./components/menu/menu"));
+__export(require("./components/menubar/menubar"));
+__export(require("./components/messages/messages"));
+__export(require("./components/multiselect/multiselect"));
+__export(require("./components/orderlist/orderlist"));
+__export(require("./components/overlaypanel/overlaypanel"));
+__export(require("./components/paginator/paginator"));
+__export(require("./components/panel/panel"));
+__export(require("./components/panelmenu/panelmenu"));
+__export(require("./components/password/password"));
+__export(require("./components/picklist/picklist"));
+__export(require("./components/progressbar/progressbar"));
+__export(require("./components/radiobutton/radiobutton"));
+__export(require("./components/rating/rating"));
+__export(require("./components/schedule/schedule"));
+__export(require("./components/selectbutton/selectbutton"));
+__export(require("./components/slidemenu/slidemenu"));
+__export(require("./components/slider/slider"));
+__export(require("./components/spinner/spinner"));
+__export(require("./components/splitbutton/splitbutton"));
+__export(require("./components/steps/steps"));
+__export(require("./components/tabview/tabview"));
+__export(require("./components/tabmenu/tabmenu"));
+__export(require("./components/terminal/terminal"));
+__export(require("./components/tieredmenu/tieredmenu"));
+__export(require("./components/togglebutton/togglebutton"));
+__export(require("./components/toolbar/toolbar"));
+__export(require("./components/tooltip/tooltip"));
+__export(require("./components/tree/tree"));
+__export(require("./components/treetable/treetable"));
+__export(require("./components/tristatecheckbox/tristatecheckbox"));
\ No newline at end of file
diff --git a/protractor.conf.js b/protractor.conf.js
new file mode 100644
index 00000000000..7ee3b5ee863
--- /dev/null
+++ b/protractor.conf.js
@@ -0,0 +1,28 @@
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter } = require('jasmine-spec-reporter');
+
+exports.config = {
+ allScriptsTimeout: 11000,
+ specs: [
+ './e2e/**/*.e2e-spec.ts'
+ ],
+ capabilities: {
+ 'browserName': 'chrome'
+ },
+ directConnect: true,
+ baseUrl: 'http://localhost:4200/',
+ framework: 'jasmine',
+ jasmineNodeOpts: {
+ showColors: true,
+ defaultTimeoutInterval: 30000,
+ print: function() {}
+ },
+ onPrepare() {
+ require('ts-node').register({
+ project: 'e2e/tsconfig.e2e.json'
+ });
+ jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
+ }
+};
diff --git a/src/app/components/accordion/accordion.css b/src/app/components/accordion/accordion.css
new file mode 100644
index 00000000000..75c03fc4edf
--- /dev/null
+++ b/src/app/components/accordion/accordion.css
@@ -0,0 +1,51 @@
+.ui-accordion {
+ width: 100%;
+}
+
+.ui-accordion .ui-accordion-header {
+ cursor: pointer;
+ position: relative;
+ margin-top: 1px;
+ zoom: 1;
+}
+
+.ui-accordion .ui-accordion-header a {
+ display: block;
+ padding: .5em .5em .5em 2em;
+}
+
+.ui-accordion .ui-accordion-header>.fa {
+ position: absolute;
+ left: .5em;
+ top: 50%;
+ margin-top: -.5em;
+}
+
+.ui-accordion .ui-accordion-content {
+ padding: 1em;
+ border-top: 0;
+ overflow: visible;
+ zoom: 1;
+}
+
+.ui-accordion .ui-accordion-header.ui-state-disabled,
+.ui-accordion .ui-accordion-header.ui-state-disabled a {
+ cursor: default;
+}
+
+.ui-accordion-content-wrapper-overflown {
+ overflow: hidden;
+}
+
+.ui-rtl .ui-accordion .ui-accordion-header a {
+ padding: .5em 2em .5em .5em;
+}
+
+.ui-rtl .ui-accordion .ui-accordion-header > .fa {
+ left: initial;
+ right: .5em;
+}
+
+.ui-rtl .ui-accordion .ui-accordion-header > .fa-caret-right:before {
+ content: '\f0d9';
+}
\ No newline at end of file
diff --git a/src/app/components/accordion/accordion.ts b/src/app/components/accordion/accordion.ts
new file mode 100644
index 00000000000..ce8e43502a5
--- /dev/null
+++ b/src/app/components/accordion/accordion.ts
@@ -0,0 +1,148 @@
+import {NgModule,Component,ElementRef,AfterContentInit,Input,Output,EventEmitter,ContentChildren,QueryList} from '@angular/core';
+import {trigger,state,style,transition,animate} from '@angular/animations';
+import {CommonModule} from '@angular/common';
+import {Header} from '../common/shared';
+import {BlockableUI} from '../common/blockableui';
+
+@Component({
+ selector: 'p-accordion',
+ template: `
+
+
+
+ `,
+})
+export class Accordion implements BlockableUI {
+
+ @Input() multiple: boolean;
+
+ @Output() onClose: EventEmitter = new EventEmitter();
+
+ @Output() onOpen: EventEmitter = new EventEmitter();
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() lazy: boolean;
+
+ public tabs: AccordionTab[] = [];
+
+ constructor(public el: ElementRef) {}
+
+ addTab(tab: AccordionTab) {
+ this.tabs.push(tab);
+ }
+
+ getBlockableElement(): HTMLElement {
+ return this.el.nativeElement.children[0];
+ }
+}
+
+@Component({
+ selector: 'p-accordionTab',
+ template: `
+
+
+ `,
+ animations: [
+ trigger('tabContent', [
+ state('hidden', style({
+ height: '0'
+ })),
+ state('visible', style({
+ height: '*'
+ })),
+ transition('visible <=> hidden', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
+ ])
+ ]
+})
+export class AccordionTab {
+
+ @Input() header: string;
+
+ @Input() selected: boolean;
+
+ @Input() disabled: boolean;
+
+ @Output() selectedChange: EventEmitter = new EventEmitter();
+
+ @ContentChildren(Header) headerFacet: QueryList;
+
+ public animating: boolean;
+
+ constructor(public accordion: Accordion) {
+ this.accordion.addTab(this);
+ }
+
+ toggle(event) {
+ if(this.disabled || this.animating) {
+ return false;
+ }
+
+ this.animating = true;
+ let index = this.findTabIndex();
+
+ if(this.selected) {
+ this.selected = false;
+ this.accordion.onClose.emit({originalEvent: event, index: index});
+ }
+ else {
+ if(!this.accordion.multiple) {
+ for(var i = 0; i < this.accordion.tabs.length; i++) {
+ this.accordion.tabs[i].selected = false;
+ this.accordion.tabs[i].selectedChange.emit(false);
+ }
+ }
+
+ this.selected = true;
+ this.accordion.onOpen.emit({originalEvent: event, index: index});
+ }
+
+ this.selectedChange.emit(this.selected);
+
+ event.preventDefault();
+ }
+
+ findTabIndex() {
+ let index = -1;
+ for(var i = 0; i < this.accordion.tabs.length; i++) {
+ if(this.accordion.tabs[i] == this) {
+ index = i;
+ break;
+ }
+ }
+ return index;
+ }
+
+ get lazy(): boolean {
+ return this.accordion.lazy;
+ }
+
+ get hasHeaderFacet(): boolean {
+ return this.headerFacet && this.headerFacet.length > 0;
+ }
+
+ onToggleDone(event: Event) {
+ this.animating = false;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Accordion,AccordionTab],
+ declarations: [Accordion,AccordionTab]
+})
+export class AccordionModule { }
diff --git a/src/app/components/autocomplete/autocomplete.css b/src/app/components/autocomplete/autocomplete.css
new file mode 100644
index 00000000000..e2f89e1e10e
--- /dev/null
+++ b/src/app/components/autocomplete/autocomplete.css
@@ -0,0 +1,142 @@
+.ui-autocomplete {
+ width: auto;
+ zoom: 1;
+ cursor: pointer;
+ -moz-box-shadow: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ position: relative;
+ display: inline-block;
+}
+
+.ui-autocomplete .ui-autocomplete-dropdown {
+ height: 100%;
+ width: 2em;
+ margin-right: 0;
+ vertical-align: top;
+}
+
+.ui-autocomplete-query {
+ font-weight: bold;
+}
+
+.ui-autocomplete-panel {
+ position: absolute;
+ overflow: auto;
+}
+
+.ui-autocomplete-panel .ui-autocomplete-list {
+ padding: 0.4em;
+ border: 0 none;
+}
+
+.ui-autocomplete-panel .ui-autocomplete-list-item {
+ border: 0 none;
+ cursor: pointer;
+ font-weight: normal;
+ margin: 1px 0;
+ padding: 0.186em 0.313em;
+ text-align: left;
+}
+
+.ui-autocomplete .ui-button-icon-only,
+.ui-autocomplete .ui-button-icon-only:hover,
+.ui-autocomplete .ui-button-icon-only:focus,
+.ui-autocomplete .ui-button-icon-only:active {
+ border-left: 0 none;
+}
+
+/* Multiple Selection */
+.ui-autocomplete-multiple-container {
+ display: inline-block;
+}
+
+.ui-autocomplete-multiple-container.ui-inputtext {
+ clear: left;
+ cursor: text;
+ list-style-type: none;
+ margin: 0;
+ overflow: hidden;
+ padding: 0 .25em;
+}
+
+.ui-autocomplete-token {
+ cursor: default;
+ display: inline-block;
+ vertical-align: middle;
+ overflow: hidden;
+ padding: .125em .5em;
+ white-space: nowrap;
+ position: relative;
+ margin-right: .125em;
+ border: 0 none;
+ font-size: .9em;
+}
+
+.ui-autocomplete-token-label {
+ display: block;
+ margin-right: 2em;
+}
+
+.ui-autocomplete-token-icon {
+ margin-top: -.5em;
+ position: absolute;
+ right: 0.2em;
+ top: 50%;
+ cursor: pointer;
+}
+
+.ui-autocomplete-input-token {
+ display: inline-block;
+ vertical-align: middle;
+ list-style-type: none;
+ margin: 0 0 0 .125em;
+ padding: .25em .25em .25em 0;
+}
+
+.ui-autocomplete-input-token input {
+ border: 0 none;
+ width: 10em;
+ outline: medium none;
+ background-color: transparent;
+ margin: 0;
+ padding: 0;
+ box-shadow: none;
+ -moz-border-radius: 0;
+ -webkit-border-radius: 0;
+ border-radius: 0;
+}
+
+.ui-autocomplete-dd input.ui-corner-all ,
+.ui-autocomplete-dd .ui-autocomplete-multiple-container.ui-corner-all {
+ -moz-border-radius-topright: 0px;
+ -webkit-border-top-right-radius: 0px;
+ border-top-right-radius: 0px;
+ -moz-border-radius-bottomright: 0px;
+ -webkit-border-bottom-right-radius: 0px;
+ border-bottom-right-radius: 0px;
+ }
+
+.ui-autocomplete-dd .ui-autocomplete-dropdown.ui-corner-all {
+ -moz-border-radius-topleft: 0px;
+ -webkit-border-top-left-radius: 0px;
+ border-top-left-radius: 0px;
+ -moz-border-radius-bottomleft: 0px;
+ -webkit-border-bottom-left-radius: 0px;
+ border-bottom-left-radius: 0px;
+}
+
+/** AutoComplete **/
+.ui-fluid .ui-autocomplete,
+.ui-fluid .ui-autocomplete-input {
+ width: 100%;
+}
+
+.ui-fluid .ui-autocomplete.ui-autocomplete-dd .ui-autocomplete-input,
+.ui-fluid .ui-autocomplete.ui-autocomplete-dd .ui-autocomplete-multiple-container {
+ width: calc(100% - 2em);
+}
+
+.ui-fluid .ui-autocomplete .ui-autocomplete-dropdown.ui-button {
+ width: 2em;
+}
\ No newline at end of file
diff --git a/src/app/components/autocomplete/autocomplete.ts b/src/app/components/autocomplete/autocomplete.ts
new file mode 100644
index 00000000000..c053bd73838
--- /dev/null
+++ b/src/app/components/autocomplete/autocomplete.ts
@@ -0,0 +1,534 @@
+import {NgModule,Component,ViewChild,ElementRef,AfterViewInit,AfterContentInit,AfterViewChecked,Input,Output,EventEmitter,ContentChildren,QueryList,TemplateRef,Renderer2,forwardRef,ChangeDetectorRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {InputTextModule} from '../inputtext/inputtext';
+import {ButtonModule} from '../button/button';
+import {SharedModule,PrimeTemplate} from '../common/shared';
+import {DomHandler} from '../dom/domhandler';
+import {ObjectUtils} from '../utils/ObjectUtils';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const AUTOCOMPLETE_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => AutoComplete),
+ multi: true
+};
+
+@Component({
+ selector: 'p-autoComplete',
+ template: `
+
+
+
+
+ -
+ {{field ? option[field] : option}}
+
+
+ - {{emptyMessage}}
+
+
+
+ `,
+ host: {
+ '[class.ui-inputwrapper-filled]': 'filled',
+ '[class.ui-inputwrapper-focus]': 'focus'
+ },
+ providers: [DomHandler,ObjectUtils,AUTOCOMPLETE_VALUE_ACCESSOR]
+})
+export class AutoComplete implements AfterViewInit,AfterViewChecked,ControlValueAccessor {
+
+ @Input() minLength: number = 1;
+
+ @Input() delay: number = 300;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() inputStyle: any;
+
+ @Input() inputId: string;
+
+ @Input() inputStyleClass: string;
+
+ @Input() placeholder: string;
+
+ @Input() readonly: boolean;
+
+ @Input() disabled: boolean;
+
+ @Input() maxlength: number;
+
+ @Input() size: number;
+
+ @Input() appendTo: any;
+
+ @Input() autoHighlight: boolean;
+
+ @Input() type: string = 'text';
+
+ @Output() completeMethod: EventEmitter = new EventEmitter();
+
+ @Output() onSelect: EventEmitter = new EventEmitter();
+
+ @Output() onUnselect: EventEmitter = new EventEmitter();
+
+ @Output() onFocus: EventEmitter = new EventEmitter();
+
+ @Output() onBlur: EventEmitter = new EventEmitter();
+
+ @Output() onDropdownClick: EventEmitter = new EventEmitter();
+
+ @Input() field: string;
+
+ @Input() scrollHeight: string = '200px';
+
+ @Input() dropdown: boolean;
+
+ @Input() multiple: boolean;
+
+ @Input() tabindex: number;
+
+ @Input() dataKey: string;
+
+ @Input() emptyMessage: string;
+
+ @ViewChild('in') inputEL: ElementRef;
+
+ @ViewChild('multiIn') multiInputEL: ElementRef;
+
+ @ViewChild('panel') panelEL: ElementRef;
+
+ @ViewChild('multiContainer') multiContainerEL: ElementRef;
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public itemTemplate: TemplateRef;
+
+ public selectedItemTemplate: TemplateRef;
+
+ value: any;
+
+ _suggestions: any[];
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ timeout: any;
+
+ differ: any;
+
+ panelVisible: boolean = false;
+
+ documentClickListener: any;
+
+ suggestionsUpdated: boolean;
+
+ highlightOption: any;
+
+ highlightOptionChanged: boolean;
+
+ focus: boolean = false;
+
+ filled: boolean;
+
+ inputClick: boolean;
+
+ inputKeyDown: boolean;
+
+ noResults: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2, public objectUtils: ObjectUtils, public cd: ChangeDetectorRef) {}
+
+ @Input() get suggestions(): any[] {
+ return this._suggestions;
+ }
+
+ set suggestions(val:any[]) {
+ this._suggestions = val;
+
+ if(this.panelEL && this.panelEL.nativeElement) {
+ if(this._suggestions && this._suggestions.length) {
+ this.noResults = false;
+ this.show();
+ this.suggestionsUpdated = true;
+
+ if(this.autoHighlight) {
+ this.highlightOption = this._suggestions[0];
+ }
+ }
+ else {
+ this.noResults = true;
+
+ if(this.emptyMessage) {
+ this.show();
+ this.suggestionsUpdated = true;
+ }
+ else {
+ this.hide();
+ }
+ }
+ }
+ }
+
+ ngAfterContentInit() {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'item':
+ this.itemTemplate = item.template;
+ break;
+
+ case 'selectedItem':
+ this.selectedItemTemplate = item.template;
+ break;
+
+ default:
+ this.itemTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ ngAfterViewInit() {
+ if(this.appendTo) {
+ if(this.appendTo === 'body')
+ document.body.appendChild(this.panelEL.nativeElement);
+ else
+ this.domHandler.appendChild(this.panelEL.nativeElement, this.appendTo);
+ }
+ }
+
+ ngAfterViewChecked() {
+ if(this.suggestionsUpdated) {
+ this.align();
+ this.suggestionsUpdated = false;
+ }
+
+ if(this.highlightOptionChanged) {
+ let listItem = this.domHandler.findSingle(this.panelEL.nativeElement, 'li.ui-state-highlight');
+ if(listItem) {
+ this.domHandler.scrollInView(this.panelEL.nativeElement, listItem);
+ }
+ this.highlightOptionChanged = false;
+ }
+ }
+
+ writeValue(value: any) : void {
+ this.value = value;
+ this.filled = this.value && this.value != '';
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ onInput(event: KeyboardEvent) {
+ if(!this.inputKeyDown) {
+ return;
+ }
+
+ let value = ( event.target).value;
+ if(!this.multiple) {
+ this.onModelChange(value);
+ }
+
+ if(value.length === 0) {
+ this.hide();
+ }
+
+ if(value.length >= this.minLength) {
+ //Cancel the search request if user types within the timeout
+ if(this.timeout) {
+ clearTimeout(this.timeout);
+ }
+
+ this.timeout = setTimeout(() => {
+ this.search(event, value);
+ }, this.delay);
+ }
+ else {
+ this.suggestions = null;
+ }
+ this.updateFilledState();
+ this.inputKeyDown = false;
+ }
+
+ onInputClick(event: MouseEvent) {
+ if(this.documentClickListener) {
+ this.inputClick = true;
+ }
+ }
+
+ search(event: any, query: string) {
+ //allow empty string but not undefined or null
+ if(query === undefined || query === null) {
+ return;
+ }
+
+ this.completeMethod.emit({
+ originalEvent: event,
+ query: query
+ });
+ }
+
+ selectItem(option: any) {
+ if(this.multiple) {
+ this.multiInputEL.nativeElement.value = '';
+ this.value = this.value||[];
+ if(!this.isSelected(option)) {
+ this.value = [...this.value,option];
+ this.onModelChange(this.value);
+ }
+ }
+ else {
+ this.inputEL.nativeElement.value = this.field ? this.objectUtils.resolveFieldData(option, this.field): option;
+ this.value = option;
+ this.onModelChange(this.value);
+ }
+
+ this.onSelect.emit(option);
+
+ this.focusInput();
+ }
+
+ show() {
+ if(this.multiInputEL || this.inputEL) {
+ let hasFocus = this.multiple ? document.activeElement == this.multiInputEL.nativeElement : document.activeElement == this.inputEL.nativeElement ;
+ if(!this.panelVisible && hasFocus) {
+ this.panelVisible = true;
+ this.panelEL.nativeElement.style.zIndex = ++DomHandler.zindex;
+ this.domHandler.fadeIn(this.panelEL.nativeElement, 200);
+ this.bindDocumentClickListener();
+ }
+ }
+ }
+
+ align() {
+ if(this.appendTo)
+ this.domHandler.absolutePosition(this.panelEL.nativeElement, (this.multiple ? this.multiContainerEL.nativeElement : this.inputEL.nativeElement));
+ else
+ this.domHandler.relativePosition(this.panelEL.nativeElement, (this.multiple ? this.multiContainerEL.nativeElement : this.inputEL.nativeElement));
+ }
+
+ hide() {
+ this.panelVisible = false;
+ this.unbindDocumentClickListener();
+ }
+
+ handleDropdownClick(event) {
+ this.focusInput();
+ let queryValue = this.multiple ? this.multiInputEL.nativeElement.value : this.inputEL.nativeElement.value;
+ this.onDropdownClick.emit({
+ originalEvent: event,
+ query: queryValue
+ });
+ }
+
+ focusInput() {
+ if(this.multiple)
+ this.multiInputEL.nativeElement.focus();
+ else
+ this.inputEL.nativeElement.focus();
+ }
+
+ removeItem(item: any) {
+ let itemIndex = this.domHandler.index(item);
+ let removedValue = this.value[itemIndex];
+ this.value = this.value.filter((val, i) => i!=itemIndex);
+ this.onUnselect.emit(removedValue);
+ this.onModelChange(this.value);
+ }
+
+ onKeydown(event) {
+ if(this.panelVisible) {
+ let highlightItemIndex = this.findOptionIndex(this.highlightOption);
+
+ switch(event.which) {
+ //down
+ case 40:
+ if(highlightItemIndex != -1) {
+ var nextItemIndex = highlightItemIndex + 1;
+ if(nextItemIndex != (this.suggestions.length)) {
+ this.highlightOption = this.suggestions[nextItemIndex];
+ this.highlightOptionChanged = true;
+ }
+ }
+ else {
+ this.highlightOption = this.suggestions[0];
+ }
+
+ event.preventDefault();
+ break;
+
+ //up
+ case 38:
+ if(highlightItemIndex > 0) {
+ let prevItemIndex = highlightItemIndex - 1;
+ this.highlightOption = this.suggestions[prevItemIndex];
+ this.highlightOptionChanged = true;
+ }
+
+ event.preventDefault();
+ break;
+
+ //enter
+ case 13:
+ if(this.highlightOption) {
+ this.selectItem(this.highlightOption);
+ this.hide();
+ }
+ event.preventDefault();
+ break;
+
+ //escape
+ case 27:
+ this.hide();
+ event.preventDefault();
+ break;
+
+
+ //tab
+ case 9:
+ if(this.highlightOption) {
+ this.selectItem(this.highlightOption);
+ }
+ this.hide();
+ break;
+ }
+ } else {
+ if(event.which === 40 && this.suggestions) {
+ this.search(event,event.target.value);
+ }
+ }
+
+ if(this.multiple) {
+ switch(event.which) {
+ //backspace
+ case 8:
+ if(this.value && this.value.length && !this.multiInputEL.nativeElement.value) {
+ this.value = [...this.value];
+ let removedValue = this.value.pop();
+ this.onUnselect.emit(removedValue);
+ this.onModelChange(this.value);
+ }
+ break;
+ }
+ }
+
+ this.inputKeyDown = true;
+ }
+
+ onInputFocus(event) {
+ this.focus = true;
+ this.onFocus.emit(event);
+ }
+
+ onInputBlur(event) {
+ this.focus = false;
+ this.onModelTouched();
+ this.onBlur.emit(event);
+ }
+
+ onInputChange(event) {
+ this.value = ( event.target).value;
+ }
+
+ isSelected(val: any): boolean {
+ let selected: boolean = false;
+ if(this.value && this.value.length) {
+ for(let i = 0; i < this.value.length; i++) {
+ if(this.objectUtils.equals(this.value[i], val, this.dataKey)) {
+ selected = true;
+ break;
+ }
+ }
+ }
+ return selected;
+ }
+
+ findOptionIndex(option): number {
+ let index: number = -1;
+ if(this.suggestions) {
+ for(let i = 0; i < this.suggestions.length; i++) {
+ if(this.objectUtils.equals(option, this.suggestions[i])) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ return index;
+ }
+
+ updateFilledState() {
+ if(this.multiple)
+ this.filled = (this.value && this.value.length) || (this.multiInputEL && this.multiInputEL.nativeElement && this.multiInputEL.nativeElement.value != '');
+ else
+ this.filled = this.inputEL && this.inputEL.nativeElement && this.inputEL.nativeElement.value != '';
+ }
+
+ bindDocumentClickListener() {
+ if(!this.documentClickListener) {
+ this.documentClickListener = this.renderer.listen('document', 'click', (event) => {
+ if(event.which === 3) {
+ return;
+ }
+
+ if(this.inputClick)
+ this.inputClick = false;
+ else
+ this.hide();
+
+ this.cd.markForCheck();
+ });
+ }
+ }
+
+ unbindDocumentClickListener() {
+ if(this.documentClickListener) {
+ this.documentClickListener();
+ this.documentClickListener = null;
+ }
+ }
+
+ ngOnDestroy() {
+ this.unbindDocumentClickListener();
+
+ if(this.appendTo) {
+ this.el.nativeElement.appendChild(this.panelEL.nativeElement);
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,InputTextModule,ButtonModule,SharedModule],
+ exports: [AutoComplete,SharedModule],
+ declarations: [AutoComplete]
+})
+export class AutoCompleteModule { }
\ No newline at end of file
diff --git a/src/app/components/blockui/blockui.css b/src/app/components/blockui/blockui.css
new file mode 100644
index 00000000000..4b00320e665
--- /dev/null
+++ b/src/app/components/blockui/blockui.css
@@ -0,0 +1,11 @@
+.ui-blockui {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.ui-blockui-document {
+ position: fixed;
+}
\ No newline at end of file
diff --git a/src/app/components/blockui/blockui.ts b/src/app/components/blockui/blockui.ts
new file mode 100644
index 00000000000..a3859da1223
--- /dev/null
+++ b/src/app/components/blockui/blockui.ts
@@ -0,0 +1,74 @@
+import {NgModule,Component,Input,AfterViewInit,OnDestroy,EventEmitter,ElementRef,ViewChild} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {BlockableUI} from '../common/blockableui';
+
+@Component({
+ selector: 'p-blockUI',
+ template: `
+
+
+
+ `,
+ providers: [DomHandler]
+})
+export class BlockUI implements AfterViewInit,OnDestroy {
+
+ @Input() target: any;
+
+ @ViewChild('mask') mask: ElementRef;
+
+ _blocked: boolean;
+
+ constructor(public el: ElementRef,public domHandler: DomHandler) {}
+
+ @Input() get blocked(): boolean {
+ return this._blocked;
+ }
+
+ set blocked(val: boolean) {
+ this._blocked = val;
+
+ if(this.mask.nativeElement) {
+ if(this._blocked)
+ this.block();
+ else
+ this.unblock();
+ }
+ }
+
+ ngAfterViewInit() {
+ if(this.target && !this.target.getBlockableElement) {
+ throw 'Target of BlockUI must implement BlockableUI interface';
+ }
+ }
+
+ block() {
+ if(this.target) {
+ this.target.getBlockableElement().appendChild(this.mask.nativeElement);
+ let style = this.target.style||{};
+ style.position = 'relative';
+ this.target.style = style;
+ }
+ else {
+ document.body.appendChild(this.mask.nativeElement);
+ }
+
+ this.mask.nativeElement.style.zIndex = String(++DomHandler.zindex);
+ }
+
+ unblock() {
+ this.el.nativeElement.appendChild(this.mask.nativeElement);
+ }
+
+ ngOnDestroy() {
+
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [BlockUI],
+ declarations: [BlockUI]
+})
+export class BlockUIModule { }
\ No newline at end of file
diff --git a/src/app/components/breadcrumb/breadcrumb.css b/src/app/components/breadcrumb/breadcrumb.css
new file mode 100644
index 00000000000..1a6c5573fd7
--- /dev/null
+++ b/src/app/components/breadcrumb/breadcrumb.css
@@ -0,0 +1,20 @@
+/** Breadcrumb **/
+.ui-breadcrumb {
+ margin: 0;
+ padding: 0;
+ padding: .3em;
+}
+
+.ui-breadcrumb ul {
+ margin: 0;
+ padding: 0;
+}
+
+.ui-breadcrumb ul li {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ui-breadcrumb ul li .ui-menuitem-link {
+ text-decoration: none;
+}
\ No newline at end of file
diff --git a/src/app/components/breadcrumb/breadcrumb.ts b/src/app/components/breadcrumb/breadcrumb.ts
new file mode 100644
index 00000000000..24681de122c
--- /dev/null
+++ b/src/app/components/breadcrumb/breadcrumb.ts
@@ -0,0 +1,97 @@
+import {NgModule,Component,Input,OnDestroy,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {MenuItem} from '../common/menuitem';
+import {Location} from '@angular/common';
+import {RouterModule} from '@angular/router';
+
+@Component({
+ selector: 'p-breadcrumb',
+ template: `
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ `
+})
+export class Breadcrumb implements OnDestroy {
+
+ @Input() model: MenuItem[];
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() home: MenuItem;
+
+ itemClick(event, item: MenuItem) {
+ if(item.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if(!item.url) {
+ event.preventDefault();
+ }
+
+ if(item.command) {
+ if(!item.eventEmitter) {
+ item.eventEmitter = new EventEmitter();
+ item.eventEmitter.subscribe(item.command);
+ }
+
+ item.eventEmitter.emit({
+ originalEvent: event,
+ item: item
+ });
+ }
+ }
+
+ onHomeClick(event) {
+ if(this.home) {
+ this.itemClick(event, this.home);
+ }
+ }
+
+ ngOnDestroy() {
+ if(this.model) {
+ for(let item of this.model) {
+ if(item.eventEmitter) {
+ item.eventEmitter.unsubscribe();
+ }
+ }
+ }
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule,RouterModule],
+ exports: [Breadcrumb,RouterModule],
+ declarations: [Breadcrumb]
+})
+export class BreadcrumbModule { }
\ No newline at end of file
diff --git a/src/app/components/button/button.css b/src/app/components/button/button.css
new file mode 100644
index 00000000000..bdfecfa3da3
--- /dev/null
+++ b/src/app/components/button/button.css
@@ -0,0 +1,192 @@
+/* Button */
+.ui-button {
+ display: inline-block;
+ position: relative;
+ padding: 0;
+ margin-right: .1em;
+ text-decoration: none !important;
+ cursor: pointer;
+ text-align: center;
+ zoom: 1;
+ overflow: visible; /* the overflow property removes extra width in IE */
+}
+
+.ui-button-icon-only {
+ width: 2em;
+}
+
+/*button text element */
+.ui-button .ui-button-text {
+ display: block;
+ line-height: normal;
+}
+
+.ui-button-text-only .ui-button-text {
+ padding: .25em 1em;
+}
+
+.ui-button-icon-only .ui-button-text {
+ padding: .25em;
+ text-indent: -9999999px;
+}
+
+.ui-button-text-icon-left .ui-button-text {
+ padding: .25em 1em .25em 2.1em;
+}
+
+.ui-button-text-icon-right .ui-button-text {
+ padding: .25em 2.1em .25em 1em;
+}
+
+/*button icon element(s) */
+.ui-button-icon-only .fa,
+.ui-button-text-icon-left .fa,
+.ui-button-text-icon-right .fa {
+ position: absolute;
+ top: 50%;
+ margin-top: -.5em;
+}
+
+.ui-button-icon-only .fa {
+ top: 50%;
+ left: 50%;
+ margin-top: -.5em;
+ margin-left: -.6em;
+}
+
+.ui-button-icon-left {
+ left: .5em;
+}
+
+.ui-button-icon-right {
+ right: .5em;
+}
+
+/*button sets*/
+.ui-buttonset .ui-button {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+/* workarounds */
+button.ui-button::-moz-focus-inner {
+ border: 0; padding: 0; /* reset extra padding in Firefox */
+}
+
+/** Fluid **/
+.ui-fluid .ui-button {
+ width: 100%;
+ box-sizing: border-box;
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing: border-box;
+}
+
+.ui-fluid .ui-button-text-icon-left .ui-button-text,
+.ui-fluid .ui-button-text-icon-right .ui-button-text {
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+/** ButtonSet **/
+.ui-fluid .ui-buttonset {
+ width: 100%;
+}
+
+.ui-fluid .ui-buttonset.ui-buttonset-1 .ui-button {width: 100%;}
+.ui-fluid .ui-buttonset.ui-buttonset-2 .ui-button {width: 50%;}
+.ui-fluid .ui-buttonset.ui-buttonset-3 .ui-button {width: 33.3%;}
+.ui-fluid .ui-buttonset.ui-buttonset-4 .ui-button {width: 25%;}
+.ui-fluid .ui-buttonset.ui-buttonset-5 .ui-button {width: 20%;}
+.ui-fluid .ui-buttonset.ui-buttonset-6 .ui-button {width: 16.6%;}
+
+@media (max-width: 640px) {
+ .ui-fluid .ui-buttonset.ui-buttonset-1 .ui-button,
+ .ui-fluid .ui-buttonset.ui-buttonset-2 .ui-button,
+ .ui-fluid .ui-buttonset.ui-buttonset-3 .ui-button,
+ .ui-fluid .ui-buttonset.ui-buttonset-4 .ui-button,
+ .ui-fluid .ui-buttonset.ui-buttonset-5 .ui-button,
+ .ui-fluid .ui-buttonset.ui-buttonset-6 .ui-button {
+ width: 100%;
+ }
+}
+
+/* Severity Buttons */
+/* Secondary */
+.ui-button.ui-button-secondary.ui-state-default {
+ background-color: #ffffff;
+ border-color: #cccccc;
+ color: #373a3c;
+}
+
+.ui-button.ui-button-secondary:enabled:hover,
+.ui-button.ui-button-secondary:focus {
+ background-color: #f2f2f2;
+}
+
+.ui-button.ui-button-secondary:enabled:active {
+ background-color: #e6e6e6;
+}
+
+/* Success */
+.ui-button.ui-button-success.ui-state-default {
+ background-color: #5cb85c;
+ border-color: #5cb85c;
+ color: #ffffff;
+}
+
+.ui-button.ui-button-success:enabled:hover,
+.ui-button.ui-button-success:focus {
+ background-color: #4cae4c;
+}
+
+.ui-button.ui-button-success:enabled:active {
+ background-color: #449d44;
+}
+
+/* Info */
+.ui-button.ui-button-info.ui-state-default {
+ background-color: #5bc0de;
+ border-color: #5bc0de;
+ color: #ffffff;
+}
+
+.ui-button.ui-button-info:enabled:hover,
+.ui-button.ui-button-info:focus {
+ background-color: #46b8da;
+}
+
+.ui-button.ui-button-info:enabled:active {
+ background-color: #31b0d5;
+}
+
+/* Warning */
+.ui-button.ui-button-warning.ui-state-default {
+ background-color: #f0ad4e;
+ border-color: #f0ad4e;
+ color: #ffffff;
+}
+
+.ui-button.ui-button-warning:enabled:hover,
+.ui-button.ui-button-warning:focus {
+ background-color: #eea236;
+}
+
+.ui-button.ui-button-warning:enabled:active {
+ background-color: #ec971f;
+}
+
+/* Danger */
+.ui-button.ui-button-danger.ui-state-default {
+ background-color: #d9534f;
+ border-color: #d9534f;
+ color: #ffffff;
+}
+
+.ui-button.ui-button-danger:enabled:hover,
+.ui-button.ui-button-danger:focus {
+ background-color: #d43f3a;
+}
+
+.ui-button.ui-button-danger:enabled:active {
+ background-color: #c9302c;
+}
\ No newline at end of file
diff --git a/src/app/components/button/button.ts b/src/app/components/button/button.ts
new file mode 100644
index 00000000000..1c0a27f776d
--- /dev/null
+++ b/src/app/components/button/button.ts
@@ -0,0 +1,98 @@
+import {NgModule,Directive,ElementRef,AfterViewInit,OnDestroy,HostBinding,HostListener,Input} from '@angular/core';
+import {DomHandler} from '../dom/domhandler';
+import {CommonModule} from '@angular/common';
+
+@Directive({
+ selector: '[pButton]',
+ providers: [DomHandler]
+})
+export class Button implements AfterViewInit, OnDestroy {
+
+ @Input() iconPos: string = 'left';
+
+ @Input() cornerStyleClass: string = 'ui-corner-all';
+
+ public _label: string;
+
+ public _icon: string;
+
+ public initialized: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ ngAfterViewInit() {
+ this.domHandler.addMultipleClasses(this.el.nativeElement, this.getStyleClass());
+ if(this.icon) {
+ let iconElement = document.createElement("span");
+ let iconPosClass = (this.iconPos == 'right') ? 'ui-button-icon-right': 'ui-button-icon-left';
+ iconElement.className = iconPosClass + ' ui-c fa fa-fw ' + this.icon;
+ this.el.nativeElement.appendChild(iconElement);
+ }
+
+ let labelElement = document.createElement("span");
+ labelElement.className = 'ui-button-text ui-c';
+ labelElement.appendChild(document.createTextNode(this.label||'ui-btn'));
+ this.el.nativeElement.appendChild(labelElement);
+ this.initialized = true;
+ }
+
+ getStyleClass(): string {
+ let styleClass = 'ui-button ui-widget ui-state-default ' + this.cornerStyleClass;
+ if(this.icon) {
+ if(this.label != null && this.label != undefined) {
+ if(this.iconPos == 'left')
+ styleClass = styleClass + ' ui-button-text-icon-left';
+ else
+ styleClass = styleClass + ' ui-button-text-icon-right';
+ }
+ else {
+ styleClass = styleClass + ' ui-button-icon-only';
+ }
+ }
+ else {
+ styleClass = styleClass + ' ui-button-text-only';
+ }
+
+ return styleClass;
+ }
+
+ @Input() get label(): string {
+ return this._label;
+ }
+
+ set label(val: string) {
+ this._label = val;
+
+ if(this.initialized) {
+ this.domHandler.findSingle(this.el.nativeElement, '.ui-button-text').textContent = this._label;
+ }
+ }
+
+ @Input() get icon(): string {
+ return this._icon;
+ }
+
+ set icon(val: string) {
+ this._icon = val;
+
+ if(this.initialized) {
+ let iconPosClass = (this.iconPos == 'right') ? 'ui-button-icon-right': 'ui-button-icon-left';
+ this.domHandler.findSingle(this.el.nativeElement, '.fa').className = iconPosClass + ' ui-c fa fa-fw ' + this.icon;
+ }
+ }
+
+ ngOnDestroy() {
+ while(this.el.nativeElement.hasChildNodes()) {
+ this.el.nativeElement.removeChild(this.el.nativeElement.lastChild);
+ }
+
+ this.initialized = false;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Button],
+ declarations: [Button]
+})
+export class ButtonModule { }
diff --git a/src/app/components/calendar/calendar.css b/src/app/components/calendar/calendar.css
new file mode 100644
index 00000000000..faaf79efe0c
--- /dev/null
+++ b/src/app/components/calendar/calendar.css
@@ -0,0 +1,217 @@
+.ui-calendar {
+ position: relative;
+ display: inline-block;
+}
+
+.ui-calendar button {
+ position: absolute;
+ height: 100%;
+ border-top-left-radius: 0px;
+ border-bottom-left-radius: 0px;
+ position: absolute;
+ width: 2em;
+ border-left: 0 none;
+}
+
+.ui-calendar button:enabled:hover,
+.ui-calendar button:focus {
+ border-left: 0 none;
+}
+
+/* Fluid */
+.ui-fluid .ui-calendar {
+ width: 100%;
+}
+
+.ui-fluid .ui-calendar button {
+ width: 2em;
+}
+
+.ui-fluid .ui-calendar.ui-calendar-w-btn .ui-inputtext {
+ width: calc(100% - 2em);
+}
+
+/* Datepicker */
+.ui-datepicker {
+ width: 17em;
+ padding: .2em;
+ display: none;
+ position: absolute;
+}
+.ui-datepicker.ui-datepicker-inline {
+ display: block;
+ position: static;
+}
+.ui-datepicker .ui-datepicker-header {
+ position: relative;
+ padding: .2em 0;
+}
+.ui-datepicker .ui-datepicker-prev,
+.ui-datepicker .ui-datepicker-next {
+ position: absolute;
+ top: .125em;
+ width: 1.8em;
+ height: 1.8em;
+}
+
+.ui-datepicker .ui-datepicker-prev {
+ left: .125em;
+}
+.ui-datepicker .ui-datepicker-next {
+ right: .125em;
+}
+.ui-datepicker .ui-datepicker-prev span,
+.ui-datepicker .ui-datepicker-next span {
+ display: block;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ margin-top: -.5em;
+}
+.ui-datepicker .ui-datepicker-prev span {
+ margin-left: -.25em;
+}
+.ui-datepicker .ui-datepicker-next span {
+ margin-left: -.125em;
+}
+.ui-datepicker .ui-datepicker-title {
+ margin: 0 2.3em;
+ line-height: 1.8em;
+ text-align: center;
+}
+.ui-datepicker .ui-datepicker-title select {
+ font-size: 1em;
+ margin: .125em 0;
+}
+.ui-datepicker select.ui-datepicker-month {
+ width: 55%;
+}
+.ui-datepicker select.ui-datepicker-year {
+ width: 35%;
+}
+.ui-datepicker select.ui-datepicker-month {
+ margin-right: .25em;
+}
+.ui-datepicker table {
+ width: 100%;
+ font-size: .9em;
+ border-collapse: collapse;
+ margin: 0 0 .4em;
+}
+.ui-datepicker th {
+ padding: .7em .3em;
+ text-align: center;
+ font-weight: bold;
+ border: 0;
+}
+.ui-datepicker td {
+ border: 0;
+ padding: .125em;
+}
+.ui-datepicker td span,
+.ui-datepicker td a {
+ display: block;
+ padding: .2em;
+ text-align: right;
+ text-decoration: none;
+}
+.ui-datepicker .ui-datepicker-buttonpane {
+ background-image: none;
+ margin: .7em 0 0 0;
+ padding: 0 .2em;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 0;
+}
+.ui-datepicker .ui-datepicker-buttonpane button {
+ float: right;
+ margin: .5em .2em .4em;
+ cursor: pointer;
+ padding: .2em .6em .3em .6em;
+ width: auto;
+ overflow: visible;
+}
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
+ float: left;
+}
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi {
+ width: auto;
+}
+.ui-datepicker-multi .ui-datepicker-group {
+ float: left;
+}
+.ui-datepicker-multi .ui-datepicker-group table {
+ width: 95%;
+ margin: 0 auto .4em;
+}
+.ui-datepicker-multi-2 .ui-datepicker-group {
+ width: 50%;
+}
+.ui-datepicker-multi-3 .ui-datepicker-group {
+ width: 33.3%;
+}
+.ui-datepicker-multi-4 .ui-datepicker-group {
+ width: 25%;
+}
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
+ border-left-width: 0;
+}
+.ui-datepicker-multi .ui-datepicker-buttonpane {
+ clear: left;
+}
+.ui-datepicker-row-break {
+ clear: both;
+ width: 100%;
+ font-size: 0;
+}
+
+.ui-calendar.ui-calendar-w-btn input {
+ -moz-border-radius-topright: 0px;
+ -webkit-border-top-right-radius: 0px;
+ -khtml-border-top-right-radius: 0px;
+ border-top-right-radius: 0px;
+ -moz-border-radius-bottomright: 0px;
+ -webkit-border-bottom-right-radius: 0px;
+ -khtml-border-bottom-right-radius: 0px;
+ border-bottom-right-radius: 0px;
+}
+
+.ui-timepicker {
+ text-align: center;
+ padding: .5em 0;
+}
+
+.ui-timepicker > div {
+ display: inline-block;
+ margin-left: .5em;
+ min-width: 1.5em;
+}
+
+.ui-timepicker > .ui-minute-picker,
+.ui-timepicker > .ui-second-picker {
+ margin-left: 0;
+}
+
+.ui-timepicker > .ui-separator {
+ margin-left: 0px;
+ min-width: .75em;
+}
+
+.ui-timepicker > .ui-separator a {
+ visibility: hidden;
+}
+
+.ui-timepicker > div a {
+ display: block;
+ opacity: 0.7;
+ filter:Alpha(Opacity=70);
+}
+
+.ui-timepicker > div a:hover {
+ display: block;
+ opacity: 1;
+ filter:Alpha(Opacity=100);
+}
\ No newline at end of file
diff --git a/src/app/components/calendar/calendar.ts b/src/app/components/calendar/calendar.ts
new file mode 100644
index 00000000000..54702d909fe
--- /dev/null
+++ b/src/app/components/calendar/calendar.ts
@@ -0,0 +1,1287 @@
+import {NgModule,Component,ElementRef,AfterViewInit,AfterViewChecked,OnDestroy,OnInit,Input,Output,SimpleChange,EventEmitter,forwardRef,Renderer2,ViewChild,ChangeDetectorRef} from '@angular/core';
+import {trigger,state,style,transition,animate} from '@angular/animations';
+import {CommonModule} from '@angular/common';
+import {ButtonModule} from '../button/button';
+import {DomHandler} from '../dom/domhandler';
+import {AbstractControl, NG_VALUE_ACCESSOR, NG_VALIDATORS, ControlValueAccessor} from '@angular/forms';
+
+export const CALENDAR_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => Calendar),
+ multi: true
+};
+
+export const CALENDAR_VALIDATOR: any = {
+ provide: NG_VALIDATORS,
+ useExisting: forwardRef(() => Calendar),
+ multi: true
+};
+
+export interface LocaleSettings {
+ firstDayOfWeek?: number;
+ dayNames: string[];
+ dayNamesShort: string[];
+ dayNamesMin: string[];
+ monthNames: string[];
+ monthNamesShort: string[];
+}
+
+@Component({
+ selector: 'p-calendar',
+ template: `
+
+
+
+
+
+
+ `,
+ animations: [
+ trigger('overlayState', [
+ state('hidden', style({
+ opacity: 0
+ })),
+ state('visible', style({
+ opacity: 1
+ })),
+ transition('visible => hidden', animate('400ms ease-in')),
+ transition('hidden => visible', animate('400ms ease-out'))
+ ])
+ ],
+ host: {
+ '[class.ui-inputwrapper-filled]': 'filled',
+ '[class.ui-inputwrapper-focus]': 'focus'
+ },
+ providers: [DomHandler,CALENDAR_VALUE_ACCESSOR,CALENDAR_VALIDATOR]
+})
+export class Calendar implements AfterViewInit,AfterViewChecked,OnInit,OnDestroy,ControlValueAccessor {
+
+ @Input() defaultDate: Date;
+
+ @Input() style: string;
+
+ @Input() styleClass: string;
+
+ @Input() inputStyle: string;
+
+ @Input() inputId: string;
+
+ @Input() inputStyleClass: string;
+
+ @Input() placeholder: string;
+
+ @Input() disabled: any;
+
+ @Input() dateFormat: string = 'mm/dd/yy';
+
+ @Input() inline: boolean = false;
+
+ @Input() showOtherMonths: boolean = true;
+
+ @Input() selectOtherMonths: boolean;
+
+ @Input() showIcon: boolean;
+
+ @Input() icon: string = 'fa-calendar';
+
+ @Input() appendTo: any;
+
+ @Input() readonlyInput: boolean;
+
+ @Input() shortYearCutoff: any = '+10';
+
+ @Input() monthNavigator: boolean;
+
+ @Input() yearNavigator: boolean;
+
+ @Input() yearRange: string;
+
+ @Input() showTime: boolean;
+
+ @Input() hourFormat: string = '24';
+
+ @Input() timeOnly: boolean;
+
+ @Input() stepHour: number = 1;
+
+ @Input() stepMinute: number = 1;
+
+ @Input() stepSecond: number = 1;
+
+ @Input() showSeconds: boolean = false;
+
+ @Input() required: boolean;
+
+ @Input() showOnFocus: boolean = true;
+
+ @Input() dataType: string = 'date';
+
+ @Input() disabledDates: Array;
+
+ @Input() disabledDays: Array;
+
+ @Input() utc: boolean;
+
+ @Output() onFocus: EventEmitter = new EventEmitter();
+
+ @Output() onBlur: EventEmitter = new EventEmitter();
+
+ @Output() onSelect: EventEmitter = new EventEmitter();
+
+ @Output() onInput: EventEmitter = new EventEmitter();
+
+ _locale: LocaleSettings = {
+ firstDayOfWeek: 0,
+ dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
+ dayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
+ dayNamesMin: ["Su","Mo","Tu","We","Th","Fr","Sa"],
+ monthNames: [ "January","February","March","April","May","June","July","August","September","October","November","December" ],
+ monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]
+ };
+
+ @Input() tabindex: number;
+
+ @ViewChild('datepicker') overlayViewChild: ElementRef;
+
+ @ViewChild('inputfield') inputfieldViewChild: ElementRef;
+
+ value: Date;
+
+ dates: any[];
+
+ weekDays: string[];
+
+ currentMonthText: string;
+
+ currentMonth: number;
+
+ currentYear: number;
+
+ currentHour: number;
+
+ currentMinute: number;
+
+ currentSecond: number;
+
+ pm: boolean;
+
+ overlay: HTMLDivElement;
+
+ overlayVisible: boolean;
+
+ overlayShown: boolean;
+
+ closeOverlay: boolean = true;
+
+ dateClick: boolean;
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ calendarElement: any;
+
+ documentClickListener: any;
+
+ ticksTo1970: number;
+
+ yearOptions: number[];
+
+ focus: boolean;
+
+ filled: boolean;
+
+ inputFieldValue: string = null;
+
+ _minDate: Date;
+
+ _maxDate: Date;
+
+ _isValid: boolean = true;
+
+ @Input() get minDate(): Date {
+ return this._minDate;
+ }
+
+ set minDate(date: Date) {
+ this._minDate = date;
+ this.createMonth(this.currentMonth, this.currentYear);
+ }
+
+ @Input() get maxDate(): Date {
+ return this._maxDate;
+ }
+
+ set maxDate(date: Date) {
+ this._maxDate = date;
+ this.createMonth(this.currentMonth, this.currentYear);
+ }
+
+ get locale() {
+ return this._locale;
+ }
+
+ @Input()
+ set locale(newLocale: LocaleSettings) {
+ this._locale = newLocale;
+ this.createWeekDays();
+ this.createMonth(this.currentMonth, this.currentYear);
+ }
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2, public cd: ChangeDetectorRef) {}
+
+ ngOnInit() {
+ let date = this.defaultDate||new Date();
+ this.createWeekDays();
+
+ this.currentMonth = date.getMonth();
+ this.currentYear = date.getFullYear();
+ this.pm = date.getHours() > 11;
+ if(this.showTime) {
+ this.currentMinute = date.getMinutes();
+ this.currentSecond = date.getSeconds();
+
+ if(this.hourFormat == '12')
+ this.currentHour = date.getHours() == 0 ? 12 : date.getHours() % 12;
+ else
+ this.currentHour = date.getHours();
+ }
+ else if(this.timeOnly) {
+ this.currentMinute = 0;
+ this.currentHour = 0;
+ this.currentSecond = 0;
+ }
+
+ this.createMonth(this.currentMonth, this.currentYear);
+
+ this.ticksTo1970 = (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
+ Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000);
+
+ if(this.yearNavigator && this.yearRange) {
+ this.yearOptions = [];
+ let years = this.yearRange.split(':'),
+ yearStart = parseInt(years[0]),
+ yearEnd = parseInt(years[1]);
+
+ for(let i = yearStart; i <= yearEnd; i++) {
+ this.yearOptions.push(i);
+ }
+ }
+ }
+
+ ngAfterViewInit() {
+ this.overlay = this.overlayViewChild.nativeElement;
+
+ if(!this.inline && this.appendTo) {
+ if(this.appendTo === 'body')
+ document.body.appendChild(this.overlay);
+ else
+ this.domHandler.appendChild(this.overlay, this.appendTo);
+ }
+ }
+
+ ngAfterViewChecked() {
+ if(this.overlayShown) {
+ this.alignOverlay();
+ this.overlayShown = false;
+ }
+ }
+
+ createWeekDays() {
+ this.weekDays = [];
+ let dayIndex = this.locale.firstDayOfWeek;
+ for(let i = 0; i < 7; i++) {
+ this.weekDays.push(this.locale.dayNamesMin[dayIndex]);
+ dayIndex = (dayIndex == 6) ? 0 : ++dayIndex;
+ }
+ }
+
+ createMonth(month: number, year: number) {
+ this.dates = [];
+ this.currentMonth = month;
+ this.currentYear = year;
+ this.currentMonthText = this.locale.monthNames[month];
+ let firstDay = this.getFirstDayOfMonthIndex(month, year);
+ let daysLength = this.getDaysCountInMonth(month, year);
+ let prevMonthDaysLength = this.getDaysCountInPrevMonth(month, year);
+ let sundayIndex = this.getSundayIndex();
+ let dayNo = 1;
+ let today = new Date();
+
+ for(let i = 0; i < 6; i++) {
+ let week = [];
+
+ if(i == 0) {
+ for(let j = (prevMonthDaysLength - firstDay + 1); j <= prevMonthDaysLength; j++) {
+ let prev = this.getPreviousMonthAndYear(month, year);
+ week.push({day: j, month: prev.month, year: prev.year, otherMonth: true,
+ today: this.isToday(today, j, prev.month, prev.year), selectable: this.isSelectable(j, prev.month, prev.year)});
+ }
+
+ let remainingDaysLength = 7 - week.length;
+ for(let j = 0; j < remainingDaysLength; j++) {
+ week.push({day: dayNo, month: month, year: year, today: this.isToday(today, dayNo, month, year),
+ selectable: this.isSelectable(dayNo, month, year)});
+ dayNo++;
+ }
+ }
+ else {
+ for (let j = 0; j < 7; j++) {
+ if(dayNo > daysLength) {
+ let next = this.getNextMonthAndYear(month, year);
+ week.push({day: dayNo - daysLength, month: next.month, year: next.year, otherMonth:true,
+ today: this.isToday(today, dayNo - daysLength, next.month, next.year),
+ selectable: this.isSelectable((dayNo - daysLength), next.month, next.year)});
+ }
+ else {
+ week.push({day: dayNo, month: month, year: year, today: this.isToday(today, dayNo, month, year),
+ selectable: this.isSelectable(dayNo, month, year)});
+ }
+
+ dayNo++;
+ }
+ }
+
+ this.dates.push(week);
+ }
+ }
+
+ prevMonth(event) {
+ if(this.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if(this.currentMonth === 0) {
+ this.currentMonth = 11;
+ this.currentYear--;
+ }
+ else {
+ this.currentMonth--;
+ }
+
+ this.createMonth(this.currentMonth, this.currentYear);
+ event.preventDefault();
+ }
+
+ nextMonth(event) {
+ if(this.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if(this.currentMonth === 11) {
+ this.currentMonth = 0;
+ this.currentYear++;
+ }
+ else {
+ this.currentMonth++;
+ }
+
+ this.createMonth(this.currentMonth, this.currentYear);
+ event.preventDefault();
+ }
+
+ onDateSelect(event,dateMeta) {
+ if(this.disabled || !dateMeta.selectable) {
+ event.preventDefault();
+ return;
+ }
+
+ if(dateMeta.otherMonth) {
+ if(this.selectOtherMonths) {
+ this.currentMonth = dateMeta.month;
+ this.currentYear = dateMeta.year;
+ this.createMonth(this.currentMonth, this.currentYear);
+ this.selectDate(dateMeta);
+ }
+ }
+ else {
+ this.selectDate(dateMeta);
+ }
+
+ this.dateClick = true;
+ this.updateInputfield();
+ event.preventDefault();
+ }
+
+ updateInputfield() {
+ if(this.value) {
+ let formattedValue;
+
+ if(this.timeOnly) {
+ formattedValue = this.formatTime(this.value);
+ }
+ else {
+ formattedValue = this.formatDate(this.value, this.dateFormat);
+ if(this.showTime) {
+ formattedValue += ' ' + this.formatTime(this.value);
+ }
+ }
+
+ this.inputFieldValue = formattedValue;
+ }
+ else {
+ this.inputFieldValue = '';
+ }
+
+ this.updateFilledState();
+ }
+
+ selectDate(dateMeta) {
+ if(this.utc)
+ this.value = new Date(Date.UTC(dateMeta.year, dateMeta.month, dateMeta.day));
+ else
+ this.value = new Date(dateMeta.year, dateMeta.month, dateMeta.day);
+
+ if(this.showTime) {
+ if(this.hourFormat === '12' && this.pm && this.currentHour != 12)
+ this.value.setHours(this.currentHour + 12);
+ else
+ this.value.setHours(this.currentHour);
+
+ this.value.setMinutes(this.currentMinute);
+ this.value.setSeconds(this.currentSecond);
+ }
+ this._isValid = true;
+ this.updateModel();
+ this.onSelect.emit(this.value);
+ }
+
+ updateModel() {
+ if(this.dataType == 'date'){
+ this.onModelChange(this.value);
+ }
+ else if(this.dataType == 'string') {
+ if(this.timeOnly)
+ this.onModelChange(this.formatTime(this.value));
+ else
+ this.onModelChange(this.formatDate(this.value, this.dateFormat));
+ }
+ }
+
+ getFirstDayOfMonthIndex(month: number, year: number) {
+ let day = new Date();
+ day.setDate(1);
+ day.setMonth(month);
+ day.setFullYear(year);
+
+ let dayIndex = day.getDay() + this.getSundayIndex();
+ return dayIndex >= 7 ? dayIndex - 7 : dayIndex;
+ }
+
+ getDaysCountInMonth(month: number, year: number) {
+ return 32 - this.daylightSavingAdjust(new Date(year, month, 32)).getDate();
+ }
+
+ getDaysCountInPrevMonth(month: number, year: number) {
+ let prev = this.getPreviousMonthAndYear(month, year);
+ return this.getDaysCountInMonth(prev.month, prev.year);
+ }
+
+ getPreviousMonthAndYear(month: number, year: number) {
+ let m, y;
+
+ if(month === 0) {
+ m = 11;
+ y = year - 1;
+ }
+ else {
+ m = month - 1;
+ y = year;
+ }
+
+ return {'month':m,'year':y};
+ }
+
+ getNextMonthAndYear(month: number, year: number) {
+ let m, y;
+
+ if(month === 11) {
+ m = 0;
+ y = year + 1;
+ }
+ else {
+ m = month + 1;
+ y = year;
+ }
+
+ return {'month':m,'year':y};
+ }
+
+ getSundayIndex() {
+ return this.locale.firstDayOfWeek > 0 ? 7 - this.locale.firstDayOfWeek : 0;
+ }
+
+ isSelected(dateMeta): boolean {
+ if(this.value)
+ return this.value.getDate() === dateMeta.day && this.value.getMonth() === dateMeta.month && this.value.getFullYear() === dateMeta.year;
+ else
+ return false;
+ }
+
+ isToday(today, day, month, year): boolean {
+ return today.getDate() === day && today.getMonth() === month && today.getFullYear() === year;
+ }
+
+ isSelectable(day, month, year): boolean {
+ let validMin = true;
+ let validMax = true;
+ let validDate = true;
+ let validDay = true;
+
+ if(this.minDate) {
+ if(this.minDate.getFullYear() > year) {
+ validMin = false;
+ }
+ else if(this.minDate.getFullYear() === year) {
+ if(this.minDate.getMonth() > month) {
+ validMin = false;
+ }
+ else if(this.minDate.getMonth() === month) {
+ if(this.minDate.getDate() > day) {
+ validMin = false;
+ }
+ }
+ }
+ }
+
+ if(this.maxDate) {
+ if(this.maxDate.getFullYear() < year) {
+ validMax = false;
+ }
+ else if(this.maxDate.getFullYear() === year) {
+ if(this.maxDate.getMonth() < month) {
+ validMax = false;
+ }
+ else if(this.maxDate.getMonth() === month) {
+ if(this.maxDate.getDate() < day) {
+ validMax = false;
+ }
+ }
+ }
+ }
+
+ if(this.disabledDates) {
+ validDate = !this.isDateDisabled(day,month,year);
+ }
+
+ if(this.disabledDays) {
+ validDay = !this.isDayDisabled(day,month,year)
+ }
+
+ return validMin && validMax && validDate && validDay;
+ }
+
+ isDateDisabled(day:number, month:number, year:number):boolean {
+ if(this.disabledDates) {
+ for(let disabledDate of this.disabledDates) {
+ if(disabledDate.getFullYear() === year && disabledDate.getMonth() === month && disabledDate.getDate() === day) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ isDayDisabled(day:number, month:number, year:number):boolean {
+ if(this.disabledDays) {
+ let weekday = new Date(year, month, day);
+ let weekdayNumber = weekday.getDay();
+ return this.disabledDays.indexOf(weekdayNumber) !== -1;
+ }
+ return false;
+ }
+
+ onInputFocus(event: Event) {
+ this.focus = true;
+ if(this.showOnFocus) {
+ this.showOverlay();
+ }
+ this.onFocus.emit(event);
+ }
+
+ onInputBlur(event: Event) {
+ this.focus = false;
+ this.onBlur.emit(event);
+ this.updateInputfield();
+ this.onModelTouched();
+ }
+
+ onButtonClick(event,inputfield) {
+ this.closeOverlay = false;
+
+ if(!this.overlay.offsetParent) {
+ inputfield.focus();
+ this.showOverlay();
+ }
+ else
+ this.closeOverlay = true;
+ }
+
+ onInputKeydown(event) {
+ if(event.keyCode === 9) {
+ this.overlayVisible = false;
+ }
+ }
+
+ onMonthDropdownChange(m: string) {
+ this.currentMonth = parseInt(m);
+ this.createMonth(this.currentMonth, this.currentYear);
+ }
+
+ onYearDropdownChange(y: string) {
+ this.currentYear = parseInt(y);
+ this.createMonth(this.currentMonth, this.currentYear);
+ }
+
+ incrementHour(event) {
+ let newHour = this.currentHour + this.stepHour;
+ if(this.hourFormat == '24')
+ this.currentHour = (newHour >= 24) ? (newHour - 24) : newHour;
+ else if(this.hourFormat == '12')
+ this.currentHour = (newHour >= 13) ? (newHour - 12) : newHour;
+
+ this.updateTime();
+
+ event.preventDefault();
+ }
+
+ decrementHour(event) {
+ let newHour = this.currentHour - this.stepHour;
+ if(this.hourFormat == '24')
+ this.currentHour = (newHour < 0) ? (24 + newHour) : newHour;
+ else if(this.hourFormat == '12')
+ this.currentHour = (newHour <= 0) ? (12 + newHour) : newHour;
+
+ this.updateTime();
+
+ event.preventDefault();
+ }
+
+ incrementMinute(event) {
+ let newMinute = this.currentMinute + this.stepMinute;
+ this.currentMinute = (newMinute > 59) ? newMinute - 60 : newMinute;
+
+ this.updateTime();
+
+ event.preventDefault();
+ }
+
+ decrementMinute(event) {
+ let newMinute = this.currentMinute - this.stepMinute;
+ this.currentMinute = (newMinute < 0) ? 60 + newMinute : newMinute;
+
+ this.updateTime();
+
+ event.preventDefault();
+ }
+
+ incrementSecond(event) {
+ let newSecond = this.currentSecond + this.stepSecond;
+ this.currentSecond = (newSecond > 59) ? newSecond - 60 : newSecond;
+
+ this.updateTime();
+
+ event.preventDefault();
+ }
+
+ decrementSecond(event) {
+ let newSecond = this.currentSecond - this.stepSecond;
+ this.currentSecond = (newSecond < 0) ? 60 + newSecond : newSecond;
+
+ this.updateTime();
+
+ event.preventDefault();
+ }
+
+ updateTime() {
+ this.value = this.value||new Date();
+ if(this.hourFormat === '12' && this.pm && this.currentHour != 12)
+ this.value.setHours(this.currentHour + 12);
+ else
+ this.value.setHours(this.currentHour);
+
+ this.value.setMinutes(this.currentMinute);
+ this.value.setSeconds(this.currentSecond);
+ this.updateModel();
+ this.onSelect.emit(this.value);
+ this.updateInputfield();
+ }
+
+ toggleAMPM(event) {
+ this.pm = !this.pm;
+ this.updateTime();
+ event.preventDefault();
+ }
+
+ onUserInput(event) {
+ let val = event.target.value;
+ try {
+ this.value = this.parseValueFromString(val);
+ this.updateUI();
+ this._isValid = true;
+ }
+ catch(err) {
+ //invalid date
+ this.value = null;
+ this._isValid = false;
+ }
+
+ this.filled = val != null && val.length;
+ this.updateModel();
+ this.onInput.emit(event);
+ }
+
+ parseValueFromString(text: string): Date {
+ let dateValue;
+ let parts: string[] = text.split(' ');
+
+ if(this.timeOnly) {
+ dateValue = new Date();
+ this.populateTime(dateValue, parts[0], parts[1]);
+ }
+ else {
+ if(this.showTime) {
+ dateValue = this.parseDate(parts[0], this.dateFormat);
+ this.populateTime(dateValue, parts[1], parts[2]);
+ }
+ else {
+ dateValue = this.parseDate(text, this.dateFormat);
+ }
+ }
+
+ return dateValue;
+ }
+
+ populateTime(value, timeString, ampm) {
+ if(this.hourFormat == '12' && !ampm) {
+ throw 'Invalid Time';
+ }
+
+ this.pm = (ampm === 'PM' || ampm === 'pm');
+ let time = this.parseTime(timeString);
+ value.setHours(time.hour);
+ value.setMinutes(time.minute);
+ value.setSeconds(time.second);
+ }
+
+ updateUI() {
+ let val = this.value||this.defaultDate||new Date();
+ this.createMonth(val.getMonth(), val.getFullYear());
+
+ if(this.showTime||this.timeOnly) {
+ let hours = val.getHours();
+
+ if(this.hourFormat == '12') {
+ if(hours >= 12) {
+ this.currentHour = (hours == 12) ? 12 : hours - 12;
+ }
+ else {
+ this.currentHour = (hours == 0) ? 12 : hours;
+ }
+ }
+ else {
+ this.currentHour = val.getHours();
+ }
+
+ this.currentMinute = val.getMinutes();
+ this.currentSecond = val.getSeconds();
+ }
+ }
+
+ onDatePickerClick(event) {
+ this.closeOverlay = this.dateClick;
+ }
+
+ showOverlay() {
+ this.overlayVisible = true;
+ this.overlayShown = true;
+ this.overlay.style.zIndex = String(++DomHandler.zindex);
+
+ this.bindDocumentClickListener();
+ }
+
+ alignOverlay() {
+ if(this.appendTo)
+ this.domHandler.absolutePosition(this.overlay, this.inputfieldViewChild.nativeElement);
+ else
+ this.domHandler.relativePosition(this.overlay, this.inputfieldViewChild.nativeElement);
+ }
+
+ writeValue(value: any) : void {
+ this.value = value;
+ if(this.value && typeof this.value === 'string') {
+ this.value = this.parseValueFromString(this.value);
+ }
+
+ this.updateInputfield();
+ this.updateUI();
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ // Ported from jquery-ui datepicker formatDate
+ formatDate(date, format) {
+ if(!date) {
+ return "";
+ }
+
+ let iFormat,
+ lookAhead = (match) => {
+ let matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
+ if(matches) {
+ iFormat++;
+ }
+ return matches;
+ },
+ formatNumber = (match, value, len) => {
+ let num = "" + value;
+ if(lookAhead(match)) {
+ while (num.length < len) {
+ num = "0" + num;
+ }
+ }
+ return num;
+ },
+ formatName = (match, value, shortNames, longNames) => {
+ return (lookAhead(match) ? longNames[ value ] : shortNames[ value ]);
+ },
+ output = "",
+ literal = false;
+
+ if(date) {
+ for(iFormat = 0; iFormat < format.length; iFormat++) {
+ if(literal) {
+ if(format.charAt(iFormat) === "'" && !lookAhead("'"))
+ literal = false;
+ else
+ output += format.charAt(iFormat);
+ }
+ else {
+ switch (format.charAt(iFormat)) {
+ case "d":
+ output += formatNumber("d", date.getDate(), 2);
+ break;
+ case "D":
+ output += formatName("D", date.getDay(), this.locale.dayNamesShort, this.locale.dayNames);
+ break;
+ case "o":
+ output += formatNumber("o",
+ Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3);
+ break;
+ case "m":
+ output += formatNumber("m", date.getMonth() + 1, 2);
+ break;
+ case "M":
+ output += formatName("M", date.getMonth(), this.locale.monthNamesShort, this.locale.monthNames);
+ break;
+ case "y":
+ output += (lookAhead("y") ? date.getFullYear() :
+ (date.getFullYear() % 100 < 10 ? "0" : "") + date.getFullYear() % 100);
+ break;
+ case "@":
+ output += date.getTime();
+ break;
+ case "!":
+ output += date.getTime() * 10000 + this.ticksTo1970;
+ break;
+ case "'":
+ if(lookAhead("'"))
+ output += "'";
+ else
+ literal = true;
+
+ break;
+ default:
+ output += format.charAt(iFormat);
+ }
+ }
+ }
+ }
+ return output;
+ }
+
+ formatTime(date) {
+ if(!date) {
+ return '';
+ }
+
+ let output = '';
+ let hours = date.getHours();
+ let minutes = date.getMinutes();
+ let seconds = date.getSeconds();
+
+ if(this.hourFormat == '12' && hours > 11 && hours != 12) {
+ hours-=12;
+ }
+
+ output += (hours < 10) ? '0' + hours : hours;
+ output += ':';
+ output += (minutes < 10) ? '0' + minutes : minutes;
+
+ if(this.showSeconds) {
+ output += ':';
+ output += (seconds < 10) ? '0' + seconds : seconds;
+ }
+
+ if(this.hourFormat == '12') {
+ output += date.getHours() > 11 ? ' PM' : ' AM';
+ }
+
+ return output;
+ }
+
+ parseTime(value) {
+ let tokens: string[] = value.split(':');
+ let validTokenLength = this.showSeconds ? 3 : 2;
+
+ if(tokens.length !== validTokenLength) {
+ throw "Invalid time";
+ }
+
+ let h = parseInt(tokens[0]);
+ let m = parseInt(tokens[1]);
+ let s = this.showSeconds ? parseInt(tokens[2]) : null;
+
+ if(isNaN(h) || isNaN(m) || h > 23 || m > 59 || (this.hourFormat == '12' && h > 12) || (this.showSeconds && (isNaN(s) || s > 59))) {
+ throw "Invalid time";
+ }
+ else {
+ if(this.hourFormat == '12' && h !== 12 && this.pm) {
+ h+= 12;
+ }
+
+ return {hour: h, minute: m, second: s};
+ }
+ }
+
+ // Ported from jquery-ui datepicker parseDate
+ parseDate(value, format) {
+ if(format == null || value == null) {
+ throw "Invalid arguments";
+ }
+
+ value = (typeof value === "object" ? value.toString() : value + "");
+ if(value === "") {
+ return null;
+ }
+
+ let iFormat, dim, extra,
+ iValue = 0,
+ shortYearCutoff = (typeof this.shortYearCutoff !== "string" ? this.shortYearCutoff : new Date().getFullYear() % 100 + parseInt(this.shortYearCutoff, 10)),
+ year = -1,
+ month = -1,
+ day = -1,
+ doy = -1,
+ literal = false,
+ date,
+ lookAhead = (match) => {
+ let matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
+ if(matches) {
+ iFormat++;
+ }
+ return matches;
+ },
+ getNumber = (match) => {
+ let isDoubled = lookAhead(match),
+ size = (match === "@" ? 14 : (match === "!" ? 20 :
+ (match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))),
+ minSize = (match === "y" ? size : 1),
+ digits = new RegExp("^\\d{" + minSize + "," + size + "}"),
+ num = value.substring(iValue).match(digits);
+ if(!num) {
+ throw "Missing number at position " + iValue;
+ }
+ iValue += num[ 0 ].length;
+ return parseInt(num[ 0 ], 10);
+ },
+ getName = (match, shortNames, longNames) => {
+ let index = -1;
+ let arr = lookAhead(match) ? longNames : shortNames;
+ let names = [];
+
+ for(let i = 0; i < arr.length; i++) {
+ names.push([i,arr[i]]);
+ }
+ names.sort((a,b) => {
+ return -(a[ 1 ].length - b[ 1 ].length);
+ });
+
+ for(let i = 0; i < names.length; i++) {
+ let name = names[i][1];
+ if(value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) {
+ index = names[i][0];
+ iValue += name.length;
+ break;
+ }
+ }
+
+ if(index !== -1) {
+ return index + 1;
+ } else {
+ throw "Unknown name at position " + iValue;
+ }
+ },
+ checkLiteral = () => {
+ if(value.charAt(iValue) !== format.charAt(iFormat)) {
+ throw "Unexpected literal at position " + iValue;
+ }
+ iValue++;
+ };
+
+ for (iFormat = 0; iFormat < format.length; iFormat++) {
+ if(literal) {
+ if(format.charAt(iFormat) === "'" && !lookAhead("'")) {
+ literal = false;
+ } else {
+ checkLiteral();
+ }
+ } else {
+ switch (format.charAt(iFormat)) {
+ case "d":
+ day = getNumber("d");
+ break;
+ case "D":
+ getName("D", this.locale.dayNamesShort, this.locale.dayNames);
+ break;
+ case "o":
+ doy = getNumber("o");
+ break;
+ case "m":
+ month = getNumber("m");
+ break;
+ case "M":
+ month = getName("M", this.locale.monthNamesShort, this.locale.monthNames);
+ break;
+ case "y":
+ year = getNumber("y");
+ break;
+ case "@":
+ date = new Date(getNumber("@"));
+ year = date.getFullYear();
+ month = date.getMonth() + 1;
+ day = date.getDate();
+ break;
+ case "!":
+ date = new Date((getNumber("!") - this.ticksTo1970) / 10000);
+ year = date.getFullYear();
+ month = date.getMonth() + 1;
+ day = date.getDate();
+ break;
+ case "'":
+ if(lookAhead("'")) {
+ checkLiteral();
+ } else {
+ literal = true;
+ }
+ break;
+ default:
+ checkLiteral();
+ }
+ }
+ }
+
+ if(iValue < value.length) {
+ extra = value.substr(iValue);
+ if(!/^\s+/.test(extra)) {
+ throw "Extra/unparsed characters found in date: " + extra;
+ }
+ }
+
+ if(year === -1) {
+ year = new Date().getFullYear();
+ } else if(year < 100) {
+ year += new Date().getFullYear() - new Date().getFullYear() % 100 +
+ (year <= shortYearCutoff ? 0 : -100);
+ }
+
+ if(doy > -1) {
+ month = 1;
+ day = doy;
+ do {
+ dim = this.getDaysCountInMonth(year, month - 1);
+ if(day <= dim) {
+ break;
+ }
+ month++;
+ day -= dim;
+ } while (true);
+ }
+
+ date = this.daylightSavingAdjust(new Date(year, month - 1, day));
+ if(date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) {
+ throw "Invalid date"; // E.g. 31/02/00
+ }
+ return date;
+ }
+
+ daylightSavingAdjust(date) {
+ if(!date) {
+ return null;
+ }
+ date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
+ return date;
+ }
+
+ updateFilledState() {
+ this.filled = this.inputFieldValue && this.inputFieldValue != '';
+ }
+
+ bindDocumentClickListener() {
+ if(!this.documentClickListener) {
+ this.documentClickListener = this.renderer.listen('document', 'click', () => {
+ if(this.closeOverlay) {
+ this.overlayVisible = false;
+ }
+
+ this.closeOverlay = true;
+ this.dateClick = false;
+ this.cd.detectChanges();
+ });
+ }
+ }
+
+ unbindDocumentClickListener() {
+ if(this.documentClickListener) {
+ this.documentClickListener();
+ this.documentClickListener = null;
+ }
+ }
+
+ ngOnDestroy() {
+ this.unbindDocumentClickListener();
+
+ if(!this.inline && this.appendTo) {
+ this.el.nativeElement.appendChild(this.overlay);
+ }
+ }
+
+ validate(c: AbstractControl) {
+ if (!this._isValid) {
+ return { invalidDate: true };
+ }
+
+ return null;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,ButtonModule],
+ exports: [Calendar,ButtonModule],
+ declarations: [Calendar]
+})
+export class CalendarModule { }
diff --git a/src/app/components/captcha/captcha.ts b/src/app/components/captcha/captcha.ts
new file mode 100644
index 00000000000..54cb4c9de75
--- /dev/null
+++ b/src/app/components/captcha/captcha.ts
@@ -0,0 +1,93 @@
+import {NgModule,AfterViewInit,Component,EventEmitter,Input,NgZone,OnDestroy,Output,ViewChild, ElementRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+@Component({
+ selector: 'p-captcha',
+ template: ``
+})
+export class Captcha implements AfterViewInit {
+
+ @Input() siteKey: string = null;
+
+ @Input() theme = 'light';
+
+ @Input() type = 'image';
+
+ @Input() size = 'normal';
+
+ @Input() tabindex = 0;
+
+ @Input() language: string = null;
+
+ @Input() initCallback = "initRecaptcha";
+
+ @Output() onResponse: EventEmitter = new EventEmitter();
+
+ @Output() onExpire: EventEmitter = new EventEmitter();
+
+ @ViewChild('target') el: ElementRef;
+
+ private _instance: any = null;
+
+ constructor(public _zone:NgZone) {}
+
+ ngAfterViewInit() {
+ if ((window).grecaptcha)
+ this.init();
+ else {
+ (window)[this.initCallback] = () => {
+ this.init();
+ }
+ }
+ }
+
+ init() {
+ this._instance = (window).grecaptcha.render(this.el.nativeElement, {
+ 'sitekey': this.siteKey,
+ 'theme': this.theme,
+ 'type': this.type,
+ 'size': this.size,
+ 'tabindex': this.tabindex,
+ 'hl': this.language,
+ 'callback': (response: string) => {this._zone.run(() => this.recaptchaCallback(response))},
+ 'expired-callback': () => {this._zone.run(() => this.recaptchaExpiredCallback())}
+ });
+ }
+
+ reset() {
+ if(this._instance === null)
+ return;
+
+ (window).grecaptcha.reset(this._instance);
+ }
+
+ getResponse(): String {
+ if (this._instance === null)
+ return null;
+
+ return (window).grecaptcha.getResponse(this._instance);
+ }
+
+ recaptchaCallback(response: string) {
+ this.onResponse.emit({
+ response: response
+ });
+ }
+
+ recaptchaExpiredCallback() {
+ this.onExpire.emit();
+ }
+
+ ngOnDestroy() {
+ if (this._instance != null) {
+ (window).grecaptcha.reset(this._instance);
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Captcha],
+ declarations: [Captcha]
+})
+export class CaptchaModule { }
diff --git a/src/app/components/carousel/carousel.css b/src/app/components/carousel/carousel.css
new file mode 100644
index 00000000000..a10453d839d
--- /dev/null
+++ b/src/app/components/carousel/carousel.css
@@ -0,0 +1,85 @@
+.ui-carousel {
+ position: relative;
+ padding: .063em;
+}
+
+.ui-carousel .ui-carousel-viewport .ui-carousel-items {
+ list-style: none outside none;
+ margin: 0;
+ padding:0;
+ position: relative;
+ width: 32000px;
+ left: 0;
+}
+
+.ui-carousel .ui-carousel-viewport .ui-carousel-items .ui-carousel-item {
+ margin: 1px;
+ padding: 0;
+ float: left;
+ box-sizing: border-box;
+}
+
+.ui-carousel .ui-carousel-viewport {
+ overflow: hidden;
+ position: relative;
+ border: 0;
+}
+
+.ui-carousel .ui-carousel-footer {
+ margin: 1px 1px 0px 1px;
+ padding: .5em;
+ overflow: hidden;
+}
+
+.ui-carousel .ui-carousel-header {
+ margin: 0 1px;
+ overflow: hidden;
+ padding: .625em;
+}
+
+.ui-carousel .ui-carousel-header .ui-carousel-header-title {
+ display: inline-block;
+ overflow: hidden;
+}
+
+.ui-carousel .ui-carousel-dropdown,
+.ui-carousel .ui-carousel-mobiledropdown {
+ float: right;
+ margin: 0px .625em;
+ background-image: none;
+}
+
+.ui-carousel .ui-carousel-dropdown option,
+.ui-carousel .ui-carousel-mobiledropdown option{
+ background-image: none;
+ border: 0 none;
+ box-shadow: none;
+ -moz-box-shadow: none;
+ -webkit-box-shadow: none;
+}
+
+.ui-carousel .ui-carousel-button {
+ float: right;
+ margin: .125em;
+}
+
+.ui-carousel .ui-carousel-page-link {
+ float: left;
+ margin: 0 .125em;
+ text-decoration: none;
+}
+
+.ui-carousel .ui-carousel-page-link,
+.ui-carousel .ui-carousel-button {
+ cursor: pointer;
+}
+
+.ui-carousel .ui-carousel-page-links {
+ margin: 0px .5em;
+ margin-top: .125em;
+ float: right;
+}
+
+.ui-carousel .ui-carousel-mobiledropdown {
+ display: none;
+}
\ No newline at end of file
diff --git a/src/app/components/carousel/carousel.ts b/src/app/components/carousel/carousel.ts
new file mode 100644
index 00000000000..63fe8af3a37
--- /dev/null
+++ b/src/app/components/carousel/carousel.ts
@@ -0,0 +1,330 @@
+import {NgModule,Component,ElementRef,AfterViewInit,AfterViewChecked,AfterContentInit,EventEmitter,OnDestroy,Input,Output,TemplateRef,ContentChildren,QueryList,Renderer2,ViewChild} from '@angular/core';
+import {DomHandler} from '../dom/domhandler';
+import {SharedModule,PrimeTemplate} from '../common/shared';
+import {CommonModule} from '@angular/common';
+
+@Component({
+ selector: 'p-carousel',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class Carousel implements AfterViewChecked,AfterViewInit,OnDestroy{
+
+ @Input() numVisible: number = 3;
+
+ @Input() firstVisible: number = 0;
+
+ @Input() headerText: string;
+
+ @Input() circular: boolean = false;
+
+ @Input() breakpoint: number = 560;
+
+ @Input() responsive: boolean = true;
+
+ @Input() autoplayInterval: number = 0;
+
+ @Input() effectDuration: any = '1s';
+
+ @Input() easing: string = 'ease-out';
+
+ @Input() pageLinks: number = 3;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Output() onPage: EventEmitter = new EventEmitter();
+
+ @ViewChild('container') containerViewChild: ElementRef;
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public _value: any[];
+
+ public itemTemplate: TemplateRef;
+
+ public container: any;
+
+ public left: any = 0;
+
+ public viewport: any;
+
+ public itemsContainer: any;
+
+ public items: any;
+
+ public columns: any;
+
+ public page: number;
+
+ public valuesChanged: any;
+
+ public interval: any;
+
+ public anchorPageLinks: any[];
+
+ public mobileDropdownOptions: any[];
+
+ public selectDropdownOptions: any[];
+
+ public shrinked: boolean;
+
+ documentResponsiveListener: any;
+
+ differ: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2) {}
+
+ ngAfterContentInit() {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'item':
+ this.itemTemplate = item.template;
+ break;
+
+ default:
+ this.itemTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ @Input() get value(): any[] {
+ return this._value;
+ }
+
+ set value(val:any[]) {
+ this._value = val;
+ this.handleDataChange();
+ }
+
+ handleDataChange() {
+ if(this.value && this.value.length) {
+ if(this.value.length && this.firstVisible >= this.value.length) {
+ this.setPage(this.totalPages - 1);
+ }
+ }
+ else {
+ this.setPage(0);
+ }
+
+ this.valuesChanged = true;
+ }
+
+ ngAfterViewChecked() {
+ if(this.valuesChanged && this.containerViewChild.nativeElement.offsetParent) {
+ this.render();
+ this.valuesChanged = false;
+ }
+ }
+
+ ngAfterViewInit() {
+ this.container = this.el.nativeElement.children[0];
+ this.viewport = this.domHandler.findSingle(this.el.nativeElement, 'div.ui-carousel-viewport');
+ this.itemsContainer = this.domHandler.findSingle(this.el.nativeElement, 'ul.ui-carousel-items');
+
+ if(this.responsive) {
+ this.documentResponsiveListener = this.renderer.listen('window', 'resize', (event) => {
+ this.updateState();
+ });
+ }
+ }
+
+ updateLinks() {
+ this.anchorPageLinks = [];
+ for (let i = 0; i < this.totalPages; i++) {
+ this.anchorPageLinks.push(i);
+ }
+ }
+
+ updateDropdown() {
+ this.selectDropdownOptions = [];
+ for (let i = 0; i < this.totalPages; i++) {
+ this.selectDropdownOptions.push(i);
+ }
+ }
+
+ updateMobileDropdown() {
+ this.mobileDropdownOptions = [];
+ for (let i = 0; i < this.value.length; i++) {
+ this.mobileDropdownOptions.push(i);
+ }
+ }
+
+ render() {
+ if(this.autoplayInterval) {
+ this.stopAutoplay();
+ }
+
+ this.items = this.domHandler.find(this.itemsContainer,'li');
+ this.calculateColumns();
+ this.calculateItemWidths();
+
+ if(!this.responsive) {
+ this.container.style.width = (this.domHandler.width(this.container)) + 'px';
+ }
+
+ if(this.autoplayInterval) {
+ this.circular = true;
+ this.startAutoplay();
+ }
+
+ this.updateMobileDropdown();
+ this.updateLinks();
+ this.updateDropdown();
+ }
+
+ calculateItemWidths () {
+ let firstItem = (this.items && this.items.length) ? this.items[0] : null;
+ if(firstItem) {
+ for (let i = 0; i < this.items.length; i++) {
+ this.items[i].style.width = ((this.domHandler.innerWidth(this.viewport) - (this.domHandler.getHorizontalMargin(firstItem) * this.columns)) / this.columns) + 'px';
+ }
+ }
+ }
+
+ calculateColumns() {
+ if(window.innerWidth <= this.breakpoint) {
+ this.shrinked = true;
+ this.columns = 1;
+ }
+ else {
+ this.shrinked = false;
+ this.columns = this.numVisible;
+ }
+ this.page = Math.floor(this.firstVisible / this.columns);
+ }
+
+ onNextNav() {
+ let lastPage = (this.page === (this.totalPages - 1));
+
+ if(!lastPage)
+ this.setPage(this.page + 1);
+ else if(this.circular)
+ this.setPage(0);
+ }
+
+ onPrevNav() {
+ if(this.page !== 0)
+ this.setPage(this.page - 1);
+ else if(this.circular)
+ this.setPage(this.totalPages - 1);
+ }
+
+ setPageWithLink(event, p: number) {
+ this.setPage(p);
+ event.preventDefault();
+ }
+
+ setPage(p, enforce?: boolean) {
+ if(p !== this.page || enforce) {
+ this.page = p;
+ this.left = (-1 * (this.domHandler.innerWidth(this.viewport) * this.page));
+ this.firstVisible = this.page * this.columns;
+ this.onPage.emit({
+ page: this.page
+ });
+ }
+ }
+
+ onDropdownChange(val: string) {
+ this.setPage(parseInt(val));
+ }
+
+ get displayPageLinks(): boolean {
+ return (this.totalPages <= this.pageLinks && !this.shrinked);
+ }
+
+ get displayPageDropdown(): boolean {
+ return (this.totalPages > this.pageLinks && !this.shrinked);
+ }
+
+ get totalPages(): number {
+ return (this.value && this.value.length) ? Math.ceil(this.value.length / this.columns) : 0;
+ }
+
+ routerDisplay () {
+ let win = window;
+ if(win.innerWidth <= this.breakpoint)
+ return true;
+ else
+ return false;
+ }
+
+ updateState() {
+ let win = window;
+ if(win.innerWidth <= this.breakpoint) {
+ this.shrinked = true;
+ this.columns = 1;
+ }
+ else if(this.shrinked) {
+ this.shrinked = false;
+ this.columns = this.numVisible;
+ this.updateLinks();
+ this.updateDropdown();
+ }
+
+ this.calculateItemWidths();
+ this.setPage(Math.floor(this.firstVisible / this.columns), true);
+ }
+
+ startAutoplay() {
+ this.interval = setInterval(() => {
+ if(this.page === (this.totalPages - 1))
+ this.setPage(0);
+ else
+ this.setPage(this.page + 1);
+ },
+ this.autoplayInterval);
+ }
+
+ stopAutoplay() {
+ clearInterval(this.interval);
+ }
+
+ ngOnDestroy() {
+ if(this.documentResponsiveListener) {
+ this.documentResponsiveListener();
+ }
+
+ if(this.autoplayInterval) {
+ this.stopAutoplay();
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,SharedModule],
+ exports: [Carousel,SharedModule],
+ declarations: [Carousel]
+})
+export class CarouselModule { }
\ No newline at end of file
diff --git a/src/app/components/chart/chart.ts b/src/app/components/chart/chart.ts
new file mode 100644
index 00000000000..e1c027e3754
--- /dev/null
+++ b/src/app/components/chart/chart.ts
@@ -0,0 +1,107 @@
+import {NgModule,Component,ElementRef,AfterViewInit,OnDestroy,Input,Output,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+declare var Chart: any;
+
+@Component({
+ selector: 'p-chart',
+ template: `
+
+
+
+ `
+})
+export class UIChart implements AfterViewInit, OnDestroy {
+
+ @Input() type: string;
+
+ @Input() options: any;
+
+ @Input() width: string;
+
+ @Input() height: string;
+
+ @Output() onDataSelect: EventEmitter = new EventEmitter();
+
+ initialized: boolean;
+
+ _data: any;
+
+ chart: any;
+
+ constructor(public el: ElementRef) {}
+
+ @Input() get data(): any {
+ return this._data;
+ }
+
+ set data(val:any) {
+ this._data = val;
+ this.reinit();
+ }
+
+ ngAfterViewInit() {
+ this.initChart();
+ this.initialized = true;
+ }
+
+ onCanvasClick(event) {
+ if(this.chart) {
+ let element = this.chart.getElementAtEvent(event);
+ let dataset = this.chart.getDatasetAtEvent(event);
+ if(element&&element[0]&&dataset) {
+ this.onDataSelect.emit({originalEvent: event, element: element[0], dataset: dataset});
+ }
+ }
+ }
+
+ initChart() {
+ this.chart = new Chart(this.el.nativeElement.children[0].children[0], {
+ type: this.type,
+ data: this.data,
+ options: this.options
+ });
+ }
+
+ getCanvas() {
+ return this.el.nativeElement.children[0].children[0];
+ }
+
+ getBase64Image() {
+ return this.chart.toBase64Image();
+ }
+
+ generateLegend() {
+ if(this.chart) {
+ this.chart.generateLegend();
+ }
+ }
+
+ refresh() {
+ if(this.chart) {
+ this.chart.update();
+ }
+ }
+
+ reinit() {
+ if(this.chart) {
+ this.chart.destroy();
+ this.initChart();
+ }
+ }
+
+ ngOnDestroy() {
+ if(this.chart) {
+ this.chart.destroy();
+ this.initialized = false;
+ this.chart = null;
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [UIChart],
+ declarations: [UIChart]
+})
+export class ChartModule { }
diff --git a/src/app/components/checkbox/checkbox.css b/src/app/components/checkbox/checkbox.css
new file mode 100644
index 00000000000..2bb84c1b6f5
--- /dev/null
+++ b/src/app/components/checkbox/checkbox.css
@@ -0,0 +1,26 @@
+.ui-chkbox {
+ display: inline-block;
+ cursor: pointer;
+ vertical-align: middle;
+ margin-right: .25em;
+}
+
+.ui-chkbox .ui-chkbox-box {
+ width: 1.125em;
+ height: 1.125em;
+ line-height: 1.125em;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+ text-align: center;
+}
+
+.ui-chkbox .ui-chkbox-icon {
+ line-height: inherit;
+ display: block;
+}
+
+.ui-chkbox-label {
+ vertical-align: middle;
+}
+
diff --git a/src/app/components/checkbox/checkbox.ts b/src/app/components/checkbox/checkbox.ts
new file mode 100644
index 00000000000..fc7048c0b3b
--- /dev/null
+++ b/src/app/components/checkbox/checkbox.ts
@@ -0,0 +1,149 @@
+import {NgModule,Component,Input,Output,EventEmitter,forwardRef,ChangeDetectorRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const CHECKBOX_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => Checkbox),
+ multi: true
+};
+
+@Component({
+ selector: 'p-checkbox',
+ template: `
+
+
+ `,
+ providers: [CHECKBOX_VALUE_ACCESSOR]
+})
+export class Checkbox implements ControlValueAccessor {
+
+ @Input() value: any;
+
+ @Input() name: string;
+
+ @Input() disabled: boolean;
+
+ @Input() binary: string;
+
+ @Input() label: string;
+
+ @Input() tabindex: number;
+
+ @Input() inputId: string;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ model: any;
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ focused: boolean = false;
+
+ checked: boolean = false;
+
+ constructor(private cd: ChangeDetectorRef) {}
+
+ onClick(event,checkbox,focus:boolean) {
+ event.preventDefault();
+
+ if(this.disabled) {
+ return;
+ }
+
+ this.checked = !this.checked;
+ this.updateModel();
+
+ if(focus) {
+ checkbox.focus();
+ }
+ }
+
+ updateModel() {
+ if(!this.binary) {
+ if(this.checked)
+ this.addValue();
+ else
+ this.removeValue();
+
+ this.onModelChange(this.model);
+ }
+ else {
+ this.onModelChange(this.checked);
+ }
+
+ this.onChange.emit(this.checked);
+ }
+
+ handleChange(event) {
+ this.checked = event.target.checked;
+ this.updateModel();
+ }
+
+ isChecked(): boolean {
+ if(this.binary)
+ return this.model;
+ else
+ return this.model && this.model.indexOf(this.value) > -1;
+ }
+
+ removeValue() {
+ this.model = this.model.filter(val => val !== this.value);
+ }
+
+ addValue() {
+ if(this.model)
+ this.model = [...this.model, this.value];
+ else
+ this.model = [this.value];
+ }
+
+ onFocus(event) {
+ this.focused = true;
+ }
+
+ onBlur(event) {
+ this.focused = false;
+ this.onModelTouched();
+ }
+
+ writeValue(model: any) : void {
+ this.model = model;
+ this.checked = this.isChecked();
+ this.cd.markForCheck();
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Checkbox],
+ declarations: [Checkbox]
+})
+export class CheckboxModule { }
\ No newline at end of file
diff --git a/src/app/components/chips/chips.css b/src/app/components/chips/chips.css
new file mode 100644
index 00000000000..ae3568a936f
--- /dev/null
+++ b/src/app/components/chips/chips.css
@@ -0,0 +1,59 @@
+.ui-chips > ul.ui-inputtext {
+ clear: left;
+ cursor: text;
+ list-style-type: none;
+ margin: 0;
+ overflow: hidden;
+ padding: 0 .25em;
+}
+
+.ui-chips-token {
+ cursor: default;
+ display: inline-block;
+ vertical-align: middle;
+ overflow: hidden;
+ padding: .125em .5em;
+ white-space: nowrap;
+ position: relative;
+ margin-right: .125em;
+ border: 0 none;
+ font-size: .9em;
+}
+
+.ui-chips-token .ui-chips-token-label {
+ display: block;
+ margin-right: 2em;
+}
+
+.ui-chips > .ui-state-disabled .ui-chips-token-label {
+ margin-right: 0;
+}
+
+.ui-chips-token .ui-chips-token-icon {
+ margin-top: -.5em;
+ position: absolute;
+ right: 0.2em;
+ top: 50%;
+ cursor: pointer;
+}
+
+.ui-chips-input-token {
+ display: inline-block;
+ vertical-align: middle;
+ list-style-type: none;
+ margin: 0 0 0 .125em;
+ padding: .25em .25em .25em 0;
+}
+
+.ui-chips-input-token input {
+ border: 0 none;
+ width: 10em;
+ outline: medium none;
+ background-color: transparent;
+ margin: 0;
+ padding: 0;
+ box-shadow: none;
+ -moz-border-radius: 0;
+ -webkit-border-radius: 0;
+ border-radius: 0;
+}
\ No newline at end of file
diff --git a/src/app/components/chips/chips.ts b/src/app/components/chips/chips.ts
new file mode 100644
index 00000000000..ffea29ee6e7
--- /dev/null
+++ b/src/app/components/chips/chips.ts
@@ -0,0 +1,191 @@
+import {NgModule,Component,ElementRef,Input,Output,EventEmitter,AfterContentInit,ContentChildren,QueryList,TemplateRef,IterableDiffers,forwardRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {SharedModule,PrimeTemplate} from '../common/shared';
+import {InputTextModule} from '../inputtext/inputtext';
+import {DomHandler} from '../dom/domhandler';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const CHIPS_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => Chips),
+ multi: true
+};
+
+@Component({
+ selector: 'p-chips',
+ template: `
+
+ `,
+ providers: [DomHandler,CHIPS_VALUE_ACCESSOR]
+})
+export class Chips implements AfterContentInit,ControlValueAccessor {
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() disabled: boolean;
+
+ @Output() onAdd: EventEmitter = new EventEmitter();
+
+ @Output() onRemove: EventEmitter = new EventEmitter();
+
+ @Input() field: string;
+
+ @Input() placeholder: string;
+
+ @Input() max: number;
+
+ @Input() tabindex: number;
+
+ @Input() inputId: string;
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public itemTemplate: TemplateRef;
+
+ value: any;
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ valueChanged: boolean;
+
+ focus: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ ngAfterContentInit() {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'item':
+ this.itemTemplate = item.template;
+ break;
+
+ default:
+ this.itemTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ writeValue(value: any) : void {
+ this.value = value;
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ resolveFieldData(data: any, field: string): any {
+ if(data && field) {
+ if(field.indexOf('.') == -1) {
+ return data[field];
+ }
+ else {
+ let fields: string[] = field.split('.');
+ let value = data;
+ for(var i = 0, len = fields.length; i < len; ++i) {
+ value = value[fields[i]];
+ }
+ return value;
+ }
+ }
+ else {
+ return null;
+ }
+ }
+
+ onFocus() {
+ this.focus = true;
+ }
+
+ onBlur() {
+ this.focus = false;
+ this.onModelTouched();
+ }
+
+ removeItem(event: Event, index: number): void {
+ if(this.disabled) {
+ return;
+ }
+
+ let removedItem = this.value[index];
+ this.value = this.value.filter((val, i) => i!=index);
+ this.onModelChange(this.value);
+ this.onRemove.emit({
+ originalEvent: event,
+ value: removedItem
+ });
+ }
+
+ onKeydown(event: KeyboardEvent, inputEL: HTMLInputElement): void {
+ switch(event.which) {
+ //backspace
+ case 8:
+ if(inputEL.value.length === 0 && this.value && this.value.length > 0) {
+ this.value = [...this.value];
+ let removedItem = this.value.pop();
+ this.onModelChange(this.value);
+ this.onRemove.emit({
+ originalEvent: event,
+ value: removedItem
+ });
+ }
+ break;
+
+ //enter
+ case 13:
+ this.value = this.value||[];
+ if(inputEL.value && inputEL.value.trim().length && (!this.max||this.max > this.value.length)) {
+ this.value = [...this.value,inputEL.value];
+ this.onModelChange(this.value);
+ this.onAdd.emit({
+ originalEvent: event,
+ value: inputEL.value
+ });
+ }
+ inputEL.value = '';
+ event.preventDefault();
+ break;
+
+ default:
+ if(this.max && this.value && this.max === this.value.length) {
+ event.preventDefault();
+ }
+ break;
+ }
+ }
+
+ get maxedOut(): boolean {
+ return this.max && this.value && this.max === this.value.length;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,InputTextModule,SharedModule],
+ exports: [Chips,InputTextModule,SharedModule],
+ declarations: [Chips]
+})
+export class ChipsModule { }
\ No newline at end of file
diff --git a/src/app/components/codehighlighter/codehighlighter.ts b/src/app/components/codehighlighter/codehighlighter.ts
new file mode 100644
index 00000000000..aefce9b0e76
--- /dev/null
+++ b/src/app/components/codehighlighter/codehighlighter.ts
@@ -0,0 +1,25 @@
+import {NgModule,Directive,ElementRef,OnInit} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+declare var Prism: any;
+
+@Directive({
+ selector: '[pCode]'
+})
+export class CodeHighlighter implements OnInit {
+
+ constructor(public el: ElementRef) {}
+
+ ngOnInit() {
+ Prism.highlightElement(this.el.nativeElement);
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [CodeHighlighter],
+ declarations: [CodeHighlighter]
+})
+export class CodeHighlighterModule { }
+
+
diff --git a/src/app/components/common/api.ts b/src/app/components/common/api.ts
new file mode 100644
index 00000000000..f74057ea914
--- /dev/null
+++ b/src/app/components/common/api.ts
@@ -0,0 +1,24 @@
+export { DomHandler } from '../dom/domhandler';
+export { TreeNode } from './treenode';
+export { TreeDragDropService } from './treedragdropservice';
+export { TreeNodeDragEvent } from './treenodedragevent';
+export { BlockableUI } from './blockableui';
+export { Confirmation } from './confirmation';
+export { ConfirmationService } from './confirmationservice';
+export { FilterMetadata } from './filtermetadata';
+export { LazyLoadEvent } from './lazyloadevent';
+export { MenuItem } from './menuitem';
+export { Message } from './message';
+export { SelectItem } from './selectitem';
+export { SortMeta } from './sortmeta';
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/common/blockableui.ts b/src/app/components/common/blockableui.ts
new file mode 100644
index 00000000000..fd62e1705d2
--- /dev/null
+++ b/src/app/components/common/blockableui.ts
@@ -0,0 +1,3 @@
+export interface BlockableUI {
+ getBlockableElement(): HTMLElement;
+}
\ No newline at end of file
diff --git a/src/app/components/common/common.css b/src/app/components/common/common.css
new file mode 100644
index 00000000000..301959e666c
--- /dev/null
+++ b/src/app/components/common/common.css
@@ -0,0 +1,152 @@
+.ui-widget, .ui-widget * {
+ box-sizing: border-box;
+}
+.ui-helper-hidden {
+ display: none;
+}
+.ui-helper-hidden-accessible {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+.ui-helper-reset {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ line-height: 1.3;
+ text-decoration: none;
+ font-size: 100%;
+ list-style: none;
+}
+.ui-helper-clearfix:before,
+.ui-helper-clearfix:after {
+ content: "";
+ display: table;
+}
+.ui-helper-clearfix:after {
+ clear: both;
+}
+.ui-helper-clearfix {
+ zoom: 1;
+}
+.ui-helper-zfix {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ position: absolute;
+ opacity: 0;
+ filter: Alpha(Opacity=0);
+}
+.ui-state-disabled {
+ cursor: default !important;
+}
+.ui-state-disabled a {
+ cursor: default !important;
+}
+.ui-icon {
+ display: block;
+ text-indent: -99999px;
+ overflow: hidden;
+ background-repeat: no-repeat;
+}
+.ui-widget-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.ui-resizable {
+ position: relative;
+}
+.ui-resizable-handle {
+ position: absolute;
+ font-size: 0.1px;
+ display: block;
+}
+.ui-resizable-disabled .ui-resizable-handle,
+.ui-resizable-autohide .ui-resizable-handle {
+ display: none;
+}
+.ui-resizable-n {
+ cursor: n-resize;
+ height: 7px;
+ width: 100%;
+ top: -5px;
+ left: 0;
+}
+.ui-resizable-s {
+ cursor: s-resize;
+ height: 7px;
+ width: 100%;
+ bottom: -5px;
+ left: 0;
+}
+.ui-resizable-e {
+ cursor: e-resize;
+ width: 7px;
+ right: -5px;
+ top: 0;
+ height: 100%;
+}
+.ui-resizable-w {
+ cursor: w-resize;
+ width: 7px;
+ left: -5px;
+ top: 0;
+ height: 100%;
+}
+.ui-resizable-se {
+ cursor: se-resize;
+ width: 12px;
+ height: 12px;
+ right: 1px;
+ bottom: 1px;
+}
+.ui-resizable-sw {
+ cursor: sw-resize;
+ width: 9px;
+ height: 9px;
+ left: -5px;
+ bottom: -5px;
+}
+.ui-resizable-nw {
+ cursor: nw-resize;
+ width: 9px;
+ height: 9px;
+ left: -5px;
+ top: -5px;
+}
+.ui-resizable-ne {
+ cursor: ne-resize;
+ width: 9px;
+ height: 9px;
+ right: -5px;
+ top: -5px;
+}
+.ui-shadow {
+ -webkit-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.3);
+ -moz-box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.3);
+ box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.3);
+}
+.ui-unselectable-text {
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+.ui-scrollbar-measure {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ position: absolute;
+ top: -9999px;
+}
\ No newline at end of file
diff --git a/src/app/components/common/confirmation.ts b/src/app/components/common/confirmation.ts
new file mode 100644
index 00000000000..27c7e9b49e1
--- /dev/null
+++ b/src/app/components/common/confirmation.ts
@@ -0,0 +1,14 @@
+import { EventEmitter } from '@angular/core';
+
+export interface Confirmation {
+ message: string;
+ key?: string;
+ icon?: string;
+ header?: string;
+ accept?: Function;
+ reject?: Function;
+ acceptVisible?: boolean;
+ rejectVisible?: boolean;
+ acceptEvent?: EventEmitter;
+ rejectEvent?: EventEmitter;
+}
diff --git a/src/app/components/common/confirmationservice.ts b/src/app/components/common/confirmationservice.ts
new file mode 100644
index 00000000000..c96f104c459
--- /dev/null
+++ b/src/app/components/common/confirmationservice.ts
@@ -0,0 +1,23 @@
+import { Injectable } from '@angular/core';
+import { Subject } from 'rxjs/Subject';
+import { Observable } from 'rxjs/Observable';
+import { Confirmation } from './confirmation';
+
+@Injectable()
+export class ConfirmationService {
+
+ private requireConfirmationSource = new Subject();
+ private acceptConfirmationSource = new Subject();
+
+ requireConfirmation$ = this.requireConfirmationSource.asObservable();
+ accept = this.acceptConfirmationSource.asObservable();
+
+ confirm(confirmation: Confirmation) {
+ this.requireConfirmationSource.next(confirmation);
+ return this;
+ }
+
+ onAccept() {
+ this.acceptConfirmationSource.next();
+ }
+}
\ No newline at end of file
diff --git a/src/app/components/common/filtermetadata.ts b/src/app/components/common/filtermetadata.ts
new file mode 100644
index 00000000000..2fc78e4f420
--- /dev/null
+++ b/src/app/components/common/filtermetadata.ts
@@ -0,0 +1,4 @@
+export interface FilterMetadata {
+ value?: any;
+ matchMode?: string;
+}
\ No newline at end of file
diff --git a/src/app/components/common/lazyloadevent.ts b/src/app/components/common/lazyloadevent.ts
new file mode 100644
index 00000000000..fa962033acd
--- /dev/null
+++ b/src/app/components/common/lazyloadevent.ts
@@ -0,0 +1,12 @@
+import { SortMeta } from './sortmeta';
+import { FilterMetadata } from './filtermetadata';
+
+export interface LazyLoadEvent {
+ first?: number;
+ rows?: number;
+ sortField?: string;
+ sortOrder?: number;
+ multiSortMeta?: SortMeta[];
+ filters?: {[s: string]: FilterMetadata;};
+ globalFilter?: any;
+}
\ No newline at end of file
diff --git a/src/app/components/common/menuitem.ts b/src/app/components/common/menuitem.ts
new file mode 100644
index 00000000000..62f187fecce
--- /dev/null
+++ b/src/app/components/common/menuitem.ts
@@ -0,0 +1,16 @@
+import {EventEmitter} from '@angular/core';
+
+export interface MenuItem {
+ label?: string;
+ icon?: string;
+ command?: (event?: any) => void;
+ url?: string;
+ routerLink?: any;
+ eventEmitter?: EventEmitter;
+ items?: MenuItem[];
+ expanded?: boolean;
+ disabled?: boolean;
+ visible?: boolean;
+ target?: string;
+ routerLinkActiveOptions?: any;
+}
\ No newline at end of file
diff --git a/src/app/components/common/message.ts b/src/app/components/common/message.ts
new file mode 100644
index 00000000000..bbdf4d0b654
--- /dev/null
+++ b/src/app/components/common/message.ts
@@ -0,0 +1,6 @@
+export interface Message {
+ severity?: string;
+ summary?: string;
+ detail?: string;
+ id?: any;
+}
\ No newline at end of file
diff --git a/src/app/components/common/selectitem.ts b/src/app/components/common/selectitem.ts
new file mode 100644
index 00000000000..04d19e971b8
--- /dev/null
+++ b/src/app/components/common/selectitem.ts
@@ -0,0 +1,4 @@
+export interface SelectItem {
+ label: string;
+ value: any;
+}
\ No newline at end of file
diff --git a/src/app/components/common/shared.ts b/src/app/components/common/shared.ts
new file mode 100644
index 00000000000..f134be83308
--- /dev/null
+++ b/src/app/components/common/shared.ts
@@ -0,0 +1,329 @@
+import {NgModule,EventEmitter,Directive,ViewContainerRef,Input,Output,ContentChildren,ContentChild,TemplateRef,OnInit,OnChanges,OnDestroy,AfterContentInit,QueryList,SimpleChanges,EmbeddedViewRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {Component} from '@angular/core';
+
+@Component({
+ selector: 'p-header',
+ template: ''
+})
+export class Header {}
+
+@Component({
+ selector: 'p-footer',
+ template: ''
+})
+export class Footer {}
+
+@Directive({
+ selector: '[pTemplate]',
+ host: {
+ }
+})
+export class PrimeTemplate {
+
+ @Input() type: string;
+
+ @Input('pTemplate') name: string;
+
+ constructor(public template: TemplateRef) {}
+
+ getType(): string {
+ if(this.type) {
+ console.log('Defining a pTemplate with type property is deprecated use pTemplate="type" instead.');
+ return this.type;
+ }
+ else {
+ return this.name;
+ }
+ }
+}
+
+@Directive({
+ selector: '[pTemplateWrapper]'
+})
+export class TemplateWrapper implements OnInit, OnDestroy {
+
+ @Input() item: any;
+
+ @Input() index: number;
+
+ @Input('pTemplateWrapper') templateRef: TemplateRef;
+
+ view: EmbeddedViewRef;
+
+ constructor(public viewContainer: ViewContainerRef) {}
+
+ ngOnInit() {
+ this.view = this.viewContainer.createEmbeddedView(this.templateRef, {
+ '\$implicit': this.item,
+ 'index': this.index
+ });
+ }
+
+ ngOnDestroy() {
+ this.view.destroy();
+ }
+}
+
+@Component({
+ selector: 'p-column',
+ template: ``
+})
+export class Column implements AfterContentInit{
+ @Input() field: string;
+ @Input() sortField: string;
+ @Input() header: string;
+ @Input() footer: string;
+ @Input() sortable: any;
+ @Input() editable: boolean;
+ @Input() filter: boolean;
+ @Input() filterMatchMode: string;
+ @Input() filterType: string = 'text';
+ @Input() rowspan: number;
+ @Input() colspan: number;
+ @Input() style: any;
+ @Input() styleClass: string;
+ @Input() hidden: boolean;
+ @Input() expander: boolean;
+ @Input() selectionMode: string;
+ @Input() filterPlaceholder: string;
+ @Input() frozen: boolean;
+ @Output() sortFunction: EventEmitter = new EventEmitter();
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+ @ContentChild(TemplateRef) template: TemplateRef;
+
+ public headerTemplate: TemplateRef;
+ public bodyTemplate: TemplateRef;
+ public footerTemplate: TemplateRef;
+ public filterTemplate: TemplateRef;
+ public editorTemplate: TemplateRef;
+
+ ngAfterContentInit():void {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'header':
+ this.headerTemplate = item.template;
+ break;
+
+ case 'body':
+ this.bodyTemplate = item.template;
+ break;
+
+ case 'footer':
+ this.footerTemplate = item.template;
+ break;
+
+ case 'filter':
+ this.filterTemplate = item.template;
+ break;
+
+ case 'editor':
+ this.editorTemplate = item.template;
+ break;
+
+ default:
+ this.bodyTemplate = item.template;
+ break;
+ }
+ });
+ }
+}
+
+@Component({
+ selector: 'p-row',
+ template: ``
+})
+export class Row {
+
+ @ContentChildren(Column) columns: QueryList;
+
+}
+
+@Component({
+ selector: 'p-headerColumnGroup',
+ template: ``
+})
+export class HeaderColumnGroup {
+
+ @ContentChildren(Row) rows: QueryList;
+}
+
+@Component({
+ selector: 'p-footerColumnGroup',
+ template: ``
+})
+export class FooterColumnGroup {
+
+ @ContentChildren(Row) rows: QueryList;
+}
+
+@Component({
+ selector: 'p-columnBodyTemplateLoader',
+ template: ``
+})
+export class ColumnBodyTemplateLoader implements OnInit, OnChanges, OnDestroy {
+
+ @Input() column: any;
+
+ @Input() rowData: any;
+
+ @Input() rowIndex: number;
+
+ view: EmbeddedViewRef;
+
+ constructor(public viewContainer: ViewContainerRef) {}
+
+ ngOnInit() {
+ this.view = this.viewContainer.createEmbeddedView(this.column.bodyTemplate, {
+ '\$implicit': this.column,
+ 'rowData': this.rowData,
+ 'rowIndex': this.rowIndex
+ });
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if(!this.view) {
+ return;
+ }
+
+ if('rowIndex' in changes) {
+ this.view.context.rowIndex = changes['rowIndex'].currentValue;
+ }
+ }
+
+ ngOnDestroy() {
+ this.view.destroy();
+ }
+}
+
+@Component({
+ selector: 'p-columnHeaderTemplateLoader',
+ template: ``
+})
+export class ColumnHeaderTemplateLoader implements OnInit, OnDestroy {
+
+ @Input() column: any;
+
+ view: EmbeddedViewRef;
+
+ constructor(public viewContainer: ViewContainerRef) {}
+
+ ngOnInit() {
+ this.view = this.viewContainer.createEmbeddedView(this.column.headerTemplate, {
+ '\$implicit': this.column
+ });
+ }
+
+ ngOnDestroy() {
+ this.view.destroy();
+ }
+}
+
+@Component({
+ selector: 'p-columnFooterTemplateLoader',
+ template: ``
+})
+export class ColumnFooterTemplateLoader implements OnInit, OnDestroy {
+
+ @Input() column: any;
+
+ view: EmbeddedViewRef;
+
+ constructor(public viewContainer: ViewContainerRef) {}
+
+ ngOnInit() {
+ this.view = this.viewContainer.createEmbeddedView(this.column.footerTemplate, {
+ '\$implicit': this.column
+ });
+ }
+
+ ngOnDestroy() {
+ this.view.destroy();
+ }
+}
+
+@Component({
+ selector: 'p-columnFilterTemplateLoader',
+ template: ``
+})
+export class ColumnFilterTemplateLoader implements OnInit, OnDestroy {
+
+ @Input() column: any;
+
+ view: EmbeddedViewRef;
+
+ constructor(public viewContainer: ViewContainerRef) {}
+
+ ngOnInit() {
+ this.view = this.viewContainer.createEmbeddedView(this.column.filterTemplate, {
+ '\$implicit': this.column
+ });
+ }
+
+ ngOnDestroy() {
+ this.view.destroy();
+ }
+}
+
+@Component({
+ selector: 'p-columnEditorTemplateLoader',
+ template: ``
+})
+export class ColumnEditorTemplateLoader implements OnInit, OnDestroy {
+
+ @Input() column: any;
+
+ @Input() rowData: any;
+
+ @Input() rowIndex: any;
+
+ view: EmbeddedViewRef;
+
+ constructor(public viewContainer: ViewContainerRef) {}
+
+ ngOnInit() {
+ this.view = this.viewContainer.createEmbeddedView(this.column.editorTemplate, {
+ '\$implicit': this.column,
+ 'rowData': this.rowData,
+ 'rowIndex': this.rowIndex
+ });
+ }
+
+ ngOnDestroy() {
+ this.view.destroy();
+ }
+}
+
+@Component({
+ selector: 'p-templateLoader',
+ template: ``
+})
+export class TemplateLoader implements OnInit, OnDestroy {
+
+ @Input() template: TemplateRef;
+
+ @Input() data: any;
+
+ view: EmbeddedViewRef;
+
+ constructor(public viewContainer: ViewContainerRef) {}
+
+ ngOnInit() {
+ if(this.template) {
+ this.view = this.viewContainer.createEmbeddedView(this.template, {
+ '\$implicit': this.data
+ });
+ }
+ }
+
+ ngOnDestroy() {
+ if (this.view) this.view.destroy();
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Header,Footer,Column,TemplateWrapper,ColumnHeaderTemplateLoader,ColumnBodyTemplateLoader,ColumnFooterTemplateLoader,ColumnFilterTemplateLoader,PrimeTemplate,TemplateLoader,Row,HeaderColumnGroup,FooterColumnGroup,ColumnEditorTemplateLoader],
+ declarations: [Header,Footer,Column,TemplateWrapper,ColumnHeaderTemplateLoader,ColumnBodyTemplateLoader,ColumnFooterTemplateLoader,ColumnFilterTemplateLoader,PrimeTemplate,TemplateLoader,Row,HeaderColumnGroup,FooterColumnGroup,ColumnEditorTemplateLoader]
+})
+export class SharedModule { }
diff --git a/src/app/components/common/sortmeta.ts b/src/app/components/common/sortmeta.ts
new file mode 100644
index 00000000000..7b2c7e22f2d
--- /dev/null
+++ b/src/app/components/common/sortmeta.ts
@@ -0,0 +1,4 @@
+export interface SortMeta {
+ field: string;
+ order: number;
+}
\ No newline at end of file
diff --git a/src/app/components/common/treedragdropservice.ts b/src/app/components/common/treedragdropservice.ts
new file mode 100644
index 00000000000..d19bf4a80f8
--- /dev/null
+++ b/src/app/components/common/treedragdropservice.ts
@@ -0,0 +1,23 @@
+import { Injectable } from '@angular/core';
+import { Subject } from 'rxjs/Subject';
+import { Observable } from 'rxjs/Observable';
+import { TreeNode } from './treenode';
+import { TreeNodeDragEvent } from './treenodedragevent';
+
+@Injectable()
+export class TreeDragDropService {
+
+ private dragStartSource = new Subject();
+ private dragStopSource = new Subject();
+
+ dragStart$ = this.dragStartSource.asObservable();
+ dragStop$ = this.dragStopSource.asObservable();
+
+ startDrag(event: TreeNodeDragEvent) {
+ this.dragStartSource.next(event);
+ }
+
+ stopDrag(event: TreeNodeDragEvent) {
+ this.dragStopSource.next(event);
+ }
+}
\ No newline at end of file
diff --git a/src/app/components/common/treenode.ts b/src/app/components/common/treenode.ts
new file mode 100644
index 00000000000..42016563cbc
--- /dev/null
+++ b/src/app/components/common/treenode.ts
@@ -0,0 +1,17 @@
+export interface TreeNode {
+ label?: string;
+ data?: any;
+ icon?: any;
+ expandedIcon?: any;
+ collapsedIcon?: any;
+ children?: TreeNode[];
+ leaf?: boolean;
+ expanded?: boolean;
+ type?: string;
+ parent?: TreeNode;
+ partialSelected?: boolean;
+ styleClass?: string;
+ draggable?: boolean;
+ droppable?: boolean;
+ selectable?: boolean;
+}
\ No newline at end of file
diff --git a/src/app/components/common/treenodedragevent.ts b/src/app/components/common/treenodedragevent.ts
new file mode 100644
index 00000000000..0509cfa3bb8
--- /dev/null
+++ b/src/app/components/common/treenodedragevent.ts
@@ -0,0 +1,9 @@
+import { TreeNode } from './treenode';
+
+export interface TreeNodeDragEvent {
+ tree?: any;
+ node?: TreeNode;
+ subNodes?: TreeNode[];
+ index?: number;
+ scope?: any;
+}
\ No newline at end of file
diff --git a/src/app/components/confirmdialog/confirmdialog.ts b/src/app/components/confirmdialog/confirmdialog.ts
new file mode 100644
index 00000000000..a8d730cf051
--- /dev/null
+++ b/src/app/components/confirmdialog/confirmdialog.ts
@@ -0,0 +1,268 @@
+import {NgModule,Component,ElementRef,AfterViewInit,OnDestroy,Input,Output,EventEmitter,Renderer2,ContentChild} from '@angular/core';
+import {trigger,state,style,transition,animate} from '@angular/animations';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {Header,Footer,SharedModule} from '../common/shared';
+import {ButtonModule} from '../button/button';
+import {Confirmation} from '../common/confirmation';
+import {ConfirmationService} from '../common/confirmationservice';
+import {Subscription} from 'rxjs/Subscription';
+
+@Component({
+ selector: 'p-confirmDialog',
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ animations: [
+ trigger('dialogState', [
+ state('hidden', style({
+ opacity: 0
+ })),
+ state('visible', style({
+ opacity: 1
+ })),
+ transition('visible => hidden', animate('400ms ease-in')),
+ transition('hidden => visible', animate('400ms ease-out'))
+ ])
+ ],
+ providers: [DomHandler]
+})
+export class ConfirmDialog implements AfterViewInit,OnDestroy {
+
+ @Input() header: string;
+
+ @Input() icon: string;
+
+ @Input() message: string;
+
+ @Input() acceptIcon: string = 'fa-check';
+
+ @Input() acceptLabel: string = 'Yes';
+
+ @Input() acceptVisible: boolean = true;
+
+ @Input() rejectIcon: string = 'fa-close';
+
+ @Input() rejectLabel: string = 'No';
+
+ @Input() rejectVisible: boolean = true;
+
+ @Input() width: any;
+
+ @Input() height: any;
+
+ @Input() closeOnEscape: boolean = true;
+
+ @Input() rtl: boolean;
+
+ @Input() closable: boolean = true;
+
+ @Input() responsive: boolean = true;
+
+ @Input() appendTo: any;
+
+ @Input() key: string;
+
+ @ContentChild(Footer) footer;
+
+ confirmation: Confirmation;
+
+ _visible: boolean;
+
+ documentEscapeListener: any;
+
+ documentResponsiveListener: any;
+
+ mask: any;
+
+ contentContainer: any;
+
+ positionInitialized: boolean;
+
+ subscription: Subscription;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler,
+ public renderer: Renderer2, private confirmationService: ConfirmationService) {
+ this.subscription = confirmationService.requireConfirmation$.subscribe(confirmation => {
+ if(confirmation.key === this.key) {
+ this.confirmation = confirmation;
+ this.message = this.confirmation.message||this.message;
+ this.icon = this.confirmation.icon||this.icon;
+ this.header = this.confirmation.header||this.header;
+ this.rejectVisible = this.confirmation.rejectVisible == null ? this.rejectVisible : this.confirmation.rejectVisible;
+ this.acceptVisible = this.confirmation.acceptVisible == null ? this.acceptVisible : this.confirmation.acceptVisible;
+
+ if(this.confirmation.accept) {
+ this.confirmation.acceptEvent = new EventEmitter();
+ this.confirmation.acceptEvent.subscribe(this.confirmation.accept);
+ }
+
+ if(this.confirmation.reject) {
+ this.confirmation.rejectEvent = new EventEmitter();
+ this.confirmation.rejectEvent.subscribe(this.confirmation.reject);
+ }
+
+ this.visible = true;
+ }
+ });
+ }
+
+ @Input() get visible(): boolean {
+ return this._visible;
+ }
+
+ set visible(val:boolean) {
+ this._visible = val;
+
+ if(this._visible) {
+ if(!this.positionInitialized) {
+ this.center();
+ this.positionInitialized = true;
+ }
+
+ this.el.nativeElement.children[0].style.zIndex = ++DomHandler.zindex;
+ }
+
+ if(this._visible)
+ this.enableModality();
+ else
+ this.disableModality();
+ }
+
+ ngAfterViewInit() {
+ this.contentContainer = this.domHandler.findSingle(this.el.nativeElement, '.ui-dialog-content');
+
+ if(this.responsive) {
+ this.documentResponsiveListener = this.renderer.listen('window', 'resize', (event) => {
+ this.center();
+ });
+ }
+
+ if(this.closeOnEscape && this.closable) {
+ this.documentEscapeListener = this.renderer.listen('document', 'keydown', (event) => {
+ if(event.which == 27) {
+ if(this.el.nativeElement.children[0].style.zIndex == DomHandler.zindex) {
+ this.hide(event);
+ }
+ }
+ });
+ }
+
+ if(this.appendTo) {
+ if(this.appendTo === 'body')
+ document.body.appendChild(this.el.nativeElement);
+ else
+ this.domHandler.appendChild(this.el.nativeElement, this.appendTo);
+ }
+ }
+
+ center() {
+ let container = this.el.nativeElement.children[0];
+ let elementWidth = this.domHandler.getOuterWidth(container);
+ let elementHeight = this.domHandler.getOuterHeight(container);
+ if(elementWidth == 0 && elementHeight == 0) {
+ container.style.visibility = 'hidden';
+ container.style.display = 'block';
+ elementWidth = this.domHandler.getOuterWidth(container);
+ elementHeight = this.domHandler.getOuterHeight(container);
+ container.style.display = 'none';
+ container.style.visibility = 'visible';
+ }
+ let viewport = this.domHandler.getViewport();
+ let x = (viewport.width - elementWidth) / 2;
+ let y = (viewport.height - elementHeight) / 2;
+
+ container.style.left = x + 'px';
+ container.style.top = y + 'px';
+ }
+
+ enableModality() {
+ if(!this.mask) {
+ this.mask = document.createElement('div');
+ this.mask.style.zIndex = this.el.nativeElement.children[0].style.zIndex - 1;
+ this.domHandler.addMultipleClasses(this.mask, 'ui-widget-overlay ui-dialog-mask');
+ document.body.appendChild(this.mask);
+ }
+ }
+
+ disableModality() {
+ if(this.mask) {
+ document.body.removeChild(this.mask);
+ this.mask = null;
+ }
+ }
+
+ hide(event?:Event) {
+ this.visible = false;
+
+ if(event) {
+ event.preventDefault();
+ }
+ }
+
+ moveOnTop() {
+ this.el.nativeElement.children[0].style.zIndex = ++DomHandler.zindex;
+ }
+
+ ngOnDestroy() {
+ this.disableModality();
+
+ if(this.documentResponsiveListener) {
+ this.documentResponsiveListener();
+ }
+
+ if(this.documentEscapeListener) {
+ this.documentEscapeListener();
+ }
+
+ if(this.appendTo && this.appendTo === 'body') {
+ document.body.removeChild(this.el.nativeElement);
+ }
+
+ this.subscription.unsubscribe();
+ }
+
+ accept() {
+ if(this.confirmation.acceptEvent) {
+ this.confirmation.acceptEvent.emit();
+ }
+
+ this.hide();
+ this.confirmation = null;
+ }
+
+ reject() {
+ if(this.confirmation.rejectEvent) {
+ this.confirmation.rejectEvent.emit();
+ }
+
+ this.hide();
+ this.confirmation = null;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,ButtonModule],
+ exports: [ConfirmDialog,ButtonModule,SharedModule],
+ declarations: [ConfirmDialog]
+})
+export class ConfirmDialogModule { }
\ No newline at end of file
diff --git a/src/app/components/contextmenu/contextmenu.ts b/src/app/components/contextmenu/contextmenu.ts
new file mode 100644
index 00000000000..87853b72531
--- /dev/null
+++ b/src/app/components/contextmenu/contextmenu.ts
@@ -0,0 +1,278 @@
+import {NgModule,Component,ElementRef,AfterViewInit,OnDestroy,Input,Output,Renderer2,EventEmitter,Inject,forwardRef,ViewChild} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {MenuItem} from '../common/menuitem';
+import {Location} from '@angular/common';
+import {RouterModule} from '@angular/router';
+
+@Component({
+ selector: 'p-contextMenuSub',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class ContextMenuSub {
+
+ @Input() item: MenuItem;
+
+ @Input() root: boolean;
+
+ constructor(public domHandler: DomHandler, @Inject(forwardRef(() => ContextMenu)) public contextMenu: ContextMenu) {}
+
+ activeItem: any;
+
+ containerLeft: any;
+
+ onItemMouseEnter(event, item, menuitem) {
+ if(menuitem.disabled) {
+ return;
+ }
+
+ this.activeItem = item;
+ let nextElement = item.children[0].nextElementSibling;
+ if(nextElement) {
+ let sublist = nextElement.children[0];
+ sublist.style.zIndex = ++DomHandler.zindex;
+ this.position(sublist, item);
+ }
+ }
+
+ onItemMouseLeave(event, link) {
+ this.activeItem = null;
+ }
+
+ itemClick(event, item: MenuItem) {
+ if(item.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if(!item.url) {
+ event.preventDefault();
+ }
+
+ if(item.command) {
+ if(!item.eventEmitter) {
+ item.eventEmitter = new EventEmitter();
+ item.eventEmitter.subscribe(item.command);
+ }
+
+ item.eventEmitter.emit({
+ originalEvent: event,
+ item: item
+ });
+ }
+ }
+
+ listClick(event) {
+ this.activeItem = null;
+ }
+
+ position(sublist, item) {
+ this.containerLeft = this.domHandler.getOffset(item.parentElement)
+ let viewport = this.domHandler.getViewport();
+ let sublistWidth = sublist.offsetParent ? sublist.offsetWidth: this.domHandler.getHiddenElementOuterWidth(sublist);
+ let itemOuterWidth = this.domHandler.getOuterWidth(item.children[0]);
+
+ sublist.style.top = '0px';
+
+ if((parseInt(this.containerLeft.left) + itemOuterWidth + sublistWidth) > (viewport.width - this.calculateScrollbarWidth())) {
+ sublist.style.left = -sublistWidth + 'px';
+ }
+ else {
+ sublist.style.left = itemOuterWidth + 'px';
+ }
+ }
+
+ calculateScrollbarWidth(): number {
+ let scrollDiv = document.createElement("div");
+ scrollDiv.className = "ui-scrollbar-measure";
+ document.body.appendChild(scrollDiv);
+
+ let scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
+ document.body.removeChild(scrollDiv);
+
+ return scrollbarWidth;
+ }
+}
+
+@Component({
+ selector: 'p-contextMenu',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class ContextMenu implements AfterViewInit,OnDestroy {
+
+ @Input() model: MenuItem[];
+
+ @Input() global: boolean;
+
+ @Input() target: any;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() appendTo: any;
+
+ @ViewChild('container') containerViewChild: ElementRef;
+
+ container: HTMLDivElement;
+
+ visible: boolean;
+
+ documentClickListener: any;
+
+ rightClickListener: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2) {}
+
+ ngAfterViewInit() {
+ this.container = this.containerViewChild.nativeElement;
+
+ this.documentClickListener = this.renderer.listen('document', 'click', () => {
+ this.hide();
+ });
+
+ if(this.global) {
+ this.rightClickListener = this.renderer.listen('document', 'contextmenu', (event) => {
+ this.show(event);
+ event.preventDefault();
+ });
+ }
+ else if(this.target) {
+ this.rightClickListener = this.renderer.listen(this.target, 'contextmenu', (event) => {
+ this.show(event);
+ event.preventDefault();
+ event.stopPropagation();
+ });
+ }
+
+ if(this.appendTo) {
+ if(this.appendTo === 'body')
+ document.body.appendChild(this.container);
+ else
+ this.domHandler.appendChild(this.container, this.appendTo);
+ }
+ }
+
+ show(event?: MouseEvent) {
+ this.position(event);
+ this.visible = true;
+ this.domHandler.fadeIn(this.container, 250);
+
+ if(event) {
+ event.preventDefault();
+ }
+ }
+
+ hide() {
+ this.visible = false;
+ }
+
+ toggle(event?: MouseEvent) {
+ if(this.visible)
+ this.hide();
+ else
+ this.show(event);
+ }
+
+ position(event?: MouseEvent) {
+ if(event) {
+ let left = event.pageX + 1;
+ let top = event.pageY + 1;
+ let width = this.container.offsetParent ? this.container.offsetWidth: this.domHandler.getHiddenElementOuterWidth(this.container);
+ let height = this.container.offsetParent ? this.container.offsetHeight: this.domHandler.getHiddenElementOuterHeight(this.container);
+ let viewport = this.domHandler.getViewport();
+
+ //flip
+ if(left + width - document.body.scrollLeft > viewport.width) {
+ left -= width;
+ }
+
+ //flip
+ if(top + height - document.body.scrollTop > viewport.height) {
+ top -= height;
+ }
+
+ //fit
+ if(left < document.body.scrollLeft) {
+ left = document.body.scrollLeft;
+ }
+
+ //fit
+ if(top < document.body.scrollTop) {
+ top = document.body.scrollTop;
+ }
+
+ this.container.style.left = left + 'px';
+ this.container.style.top = top + 'px';
+ }
+ }
+
+ unsubscribe(item: any) {
+ if(item.eventEmitter) {
+ item.eventEmitter.unsubscribe();
+ }
+
+ if(item.items) {
+ for(let childItem of item.items) {
+ this.unsubscribe(childItem);
+ }
+ }
+ }
+
+ ngOnDestroy() {
+ if(this.documentClickListener) {
+ this.documentClickListener();
+ }
+
+ if(this.rightClickListener) {
+ this.rightClickListener();
+ }
+
+ if(this.model) {
+ for(let item of this.model) {
+ this.unsubscribe(item);
+ }
+ }
+
+ if(this.appendTo) {
+ this.el.nativeElement.appendChild(this.container);
+ }
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule,RouterModule],
+ exports: [ContextMenu,RouterModule],
+ declarations: [ContextMenu,ContextMenuSub]
+})
+export class ContextMenuModule { }
\ No newline at end of file
diff --git a/src/app/components/datagrid/datagrid.css b/src/app/components/datagrid/datagrid.css
new file mode 100644
index 00000000000..d044f7b01f0
--- /dev/null
+++ b/src/app/components/datagrid/datagrid.css
@@ -0,0 +1,35 @@
+.ui-datagrid .ui-paginator {
+ text-align: center;
+ border-top: 0 none;
+}
+
+.ui-datagrid-column {
+ padding: .25em;
+}
+
+.ui-datagrid-content-empty {
+ padding: .25em .625em;
+}
+
+.ui-datagrid .ui-datagrid-header,
+.ui-datagrid .ui-datagrid-footer {
+ text-align:center;
+ padding: .5em .75em;
+}
+
+.ui-datagrid .ui-datagrid-header {
+ border-bottom: 0 none;
+}
+
+.ui-datagrid .ui-datagrid-footer {
+ border-top: 0 none;
+}
+
+.ui-datagrid .ui-paginator-top {
+ border-bottom: 0 none;
+}
+
+.ui-datagrid .ui-paginator-bottom {
+ border-top: 0 none;
+}
+
diff --git a/src/app/components/datagrid/datagrid.ts b/src/app/components/datagrid/datagrid.ts
new file mode 100644
index 00000000000..fc55dac4bd2
--- /dev/null
+++ b/src/app/components/datagrid/datagrid.ts
@@ -0,0 +1,183 @@
+import {NgModule,Component,ElementRef,AfterViewInit,AfterContentInit,OnDestroy,Input,Output,SimpleChange,EventEmitter,ContentChild,ContentChildren,QueryList,TemplateRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {Header,Footer,PrimeTemplate,SharedModule} from '../common/shared';
+import {PaginatorModule} from '../paginator/paginator';
+import {BlockableUI} from '../common/blockableui';
+
+@Component({
+ selector: 'p-dataGrid',
+ template: `
+
+ `
+})
+export class DataGrid implements AfterViewInit,AfterContentInit,BlockableUI {
+
+ @Input() paginator: boolean;
+
+ @Input() rows: number;
+
+ @Input() totalRecords: number;
+
+ @Input() pageLinks: number = 5;
+
+ @Input() rowsPerPageOptions: number[];
+
+ @Input() lazy: boolean;
+
+ @Input() emptyMessage: string = 'No records found';
+
+ @Output() onLazyLoad: EventEmitter = new EventEmitter();
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() paginatorPosition: string = 'bottom';
+
+ @Input() alwaysShowPaginator: boolean = true;
+
+ @Input() trackBy: Function = (index: number, item: any) => item;
+
+ @Output() onPage: EventEmitter = new EventEmitter();
+
+ @ContentChild(Header) header;
+
+ @ContentChild(Footer) footer;
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public _value: any[];
+
+ public itemTemplate: TemplateRef;
+
+ public dataToRender: any[];
+
+ public first: number = 0;
+
+ public page: number = 0;
+
+ constructor(public el: ElementRef) {}
+
+ ngAfterViewInit() {
+ if(this.lazy) {
+ this.onLazyLoad.emit({
+ first: this.first,
+ rows: this.rows
+ });
+ }
+ }
+
+ ngAfterContentInit() {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'item':
+ this.itemTemplate = item.template;
+ break;
+
+ default:
+ this.itemTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ @Input() get value(): any[] {
+ return this._value;
+ }
+
+ set value(val:any[]) {
+ this._value = val;
+ this.handleDataChange();
+ }
+
+ handleDataChange() {
+ if(this.paginator) {
+ this.updatePaginator();
+ }
+ this.updateDataToRender(this.value);
+ }
+
+ updatePaginator() {
+ //total records
+ this.totalRecords = this.lazy ? this.totalRecords : (this.value ? this.value.length: 0);
+
+ //first
+ if(this.totalRecords && this.first >= this.totalRecords) {
+ let numberOfPages = Math.ceil(this.totalRecords/this.rows);
+ this.first = Math.max((numberOfPages-1) * this.rows, 0);
+ }
+ }
+
+ paginate(event) {
+ this.first = event.first;
+ this.rows = event.rows;
+
+ if(this.lazy) {
+ this.onLazyLoad.emit(this.createLazyLoadMetadata());
+ }
+ else {
+ this.updateDataToRender(this.value);
+ }
+
+ this.onPage.emit({
+ first: this.first,
+ rows: this.rows
+ });
+ }
+
+ updateDataToRender(datasource) {
+ if(this.paginator && datasource) {
+ this.dataToRender = [];
+ let startIndex = this.lazy ? 0 : this.first;
+ for(let i = startIndex; i < (startIndex+ this.rows); i++) {
+ if(i >= datasource.length) {
+ break;
+ }
+
+ this.dataToRender.push(datasource[i]);
+ }
+ }
+ else {
+ this.dataToRender = datasource;
+ }
+ }
+
+ isEmpty() {
+ return !this.dataToRender||(this.dataToRender.length == 0);
+ }
+
+ createLazyLoadMetadata(): any {
+ return {
+ first: this.first,
+ rows: this.rows
+ };
+ }
+
+ getBlockableElement(): HTMLElement {
+ return this.el.nativeElement.children[0];
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,SharedModule,PaginatorModule],
+ exports: [DataGrid,SharedModule],
+ declarations: [DataGrid]
+})
+export class DataGridModule { }
diff --git a/src/app/components/datalist/datalist.css b/src/app/components/datalist/datalist.css
new file mode 100644
index 00000000000..9aa70e674d4
--- /dev/null
+++ b/src/app/components/datalist/datalist.css
@@ -0,0 +1,31 @@
+.ui-datalist .ui-datalist-header,
+.ui-datalist .ui-datalist-footer {
+ text-align:center;
+ padding: .5em .75em;
+}
+
+.ui-datalist .ui-datalist-header {
+ border-bottom: 0 none;
+}
+
+.ui-datalist .ui-datalist-footer {
+ border-top: 0 none;
+}
+
+.ui-datalist .ui-paginator {
+ border-top: 0 none;
+}
+
+.ui-datalist .ui-datalist-data {
+ margin: 0;
+ padding: 0;
+}
+
+.ui-datalist .ui-datalist-data > li {
+ list-style-type: none;
+
+}
+
+.ui-datalist .ui-datalist-emptymessage {
+ padding: .5em .75em;
+}
\ No newline at end of file
diff --git a/src/app/components/datalist/datalist.ts b/src/app/components/datalist/datalist.ts
new file mode 100644
index 00000000000..15fa40d9f37
--- /dev/null
+++ b/src/app/components/datalist/datalist.ts
@@ -0,0 +1,185 @@
+import {NgModule,Component,ElementRef,AfterViewInit,AfterContentInit,OnDestroy,Input,Output,SimpleChange,EventEmitter,ContentChild,ContentChildren,TemplateRef,QueryList} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {SharedModule,Header,Footer,PrimeTemplate} from '../common/shared';
+import {PaginatorModule} from '../paginator/paginator';
+import {BlockableUI} from '../common/blockableui';
+
+@Component({
+ selector: 'p-dataList',
+ template: `
+
+ `
+})
+export class DataList implements AfterViewInit,AfterContentInit,BlockableUI {
+
+ @Input() paginator: boolean;
+
+ @Input() rows: number;
+
+ @Input() totalRecords: number;
+
+ @Input() pageLinks: number = 5;
+
+ @Input() rowsPerPageOptions: number[];
+
+ @Input() lazy: boolean;
+
+ @Output() onLazyLoad: EventEmitter = new EventEmitter();
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() paginatorPosition: string = 'bottom';
+
+ @Input() emptyMessage: string = 'No records found';
+
+ @Input() alwaysShowPaginator: boolean = true;
+
+ @Input() trackBy: Function = (index: number, item: any) => item;
+
+ @Output() onPage: EventEmitter = new EventEmitter();
+
+ @ContentChild(Header) header;
+
+ @ContentChild(Footer) footer;
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public _value: any[];
+
+ public itemTemplate: TemplateRef;
+
+ public dataToRender: any[];
+
+ public first: number = 0;
+
+ public page: number = 0;
+
+ constructor(public el: ElementRef) {}
+
+ ngAfterContentInit() {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'item':
+ this.itemTemplate = item.template;
+ break;
+
+ default:
+ this.itemTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ ngAfterViewInit() {
+ if(this.lazy) {
+ this.onLazyLoad.emit({
+ first: this.first,
+ rows: this.rows
+ });
+ }
+ }
+
+ @Input() get value(): any[] {
+ return this._value;
+ }
+
+ set value(val:any[]) {
+ this._value = val;
+ this.handleDataChange();
+ }
+
+ handleDataChange() {
+ if(this.paginator) {
+ this.updatePaginator();
+ }
+ this.updateDataToRender(this.value);
+ }
+
+ updatePaginator() {
+ //total records
+ this.totalRecords = this.lazy ? this.totalRecords : (this.value ? this.value.length: 0);
+
+ //first
+ if(this.totalRecords && this.first >= this.totalRecords) {
+ let numberOfPages = Math.ceil(this.totalRecords/this.rows);
+ this.first = Math.max((numberOfPages-1) * this.rows, 0);
+ }
+ }
+
+ paginate(event) {
+ this.first = event.first;
+ this.rows = event.rows;
+
+ if(this.lazy) {
+ this.onLazyLoad.emit(this.createLazyLoadMetadata());
+ }
+ else {
+ this.updateDataToRender(this.value);
+ }
+
+ this.onPage.emit({
+ first: this.first,
+ rows: this.rows
+ });
+ }
+
+ updateDataToRender(datasource) {
+ if(this.paginator && datasource) {
+ this.dataToRender = [];
+ let startIndex = this.lazy ? 0 : this.first;
+ for(let i = startIndex; i < (startIndex+ this.rows); i++) {
+ if(i >= datasource.length) {
+ break;
+ }
+
+ this.dataToRender.push(datasource[i]);
+ }
+ }
+ else {
+ this.dataToRender = datasource;
+ }
+ }
+
+ isEmpty() {
+ return !this.dataToRender||(this.dataToRender.length == 0);
+ }
+
+ createLazyLoadMetadata(): any {
+ return {
+ first: this.first,
+ rows: this.rows
+ };
+ }
+
+ getBlockableElement(): HTMLElement {
+ return this.el.nativeElement.children[0];
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,SharedModule,PaginatorModule],
+ exports: [DataList,SharedModule],
+ declarations: [DataList]
+})
+export class DataListModule { }
diff --git a/src/app/components/datascroller/datascroller.css b/src/app/components/datascroller/datascroller.css
new file mode 100644
index 00000000000..eefe766a8db
--- /dev/null
+++ b/src/app/components/datascroller/datascroller.css
@@ -0,0 +1,28 @@
+.ui-datascroller {
+}
+
+.ui-datascroller .ui-datascroller-header {
+ text-align: center;
+ padding: .5em .75em;
+ border-bottom: 0 none;
+}
+
+.ui-datascroller .ui-datascroller-footer {
+ text-align: center;
+ padding: .25em .625em;
+ border-top: 0px none;
+}
+
+.ui-datascroller .ui-datascroller-content {
+ padding: .25em .625em;
+}
+
+.ui-datascroller-inline .ui-datascroller-content {
+ overflow: auto;
+}
+
+.ui-datascroller .ui-datascroller-list {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
\ No newline at end of file
diff --git a/src/app/components/datascroller/datascroller.ts b/src/app/components/datascroller/datascroller.ts
new file mode 100644
index 00000000000..88868ad6318
--- /dev/null
+++ b/src/app/components/datascroller/datascroller.ts
@@ -0,0 +1,200 @@
+import {NgModule,Component,ElementRef,AfterViewInit,AfterContentInit,OnDestroy,Input,Output,Renderer2,ViewChild,EventEmitter,ContentChild,ContentChildren,QueryList,TemplateRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {Header,Footer,PrimeTemplate,SharedModule} from '../common/shared';
+import {DomHandler} from '../dom/domhandler';
+
+@Component({
+ selector: 'p-dataScroller',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class DataScroller implements AfterViewInit,OnDestroy {
+
+ @Input() rows: number;
+
+ @Input() lazy: boolean;
+
+ @Output() onLazyLoad: EventEmitter = new EventEmitter();
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() buffer: number = 0.9;
+
+ @Input() inline: boolean;
+
+ @Input() scrollHeight: any;
+
+ @Input() loader: any;
+
+ @ViewChild('content') contentViewChild: ElementRef;
+
+ @ContentChild(Header) header;
+
+ @ContentChild(Footer) footer;
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public _value: any[];
+
+ public itemTemplate: TemplateRef;
+
+ public dataToRender: any[] = [];
+
+ public first: number = 0;
+
+ scrollFunction: any;
+
+ contentElement: HTMLDivElement;
+
+ constructor(public el: ElementRef, public renderer: Renderer2, public domHandler: DomHandler) {}
+
+ ngAfterViewInit() {
+ if(this.lazy) {
+ this.load();
+ }
+
+ if(this.loader) {
+ this.scrollFunction = this.renderer.listen(this.loader, 'click', () => {
+ this.load();
+ });
+ }
+ else {
+ this.bindScrollListener();
+ }
+ }
+
+ ngAfterContentInit() {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'item':
+ this.itemTemplate = item.template;
+ break;
+
+ default:
+ this.itemTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ @Input() get value(): any[] {
+ return this._value;
+ }
+
+ set value(val:any[]) {
+ this._value = val;
+ this.handleDataChange();
+ }
+
+ handleDataChange() {
+ if(this.lazy)
+ this.dataToRender = this.value;
+ else
+ this.load();
+ }
+
+ load() {
+ if(this.lazy) {
+ this.onLazyLoad.emit({
+ first: this.first,
+ rows: this.rows
+ });
+
+ this.first = this.first + this.rows;
+ }
+ else {
+ if(this.value) {
+ for(let i = this.first; i < (this.first + this.rows); i++) {
+ if(i >= this.value.length) {
+ break;
+ }
+
+ this.dataToRender.push(this.value[i]);
+ }
+
+ this.first = this.first + this.rows;
+ }
+ }
+ }
+
+ reset() {
+ this.first = 0;
+ this.dataToRender = [];
+ this.load();
+ }
+
+ isEmpty() {
+ return !this.dataToRender||(this.dataToRender.length == 0);
+ }
+
+ createLazyLoadMetadata(): any {
+ return {
+ first: this.first,
+ rows: this.rows
+ };
+ }
+
+ bindScrollListener() {
+ if(this.inline) {
+ this.contentElement = this.contentViewChild.nativeElement;
+
+ this.scrollFunction = this.renderer.listen(this.contentElement, 'scroll', () => {
+ let scrollTop = this.contentElement.scrollTop;
+ let scrollHeight = this.contentElement.scrollHeight;
+ let viewportHeight = this.contentElement.clientHeight;
+
+ if((scrollTop >= ((scrollHeight * this.buffer) - (viewportHeight)))) {
+ this.load();
+ }
+ });
+ }
+ else {
+ this.scrollFunction = this.renderer.listen('window', 'scroll', () => {
+ let docBody = document.body;
+ let docElement = document.documentElement;
+ let scrollTop = (window.pageYOffset||document.documentElement.scrollTop);
+ let winHeight = docElement.clientHeight;
+ let docHeight = Math.max(docBody.scrollHeight, docBody.offsetHeight, winHeight, docElement.scrollHeight, docElement.offsetHeight);
+
+ if(scrollTop >= ((docHeight * this.buffer) - winHeight)) {
+ this.load();
+ }
+ });
+ }
+
+ }
+
+ ngOnDestroy() {
+ //unbind
+ if(this.scrollFunction) {
+ this.scrollFunction();
+ this.contentElement = null;
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,SharedModule],
+ exports: [DataScroller,SharedModule],
+ declarations: [DataScroller]
+})
+export class DataScrollerModule { }
+
diff --git a/src/app/components/datatable/datatable.css b/src/app/components/datatable/datatable.css
new file mode 100644
index 00000000000..1157a01446e
--- /dev/null
+++ b/src/app/components/datatable/datatable.css
@@ -0,0 +1,348 @@
+.ui-datatable {
+ position: relative;
+}
+
+.ui-datatable table {
+ border-collapse:collapse;
+ width: 100%;
+ table-layout: fixed;
+}
+
+.ui-datatable .ui-datatable-header,
+.ui-datatable .ui-datatable-caption,
+.ui-datatable .ui-datatable-footer {
+ text-align: center;
+ padding: .5em .75em;
+ box-sizing: border-box;
+}
+
+.ui-datatable .ui-datatable-caption,
+.ui-datatable .ui-datatable-header {
+ border-bottom: 0 none;
+}
+
+.ui-datatable .ui-datatable-footer {
+ border-top: 0 none;
+}
+
+.ui-datatable thead th, .ui-datatable tfoot td {
+ text-align: center;
+}
+
+.ui-datatable thead tr {
+ border-width: 0;
+}
+
+.ui-datatable .ui-datatable-thead > tr > th,
+.ui-datatable .ui-datatable-tfoot > tr > td,
+.ui-datatable .ui-datatable-data > tr > td {
+ border-color: inherit;
+ box-sizing: border-box;
+ padding: .25em .5em;
+ overflow: hidden;
+ border-width: 1px;
+ border-style: solid;
+}
+
+.ui-datatable .ui-datatable-thead > tr > th,
+.ui-datatable .ui-datatable-tfoot > tr > td {
+ font-weight: normal;
+}
+
+.ui-datatable tbody {
+ outline: 0;
+}
+
+.ui-datatable .ui-sortable-column {
+ cursor: pointer;
+}
+
+.ui-datatable .ui-sortable-column-icon {
+ display: inline-block;
+ margin-left: .125em;
+}
+
+.ui-datatable tr.ui-state-highlight {
+ cursor: pointer;
+}
+
+/* Scrollable */
+.ui-datatable-scrollable-body {
+ overflow:auto;
+}
+.ui-datatable-scrollable-header {
+ overflow: hidden;
+}
+
+.ui-datatable-scrollable .ui-datatable-scrollable-header,
+.ui-datatable-scrollable .ui-datatable-scrollable-footer {
+ position: relative;
+ border: 0 none;
+}
+
+.ui-datatable-scrollable .ui-datatable-scrollable-header td {
+ font-weight: normal;
+}
+
+.ui-datatable .ui-datatable-scrollable-body {
+ min-height: 0%;
+}
+
+.ui-datatable .ui-datatable-data tr.ui-state-hover,
+.ui-datatable .ui-datatable-data tr.ui-state-highlight {
+ border-color: inherit;
+ font-weight: inherit;
+ cursor: pointer;
+}
+
+.ui-datatable .ui-datatable-data tr.ui-rowgroup-header td a,
+.ui-datatable .ui-datatable-data tr.ui-rowgroup-header td span.ui-rowgroup-header-name {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ui-datatable-scrollable-theadclone {
+ height: 0;
+}
+
+.ui-datatable-scrollable-theadclone tr {
+ height: 0;
+}
+
+.ui-datatable-scrollable-theadclone th.ui-state-default {
+ height: 0;
+ border-bottom-width: 0;
+ border-top-width: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ outline: 0 none;
+}
+
+.ui-datatable-scrollable-theadclone th span.ui-column-title {
+ display: block;
+ height: 0;
+}
+
+.ui-datatable .ui-paginator {
+ padding: .125em;
+ border-top: 0 none;
+}
+
+.ui-datatable-rtl {
+ direction: rtl;
+}
+
+.ui-datatable-rtl.ui-datatable thead th,
+.ui-datatable-rtl.ui-datatable tfoot td {
+ text-align: right;
+}
+
+/* Row Toggler */
+.ui-row-toggler {
+ cursor: pointer;
+}
+
+/* Resizable */
+.ui-datatable .ui-column-resizer {
+ display: block;
+ position: absolute !important;
+ top: 0;
+ right: 0;
+ margin: 0;
+ width: .5em;
+ height: 100%;
+ padding: 0px;
+ cursor:col-resize;
+ border: 1px solid transparent;
+}
+
+.ui-datatable .ui-column-resizer-helper {
+ width: 1px;
+ position: absolute;
+ z-index: 10;
+ display: none;
+}
+
+.ui-datatable-resizable {
+ padding-bottom: 1px; /*fix for webkit overlow*/
+ overflow:auto;
+}
+
+.ui-datatable-resizable thead th,
+.ui-datatable-resizable tbody td,
+.ui-datatable-resizable tfoot td {
+ white-space: nowrap;
+}
+
+.ui-datatable-resizable th.ui-resizable-column {
+ background-clip: padding-box;
+ position: relative;
+}
+
+/** Reflow **/
+.ui-datatable-reflow .ui-datatable-data td .ui-column-title {
+ display: none;
+}
+
+/* Filter */
+.ui-datatable .ui-column-filter {
+ display: block;
+ width: 100%;
+ box-sizing: border-box;
+ margin-top: .25em;
+}
+
+/* Editing */
+.ui-datatable .ui-editable-column input {
+ width: 100%;
+ outline: 0;
+}
+
+.ui-datatable .ui-datatable-data > tr > td.ui-editable-column {
+ padding: .5em;
+}
+
+.ui-datatable .ui-editable-column .ui-cell-editor {
+ display: none;
+}
+
+.ui-datatable .ui-datatable-data > tr > td.ui-editable-column.ui-cell-editing {
+ padding: 1px;
+}
+
+.ui-datatable .ui-editable-column.ui-cell-editing .ui-cell-editor {
+ display: block;
+}
+
+.ui-datatable .ui-editable-column.ui-cell-editing .ui-cell-data {
+ display: none;
+}
+
+.ui-datatable-stacked thead th,
+.ui-datatable-stacked tfoot td {
+ display: none !important;
+}
+
+.ui-datatable.ui-datatable-stacked .ui-datatable-data > tr > td {
+ text-align: left;
+ display: block;
+ border: 0 none;
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ float: left;
+ clear: left;
+}
+
+.ui-datatable.ui-datatable-stacked .ui-datatable-data.ui-widget-content {
+ border: 0 none;
+}
+
+.ui-datatable-stacked .ui-datatable-data tr.ui-widget-content {
+ border-left: 0 none;
+ border-right: 0 none;
+}
+
+.ui-datatable-stacked .ui-datatable-data td .ui-column-title {
+ padding: .4em;
+ min-width: 30%;
+ display: inline-block;
+ margin: -.4em 1em -.4em -.4em;
+ font-weight: bold;
+}
+
+.ui-datatable .ui-selection-column .ui-chkbox,
+.ui-datatable .ui-selection-column .ui-radiobutton {
+ margin: 0;
+ display: block;
+}
+
+.ui-datatable .ui-selection-column .ui-chkbox-box,
+.ui-datatable .ui-selection-column .ui-radiobutton-box {
+ display: block;
+ box-sizing: border-box;
+ margin: 0;
+}
+
+.ui-datatable-scrollable-wrapper {
+ position: relative;
+}
+
+.ui-datatable-scrollable-view {
+
+}
+
+.ui-datatable-frozen-view .ui-datatable-scrollable-body {
+ overflow: hidden;
+}
+
+.ui-datatable-unfrozen-view {
+ position: absolute;
+ top: 0px;
+}
+
+.ui-datatable .ui-datatable-load-status {
+ width: 100%;
+ height: 100%;
+ top: 0px;
+ left: 0px;
+}
+
+.ui-datatable .ui-datatable-virtual-table {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+}
+
+.ui-datatable .ui-datatable-loading {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
+ opacity: 0.2;
+ z-index: 1;
+}
+
+.ui-datatable .ui-datatable-loading-content {
+ position: absolute;
+ left: 50%;
+ top: 25%;
+ z-index: 2;
+}
+
+@media ( max-width: 35em ) {
+ .ui-datatable-reflow thead th,
+ .ui-datatable-reflow tfoot td {
+ display: none !important;
+ }
+
+ .ui-datatable-reflow .ui-datatable-data > tr > td {
+ text-align: left;
+ display: block;
+ border: 0 none;
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ float: left;
+ clear: left;
+ }
+
+ .ui-datatable-reflow .ui-datatable-data.ui-widget-content {
+ border: 0 none;
+ }
+
+ .ui-datatable-reflow .ui-datatable-data tr.ui-widget-content {
+ border-left: 0 none;
+ border-right: 0 none;
+ }
+
+ .ui-datatable-reflow .ui-datatable-data td .ui-column-title {
+ padding: .4em;
+ min-width: 30%;
+ display: inline-block;
+ margin: -.4em 1em -.4em -.4em;
+ font-weight: bold;
+ }
+}
\ No newline at end of file
diff --git a/src/app/components/datatable/datatable.ts b/src/app/components/datatable/datatable.ts
new file mode 100644
index 00000000000..6e83aaf1199
--- /dev/null
+++ b/src/app/components/datatable/datatable.ts
@@ -0,0 +1,2144 @@
+import {NgModule,Component,ElementRef,AfterContentInit,AfterViewInit,AfterViewChecked,OnInit,OnDestroy,Input,ViewContainerRef,ViewChild,
+ Output,SimpleChange,EventEmitter,ContentChild,ContentChildren,Renderer2,QueryList,TemplateRef,ChangeDetectorRef,Inject,forwardRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {FormsModule} from '@angular/forms'
+import {SharedModule} from '../common/shared';
+import {PaginatorModule} from '../paginator/paginator';
+import {Column,Header,Footer,HeaderColumnGroup,FooterColumnGroup,PrimeTemplate} from '../common/shared';
+import {LazyLoadEvent} from '../common/lazyloadevent';
+import {FilterMetadata} from '../common/filtermetadata';
+import {SortMeta} from '../common/sortmeta';
+import {DomHandler} from '../dom/domhandler';
+import {ObjectUtils} from '../utils/ObjectUtils';
+import {Subscription} from 'rxjs/Subscription';
+import {BlockableUI} from '../common/blockableui';
+
+@Component({
+ selector: 'p-dtRadioButton',
+ template: `
+
+ `
+})
+export class DTRadioButton {
+
+ @Input() checked: boolean;
+
+ @Output() onClick: EventEmitter = new EventEmitter();
+
+ public hover: boolean;
+
+ handleClick(event) {
+ this.onClick.emit(event);
+ }
+}
+
+@Component({
+ selector: 'p-dtCheckbox',
+ template: `
+
+ `
+})
+export class DTCheckbox {
+
+ @Input() checked: boolean;
+
+ @Input() disabled: boolean;
+
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ public hover: boolean;
+
+ handleClick(event) {
+ if(!this.disabled) {
+ this.onChange.emit({originalEvent: event, checked: !this.checked});
+ }
+ }
+}
+
+@Component({
+ selector: 'p-rowExpansionLoader',
+ template: ``
+})
+export class RowExpansionLoader {
+
+ @Input() template: TemplateRef;
+
+ @Input() rowData: any;
+
+ constructor(public viewContainer: ViewContainerRef) {}
+
+ ngOnInit() {
+ let view = this.viewContainer.createEmbeddedView(this.template, {
+ '\$implicit': this.rowData
+ });
+ }
+}
+
+@Component({
+ selector: '[pColumnHeaders]',
+ template: `
+
+
+
+ {{col.header}}
+
+
+
+
+
+
+
+ |
+
+ `
+})
+export class ColumnHeaders {
+
+ constructor(@Inject(forwardRef(() => DataTable)) public dt:DataTable) {}
+
+ @Input("pColumnHeaders") columns: Column[];
+}
+
+@Component({
+ selector: '[pColumnFooters]',
+ template: `
+
+ {{col.footer}}
+
+
+
+ |
+ `
+})
+export class ColumnFooters {
+
+ constructor(@Inject(forwardRef(() => DataTable)) public dt:DataTable) {}
+
+ @Input("pColumnFooters") columns: Column[];
+}
+
+@Component({
+ selector: '[pTableBody]',
+ template: `
+
+
+
+
+
+ {{col.header}}
+ {{dt.resolveFieldData(rowData,col.field)}}
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+
+
+ {{dt.emptyMessage}} |
+
+ `
+})
+export class TableBody {
+
+ constructor(@Inject(forwardRef(() => DataTable)) public dt:DataTable) {}
+
+ @Input("pTableBody") columns: Column[];
+
+ visibleColumns() {
+ return this.columns ? this.columns.filter(c => !c.hidden): [];
+ }
+}
+
+@Component({
+ selector: '[pScrollableView]',
+ template: `
+
+
+
+ `
+})
+export class ScrollableView implements AfterViewInit,AfterViewChecked,OnDestroy {
+
+ constructor(@Inject(forwardRef(() => DataTable)) public dt:DataTable, public domHandler: DomHandler, public el: ElementRef, public renderer: Renderer2) {}
+
+ @Input("pScrollableView") columns: Column[];
+
+ @ViewChild('scrollHeader') scrollHeaderViewChild: ElementRef;
+
+ @ViewChild('scrollHeaderBox') scrollHeaderBoxViewChild: ElementRef;
+
+ @ViewChild('scrollBody') scrollBodyViewChild: ElementRef;
+
+ @ViewChild('scrollTable') scrollTableViewChild: ElementRef;
+
+ @ViewChild('scrollTableWrapper') scrollTableWrapperViewChild: ElementRef;
+
+ @ViewChild('scrollFooter') scrollFooterViewChild: ElementRef;
+
+ @ViewChild('scrollFooterBox') scrollFooterBoxViewChild: ElementRef;
+
+ @Input() frozen: boolean;
+
+ @Input() width: string;
+
+ @Input() virtualScroll: boolean;
+
+ @Output() onVirtualScroll: EventEmitter = new EventEmitter();
+
+ @Input() loading: boolean;
+
+ public scrollBody: HTMLDivElement;
+
+ public scrollHeader: HTMLDivElement;
+
+ public scrollHeaderBox: HTMLDivElement;
+
+ public scrollTable: HTMLDivElement;
+
+ public scrollTableWrapper: HTMLDivElement;
+
+ public scrollFooter: HTMLDivElement;
+
+ public scrollFooterBox: HTMLDivElement;
+
+ public bodyScrollListener: Function;
+
+ public headerScrollListener: Function;
+
+ public scrollBodyMouseWheelListener: Function;
+
+ public scrollFunction: Function;
+
+ public rowHeight: number;
+
+ public scrollTimeout: any;
+
+ ngAfterViewInit() {
+ this.initScrolling();
+ }
+
+ ngAfterViewChecked() {
+ if(this.virtualScroll && !this.rowHeight) {
+ let row = this.domHandler.findSingle(this.scrollTable, 'tr.ui-widget-content');
+ if(row) {
+ this.rowHeight = this.domHandler.getOuterHeight(row);
+ }
+ }
+
+ if(!this.frozen) {
+ this.alignScrollBar();
+ }
+ }
+
+ initScrolling() {
+ this.scrollHeader = this.scrollHeaderViewChild.nativeElement;
+ this.scrollHeaderBox = this.scrollHeaderBoxViewChild.nativeElement;
+ this.scrollBody = this.scrollBodyViewChild.nativeElement;
+ this.scrollTable = this.scrollTableViewChild.nativeElement;
+ this.scrollTableWrapper = this.scrollTableWrapperViewChild.nativeElement;
+ this.scrollFooter = this.scrollFooterViewChild ? this.scrollFooterViewChild.nativeElement : null;
+ this.scrollFooterBox = this.scrollFooterBoxViewChild ? this.scrollFooterBoxViewChild.nativeElement : null;
+
+ if(!this.frozen) {
+ let frozenView = this.el.nativeElement.previousElementSibling;
+ if(frozenView) {
+ var frozenScrollBody = this.domHandler.findSingle(frozenView, '.ui-datatable-scrollable-body');
+ }
+
+ this.bodyScrollListener = this.renderer.listen(this.scrollBody, 'scroll', (event) => {
+ this.scrollHeaderBox.style.marginLeft = -1 * this.scrollBody.scrollLeft + 'px';
+ if(this.scrollFooterBox) {
+ this.scrollFooterBox.style.marginLeft = -1 * this.scrollBody.scrollLeft + 'px';
+ }
+ if(frozenScrollBody) {
+ frozenScrollBody.scrollTop = this.scrollBody.scrollTop;
+ }
+
+ if(this.virtualScroll) {
+ clearTimeout(this.scrollTimeout);
+ this.scrollTimeout = setTimeout(() => {
+ let viewport = this.domHandler.getOuterHeight(this.scrollBody);
+ let tableHeight = this.domHandler.getOuterHeight(this.scrollTable);
+ let pageHeight = this.rowHeight * this.dt.rows;
+ let virtualTableHeight = parseFloat(this.virtualTableHeight);
+ let pageCount = (virtualTableHeight / pageHeight)||1;
+
+ if(this.scrollBody.scrollTop + viewport > parseFloat(this.scrollTable.style.top) + tableHeight || this.scrollBody.scrollTop < parseFloat(this.scrollTable.style.top)) {
+ let page = Math.floor((this.scrollBody.scrollTop * pageCount) / (this.scrollBody.scrollHeight)) + 1;
+ this.onVirtualScroll.emit({
+ page: page
+ });
+ this.scrollTable.style.top = ((page - 1) * pageHeight) + 'px';
+ }
+ }, 200);
+ }
+ });
+
+ //to trigger change detection
+ this.scrollBodyMouseWheelListener = this.renderer.listen(this.scrollBody, 'mousewheel', (event) => {});
+
+ this.headerScrollListener = this.renderer.listen(this.scrollHeader, 'scroll', () => {
+ this.scrollHeader.scrollLeft = 0;
+ });
+ }
+
+ if(!this.frozen)
+ this.alignScrollBar();
+ else
+ this.scrollBody.style.paddingBottom = this.domHandler.calculateScrollbarWidth() + 'px';
+ }
+
+ hasVerticalOverflow() {
+ return this.domHandler.getOuterHeight(this.scrollTable) > this.domHandler.getOuterHeight(this.scrollBody);
+ }
+
+ alignScrollBar() {
+ let scrollBarWidth = this.hasVerticalOverflow() ? this.domHandler.calculateScrollbarWidth() : 0;
+
+ this.scrollHeaderBox.style.marginRight = scrollBarWidth + 'px';
+ if(this.scrollFooterBox) {
+ this.scrollFooterBox.style.marginRight = scrollBarWidth + 'px';
+ }
+ }
+
+ get virtualTableHeight(): string {
+ let totalRecords = this.dt.lazy ? this.dt.totalRecords : (this.dt.value ? this.dt.value.length: 0);
+ return (totalRecords * this.rowHeight) + 'px';
+ }
+
+ ngOnDestroy() {
+ if(this.bodyScrollListener) {
+ this.bodyScrollListener();
+ }
+
+ if(this.scrollBodyMouseWheelListener) {
+ this.scrollBodyMouseWheelListener();
+ }
+
+ if(this.headerScrollListener) {
+ this.headerScrollListener();
+ }
+ }
+}
+
+@Component({
+ selector: 'p-dataTable',
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ providers: [DomHandler,ObjectUtils]
+})
+export class DataTable implements AfterViewChecked,AfterViewInit,AfterContentInit,OnInit,OnDestroy,BlockableUI {
+
+ @Input() paginator: boolean;
+
+ @Input() rows: number;
+
+ @Input() totalRecords: number;
+
+ @Input() pageLinks: number = 5;
+
+ @Input() rowsPerPageOptions: number[];
+
+ @Input() responsive: boolean;
+
+ @Input() stacked: boolean;
+
+ @Input() selectionMode: string;
+
+ @Input() selection: any;
+
+ @Output() selectionChange: EventEmitter = new EventEmitter();
+
+ @Input() editable: boolean;
+
+ @Output() onRowClick: EventEmitter = new EventEmitter();
+
+ @Output() onRowSelect: EventEmitter = new EventEmitter();
+
+ @Output() onRowUnselect: EventEmitter = new EventEmitter();
+
+ @Output() onRowDblclick: EventEmitter = new EventEmitter();
+
+ @Output() onHeaderCheckboxToggle: EventEmitter = new EventEmitter();
+
+ @Input() headerCheckboxToggleAllPages: boolean;
+
+ @Output() onContextMenuSelect: EventEmitter = new EventEmitter();
+
+ @Input() filterDelay: number = 300;
+
+ @Input() lazy: boolean;
+
+ @Output() onLazyLoad: EventEmitter = new EventEmitter();
+
+ @Input() resizableColumns: boolean;
+
+ @Input() columnResizeMode: string = 'fit';
+
+ @Output() onColResize: EventEmitter = new EventEmitter();
+
+ @Input() reorderableColumns: boolean;
+
+ @Output() onColReorder: EventEmitter = new EventEmitter();
+
+ @Input() scrollable: boolean;
+
+ @Input() virtualScroll: boolean;
+
+ @Input() scrollHeight: any;
+
+ @Input() scrollWidth: any;
+
+ @Input() frozenWidth: any;
+
+ @Input() unfrozenWidth: any;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() tableStyle: any;
+
+ @Input() tableStyleClass: string;
+
+ @Input() globalFilter: any;
+
+ @Input() sortMode: string = 'single';
+
+ @Input() sortField: string;
+
+ @Input() sortOrder: number = 1;
+
+ @Input() groupField: string;
+
+ @Input() multiSortMeta: SortMeta[];
+
+ @Input() contextMenu: any;
+
+ @Input() csvSeparator: string = ',';
+
+ @Input() exportFilename: string = 'download';
+
+ @Input() emptyMessage: string = 'No records found';
+
+ @Input() paginatorPosition: string = 'bottom';
+
+ @Input() alwaysShowPaginator: boolean = true;
+
+ @Input() metaKeySelection: boolean = true;
+
+ @Input() rowTrackBy: Function = (index: number, item: any) => item;
+
+ @Output() onEditInit: EventEmitter = new EventEmitter();
+
+ @Output() onEditComplete: EventEmitter = new EventEmitter();
+
+ @Output() onEdit: EventEmitter = new EventEmitter();
+
+ @Output() onEditCancel: EventEmitter = new EventEmitter();
+
+ @Output() onPage: EventEmitter = new EventEmitter();
+
+ @Output() onSort: EventEmitter = new EventEmitter();
+
+ @Output() onFilter: EventEmitter = new EventEmitter();
+
+ @ContentChild(Header) header;
+
+ @ContentChild(Footer) footer;
+
+ @Input() expandableRows: boolean;
+
+ @Input() expandedRows: any[];
+
+ @Input() expandableRowGroups: boolean;
+
+ @Input() rowExpandMode: string = 'multiple';
+
+ @Input() public expandedRowsGroups: any[];
+
+ @Input() tabindex: number = 1;
+
+ @Input() rowStyleClass: Function;
+
+ @Input() rowGroupMode: string;
+
+ @Input() sortableRowGroup: boolean = true;
+
+ @Input() sortFile: string;
+
+ @Input() rowHover: boolean;
+
+ @Input() first: number = 0;
+
+ @Input() public filters: {[s: string]: FilterMetadata;} = {};
+
+ @Input() dataKey: string;
+
+ @Input() loading: boolean;
+
+ @Output() onRowExpand: EventEmitter = new EventEmitter();
+
+ @Output() onRowCollapse: EventEmitter = new EventEmitter();
+
+ @Output() onRowGroupExpand: EventEmitter = new EventEmitter();
+
+ @Output() onRowGroupCollapse: EventEmitter = new EventEmitter();
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ @ContentChildren(Column) cols: QueryList;
+
+ @ContentChild(HeaderColumnGroup) headerColumnGroup: HeaderColumnGroup;
+
+ @ContentChild(FooterColumnGroup) footerColumnGroup: FooterColumnGroup;
+
+ public _value: any[];
+
+ public dataToRender: any[];
+
+ public page: number = 0;
+
+ public filterTimeout: any;
+
+ public filteredValue: any[];
+
+ public columns: Column[];
+
+ public frozenColumns: Column[];
+
+ public scrollableColumns: Column[];
+
+ public columnsChanged: boolean = false;
+
+ public sortColumn: Column;
+
+ public columnResizing: boolean;
+
+ public lastResizerHelperX: number;
+
+ public documentClickListener: Function;
+
+ public documentColumnResizeListener: Function;
+
+ public documentColumnResizeEndListener: Function;
+
+ public resizerHelper: any;
+
+ public resizeColumn: any;
+
+ public reorderIndicatorUp: any;
+
+ public reorderIndicatorDown: any;
+
+ public draggedColumn: any;
+
+ public dropPosition: number;
+
+ public tbody: any;
+
+ public rowTouched: boolean;
+
+ public rowGroupToggleClick: boolean;
+
+ public editingCell: any;
+
+ public stopFilterPropagation: boolean;
+
+ public rowGroupMetadata: any;
+
+ public rowGroupHeaderTemplate: TemplateRef;
+
+ public rowGroupFooterTemplate: TemplateRef;
+
+ public rowExpansionTemplate: TemplateRef;
+
+ public scrollBarWidth: number;
+
+ public editorClick: boolean;
+
+ globalFilterFunction: any;
+
+ columnsSubscription: Subscription;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler,
+ public renderer: Renderer2, public changeDetector: ChangeDetectorRef, public objectUtils: ObjectUtils) {
+ }
+
+ ngOnInit() {
+ if(this.lazy) {
+ this.onLazyLoad.emit(this.createLazyLoadMetadata());
+ }
+ }
+
+ ngAfterContentInit() {
+ this.initColumns();
+
+ this.columnsSubscription = this.cols.changes.subscribe(_ => {
+ this.initColumns();
+ this.changeDetector.markForCheck();
+ });
+
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'rowexpansion':
+ this.rowExpansionTemplate = item.template;
+ break;
+
+ case 'rowgroupheader':
+ this.rowGroupHeaderTemplate = item.template;
+ break;
+
+ case 'rowgroupfooter':
+ this.rowGroupFooterTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ ngAfterViewChecked() {
+ if(this.columnsChanged && this.el.nativeElement.offsetParent) {
+ if(this.resizableColumns) {
+ this.initResizableColumns();
+ }
+
+ if(this.reorderableColumns) {
+ this.initColumnReordering();
+ }
+
+ this.columnsChanged = false;
+ }
+ }
+
+ ngAfterViewInit() {
+ if(this.globalFilter) {
+ this.globalFilterFunction = this.renderer.listen(this.globalFilter, 'keyup', () => {
+ this.filterTimeout = setTimeout(() => {
+ this._filter();
+ this.filterTimeout = null;
+ }, this.filterDelay);
+ });
+ }
+
+ if(this.editable) {
+ this.documentClickListener = this.renderer.listen('document', 'click', (event) => {
+ if(!this.editorClick) {
+ this.closeCell();
+ }
+ this.editorClick = false;
+ });
+ }
+ }
+
+ @Input() get value(): any[] {
+ return this._value;
+ }
+
+ set value(val:any[]) {
+ this._value = val ? [...val] : null;
+ this.handleDataChange();
+ }
+
+ handleDataChange() {
+ if(this.paginator) {
+ this.updatePaginator();
+ }
+
+ if(!this.lazy) {
+ if(this.hasFilter()) {
+ this._filter();
+ }
+
+ if(this.sortField||this.multiSortMeta) {
+ if(!this.sortColumn && this.columns) {
+ this.sortColumn = this.columns.find(col => col.field === this.sortField && col.sortable === 'custom');
+ }
+
+ if(this.sortMode == 'single')
+ this.sortSingle();
+ else if(this.sortMode == 'multiple')
+ this.sortMultiple();
+ }
+ }
+
+ this.updateDataToRender(this.filteredValue||this.value);
+ }
+
+ initColumns(): void {
+ this.columns = this.cols.toArray();
+
+ if(this.scrollable) {
+ this.scrollableColumns = [];
+ this.cols.forEach((col) => {
+ if(col.frozen) {
+ this.frozenColumns = this.frozenColumns||[];
+ this.frozenColumns.push(col);
+ }
+ else {
+ this.scrollableColumns.push(col);
+ }
+ });
+ }
+
+ this.columnsChanged = true;
+ }
+
+ resolveFieldData(data: any, field: string): any {
+ if(data && field) {
+ if(field.indexOf('.') == -1) {
+ return data[field];
+ }
+ else {
+ let fields: string[] = field.split('.');
+ let value = data;
+ for(var i = 0, len = fields.length; i < len; ++i) {
+ if (value == null) {
+ return null;
+ }
+ value = value[fields[i]];
+ }
+ return value;
+ }
+ }
+ else {
+ return null;
+ }
+ }
+
+ updateRowGroupMetadata() {
+ this.rowGroupMetadata = {};
+ if(this.dataToRender) {
+ for(let i = 0; i < this.dataToRender.length; i++) {
+ let rowData = this.dataToRender[i];
+ let group = this.resolveFieldData(rowData, this.sortField);
+ if(i == 0) {
+ this.rowGroupMetadata[group] = {index:0, size: 1};
+ }
+ else {
+ let previousRowData = this.dataToRender[i-1];
+ let previousRowGroup = this.resolveFieldData(previousRowData, this.sortField);
+ if(group === previousRowGroup) {
+ this.rowGroupMetadata[group].size++;
+ }
+ else {
+ this.rowGroupMetadata[group] = {index:i, size: 1};
+ }
+ }
+ }
+ }
+ }
+
+ updatePaginator() {
+ //total records
+ this.totalRecords = this.lazy ? this.totalRecords : (this.value ? this.value.length: 0);
+
+ //first
+ if(this.totalRecords && this.first >= this.totalRecords) {
+ let numberOfPages = Math.ceil(this.totalRecords/this.rows);
+ this.first = Math.max((numberOfPages-1) * this.rows, 0);
+ }
+ }
+
+ paginate(event) {
+ this.first = event.first;
+ this.rows = event.rows;
+
+ if(this.lazy) {
+ this.stopFilterPropagation = true;
+ this.onLazyLoad.emit(this.createLazyLoadMetadata());
+ }
+ else {
+ this.updateDataToRender(this.filteredValue||this.value);
+ }
+
+ this.onPage.emit({
+ first: this.first,
+ rows: this.rows
+ });
+ }
+
+ updateDataToRender(datasource) {
+ if((this.paginator || this.virtualScroll) && datasource) {
+ this.dataToRender = [];
+ let startIndex: number = this.lazy ? 0 : this.first;
+ let endIndex: number = this.virtualScroll ? this.first + this.rows * 2 : startIndex + this.rows;
+
+ for(let i = startIndex; i < endIndex; i++) {
+ if(i >= datasource.length) {
+ break;
+ }
+
+ this.dataToRender.push(datasource[i]);
+ }
+ }
+ else {
+ this.dataToRender = datasource;
+ }
+
+ if(this.rowGroupMode) {
+ this.updateRowGroupMetadata();
+ }
+ }
+
+ onVirtualScroll(event) {
+ this.first = (event.page - 1) * this.rows;
+
+ if(this.lazy) {
+ this.stopFilterPropagation = true;
+ this.onLazyLoad.emit(this.createLazyLoadMetadata());
+ }
+ else {
+ this.updateDataToRender(this.filteredValue||this.value);
+ }
+ }
+
+ onHeaderKeydown(event, column: Column) {
+ if(event.keyCode == 13) {
+ this.sort(event, column);
+ event.preventDefault();
+ }
+ }
+
+ onHeaderMousedown(event, header: any) {
+ if(this.reorderableColumns) {
+ if(event.target.nodeName !== 'INPUT') {
+ header.draggable = true;
+ } else if(event.target.nodeName === 'INPUT') {
+ header.draggable = false;
+ }
+ }
+ }
+
+ sort(event, column: Column) {
+ if(!column.sortable) {
+ return;
+ }
+
+ let targetNode = event.target.nodeName;
+ if(targetNode == 'TH' || (targetNode == 'SPAN' && !this.domHandler.hasClass(event.target, 'ui-c'))) {
+ let columnSortField = column.sortField||column.field;
+ this.sortOrder = (this.sortField === columnSortField) ? this.sortOrder * -1 : 1;
+ this.sortField = columnSortField;
+ this.sortColumn = column;
+ let metaKey = event.metaKey||event.ctrlKey;
+
+ if(this.sortMode == 'multiple') {
+ if(!this.multiSortMeta||!metaKey) {
+ this.multiSortMeta = [];
+ }
+
+ this.addSortMeta({field: this.sortField, order: this.sortOrder});
+ }
+
+ if(this.lazy) {
+ this.first = 0;
+ this.stopFilterPropagation = true;
+ this.onLazyLoad.emit(this.createLazyLoadMetadata());
+ }
+ else {
+ if(this.sortMode == 'multiple')
+ this.sortMultiple();
+ else
+ this.sortSingle();
+ }
+
+ this.onSort.emit({
+ field: this.sortField,
+ order: this.sortOrder,
+ multisortmeta: this.multiSortMeta
+ });
+ }
+
+ this.updateDataToRender(this.filteredValue||this.value);
+ }
+
+ sortSingle() {
+ if(this.value) {
+ if(this.sortColumn && this.sortColumn.sortable === 'custom') {
+ this.sortColumn.sortFunction.emit({
+ field: this.sortField,
+ order: this.sortOrder
+ });
+ }
+ else {
+ this.value.sort((data1, data2) => {
+ let value1 = this.resolveFieldData(data1, this.sortField);
+ let value2 = this.resolveFieldData(data2, this.sortField);
+ let result = null;
+
+ if (value1 == null && value2 != null)
+ result = -1;
+ else if (value1 != null && value2 == null)
+ result = 1;
+ else if (value1 == null && value2 == null)
+ result = 0;
+ else if (typeof value1 === 'string' && typeof value2 === 'string')
+ result = value1.localeCompare(value2);
+ else
+ result = (value1 < value2) ? -1 : (value1 > value2) ? 1 : 0;
+
+ return (this.sortOrder * result);
+ });
+ }
+
+ this.first = 0;
+
+ if(this.hasFilter()) {
+ this._filter();
+ }
+ }
+ }
+
+ sortMultiple() {
+ if(this.value) {
+ this.value.sort((data1,data2) => {
+ return this.multisortField(data1, data2, this.multiSortMeta, 0);
+ });
+
+ if(this.hasFilter()) {
+ this._filter();
+ }
+ }
+ }
+
+ multisortField(data1,data2,multiSortMeta,index) {
+ let value1 = this.resolveFieldData(data1, multiSortMeta[index].field);
+ let value2 = this.resolveFieldData(data2, multiSortMeta[index].field);
+ let result = null;
+
+ if (typeof value1 == 'string' || value1 instanceof String) {
+ if (value1.localeCompare && (value1 != value2)) {
+ return (multiSortMeta[index].order * value1.localeCompare(value2));
+ }
+ }
+ else {
+ result = (value1 < value2) ? -1 : 1;
+ }
+
+ if(value1 == value2) {
+ return (multiSortMeta.length - 1) > (index) ? (this.multisortField(data1, data2, multiSortMeta, index + 1)) : 0;
+ }
+
+ return (multiSortMeta[index].order * result);
+ }
+
+ addSortMeta(meta) {
+ var index = -1;
+ for(var i = 0; i < this.multiSortMeta.length; i++) {
+ if(this.multiSortMeta[i].field === meta.field) {
+ index = i;
+ break;
+ }
+ }
+
+ if(index >= 0)
+ this.multiSortMeta[index] = meta;
+ else
+ this.multiSortMeta.push(meta);
+ }
+
+ isSorted(column: Column) {
+ if(!column.sortable) {
+ return false;
+ }
+
+ let columnSortField = column.sortField||column.field;
+
+ if(this.sortMode === 'single') {
+ return (this.sortField && columnSortField === this.sortField);
+ }
+ else if(this.sortMode === 'multiple') {
+ let sorted = false;
+ if(this.multiSortMeta) {
+ for(let i = 0; i < this.multiSortMeta.length; i++) {
+ if(this.multiSortMeta[i].field == columnSortField) {
+ sorted = true;
+ break;
+ }
+ }
+ }
+ return sorted;
+ }
+ }
+
+ getSortOrder(column: Column) {
+ let order = 0;
+ let columnSortField = column.sortField||column.field;
+
+ if(this.sortMode === 'single') {
+ if(this.sortField && columnSortField === this.sortField) {
+ order = this.sortOrder;
+ }
+ }
+ else if(this.sortMode === 'multiple') {
+ if(this.multiSortMeta) {
+ for(let i = 0; i < this.multiSortMeta.length; i++) {
+ if(this.multiSortMeta[i].field == columnSortField) {
+ order = this.multiSortMeta[i].order;
+ break;
+ }
+ }
+ }
+ }
+ return order;
+ }
+
+ onRowGroupClick(event) {
+ if(this.rowGroupToggleClick) {
+ this.rowGroupToggleClick = false;
+ return;
+ }
+
+ if(this.sortableRowGroup) {
+ let targetNode = event.target.nodeName;
+ if((targetNode == 'TD' || (targetNode == 'SPAN' && !this.domHandler.hasClass(event.target, 'ui-c')))) {
+ if(this.sortField != this.groupField) {
+ this.sortField = this.groupField;
+ this.sortSingle();
+ }
+ else {
+ this.sortOrder = -1 * this.sortOrder;
+ this.sortSingle();
+ }
+ }
+ }
+ }
+
+ handleRowClick(event, rowData) {
+ let targetNode = event.target.nodeName;
+ if(targetNode == 'TD' || (targetNode == 'SPAN' && !this.domHandler.hasClass(event.target, 'ui-c'))) {
+ this.onRowClick.next({originalEvent: event, data: rowData});
+
+ if(!this.selectionMode) {
+ return;
+ }
+
+ let selected = this.isSelected(rowData);
+ let metaSelection = this.rowTouched ? false : this.metaKeySelection;
+
+ if(metaSelection) {
+ let metaKey = event.metaKey||event.ctrlKey;
+
+ if(selected && metaKey) {
+ if(this.isSingleSelectionMode()) {
+ this.selection = null;
+ this.selectionChange.emit(null);
+ }
+ else {
+ let selectionIndex = this.findIndexInSelection(rowData);
+ this.selection = this.selection.filter((val,i) => i!=selectionIndex);
+ this.selectionChange.emit(this.selection);
+ }
+
+ this.onRowUnselect.emit({originalEvent: event, data: rowData, type: 'row'});
+ }
+ else {
+ if(this.isSingleSelectionMode()) {
+ this.selection = rowData;
+ this.selectionChange.emit(rowData);
+ }
+ else if(this.isMultipleSelectionMode()) {
+ if(metaKey)
+ this.selection = this.selection||[];
+ else
+ this.selection = [];
+
+ this.selection = [...this.selection,rowData];
+ this.selectionChange.emit(this.selection);
+ }
+
+ this.onRowSelect.emit({originalEvent: event, data: rowData, type: 'row'});
+ }
+ }
+ else {
+ if(this.isSingleSelectionMode()) {
+ if(selected) {
+ this.selection = null;
+ this.onRowUnselect.emit({originalEvent: event, data: rowData, type: 'row'});
+ }
+ else {
+ this.selection = rowData;
+ this.onRowSelect.emit({originalEvent: event, data: rowData, type: 'row'});
+ }
+ }
+ else {
+ if(selected) {
+ let selectionIndex = this.findIndexInSelection(rowData);
+ this.selection = this.selection.filter((val,i) => i!=selectionIndex);
+ this.onRowUnselect.emit({originalEvent: event, data: rowData, type: 'row'});
+ }
+ else {
+ this.selection = [...this.selection||[],rowData];
+ this.onRowSelect.emit({originalEvent: event, data: rowData, type: 'row'});
+ }
+ }
+
+ this.selectionChange.emit(this.selection);
+ }
+ }
+
+ this.rowTouched = false;
+ }
+
+ handleRowTouchEnd(event) {
+ this.rowTouched = true;
+ }
+
+ selectRowWithRadio(event, rowData:any) {
+ if(this.selection != rowData) {
+ this.selection = rowData;
+ this.selectionChange.emit(this.selection);
+ this.onRowSelect.emit({originalEvent: event, data: rowData, type: 'radiobutton'});
+ }
+ }
+
+ toggleRowWithCheckbox(event,rowData) {
+ let selectionIndex = this.findIndexInSelection(rowData);
+ this.selection = this.selection||[];
+
+ if(selectionIndex != -1) {
+ this.selection = this.selection.filter((val,i) => i!=selectionIndex);
+ this.onRowUnselect.emit({originalEvent: event, data: rowData, type: 'checkbox'});
+ }
+
+ else {
+ this.selection = [...this.selection,rowData];
+ this.onRowSelect.emit({originalEvent: event, data: rowData, type: 'checkbox'});
+ }
+
+ this.selectionChange.emit(this.selection);
+ }
+
+ toggleRowsWithCheckbox(event) {
+ if(event.checked)
+ this.selection = this.headerCheckboxToggleAllPages ? this.value.slice() : this.dataToRender.slice();
+ else
+ this.selection = [];
+
+ this.selectionChange.emit(this.selection);
+
+ this.onHeaderCheckboxToggle.emit({originalEvent: event, checked: event.checked});
+ }
+
+ onRowRightClick(event, rowData) {
+ if(this.contextMenu) {
+ let selectionIndex = this.findIndexInSelection(rowData);
+ let selected = selectionIndex != -1;
+
+ if(!selected) {
+ if(this.isSingleSelectionMode()) {
+ this.selection = rowData;
+ this.selectionChange.emit(rowData);
+ }
+ else if(this.isMultipleSelectionMode()) {
+ this.selection = [rowData];
+ this.selectionChange.emit(this.selection);
+ }
+ }
+
+ this.contextMenu.show(event);
+ this.onContextMenuSelect.emit({originalEvent: event, data: rowData});
+ }
+ }
+
+ rowDblclick(event, rowData) {
+ this.onRowDblclick.emit({originalEvent: event, data: rowData});
+ }
+
+ isSingleSelectionMode() {
+ return this.selectionMode === 'single';
+ }
+
+ isMultipleSelectionMode() {
+ return this.selectionMode === 'multiple';
+ }
+
+ findIndexInSelection(rowData: any) {
+ let index: number = -1;
+ if(this.selection) {
+ for(let i = 0; i < this.selection.length; i++) {
+ if(this.objectUtils.equals(rowData, this.selection[i], this.dataKey)) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ return index;
+ }
+
+ isSelected(rowData) {
+ return ((rowData && this.objectUtils.equals(rowData, this.selection, this.dataKey)) || this.findIndexInSelection(rowData) != -1);
+ }
+
+ get allSelected() {
+ if(this.headerCheckboxToggleAllPages) {
+ return this.selection && this.value && this.selection.length === this.value.length;
+ }
+ else {
+ let val = true;
+ if(this.dataToRender && this.selection && (this.dataToRender.length <= this.selection.length)) {
+ for(let data of this.dataToRender) {
+ if(!this.isSelected(data)) {
+ val = false;
+ break;
+ }
+ }
+ }
+ else {
+ val = false;
+ }
+ return val;
+ }
+ }
+
+ onFilterKeyup(value, field, matchMode) {
+ if(this.filterTimeout) {
+ clearTimeout(this.filterTimeout);
+ }
+
+ this.filterTimeout = setTimeout(() => {
+ this.filter(value, field, matchMode);
+ this.filterTimeout = null;
+ }, this.filterDelay);
+ }
+
+ filter(value, field, matchMode) {
+ if(!this.isFilterBlank(value))
+ this.filters[field] = {value: value, matchMode: matchMode};
+ else if(this.filters[field])
+ delete this.filters[field];
+
+ this._filter();
+ }
+
+ isFilterBlank(filter: any): boolean {
+ if(filter !== null && filter !== undefined) {
+ if((typeof filter === 'string' && filter.trim().length == 0) || (filter instanceof Array && filter.length == 0))
+ return true;
+ else
+ return false;
+ }
+ return true;
+ }
+
+ _filter() {
+ this.first = 0;
+
+ if(this.lazy) {
+ this.stopFilterPropagation = true;
+ this.onLazyLoad.emit(this.createLazyLoadMetadata());
+ }
+ else {
+ this.filteredValue = [];
+
+ for(let i = 0; i < this.value.length; i++) {
+ let localMatch = true;
+ let globalMatch = false;
+
+ for(let j = 0; j < this.columns.length; j++) {
+ let col = this.columns[j],
+ filterMeta = this.filters[col.field];
+
+ //local
+ if(filterMeta) {
+ let filterValue = filterMeta.value,
+ filterField = col.field,
+ filterMatchMode = filterMeta.matchMode||'startsWith',
+ dataFieldValue = this.resolveFieldData(this.value[i], filterField);
+ let filterConstraint = this.filterConstraints[filterMatchMode];
+
+ if(!filterConstraint(dataFieldValue, filterValue)) {
+ localMatch = false;
+ }
+
+ if(!localMatch) {
+ break;
+ }
+ }
+
+ //global
+ if(this.globalFilter && !globalMatch) {
+ globalMatch = this.filterConstraints['contains'](this.resolveFieldData(this.value[i], col.field), this.globalFilter.value);
+ }
+ }
+
+ let matches = localMatch;
+ if(this.globalFilter) {
+ matches = localMatch&&globalMatch;
+ }
+
+ if(matches) {
+ this.filteredValue.push(this.value[i]);
+ }
+ }
+
+ if(this.filteredValue.length === this.value.length) {
+ this.filteredValue = null;
+ }
+
+ if(this.paginator) {
+ this.totalRecords = this.filteredValue ? this.filteredValue.length: this.value ? this.value.length: 0;
+ }
+
+ this.updateDataToRender(this.filteredValue||this.value);
+ }
+
+ this.onFilter.emit({
+ filters: this.filters,
+ filteredValue: this.filteredValue||this.value
+ });
+ }
+
+ hasFilter() {
+ let empty = true;
+ for(let prop in this.filters) {
+ if(this.filters.hasOwnProperty(prop)) {
+ empty = false;
+ break;
+ }
+ }
+
+ return !empty || (this.globalFilter && this.globalFilter.value && this.globalFilter.value.trim().length);
+ }
+
+ onFilterInputClick(event) {
+ event.stopPropagation();
+ }
+
+ filterConstraints = {
+
+ startsWith(value, filter): boolean {
+ if(filter === undefined || filter === null || filter.trim() === '') {
+ return true;
+ }
+
+ if(value === undefined || value === null) {
+ return false;
+ }
+
+ let filterValue = filter.toLowerCase();
+ return value.toString().toLowerCase().slice(0, filterValue.length) === filterValue;
+ },
+
+ contains(value, filter): boolean {
+ if(filter === undefined || filter === null || (typeof filter === 'string' && filter.trim() === '')) {
+ return true;
+ }
+
+ if(value === undefined || value === null) {
+ return false;
+ }
+
+ return value.toString().toLowerCase().indexOf(filter.toLowerCase()) !== -1;
+ },
+
+ endsWith(value, filter): boolean {
+ if(filter === undefined || filter === null || filter.trim() === '') {
+ return true;
+ }
+
+ if(value === undefined || value === null) {
+ return false;
+ }
+
+ let filterValue = filter.toString().toLowerCase();
+ return value.toString().toLowerCase().indexOf(filterValue, value.toString().length - filterValue.length) !== -1;
+ },
+
+ equals(value, filter): boolean {
+ if(filter === undefined || filter === null || (typeof filter === 'string' && filter.trim() === '')) {
+ return true;
+ }
+
+ if(value === undefined || value === null) {
+ return false;
+ }
+
+ return value.toString().toLowerCase() == filter.toString().toLowerCase();
+ },
+
+ notEquals(value, filter): boolean {
+ return !this.equals(value, filter);
+ },
+
+ in(value, filter: any[]): boolean {
+ if(filter === undefined || filter === null || filter.length === 0) {
+ return true;
+ }
+
+ if(value === undefined || value === null) {
+ return false;
+ }
+
+ for(let i = 0; i < filter.length; i++) {
+ if(filter[i] === value)
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ switchCellToEditMode(cell: any, column: Column, rowData: any) {
+ if(!this.selectionMode && this.editable && column.editable) {
+ this.editorClick = true;
+
+ if(cell != this.editingCell) {
+ if(this.editingCell && this.domHandler.find(this.editingCell, '.ng-invalid.ng-dirty').length == 0) {
+ this.domHandler.removeClass(this.editingCell, 'ui-cell-editing');
+ }
+
+ this.editingCell = cell;
+ this.onEditInit.emit({column: column, data: rowData});
+ this.domHandler.addClass(cell, 'ui-cell-editing');
+ let focusable = this.domHandler.findSingle(cell, '.ui-cell-editor input');
+ if(focusable) {
+ setTimeout(() => this.domHandler.invokeElementMethod(focusable, 'focus'), 50);
+ }
+ }
+ }
+ }
+
+ switchCellToViewMode(element: any) {
+ this.editingCell = null;
+ let cell = this.findCell(element);
+ this.domHandler.removeClass(cell, 'ui-cell-editing');
+ }
+
+ closeCell() {
+ if(this.editingCell) {
+ this.domHandler.removeClass(this.editingCell, 'ui-cell-editing');
+ this.editingCell = null;
+ }
+ }
+
+ onCellEditorKeydown(event, column: Column, rowData: any, rowIndex: number) {
+ if(this.editable) {
+ this.onEdit.emit({originalEvent: event, column: column, data: rowData, index: rowIndex});
+
+ //enter
+ if(event.keyCode == 13) {
+ this.onEditComplete.emit({column: column, data: rowData, index: rowIndex});
+ this.domHandler.invokeElementMethod(event.target, 'blur');
+ this.switchCellToViewMode(event.target);
+ event.preventDefault();
+ }
+
+ //escape
+ else if(event.keyCode == 27) {
+ this.onEditCancel.emit({column: column, data: rowData, index: rowIndex});
+ this.domHandler.invokeElementMethod(event.target, 'blur');
+ this.switchCellToViewMode(event.target);
+ event.preventDefault();
+ }
+
+ //tab
+ else if(event.keyCode == 9) {
+ this.onEditComplete.emit({column: column, data: rowData, index: rowIndex});
+
+ if(event.shiftKey)
+ this.moveToPreviousCell(event);
+ else
+ this.moveToNextCell(event);
+ }
+ }
+ }
+
+ moveToPreviousCell(event: KeyboardEvent) {
+ let currentCell = this.findCell(event.target);
+ let row = currentCell.parentElement;
+ let targetCell = this.findPreviousEditableColumn(currentCell);
+
+ if(targetCell) {
+ this.domHandler.invokeElementMethod(targetCell, 'click');
+ event.preventDefault();
+ }
+ }
+
+ moveToNextCell(event: KeyboardEvent) {
+ let currentCell = this.findCell(event.target);
+ let row = currentCell.parentElement;
+ let targetCell = this.findNextEditableColumn(currentCell);
+
+ if(targetCell) {
+ this.domHandler.invokeElementMethod(targetCell, 'click');
+ event.preventDefault();
+ }
+ }
+
+ findPreviousEditableColumn(cell: Element) {
+ let prevCell = cell.previousElementSibling;
+
+ if(!prevCell) {
+ let previousRow = cell.parentElement.previousElementSibling;
+ if(previousRow) {
+ prevCell = previousRow.lastElementChild;
+ }
+ }
+
+ if(prevCell) {
+ if(this.domHandler.hasClass(prevCell, 'ui-editable-column'))
+ return prevCell;
+ else
+ return this.findPreviousEditableColumn(prevCell);
+ }
+ else {
+ return null;
+ }
+ }
+
+ findNextEditableColumn(cell: Element) {
+ let nextCell = cell.nextElementSibling;
+
+ if(!nextCell) {
+ let nextRow = cell.parentElement.nextElementSibling;
+ if(nextRow) {
+ nextCell = nextRow.firstElementChild;
+ }
+ }
+
+ if(nextCell) {
+ if(this.domHandler.hasClass(nextCell, 'ui-editable-column'))
+ return nextCell;
+ else
+ return this.findNextEditableColumn(nextCell);
+ }
+ else {
+ return null;
+ }
+ }
+
+ onCustomEditorFocusPrev(event: KeyboardEvent) {
+ this.moveToPreviousCell(event);
+ }
+
+ onCustomEditorFocusNext(event: KeyboardEvent) {
+ this.moveToNextCell(event);
+ }
+
+ findCell(element) {
+ let cell = element;
+ while(cell.tagName != 'TD') {
+ cell = cell.parentElement;
+ }
+
+ return cell;
+ }
+
+ initResizableColumns() {
+ this.tbody = this.domHandler.findSingle(this.el.nativeElement, 'tbody.ui-datatable-data');
+ this.resizerHelper = this.domHandler.findSingle(this.el.nativeElement, 'div.ui-column-resizer-helper');
+ this.fixColumnWidths();
+
+ this.documentColumnResizeListener = this.renderer.listen('document', 'mousemove', (event) => {
+ if(this.columnResizing) {
+ this.onColumnResize(event);
+ }
+ });
+
+ this.documentColumnResizeEndListener = this.renderer.listen('document', 'mouseup', (event) => {
+ if(this.columnResizing) {
+ this.columnResizing = false;
+ this.onColumnResizeEnd(event);
+ }
+ });
+ }
+
+ initColumnResize(event) {
+ let container = this.el.nativeElement.children[0];
+ let containerLeft = this.domHandler.getOffset(container).left;
+ this.resizeColumn = event.target.parentElement;
+ this.columnResizing = true;
+ this.lastResizerHelperX = (event.pageX - containerLeft);
+ }
+
+ onColumnResize(event) {
+ let container = this.el.nativeElement.children[0];
+ let containerLeft = this.domHandler.getOffset(container).left;
+ this.domHandler.addClass(container, 'ui-unselectable-text');
+ this.resizerHelper.style.height = container.offsetHeight + 'px';
+ this.resizerHelper.style.top = 0 + 'px';
+ if(event.pageX > containerLeft && event.pageX < (containerLeft + container.offsetWidth)) {
+ this.resizerHelper.style.left = (event.pageX - containerLeft) + 'px';
+ }
+
+ this.resizerHelper.style.display = 'block';
+ }
+
+ onColumnResizeEnd(event) {
+ let delta = this.resizerHelper.offsetLeft - this.lastResizerHelperX;
+ let columnWidth = this.resizeColumn.offsetWidth;
+ let newColumnWidth = columnWidth + delta;
+ let minWidth = this.resizeColumn.style.minWidth||15;
+
+ if(columnWidth + delta > parseInt(minWidth)) {
+ if(this.columnResizeMode === 'fit') {
+ let nextColumn = this.resizeColumn.nextElementSibling;
+ let nextColumnWidth = nextColumn.offsetWidth - delta;
+
+ if(newColumnWidth > 15 && nextColumnWidth > 15) {
+ this.resizeColumn.style.width = newColumnWidth + 'px';
+ if(nextColumn) {
+ nextColumn.style.width = nextColumnWidth + 'px';
+ }
+
+
+ if(this.scrollable) {
+ let colGroup = this.domHandler.findSingle(this.el.nativeElement, 'colgroup.ui-datatable-scrollable-colgroup');
+ let resizeColumnIndex = this.domHandler.index(this.resizeColumn);
+ colGroup.children[resizeColumnIndex].style.width = newColumnWidth + 'px';
+
+ if(nextColumn) {
+ colGroup.children[resizeColumnIndex + 1].style.width = nextColumnWidth + 'px';
+ }
+ }
+ }
+ }
+ else if(this.columnResizeMode === 'expand') {
+ this.tbody.parentElement.style.width = this.tbody.parentElement.offsetWidth + delta + 'px';
+ this.resizeColumn.style.width = newColumnWidth + 'px';
+ let containerWidth = this.tbody.parentElement.style.width;
+
+ if(this.scrollable) {
+ this.scrollBarWidth = this.scrollBarWidth||this.domHandler.calculateScrollbarWidth();
+ this.el.nativeElement.children[0].style.width = parseFloat(containerWidth) + this.scrollBarWidth + 'px';
+ let colGroup = this.domHandler.findSingle(this.el.nativeElement, 'colgroup.ui-datatable-scrollable-colgroup');
+ let resizeColumnIndex = this.domHandler.index(this.resizeColumn);
+ colGroup.children[resizeColumnIndex].style.width = newColumnWidth + 'px';
+ }
+ else {
+ this.el.nativeElement.children[0].style.width = containerWidth;
+ }
+ }
+
+ this.onColResize.emit({
+ element: this.resizeColumn,
+ delta: delta
+ });
+ }
+
+ this.resizerHelper.style.display = 'none';
+ this.resizeColumn = null;
+ this.domHandler.removeClass(this.el.nativeElement.children[0], 'ui-unselectable-text');
+ }
+
+ fixColumnWidths() {
+ let columns = this.domHandler.find(this.el.nativeElement, 'th.ui-resizable-column');
+ let bodyCols;
+
+ for(let i = 0; i < columns.length; i++) {
+ columns[i].style.width = columns[i].offsetWidth + 'px';
+ }
+
+ if(this.scrollable) {
+ let colGroup = this.domHandler.findSingle(this.el.nativeElement, 'colgroup.ui-datatable-scrollable-colgroup');
+ bodyCols = colGroup.children;
+
+ if(bodyCols) {
+ for(let i = 0; i < columns.length; i++) {
+ bodyCols[i].style.width = columns[i].offsetWidth + 'px';
+ }
+ }
+ }
+ }
+
+ onColumnDragStart(event) {
+ if (this.columnResizing) {
+ event.preventDefault();
+ return;
+ }
+
+ this.draggedColumn = this.findParentHeader(event.target);
+ event.dataTransfer.setData('text', 'b'); // Firefox requires this to make dragging possible
+ }
+
+ onColumnDragover(event) {
+ if(this.reorderableColumns && this.draggedColumn) {
+ event.preventDefault();
+ let iconWidth = this.domHandler.getHiddenElementOuterWidth(this.reorderIndicatorUp);
+ let iconHeight = this.domHandler.getHiddenElementOuterHeight(this.reorderIndicatorUp);
+ let dropHeader = this.findParentHeader(event.target);
+ let container = this.el.nativeElement.children[0];
+ let containerOffset = this.domHandler.getOffset(container);
+ let dropHeaderOffset = this.domHandler.getOffset(dropHeader);
+
+ if(this.draggedColumn != dropHeader) {
+ let targetLeft = dropHeaderOffset.left - containerOffset.left;
+ let targetTop = containerOffset.top - dropHeaderOffset.top;
+ let columnCenter = dropHeaderOffset.left + dropHeader.offsetWidth / 2;
+
+ this.reorderIndicatorUp.style.top = dropHeaderOffset.top - containerOffset.top - (iconHeight - 1) + 'px';
+ this.reorderIndicatorDown.style.top = dropHeaderOffset.top - containerOffset.top + dropHeader.offsetHeight + 'px';
+
+ if(event.pageX > columnCenter) {
+ this.reorderIndicatorUp.style.left = (targetLeft + dropHeader.offsetWidth - Math.ceil(iconWidth / 2)) + 'px';
+ this.reorderIndicatorDown.style.left = (targetLeft + dropHeader.offsetWidth - Math.ceil(iconWidth / 2))+ 'px';
+ this.dropPosition = 1;
+ }
+ else {
+ this.reorderIndicatorUp.style.left = (targetLeft - Math.ceil(iconWidth / 2)) + 'px';
+ this.reorderIndicatorDown.style.left = (targetLeft - Math.ceil(iconWidth / 2))+ 'px';
+ this.dropPosition = -1;
+ }
+
+ this.reorderIndicatorUp.style.display = 'block';
+ this.reorderIndicatorDown.style.display = 'block';
+ }
+ else {
+ event.dataTransfer.dropEffect = 'none';
+ }
+ }
+ }
+
+ onColumnDragleave(event) {
+ if(this.reorderableColumns && this.draggedColumn) {
+ event.preventDefault();
+ this.reorderIndicatorUp.style.display = 'none';
+ this.reorderIndicatorDown.style.display = 'none';
+ }
+ }
+
+ onColumnDrop(event) {
+ event.preventDefault();
+ if(this.draggedColumn) {
+ let dragIndex = this.domHandler.index(this.draggedColumn);
+ let dropIndex = this.domHandler.index(this.findParentHeader(event.target));
+ let allowDrop = (dragIndex != dropIndex);
+ if(allowDrop && ((dropIndex - dragIndex == 1 && this.dropPosition === -1) || (dragIndex - dropIndex == 1 && this.dropPosition === 1))) {
+ allowDrop = false;
+ }
+
+ if(allowDrop) {
+ this.columns.splice(dropIndex, 0, this.columns.splice(dragIndex, 1)[0]);
+
+ this.onColReorder.emit({
+ dragIndex: dragIndex,
+ dropIndex: dropIndex,
+ columns: this.columns
+ });
+ }
+
+ this.reorderIndicatorUp.style.display = 'none';
+ this.reorderIndicatorDown.style.display = 'none';
+ this.draggedColumn.draggable = false;
+ this.draggedColumn = null;
+ this.dropPosition = null;
+ }
+ }
+
+ initColumnReordering() {
+ this.reorderIndicatorUp = this.domHandler.findSingle(this.el.nativeElement.children[0], 'span.ui-datatable-reorder-indicator-up');
+ this.reorderIndicatorDown = this.domHandler.findSingle(this.el.nativeElement.children[0], 'span.ui-datatable-reorder-indicator-down');
+ }
+
+ findParentHeader(element) {
+ if(element.nodeName == 'TH') {
+ return element;
+ }
+ else {
+ let parent = element.parentElement;
+ while(parent.nodeName != 'TH') {
+ parent = parent.parentElement;
+ }
+ return parent;
+ }
+ }
+
+ hasFooter() {
+ if(this.footerColumnGroup) {
+ return true;
+ }
+ else {
+ if(this.columns) {
+ for(let i = 0; i < this.columns.length; i++) {
+ if(this.columns[i].footer) {
+ return true;
+ }
+ }
+ }
+
+ }
+ return false;
+ }
+
+ isEmpty() {
+ return !this.dataToRender||(this.dataToRender.length == 0);
+ }
+
+ createLazyLoadMetadata(): LazyLoadEvent {
+ return {
+ first: this.first,
+ rows: this.virtualScroll ? this.rows * 2 : this.rows,
+ sortField: this.sortField,
+ sortOrder: this.sortOrder,
+ filters: this.filters,
+ globalFilter: this.globalFilter ? this.globalFilter.value : null,
+ multiSortMeta: this.multiSortMeta
+ };
+ }
+
+ toggleRow(row: any, event?: Event) {
+ if(!this.expandedRows) {
+ this.expandedRows = [];
+ }
+
+ let expandedRowIndex = this.findExpandedRowIndex(row);
+
+ if(expandedRowIndex != -1) {
+ this.expandedRows.splice(expandedRowIndex, 1);
+ this.onRowCollapse.emit({
+ originalEvent: event,
+ data: row
+ });
+ }
+ else {
+ if(this.rowExpandMode === 'single') {
+ this.expandedRows = [];
+ }
+
+ this.expandedRows.push(row);
+ this.onRowExpand.emit({
+ originalEvent: event,
+ data: row
+ });
+ }
+
+ if(event) {
+ event.preventDefault();
+ }
+ }
+
+ findExpandedRowIndex(row: any): number {
+ let index = -1
+ if(this.expandedRows) {
+ for(let i = 0; i < this.expandedRows.length; i++) {
+ if(this.expandedRows[i] == row) {
+ index = i;
+ break;
+ }
+ }
+ }
+ return index;
+ }
+
+ isRowExpanded(row: any): boolean {
+ return this.findExpandedRowIndex(row) != -1;
+ }
+
+ findExpandedRowGroupIndex(row: any): number {
+ let index = -1;
+ if(this.expandedRowsGroups && this.expandedRowsGroups.length) {
+ for(let i = 0; i < this.expandedRowsGroups.length; i++) {
+ let group = this.expandedRowsGroups[i];
+ let rowGroupField = this.resolveFieldData(row, this.groupField);
+ if(rowGroupField === group) {
+ index = i;
+ break;
+ }
+ }
+ }
+ return index;
+ }
+
+ isRowGroupExpanded(row: any): boolean {
+ return this.findExpandedRowGroupIndex(row) != -1;
+ }
+
+ toggleRowGroup(event: Event, row: any): void {
+ this.rowGroupToggleClick = true;
+ let index = this.findExpandedRowGroupIndex(row);
+ let rowGroupField = this.resolveFieldData(row, this.groupField);
+ if(index >= 0) {
+ this.expandedRowsGroups.splice(index, 1);
+ this.onRowGroupCollapse.emit({
+ originalEvent: event,
+ group: rowGroupField
+ });
+ }
+ else {
+ this.expandedRowsGroups = this.expandedRowsGroups||[],
+ this.expandedRowsGroups.push(rowGroupField);
+ this.onRowGroupExpand.emit({
+ originalEvent: event,
+ group: rowGroupField
+ });
+ }
+ event.preventDefault();
+ }
+
+ public reset() {
+ this.sortField = null;
+ this.sortOrder = 1;
+
+ this.filteredValue = null;
+ this.filters = {};
+
+ if(this.paginator) {
+ this.paginate({
+ first: 0,
+ rows: this.rows
+ });
+ }
+ else {
+ this.updateDataToRender(this.value);
+ }
+ }
+
+ public exportCSV() {
+ let data = this.filteredValue||this.value;
+ let csv = '\ufeff';
+
+ //headers
+ for(let i = 0; i < this.columns.length; i++) {
+ if(this.columns[i].field) {
+ csv += '"' + (this.columns[i].header || this.columns[i].field) + '"';
+
+ if(i < (this.columns.length - 1)) {
+ csv += this.csvSeparator;
+ }
+ }
+ }
+
+ //body
+ data.forEach((record, i) => {
+ csv += '\n';
+ for(let i = 0; i < this.columns.length; i++) {
+ if(this.columns[i].field) {
+ csv += '"' + this.resolveFieldData(record, this.columns[i].field) + '"';
+
+ if(i < (this.columns.length - 1)) {
+ csv += this.csvSeparator;
+ }
+ }
+ }
+ });
+
+ let blob = new Blob([csv],{
+ type: 'text/csv;charset=utf-8;'
+ });
+
+ if(window.navigator.msSaveOrOpenBlob) {
+ navigator.msSaveOrOpenBlob(blob, this.exportFilename + '.csv');
+ }
+ else {
+ let link = document.createElement("a");
+ link.style.display = 'none';
+ document.body.appendChild(link);
+ if(link.download !== undefined) {
+ link.setAttribute('href', URL.createObjectURL(blob));
+ link.setAttribute('download', this.exportFilename + '.csv');
+ link.click();
+ }
+ else {
+ csv = 'data:text/csv;charset=utf-8,' + csv;
+ window.open(encodeURI(csv));
+ }
+ document.body.removeChild(link);
+ }
+ }
+
+ getBlockableElement(): HTMLElement {
+ return this.el.nativeElement.children[0];
+ }
+
+ getRowStyleClass(rowData: any, rowIndex: number) {
+ let styleClass = 'ui-widget-content';
+ if(this.rowStyleClass) {
+ let rowClass = this.rowStyleClass.call(this, rowData, rowIndex);
+ if(rowClass) {
+ styleClass += ' ' + rowClass;
+ }
+ }
+ return styleClass;
+ }
+
+ visibleColumns() {
+ return this.columns ? this.columns.filter(c => !c.hidden): [];
+ }
+
+ get containerWidth() {
+ if(this.scrollable) {
+ if(this.scrollWidth) {
+ return this.scrollWidth;
+ }
+ else if(this.frozenWidth && this.unfrozenWidth) {
+ return parseFloat(this.frozenWidth) + parseFloat(this.unfrozenWidth) + 'px';
+ }
+ }
+ else {
+ return this.style ? this.style.width : null;
+ }
+ }
+
+ ngOnDestroy() {
+ //remove event listener
+ if(this.globalFilterFunction) {
+ this.globalFilterFunction();
+ }
+
+ if(this.resizableColumns && this.documentColumnResizeListener && this.documentColumnResizeEndListener) {
+ this.documentColumnResizeListener();
+ this.documentColumnResizeEndListener();
+ }
+
+ if(this.documentClickListener) {
+ this.documentClickListener();
+ }
+
+ if(this.columnsSubscription) {
+ this.columnsSubscription.unsubscribe();
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,SharedModule,PaginatorModule,FormsModule],
+ exports: [DataTable,SharedModule],
+ declarations: [DataTable,DTRadioButton,DTCheckbox,ColumnHeaders,ColumnFooters,TableBody,ScrollableView,RowExpansionLoader]
+})
+export class DataTableModule { }
diff --git a/src/app/components/defer/defer.ts b/src/app/components/defer/defer.ts
new file mode 100644
index 00000000000..c90e31ed9d2
--- /dev/null
+++ b/src/app/components/defer/defer.ts
@@ -0,0 +1,66 @@
+import {NgModule,Directive,ElementRef,AfterViewInit,OnDestroy,Input,TemplateRef,EmbeddedViewRef,
+ ViewContainerRef,Renderer2,EventEmitter,Output,ContentChild} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+
+@Directive({
+ selector: '[pDefer]',
+ host: {
+ },
+ providers: [DomHandler]
+})
+export class DeferredLoader implements AfterViewInit,OnDestroy {
+
+ @Output() onLoad: EventEmitter = new EventEmitter();
+
+ @ContentChild(TemplateRef) template: TemplateRef;
+
+ documentScrollListener: Function;
+
+ view: EmbeddedViewRef;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2, public viewContainer: ViewContainerRef) {}
+
+ ngAfterViewInit() {
+ if(this.shouldLoad()) {
+ this.load();
+ }
+
+ this.documentScrollListener = this.renderer.listen('window', 'scroll', () => {
+ if(this.shouldLoad()) {
+ this.load();
+ this.documentScrollListener();
+ this.documentScrollListener = null;
+ }
+ });
+ }
+
+ shouldLoad(): boolean {
+ let rect = this.el.nativeElement.getBoundingClientRect();
+ let docElement = document.documentElement;
+ let scrollTop = (window.pageYOffset||document.documentElement.scrollTop);
+ let winHeight = docElement.clientHeight;
+
+ return (winHeight >= rect.top);
+ }
+
+ load(): void {
+ this.view = this.viewContainer.createEmbeddedView(this.template);
+ this.onLoad.emit();
+ }
+
+ ngOnDestroy() {
+ this.view = null;
+
+ if(this.documentScrollListener) {
+ this.documentScrollListener();
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [DeferredLoader],
+ declarations: [DeferredLoader]
+})
+export class DeferModule { }
\ No newline at end of file
diff --git a/src/app/components/dialog/dialog.css b/src/app/components/dialog/dialog.css
new file mode 100644
index 00000000000..fefaea3049f
--- /dev/null
+++ b/src/app/components/dialog/dialog.css
@@ -0,0 +1,90 @@
+.ui-dialog {
+ position: fixed;
+ padding: 0;
+}
+.ui-dialog .ui-dialog-titlebar {
+ padding: .5em .75em;
+ position: relative;
+ border: 0;
+}
+.ui-dialog .ui-dialog-content {
+ position: relative;
+ border: 0;
+ padding: .5em .75em;
+ background: none;
+ overflow: auto;
+ zoom: 1;
+}
+.ui-dialog .ui-dialog-footer {
+ padding: .25em .75em;
+ border-width: 1px 0 0 0;
+ background-image: none;
+}
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
+ float: right;
+}
+.ui-dialog .ui-dialog-buttonpane button {
+ margin: .5em .4em .5em 0;
+ cursor: pointer;
+ float: right;
+}
+.ui-dialog .ui-resizable-se {
+ width: 14px;
+ height: 14px;
+ right: 3px;
+ bottom: 3px;
+}
+.ui-draggable .ui-dialog-titlebar {
+ cursor: move;
+}
+.ui-dialog .ui-dialog-titlebar-icon {
+ text-decoration: none
+}
+.ui-dialog .ui-dialog-titlebar-close {
+ float: right;
+ padding: .125em;
+ cursor: pointer;
+ border: 1px solid transparent;
+}
+.ui-dialog .ui-dialog-titlebar-close span {
+ display: block;
+ margin: 0;
+}
+.ui-dialog-footer {
+ padding: .4em 1em;
+ border-width: 1px 0 0 0;
+ text-align: left;
+}
+
+.ui-dialog-mask {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+}
+
+/* ConfirmDialog */
+.ui-confirmdialog.ui-dialog .ui-dialog-content {
+ padding: 1em 2em;
+}
+.ui-confirmdialog .ui-dialog-content .fa {
+ font-size: 1.5em;
+ vertical-align: middle;
+ margin-right: .5em;
+}
+.ui-confirmdialog .ui-dialog-content .ui-confirmdialog-message {
+ vertical-align: middle;
+}
+
+/* Fluid */
+.ui-fluid .ui-dialog-buttonpane .ui-button {
+ width: auto;
+}
+
+/* RTL */
+.ui-rtl .ui-dialog .ui-dialog-titlebar-close {
+ float: left;
+}
+
+.ui-rtl .ui-dialog .ui-dialog-buttonpane button {
+ float: left;
+}
\ No newline at end of file
diff --git a/src/app/components/dialog/dialog.ts b/src/app/components/dialog/dialog.ts
new file mode 100644
index 00000000000..f794ac649a7
--- /dev/null
+++ b/src/app/components/dialog/dialog.ts
@@ -0,0 +1,444 @@
+import {NgModule,Component,ElementRef,AfterViewInit,OnDestroy,Input,Output,EventEmitter,Renderer2,ContentChild,ViewChild} from '@angular/core';
+import {trigger,state,style,transition,animate} from '@angular/animations';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {Header,Footer,SharedModule} from '../common/shared';
+
+@Component({
+ selector: 'p-dialog',
+ template: `
+
+ `,
+ animations: [
+ trigger('dialogState', [
+ state('hidden', style({
+ opacity: 0
+ })),
+ state('visible', style({
+ opacity: 1
+ })),
+ transition('visible => hidden', animate('400ms ease-in')),
+ transition('hidden => visible', animate('400ms ease-out'))
+ ])
+ ],
+ providers: [DomHandler]
+})
+export class Dialog implements AfterViewInit,OnDestroy {
+
+ @Input() header: string;
+
+ @Input() draggable: boolean = true;
+
+ @Input() resizable: boolean = true;
+
+ @Input() minWidth: number = 150;
+
+ @Input() minHeight: number = 150;
+
+ @Input() width: any;
+
+ @Input() height: any;
+
+ @Input() positionLeft: number;
+
+ @Input() positionTop: number;
+
+ @Input() contentStyle: any;
+
+ @Input() modal: boolean;
+
+ @Input() closeOnEscape: boolean = true;
+
+ @Input() dismissableMask: boolean;
+
+ @Input() rtl: boolean;
+
+ @Input() closable: boolean = true;
+
+ @Input() responsive: boolean = true;
+
+ @Input() appendTo: any;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() showHeader: boolean = true;
+
+ @Input() breakpoint: number = 640;
+
+ @ContentChild(Header) headerFacet;
+
+ @ContentChild(Footer) footerFacet;
+
+ @ViewChild('container') containerViewChild: ElementRef;
+
+ @ViewChild('titlebar') headerViewChild: ElementRef;
+
+ @ViewChild('content') contentViewChild: ElementRef;
+
+ @Output() onShow: EventEmitter = new EventEmitter();
+
+ @Output() onHide: EventEmitter = new EventEmitter();
+
+ @Output() visibleChange:EventEmitter = new EventEmitter();
+
+ _visible: boolean;
+
+ dragging: boolean;
+
+ documentDragListener: Function;
+
+ resizing: boolean;
+
+ documentResizeListener: Function;
+
+ documentResizeEndListener: Function;
+
+ documentResponsiveListener: Function;
+
+ documentEscapeListener: Function;
+
+ maskClickListener: Function;
+
+ lastPageX: number;
+
+ lastPageY: number;
+
+ mask: HTMLDivElement;
+
+ closeIconMouseDown: boolean;
+
+ preWidth: number;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2) {}
+
+ @Input() get visible(): boolean {
+ return this._visible;
+ }
+
+ set visible(val:boolean) {
+ this._visible = val;
+
+ if(this.containerViewChild && this.containerViewChild.nativeElement) {
+ if(this._visible)
+ this.show();
+ else
+ this.hide();
+ }
+ }
+
+ show() {
+ this.onShow.emit({});
+ this.positionOverlay();
+ this.containerViewChild.nativeElement.style.zIndex = String(++DomHandler.zindex);
+ this.bindGlobalListeners();
+
+ if(this.modal) {
+ this.enableModality();
+ }
+ }
+
+ positionOverlay() {
+ if(this.positionLeft >= 0 && this.positionTop >= 0) {
+ this.containerViewChild.nativeElement.style.left = this.positionLeft + 'px';
+ this.containerViewChild.nativeElement.style.top = this.positionTop + 'px';
+ }
+ else if (this.positionTop >= 0) {
+ this.center();
+ this.containerViewChild.nativeElement.style.top = this.positionTop + 'px';
+ }
+ else{
+ this.center();
+ }
+ }
+
+ hide() {
+ this.onHide.emit({});
+ this.unbindMaskClickListener();
+
+ if(this.modal) {
+ this.disableModality();
+ }
+ }
+
+ close(event: Event) {
+ this.hide();
+ this.visibleChange.emit(false);
+ event.preventDefault();
+ }
+
+ ngAfterViewInit() {
+ if(this.appendTo) {
+ if(this.appendTo === 'body')
+ document.body.appendChild(this.containerViewChild.nativeElement);
+ else
+ this.domHandler.appendChild(this.containerViewChild.nativeElement, this.appendTo);
+ }
+
+ if(this.visible) {
+ this.show();
+ }
+ }
+
+ center() {
+ let elementWidth = this.domHandler.getOuterWidth(this.containerViewChild.nativeElement);
+ let elementHeight = this.domHandler.getOuterHeight(this.containerViewChild.nativeElement);
+ if(elementWidth == 0 && elementHeight == 0) {
+ this.containerViewChild.nativeElement.style.visibility = 'hidden';
+ this.containerViewChild.nativeElement.style.display = 'block';
+ elementWidth = this.domHandler.getOuterWidth(this.containerViewChild.nativeElement);
+ elementHeight = this.domHandler.getOuterHeight(this.containerViewChild.nativeElement);
+ this.containerViewChild.nativeElement.style.display = 'none';
+ this.containerViewChild.nativeElement.style.visibility = 'visible';
+ }
+ let viewport = this.domHandler.getViewport();
+ let x = Math.max((viewport.width - elementWidth) / 2, 0);
+ let y = Math.max((viewport.height - elementHeight) / 2, 0);
+
+ this.containerViewChild.nativeElement.style.left = x + 'px';
+ this.containerViewChild.nativeElement.style.top = y + 'px';
+ }
+
+ enableModality() {
+ if(!this.mask) {
+ this.mask = document.createElement('div');
+ this.mask.style.zIndex = String(parseInt(this.containerViewChild.nativeElement.style.zIndex) - 1);
+ this.domHandler.addMultipleClasses(this.mask, 'ui-widget-overlay ui-dialog-mask');
+
+ if(this.closable && this.dismissableMask) {
+ this.maskClickListener = this.renderer.listen(this.mask, 'click', (event: any) => {
+ this.close(event);
+ });
+ }
+ document.body.appendChild(this.mask);
+ }
+ }
+
+ disableModality() {
+ if(this.mask) {
+ document.body.removeChild(this.mask);
+ this.mask = null;
+ }
+ }
+
+ unbindMaskClickListener() {
+ if(this.maskClickListener) {
+ this.maskClickListener();
+ this.maskClickListener = null;
+ }
+ }
+
+ moveOnTop() {
+ this.containerViewChild.nativeElement.style.zIndex = String(++DomHandler.zindex);
+ }
+
+ onCloseMouseDown(event: Event) {
+ this.closeIconMouseDown = true;
+ }
+
+ initDrag(event: MouseEvent) {
+ if(this.closeIconMouseDown) {
+ this.closeIconMouseDown = false;
+ return;
+ }
+
+ if(this.draggable) {
+ this.dragging = true;
+ this.lastPageX = event.pageX;
+ this.lastPageY = event.pageY;
+ }
+ }
+
+ onDrag(event: MouseEvent) {
+ if(this.dragging) {
+ let deltaX = event.pageX - this.lastPageX;
+ let deltaY = event.pageY - this.lastPageY;
+ let leftPos = parseInt(this.containerViewChild.nativeElement.style.left);
+ let topPos = parseInt(this.containerViewChild.nativeElement.style.top);
+
+ this.containerViewChild.nativeElement.style.left = leftPos + deltaX + 'px';
+ this.containerViewChild.nativeElement.style.top = topPos + deltaY + 'px';
+
+ this.lastPageX = event.pageX;
+ this.lastPageY = event.pageY;
+ }
+ }
+
+ endDrag(event: MouseEvent) {
+ if(this.draggable) {
+ this.dragging = false;
+ }
+ }
+
+ initResize(event: MouseEvent) {
+ if(this.resizable) {
+ this.preWidth = null;
+ this.resizing = true;
+ this.lastPageX = event.pageX;
+ this.lastPageY = event.pageY;
+ }
+ }
+
+ onResize(event: MouseEvent) {
+ if(this.resizing) {
+ let deltaX = event.pageX - this.lastPageX;
+ let deltaY = event.pageY - this.lastPageY;
+ let containerWidth = this.domHandler.getOuterWidth(this.containerViewChild.nativeElement);
+ let containerHeight = this.domHandler.getOuterHeight(this.containerViewChild.nativeElement);
+ let contentHeight = this.domHandler.getOuterHeight(this.contentViewChild.nativeElement);
+ let newWidth = containerWidth + deltaX;
+ let newHeight = containerHeight + deltaY;
+
+ if(newWidth > this.minWidth) {
+ this.containerViewChild.nativeElement.style.width = newWidth + 'px';
+ }
+
+ if(newHeight > this.minHeight) {
+ this.containerViewChild.nativeElement.style.height = newHeight + 'px';
+ this.contentViewChild.nativeElement.style.height = contentHeight + deltaY + 'px';
+ }
+
+ this.lastPageX = event.pageX;
+ this.lastPageY = event.pageY;
+ }
+ }
+
+ bindGlobalListeners() {
+ if(this.draggable) {
+ this.bindDocumentDragListener();
+ }
+
+ if(this.resizable) {
+ this.bindDocumentResizeListeners();
+ }
+
+ if(this.responsive) {
+ this.bindDocumentResponsiveListener();
+ }
+
+ if(this.closeOnEscape && this.closable) {
+ this.bindDocumentEscapeListener();
+ }
+ }
+
+ unbindGlobalListeners() {
+ this.unbindDocumentDragListener();
+ }
+
+ bindDocumentDragListener() {
+ this.documentDragListener = this.renderer.listen('document', 'mousemove', (event) => {
+ this.onDrag(event);
+ });
+ }
+
+ unbindDocumentDragListener() {
+ if(this.documentDragListener) {
+ this.documentDragListener();
+ this.documentDragListener = null;
+ }
+ }
+
+ bindDocumentResizeListeners() {
+ this.documentResizeListener = this.renderer.listen('document', 'mousemove', (event) => {
+ this.onResize(event);
+ });
+
+ this.documentResizeEndListener = this.renderer.listen('document', 'mouseup', (event) => {
+ if(this.resizing) {
+ this.resizing = false;
+ }
+ });
+ }
+
+ unbindDocumentResizeListeners() {
+ if(this.documentResizeListener && this.documentResizeEndListener) {
+ this.documentResizeListener();
+ this.documentResizeEndListener();
+ this.documentResizeListener = null;
+ this.documentResizeEndListener = null;
+ }
+ }
+
+ bindDocumentResponsiveListener() {
+ this.documentResponsiveListener = this.renderer.listen('window', 'resize', (event) => {
+ let viewport = this.domHandler.getViewport();
+ let width = this.domHandler.getOuterWidth(this.containerViewChild.nativeElement);
+ if(viewport.width <= this.breakpoint) {
+ if(!this.preWidth) {
+ this.preWidth = width;
+ }
+ this.containerViewChild.nativeElement.style.left = '0px';
+ this.containerViewChild.nativeElement.style.width = '100%';
+ }
+ else {
+ this.containerViewChild.nativeElement.style.width = this.preWidth + 'px';
+ this.positionOverlay();
+ }
+ });
+ }
+
+ unbindDocumentResponseListener() {
+ if(this.documentResponsiveListener) {
+ this.documentResponsiveListener();
+ this.documentResponsiveListener = null;
+ }
+ }
+
+ bindDocumentEscapeListener() {
+ this.documentEscapeListener = this.renderer.listen('document', 'keydown', (event) => {
+ if(event.which == 27) {
+ if(parseInt(this.containerViewChild.nativeElement.style.zIndex) == DomHandler.zindex) {
+ this.close(event);
+ }
+ }
+ });
+ }
+
+ unbindDocumentEscapeListener() {
+ if(this.documentEscapeListener) {
+ this.documentEscapeListener();
+ this.documentEscapeListener = null;
+ }
+ }
+
+ ngOnDestroy() {
+ this.disableModality();
+
+ this.unbindGlobalListeners();
+
+ if(this.appendTo) {
+ this.el.nativeElement.appendChild(this.containerViewChild.nativeElement);
+ }
+
+ this.unbindMaskClickListener();
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Dialog,SharedModule],
+ declarations: [Dialog]
+})
+export class DialogModule { }
diff --git a/src/app/components/dom/domhandler.ts b/src/app/components/dom/domhandler.ts
new file mode 100644
index 00000000000..250a605c628
--- /dev/null
+++ b/src/app/components/dom/domhandler.ts
@@ -0,0 +1,393 @@
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class DomHandler {
+
+ public static zindex: number = 1000;
+
+ public addClass(element: any, className: string): void {
+ if (element.classList)
+ element.classList.add(className);
+ else
+ element.className += ' ' + className;
+ }
+
+ public addMultipleClasses(element: any, className: string): void {
+ if (element.classList) {
+ let styles: string[] = className.split(' ');
+ for (let i = 0; i < styles.length; i++) {
+ element.classList.add(styles[i]);
+ }
+
+ }
+ else {
+ let styles: string[] = className.split(' ');
+ for (let i = 0; i < styles.length; i++) {
+ element.className += ' ' + styles[i];
+ }
+ }
+ }
+
+ public removeClass(element: any, className: string): void {
+ if (element.classList)
+ element.classList.remove(className);
+ else
+ element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
+ }
+
+ public hasClass(element: any, className: string): boolean {
+ if (element.classList)
+ return element.classList.contains(className);
+ else
+ return new RegExp('(^| )' + className + '( |$)', 'gi').test(element.className);
+ }
+
+ public siblings(element: any): any {
+ return Array.prototype.filter.call(element.parentNode.children, function (child) {
+ return child !== element;
+ });
+ }
+
+ public find(element: any, selector: string): any[] {
+ return element.querySelectorAll(selector);
+ }
+
+ public findSingle(element: any, selector: string): any {
+ return element.querySelector(selector);
+ }
+
+ public index(element: any): number {
+ let children = element.parentNode.childNodes;
+ let num = 0;
+ for (var i = 0; i < children.length; i++) {
+ if (children[i] == element) return num;
+ if (children[i].nodeType == 1) num++;
+ }
+ return -1;
+ }
+
+ public relativePosition(element: any, target: any): void {
+ let elementDimensions = element.offsetParent ? { width: element.offsetWidth, height: element.offsetHeight } : this.getHiddenElementDimensions(element);
+ let targetHeight = target.offsetHeight;
+ let targetWidth = target.offsetWidth;
+ let targetOffset = target.getBoundingClientRect();
+ let viewport = this.getViewport();
+ let top, left;
+
+ if ((targetOffset.top + targetHeight + elementDimensions.height) > viewport.height)
+ top = -1 * (elementDimensions.height);
+ else
+ top = targetHeight;
+
+ if ((targetOffset.left + elementDimensions.width) > viewport.width)
+ left = targetWidth - elementDimensions.width;
+ else
+ left = 0;
+
+ element.style.top = top + 'px';
+ element.style.left = left + 'px';
+ }
+
+ public absolutePosition(element: any, target: any): void {
+ let elementDimensions = element.offsetParent ? { width: element.offsetWidth, height: element.offsetHeight } : this.getHiddenElementDimensions(element);
+ let elementOuterHeight = elementDimensions.height;
+ let elementOuterWidth = elementDimensions.width;
+ let targetOuterHeight = target.offsetHeight;
+ let targetOuterWidth = target.offsetWidth;
+ let targetOffset = target.getBoundingClientRect();
+ let windowScrollTop = this.getWindowScrollTop();
+ let windowScrollLeft = this.getWindowScrollLeft();
+ let viewport = this.getViewport();
+ let top, left;
+
+ if (targetOffset.top + targetOuterHeight + elementOuterHeight > viewport.height) {
+ top = targetOffset.top + windowScrollTop - elementOuterHeight;
+ if(top < 0) {
+ top = 0 + windowScrollTop;
+ }
+ }
+ else {
+ top = targetOuterHeight + targetOffset.top + windowScrollTop;
+ }
+
+ if (targetOffset.left + targetOuterWidth + elementOuterWidth > viewport.width)
+ left = targetOffset.left + windowScrollLeft + targetOuterWidth - elementOuterWidth;
+ else
+ left = targetOffset.left + windowScrollLeft;
+
+ element.style.top = top + 'px';
+ element.style.left = left + 'px';
+ }
+
+ public getHiddenElementOuterHeight(element: any): number {
+ element.style.visibility = 'hidden';
+ element.style.display = 'block';
+ let elementHeight = element.offsetHeight;
+ element.style.display = 'none';
+ element.style.visibility = 'visible';
+
+ return elementHeight;
+ }
+
+ public getHiddenElementOuterWidth(element: any): number {
+ element.style.visibility = 'hidden';
+ element.style.display = 'block';
+ let elementWidth = element.offsetWidth;
+ element.style.display = 'none';
+ element.style.visibility = 'visible';
+
+ return elementWidth;
+ }
+
+ public getHiddenElementDimensions(element: any): any {
+ let dimensions: any = {};
+ element.style.visibility = 'hidden';
+ element.style.display = 'block';
+ dimensions.width = element.offsetWidth;
+ dimensions.height = element.offsetHeight;
+ element.style.display = 'none';
+ element.style.visibility = 'visible';
+
+ return dimensions;
+ }
+
+ public scrollInView(container, item) {
+ let borderTopValue: string = getComputedStyle(container).getPropertyValue('borderTopWidth');
+ let borderTop: number = borderTopValue ? parseFloat(borderTopValue) : 0;
+ let paddingTopValue: string = getComputedStyle(container).getPropertyValue('paddingTop');
+ let paddingTop: number = paddingTopValue ? parseFloat(paddingTopValue) : 0;
+ let containerRect = container.getBoundingClientRect();
+ let itemRect = item.getBoundingClientRect();
+ let offset = (itemRect.top + document.body.scrollTop) - (containerRect.top + document.body.scrollTop) - borderTop - paddingTop;
+ let scroll = container.scrollTop;
+ let elementHeight = container.clientHeight;
+ let itemHeight = this.getOuterHeight(item);
+
+ if (offset < 0) {
+ container.scrollTop = scroll + offset;
+ }
+ else if ((offset + itemHeight) > elementHeight) {
+ container.scrollTop = scroll + offset - elementHeight + itemHeight;
+ }
+ }
+
+ public fadeIn(element, duration: number): void {
+ element.style.opacity = 0;
+
+ let last = +new Date();
+ let opacity = 0;
+ let tick = function () {
+ opacity = +element.style.opacity + (new Date().getTime() - last) / duration;
+ element.style.opacity = opacity;
+ last = +new Date();
+
+ if (+opacity < 1) {
+ (window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16);
+ }
+ };
+
+ tick();
+ }
+
+ public fadeOut(element, ms) {
+ var opacity = 1,
+ interval = 50,
+ duration = ms,
+ gap = interval / duration;
+
+ let fading = setInterval(() => {
+ opacity = opacity - gap;
+
+ if (opacity <= 0) {
+ opacity = 0;
+ clearInterval(fading);
+ }
+
+ element.style.opacity = opacity;
+ }, interval);
+ }
+
+ public getWindowScrollTop(): number {
+ let doc = document.documentElement;
+ return (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
+ }
+
+ public getWindowScrollLeft(): number {
+ let doc = document.documentElement;
+ return (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+ }
+
+ public matches(element, selector: string): boolean {
+ var p = Element.prototype;
+ var f = p['matches'] || p.webkitMatchesSelector || p['mozMatchesSelector'] || p.msMatchesSelector || function (s) {
+ return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
+ };
+ return f.call(element, selector);
+ }
+
+ public getOuterWidth(el, margin?) {
+ let width = el.offsetWidth;
+
+ if (margin) {
+ let style = getComputedStyle(el);
+ width += parseFloat(style.marginLeft) + parseFloat(style.marginRight);
+ }
+
+ return width;
+ }
+
+ public getHorizontalPadding(el) {
+ let style = getComputedStyle(el);
+ return parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
+ }
+
+ public getHorizontalMargin(el) {
+ let style = getComputedStyle(el);
+ return parseFloat(style.marginLeft) + parseFloat(style.marginRight);
+ }
+
+ public innerWidth(el) {
+ let width = el.offsetWidth;
+ let style = getComputedStyle(el);
+
+ width += parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
+ return width;
+ }
+
+ public width(el) {
+ let width = el.offsetWidth;
+ let style = getComputedStyle(el);
+
+ width -= parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
+ return width;
+ }
+
+ public getInnerHeight(el) {
+ let height = el.offsetHeight;
+ let style = getComputedStyle(el);
+
+ height += parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
+ return height;
+ }
+
+ public getOuterHeight(el, margin?) {
+ let height = el.offsetHeight;
+
+ if (margin) {
+ let style = getComputedStyle(el);
+ height += parseFloat(style.marginTop) + parseFloat(style.marginBottom);
+ }
+
+ return height;
+ }
+
+ public getHeight(el): number {
+ let height = el.offsetHeight;
+ let style = getComputedStyle(el);
+
+ height -= parseFloat(style.paddingTop) + parseFloat(style.paddingBottom) + parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
+
+ return height;
+ }
+
+ public getWidth(el): number {
+ let width = el.offsetWidth;
+ let style = getComputedStyle(el);
+
+ width -= parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) + parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
+
+ return width;
+ }
+
+ public getViewport(): any {
+ let win = window,
+ d = document,
+ e = d.documentElement,
+ g = d.getElementsByTagName('body')[0],
+ w = win.innerWidth || e.clientWidth || g.clientWidth,
+ h = win.innerHeight || e.clientHeight || g.clientHeight;
+
+ return { width: w, height: h };
+ }
+
+ public getOffset(el) {
+ let x = el.offsetLeft;
+ let y = el.offsetTop;
+
+ while (el = el.offsetParent) {
+ x += el.offsetLeft;
+ y += el.offsetTop;
+ }
+
+ return {left: x, top: y};
+ }
+
+ getUserAgent(): string {
+ return navigator.userAgent;
+ }
+
+ isIE() {
+ var ua = window.navigator.userAgent;
+
+ var msie = ua.indexOf('MSIE ');
+ if (msie > 0) {
+ // IE 10 or older => return version number
+ return true;
+ }
+
+ var trident = ua.indexOf('Trident/');
+ if (trident > 0) {
+ // IE 11 => return version number
+ var rv = ua.indexOf('rv:');
+ return true;
+ }
+
+ var edge = ua.indexOf('Edge/');
+ if (edge > 0) {
+ // Edge (IE 12+) => return version number
+ return true;
+ }
+
+ // other browser
+ return false;
+ }
+
+ appendChild(element: any, target: any) {
+ if(this.isElement(target))
+ target.appendChild(element);
+ else if(target.el && target.el.nativeElement)
+ target.el.nativeElement.appendChild(element);
+ else
+ throw 'Cannot append ' + target + ' to ' + element;
+ }
+
+ removeChild(element: any, target: any) {
+ if(this.isElement(target))
+ target.removeChild(element);
+ else if(target.el && target.el.nativeElement)
+ target.el.nativeElement.removeChild(element);
+ else
+ throw 'Cannot remove ' + element + ' from ' + target;
+ }
+
+ isElement(obj: any) {
+ return (typeof HTMLElement === "object" ? obj instanceof HTMLElement :
+ obj && typeof obj === "object" && obj !== null && obj.nodeType === 1 && typeof obj.nodeName === "string"
+ );
+ }
+
+ calculateScrollbarWidth(): number {
+ let scrollDiv = document.createElement("div");
+ scrollDiv.className = "ui-scrollbar-measure";
+ document.body.appendChild(scrollDiv);
+
+ let scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
+ document.body.removeChild(scrollDiv);
+
+ return scrollbarWidth;
+ }
+
+ public invokeElementMethod(element: any, methodName: string, args?: any[]): void {
+ (element as any)[methodName].apply(element, args);
+ }
+}
diff --git a/src/app/components/dragdrop/dragdrop.ts b/src/app/components/dragdrop/dragdrop.ts
new file mode 100644
index 00000000000..f98fa41f130
--- /dev/null
+++ b/src/app/components/dragdrop/dragdrop.ts
@@ -0,0 +1,147 @@
+import {NgModule,Directive,ElementRef,HostListener,Input,Output,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+
+@Directive({
+ selector: '[pDraggable]',
+ host: {
+ '[draggable]': 'true'
+ },
+ providers: [DomHandler]
+})
+export class Draggable {
+
+ @Input('pDraggable') scope: string;
+
+ @Input() dragEffect: string;
+
+ @Input() dragHandle: string;
+
+ @Output() onDragStart: EventEmitter = new EventEmitter();
+
+ @Output() onDragEnd: EventEmitter = new EventEmitter();
+
+ @Output() onDrag: EventEmitter = new EventEmitter();
+
+ public handle: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ @HostListener('dragstart', ['$event'])
+ dragStart(event) {
+ if(this.allowDrag()) {
+ if(this.dragEffect) {
+ event.dataTransfer.effectAllowed = this.dragEffect;
+ }
+ event.dataTransfer.setData('text', this.scope);
+
+ this.onDragStart.emit(event);
+ }
+ else {
+ event.preventDefault();
+ }
+ }
+
+ @HostListener('drag', ['$event'])
+ drag(event) {
+ this.onDrag.emit(event);
+ }
+
+ @HostListener('dragend', ['$event'])
+ dragEnd(event) {
+ this.onDragEnd.emit(event);
+ }
+
+ @HostListener('mouseover', ['$event'])
+ mouseover(event) {
+ this.handle = event.target;
+ }
+
+ @HostListener('mouseleave', ['$event'])
+ mouseleave(event) {
+ this.handle = null;
+ }
+
+ allowDrag() : boolean {
+ if(this.dragHandle && this.handle)
+ return this.domHandler.matches(this.handle, this.dragHandle);
+ else
+ return true;
+ }
+
+}
+
+@Directive({
+ selector: '[pDroppable]',
+ providers: [DomHandler]
+})
+export class Droppable {
+
+ @Input('pDroppable') scope: string|string[];
+
+ @Input() dropEffect: string;
+
+ @Output() onDragEnter: EventEmitter = new EventEmitter();
+
+ @Output() onDragLeave: EventEmitter = new EventEmitter();
+
+ @Output() onDrop: EventEmitter = new EventEmitter();
+
+ @Output() onDragOver: EventEmitter = new EventEmitter();
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ @HostListener('drop', ['$event'])
+ drop(event) {
+ if(this.allowDrop(event)) {
+ event.preventDefault();
+ this.onDrop.emit(event);
+ }
+ }
+
+ @HostListener('dragenter', ['$event'])
+ dragEnter(event) {
+ event.preventDefault();
+
+ if(this.dropEffect) {
+ event.dataTransfer.dropEffect = this.dropEffect;
+ }
+
+ this.onDragEnter.emit(event);
+ }
+
+ @HostListener('dragleave', ['$event'])
+ dragLeave(event) {
+ event.preventDefault();
+
+ this.onDragLeave.emit(event);
+ }
+
+ @HostListener('dragover', ['$event'])
+ dragOver(event) {
+ event.preventDefault();
+ this.onDragOver.emit(event);
+ }
+
+ allowDrop(event): boolean {
+ let dragScope = event.dataTransfer.getData('text');
+ if(typeof (this.scope) == "string" && dragScope == this.scope) {
+ return true;
+ }
+ else if(this.scope instanceof Array) {
+ for(let j = 0; j < this.scope.length; j++) {
+ if(dragScope == this.scope[j]) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Draggable,Droppable],
+ declarations: [Draggable,Droppable]
+})
+export class DragDropModule { }
\ No newline at end of file
diff --git a/src/app/components/dropdown/dropdown.css b/src/app/components/dropdown/dropdown.css
new file mode 100644
index 00000000000..e07a1ab8477
--- /dev/null
+++ b/src/app/components/dropdown/dropdown.css
@@ -0,0 +1,107 @@
+.ui-dropdown {
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ vertical-align: middle;
+}
+
+.ui-dropdown .ui-dropdown-trigger {
+ border-right: none;
+ border-top: none;
+ border-bottom: none;
+ cursor: pointer;
+ width: 1.5em;
+ height: 100%;
+ position: absolute;
+ right: 0;
+ top: 0;
+ padding: 0 .25em;
+}
+
+.ui-dropdown .ui-dropdown-trigger .fa {
+ margin-top: .4em;
+ margin-left: -.125em;
+}
+
+.ui-dropdown .ui-dropdown-label {
+ display: block;
+ border: none;
+ white-space: nowrap;
+ overflow: hidden;
+ font-weight: normal;
+ width: 100%;
+ padding-right: 1.5em;
+}
+
+.ui-dropdown .ui-dropdown-item-empty,
+.ui-dropdown .ui-dropdown-label-empty {
+ text-indent: -9999px;
+}
+
+.ui-dropdown.ui-state-disabled .ui-dropdown-trigger,
+.ui-dropdown.ui-state-disabled .ui-dropdown-label {
+ cursor: default;
+}
+
+.ui-dropdown label.ui-dropdown-label {
+ cursor: pointer;
+}
+
+.ui-dropdown input.ui-dropdown-label {
+ cursor: default;
+}
+
+.ui-dropdown .ui-dropdown-panel {
+ min-width: 100%;
+}
+
+.ui-dropdown-panel {
+ position: absolute;
+ height: auto;
+}
+
+.ui-dropdown-panel .ui-dropdown-items-wrapper {
+ overflow: auto;
+}
+
+.ui-dropdown-panel .ui-dropdown-item {
+ font-weight: normal;
+ border: 0 none;
+ cursor: pointer;
+ margin: 1px 0;
+ padding: .125em .25em;
+ text-align: left;
+}
+
+.ui-dropdown-panel .ui-dropdown-item-group {
+ font-weight: bold;
+}
+
+.ui-dropdown-panel .ui-dropdown-list {
+ padding: 0.4em;
+ border: 0 none;
+}
+
+.ui-dropdown-panel .ui-dropdown-filter {
+ width: 100%;
+ box-sizing: border-box;
+ padding-right: 1.5em;
+}
+
+.ui-dropdown-panel .ui-dropdown-filter-container {
+ position: relative;
+ margin: 0;
+ padding: 0.4em;
+ display: inline-block;
+}
+
+.ui-dropdown-panel .ui-dropdown-filter-container .fa {
+ position: absolute;
+ top: .8em;
+ right: 1em;
+}
+
+/** Dropdown **/
+.ui-fluid .ui-dropdown {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/src/app/components/dropdown/dropdown.ts b/src/app/components/dropdown/dropdown.ts
new file mode 100644
index 00000000000..eb8322b3f8b
--- /dev/null
+++ b/src/app/components/dropdown/dropdown.ts
@@ -0,0 +1,535 @@
+import {NgModule,Component,ElementRef,OnInit,AfterViewInit,AfterContentInit,AfterViewChecked,OnDestroy,Input,Output,Renderer2,EventEmitter,ContentChildren,
+ QueryList,ViewChild,TemplateRef,forwardRef,ChangeDetectorRef} from '@angular/core';
+import {trigger,state,style,transition,animate} from '@angular/animations';
+import {CommonModule} from '@angular/common';
+import {SelectItem} from '../common/selectitem';
+import {SharedModule,PrimeTemplate} from '../common/shared';
+import {DomHandler} from '../dom/domhandler';
+import {ObjectUtils} from '../utils/ObjectUtils';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const DROPDOWN_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => Dropdown),
+ multi: true
+};
+
+@Component({
+ selector: 'p-dropdown',
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ {{option.label||'empty'}}
+
+
+
+
+
+
+ `,
+ animations: [
+ trigger('panelState', [
+ state('hidden', style({
+ opacity: 0
+ })),
+ state('visible', style({
+ opacity: 1
+ })),
+ transition('visible => hidden', animate('400ms ease-in')),
+ transition('hidden => visible', animate('400ms ease-out'))
+ ])
+ ],
+ providers: [DomHandler,ObjectUtils,DROPDOWN_VALUE_ACCESSOR]
+})
+export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterViewChecked,OnDestroy,ControlValueAccessor {
+
+ @Input() scrollHeight: string = '200px';
+
+ @Input() filter: boolean;
+
+ @Input() style: any;
+
+ @Input() panelStyle: any;
+
+ @Input() styleClass: string;
+
+ @Input() panelStyleClass: string;
+
+ @Input() disabled: boolean;
+
+ @Input() readonly: boolean;
+
+ @Input() autoWidth: boolean = true;
+
+ @Input() required: boolean;
+
+ @Input() editable: boolean;
+
+ @Input() appendTo: any;
+
+ @Input() tabindex: number;
+
+ @Input() placeholder: string;
+
+ @Input() filterPlaceholder: string;
+
+ @Input() inputId: string;
+
+ @Input() dataKey: string;
+
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ @Output() onFocus: EventEmitter = new EventEmitter();
+
+ @Output() onBlur: EventEmitter = new EventEmitter();
+
+ @ViewChild('container') containerViewChild: ElementRef;
+
+ @ViewChild('panel') panelViewChild: ElementRef;
+
+ @ViewChild('itemswrapper') itemsWrapperViewChild: ElementRef;
+
+ @ViewChild('filter') filterViewChild: ElementRef;
+
+ @ViewChild('in') focusViewChild: ElementRef;
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public itemTemplate: TemplateRef;
+
+ selectedOption: SelectItem;
+
+ _options: SelectItem[];
+
+ value: any;
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ optionsToDisplay: SelectItem[];
+
+ hover: boolean;
+
+ focus: boolean;
+
+ public panelVisible: boolean = false;
+
+ public shown: boolean;
+
+ public documentClickListener: any;
+
+ public optionsChanged: boolean;
+
+ public panel: HTMLDivElement;
+
+ public container: HTMLDivElement;
+
+ public itemsWrapper: HTMLDivElement;
+
+ public initialized: boolean;
+
+ public selfClick: boolean;
+
+ public itemClick: boolean;
+
+ public hoveredItem: any;
+
+ public selectedOptionUpdated: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2, private cd: ChangeDetectorRef, public objectUtils: ObjectUtils) {}
+
+ ngAfterContentInit() {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'item':
+ this.itemTemplate = item.template;
+ break;
+
+ default:
+ this.itemTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ ngOnInit() {
+ this.optionsToDisplay = this.options;
+ this.updateSelectedOption(null);
+ }
+
+ @Input() get options(): SelectItem[] {
+ return this._options;
+ }
+
+ set options(opts:SelectItem[]) {
+ this._options = opts;
+ this.optionsToDisplay = this._options;
+ this.updateSelectedOption(this.value);
+ this.optionsChanged = true;
+ }
+
+ ngAfterViewInit() {
+ this.container = this.containerViewChild.nativeElement;
+ this.panel = this.panelViewChild.nativeElement;
+ this.itemsWrapper = this.itemsWrapperViewChild.nativeElement;
+
+ this.updateDimensions();
+ this.initialized = true;
+
+ if(this.appendTo) {
+ if(this.appendTo === 'body')
+ document.body.appendChild(this.panel);
+ else
+ this.domHandler.appendChild(this.panel, this.appendTo);
+ }
+ }
+
+ get label(): string {
+ return (this.selectedOption ? this.selectedOption.label : this.placeholder);
+ }
+
+ get editableLabel(): string {
+ return this.value || (this.selectedOption ? this.selectedOption.label : null);
+ }
+
+ onItemClick(event, option) {
+ this.itemClick = true;
+ this.selectItem(event, option);
+ this.focusViewChild.nativeElement.focus();
+
+ this.hide();
+ }
+
+ selectItem(event, option) {
+ this.selectedOption = option;
+ this.value = option.value;
+
+ this.onModelChange(this.value);
+ this.onChange.emit({
+ originalEvent: event,
+ value: this.value
+ });
+ }
+
+ ngAfterViewChecked() {
+ if(this.shown) {
+ this.onShow();
+ this.shown = false;
+ }
+
+ if(this.optionsChanged && this.panelVisible) {
+ this.optionsChanged = false;
+ }
+
+ if(this.selectedOptionUpdated && this.itemsWrapper) {
+ let selectedItem = this.domHandler.findSingle(this.panel, 'li.ui-state-highlight');
+ if(selectedItem) {
+ this.domHandler.scrollInView(this.itemsWrapper, this.domHandler.findSingle(this.panel, 'li.ui-state-highlight'));
+ }
+ this.selectedOptionUpdated = false;
+ }
+ }
+
+ writeValue(value: any): void {
+ if(this.filter) {
+ this.resetFilter();
+ }
+
+ this.value = value;
+ this.updateSelectedOption(value);
+ this.cd.markForCheck();
+ }
+
+ resetFilter(): void {
+ if(this.filterViewChild && this.filterViewChild.nativeElement) {
+ this.filterViewChild.nativeElement.value = '';
+ }
+
+ this.optionsToDisplay = this.options;
+ }
+
+ updateSelectedOption(val: any): void {
+ this.selectedOption = this.findOption(val, this.optionsToDisplay);
+ if(!this.placeholder && !this.selectedOption && this.optionsToDisplay && this.optionsToDisplay.length && !this.editable) {
+ this.selectedOption = this.optionsToDisplay[0];
+ }
+ this.selectedOptionUpdated = true;
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ updateDimensions() {
+ if(this.autoWidth) {
+ let select = this.domHandler.findSingle(this.el.nativeElement, 'select');
+ if(!this.style||(!this.style['width']&&!this.style['min-width'])) {
+ this.el.nativeElement.children[0].style.width = select.offsetWidth + 30 + 'px';
+ }
+ }
+ }
+
+ onMouseclick(event) {
+ if(this.disabled||this.readonly) {
+ return;
+ }
+
+ this.selfClick = true;
+
+ if(!this.itemClick) {
+ this.focusViewChild.nativeElement.focus();
+
+ if(this.panelVisible)
+ this.hide();
+ else {
+ this.show();
+
+ if (this.filterViewChild != undefined) {
+ setTimeout(() => {
+ this.filterViewChild.nativeElement.focus();
+ }, 200);
+ }
+ }
+ }
+ }
+
+ onEditableInputClick(event) {
+ this.itemClick = true;
+ this.bindDocumentClickListener();
+ }
+
+ onEditableInputFocus(event) {
+ this.focus = true;
+ this.hide();
+ }
+
+ onEditableInputChange(event) {
+ this.value = event.target.value;
+ this.updateSelectedOption(this.value);
+ this.onModelChange(this.value);
+ this.onChange.emit({
+ originalEvent: event,
+ value: this.value
+ });
+ }
+
+ onShow() {
+ if(this.options && this.options.length) {
+ this.alignPanel();
+ this.bindDocumentClickListener();
+ }
+ }
+
+ show() {
+ if(this.appendTo) {
+ this.panel.style.minWidth = this.domHandler.getWidth(this.container) + 'px';
+ }
+
+ this.panel.style.zIndex = String(++DomHandler.zindex);
+ this.panelVisible = true;
+ this.shown = true;
+ }
+
+ hide() {
+ this.panelVisible = false;
+ }
+
+ alignPanel() {
+ if(this.appendTo)
+ this.domHandler.absolutePosition(this.panel, this.container);
+ else
+ this.domHandler.relativePosition(this.panel, this.container);
+ }
+
+ onInputFocus(event) {
+ this.focus = true;
+ this.onFocus.emit(event);
+ }
+
+ onInputBlur(event) {
+ this.focus = false;
+ this.onModelTouched();
+ this.onBlur.emit(event);
+ }
+
+ onKeydown(event) {
+ if(this.readonly) {
+ return;
+ }
+
+ let selectedItemIndex = this.selectedOption ? this.findOptionIndex(this.selectedOption.value, this.optionsToDisplay) : -1;
+
+ switch(event.which) {
+ //down
+ case 40:
+ if(!this.panelVisible && event.altKey) {
+ this.show();
+ }
+ else {
+ if(selectedItemIndex != -1) {
+ let nextItemIndex = selectedItemIndex + 1;
+ if(nextItemIndex != (this.optionsToDisplay.length)) {
+ this.selectedOption = this.optionsToDisplay[nextItemIndex];
+ this.selectedOptionUpdated = true;
+ this.selectItem(event, this.selectedOption);
+ }
+ }
+ else if(this.optionsToDisplay) {
+ this.selectedOption = this.optionsToDisplay[0];
+ }
+ }
+
+ event.preventDefault();
+
+ break;
+
+ //up
+ case 38:
+ if(selectedItemIndex > 0) {
+ let prevItemIndex = selectedItemIndex - 1;
+ this.selectedOption = this.optionsToDisplay[prevItemIndex];
+ this.selectedOptionUpdated = true;
+ this.selectItem(event, this.selectedOption);
+ }
+
+ event.preventDefault();
+ break;
+
+ //space
+ case 32:
+ this.panelVisible = !this.panelVisible;
+
+ event.preventDefault();
+ break;
+
+ //enter
+ case 13:
+ this.hide();
+
+ event.preventDefault();
+ break;
+
+ //escape and tab
+ case 27:
+ case 9:
+ this.panelVisible = false;
+ break;
+ }
+ }
+
+ findOptionIndex(val: any, opts: SelectItem[]): number {
+ let index: number = -1;
+ if(opts) {
+ for(let i = 0; i < opts.length; i++) {
+ if((val == null && opts[i].value == null) || this.objectUtils.equals(val, opts[i].value, this.dataKey)) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ return index;
+ }
+
+ findOption(val: any, opts: SelectItem[]): SelectItem {
+ let index: number = this.findOptionIndex(val, opts);
+ return (index != -1) ? opts[index] : null;
+ }
+
+ onFilter(event): void {
+ if(this.options && this.options.length) {
+ let val = event.target.value.toLowerCase();
+ this.optionsToDisplay = [];
+ for(let i = 0; i < this.options.length; i++) {
+ let option = this.options[i];
+ if(option.label.toLowerCase().indexOf(val) > -1) {
+ this.optionsToDisplay.push(option);
+ }
+ }
+ this.optionsChanged = true;
+ }
+
+ }
+
+ applyFocus(): void {
+ if(this.editable)
+ this.domHandler.findSingle(this.el.nativeElement, '.ui-dropdown-label.ui-inputtext').focus();
+ else
+ this.domHandler.findSingle(this.el.nativeElement, 'input[readonly]').focus();
+ }
+
+ bindDocumentClickListener() {
+ if(!this.documentClickListener) {
+ this.documentClickListener = this.renderer.listen('document', 'click', () => {
+ if(!this.selfClick&&!this.itemClick) {
+ this.panelVisible = false;
+ this.unbindDocumentClickListener();
+ }
+
+ this.selfClick = false;
+ this.itemClick = false;
+ this.cd.markForCheck();
+ });
+ }
+ }
+
+ unbindDocumentClickListener() {
+ if(this.documentClickListener) {
+ this.documentClickListener();
+ this.documentClickListener = null;
+ }
+ }
+
+ ngOnDestroy() {
+ this.initialized = false;
+
+ this.unbindDocumentClickListener();
+
+ if(this.appendTo) {
+ this.el.nativeElement.appendChild(this.panel);
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,SharedModule],
+ exports: [Dropdown,SharedModule],
+ declarations: [Dropdown]
+})
+export class DropdownModule { }
diff --git a/src/app/components/editor/editor.ts b/src/app/components/editor/editor.ts
new file mode 100644
index 00000000000..279fa351393
--- /dev/null
+++ b/src/app/components/editor/editor.ts
@@ -0,0 +1,179 @@
+import {NgModule,Component,ElementRef,AfterViewInit,Input,Output,EventEmitter,ContentChild,OnChanges,forwardRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {SharedModule,Header} from '../common/shared'
+import {DomHandler} from '../dom/domhandler';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+declare var Quill: any;
+
+export const EDITOR_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => Editor),
+ multi: true
+};
+
+@Component({
+ selector: 'p-editor',
+ template: `
+
+ `,
+ providers: [DomHandler,EDITOR_VALUE_ACCESSOR]
+})
+export class Editor implements AfterViewInit,ControlValueAccessor {
+
+ @Output() onTextChange: EventEmitter = new EventEmitter();
+
+ @Output() onSelectionChange: EventEmitter = new EventEmitter();
+
+ @ContentChild(Header) toolbar;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() placeholder: string;
+
+ @Input() readonly: boolean;
+
+ @Input() formats: string[];
+
+ @Output() onInit: EventEmitter = new EventEmitter();
+
+ value: string;
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ quill: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ ngAfterViewInit() {
+ let editorElement = this.domHandler.findSingle(this.el.nativeElement ,'div.ui-editor-content');
+ let toolbarElement = this.domHandler.findSingle(this.el.nativeElement ,'div.ui-editor-toolbar');
+
+ this.quill = new Quill(editorElement, {
+ modules: {
+ toolbar: toolbarElement
+ },
+ placeholder: this.placeholder,
+ readOnly: this.readonly,
+ theme: 'snow',
+ formats: this.formats
+ });
+
+ if(this.value) {
+ this.quill.pasteHTML(this.value);
+ }
+
+ this.quill.on('text-change', (delta, oldContents, source) => {
+ let html = editorElement.children[0].innerHTML;
+ let text = this.quill.getText();
+ if(html == '
') {
+ html = null;
+ }
+
+ this.onTextChange.emit({
+ htmlValue: html,
+ textValue: text,
+ delta: delta,
+ source: source
+ });
+
+ this.onModelChange(html);
+
+ if(source === 'user') {
+ this.onModelTouched();
+ }
+ });
+
+ this.quill.on('selection-change', (range, oldRange, source) => {
+ this.onSelectionChange.emit({
+ range: range,
+ oldRange: oldRange,
+ source: source
+ });
+ });
+
+ this.onInit.emit({
+ editor: this.quill
+ });
+ }
+
+ writeValue(value: any) : void {
+ this.value = value;
+
+ if(this.quill) {
+ if(value)
+ this.quill.pasteHTML(value);
+ else
+ this.quill.setText('');
+ }
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ getQuill() {
+ return this.quill;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Editor,SharedModule],
+ declarations: [Editor]
+})
+export class EditorModule { }
\ No newline at end of file
diff --git a/src/app/components/fieldset/fieldset.css b/src/app/components/fieldset/fieldset.css
new file mode 100644
index 00000000000..6eb6def80a3
--- /dev/null
+++ b/src/app/components/fieldset/fieldset.css
@@ -0,0 +1,19 @@
+.ui-fieldset, .ui-fieldset .ui-fieldset-legend {
+ padding: 0.6em 1em;
+}
+
+.ui-fieldset-toggleable .ui-fieldset-legend {
+ padding: 0.5em 1em 0.5em 0.5em;
+ cursor:pointer;
+ white-space: nowrap;
+}
+
+.ui-fieldset .ui-fieldset-toggler {
+ margin-right: .1em;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ui-fieldset .ui-fieldset-content-wrapper-overflown {
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/src/app/components/fieldset/fieldset.ts b/src/app/components/fieldset/fieldset.ts
new file mode 100644
index 00000000000..5ed8697d172
--- /dev/null
+++ b/src/app/components/fieldset/fieldset.ts
@@ -0,0 +1,95 @@
+import {NgModule,Component,Input,Output,EventEmitter,ElementRef} from '@angular/core';
+import {trigger,state,style,transition,animate} from '@angular/animations';
+import {CommonModule} from '@angular/common';
+import {SharedModule} from '../common/shared';
+import {BlockableUI} from '../common/blockableui';
+
+@Component({
+ selector: 'p-fieldset',
+ template: `
+
+ `,
+ animations: [
+ trigger('fieldsetContent', [
+ state('hidden', style({
+ height: '0px'
+ })),
+ state('visible', style({
+ height: '*'
+ })),
+ transition('visible => hidden', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)')),
+ transition('hidden => visible', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
+ ])
+ ]
+})
+export class Fieldset implements BlockableUI {
+
+ @Input() legend: string;
+
+ @Input() toggleable: boolean;
+
+ @Input() collapsed: boolean = false;
+
+ @Output() onBeforeToggle: EventEmitter = new EventEmitter();
+
+ @Output() onAfterToggle: EventEmitter = new EventEmitter();
+
+ @Input() style: any;
+
+ @Input() styleClass: string
+
+ public animating: boolean;
+
+ constructor(private el: ElementRef) {}
+
+ toggle(event) {
+ if(this.toggleable) {
+ this.animating = true;
+ this.onBeforeToggle.emit({originalEvent: event, collapsed: this.collapsed});
+
+ if(this.collapsed)
+ this.expand(event);
+ else
+ this.collapse(event);
+
+ this.onAfterToggle.emit({originalEvent: event, collapsed: this.collapsed});
+
+ //TODO: Use onDone of animate callback instead with RC6
+ setTimeout(() => {
+ this.animating = false;
+ }, 400);
+ }
+ }
+
+ expand(event) {
+ this.collapsed = false;
+ }
+
+ collapse(event) {
+ this.collapsed = true;
+ }
+
+ getBlockableElement(): HTMLElement {
+ return this.el.nativeElement.children[0];
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Fieldset,SharedModule],
+ declarations: [Fieldset]
+})
+export class FieldsetModule { }
\ No newline at end of file
diff --git a/src/app/components/fileupload/fileupload.css b/src/app/components/fileupload/fileupload.css
new file mode 100644
index 00000000000..8ff9f7b6e38
--- /dev/null
+++ b/src/app/components/fileupload/fileupload.css
@@ -0,0 +1,81 @@
+/*
+ * FileUpload
+ */
+.ui-fileupload-buttonbar .ui-fileupload-choose input {
+ display: none;
+}
+
+.ui-fileupload-buttonbar .ui-fileupload-choose.ui-state-disabled input {
+ cursor: default;
+}
+
+.ui-fileupload-choose {
+ position: relative;
+ overflow: hidden;
+}
+
+.ui-fileupload-buttonbar {
+ padding: .5em;
+ border-bottom: 0 none;
+}
+
+.ui-fileupload-buttonbar .ui-button {
+ vertical-align: middle;
+ margin-right: .25em;
+}
+
+.ui-fileupload-content {
+ padding: 1em;
+ position: relative;
+ transition: border-color .3s;
+}
+
+.ui-fileupload-content.ui-fileupload-highlight {
+ border-color: #156090;
+}
+
+.ui-fileupload-files img {
+ border: none;
+}
+
+.ui-fileupload-files {
+ display: table;
+}
+
+.ui-fileupload-row {
+ display: table-row;
+}
+
+.ui-fileupload-row > div {
+ display: table-cell;
+ padding: .5em 1em;
+ vertical-align: middle;
+}
+
+.ui-fileupload-content .ui-progressbar {
+ width: 100%;
+ position: absolute;
+ top: 1px;
+ left: 0;
+ height: .25em;
+ border: 0 none;
+}
+
+.ui-fileupload-content .ui-progressbar-value {
+ -moz-border-radius: 0;
+ -webkit-border-radius: 0;
+ border-radius: 0;
+ border: 0 none;
+}
+
+/* ui-fluid */
+.ui-fluid .ui-fileupload .ui-button {
+ width: auto;
+}
+
+.ui-fluid .ui-fileupload-content .ui-button-icon-only {
+ width: 2em;
+}
+
+
+
diff --git a/src/app/components/fileupload/fileupload.ts b/src/app/components/fileupload/fileupload.ts
new file mode 100644
index 00000000000..2c63ef72c01
--- /dev/null
+++ b/src/app/components/fileupload/fileupload.ts
@@ -0,0 +1,341 @@
+import {NgModule,Component,OnInit,Input,Output,EventEmitter,TemplateRef,AfterContentInit,ContentChildren,QueryList} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomSanitizer} from '@angular/platform-browser';
+import {ButtonModule} from '../button/button';
+import {MessagesModule} from '../messages/messages';
+import {ProgressBarModule} from '../progressbar/progressbar';
+import {Message} from '../common/message';
+import {PrimeTemplate,SharedModule} from '../common/shared';
+
+@Component({
+ selector: 'p-fileUpload',
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
{{file.name}}
+
{{formatSize(file.size)}}
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class FileUpload implements OnInit,AfterContentInit {
+
+ @Input() name: string;
+
+ @Input() url: string;
+
+ @Input() method: string = 'POST';
+
+ @Input() multiple: boolean;
+
+ @Input() accept: string;
+
+ @Input() disabled: boolean;
+
+ @Input() auto: boolean;
+
+ @Input() withCredentials: boolean;
+
+ @Input() maxFileSize: number;
+
+ @Input() invalidFileSizeMessageSummary: string = '{0}: Invalid file size, ';
+
+ @Input() invalidFileSizeMessageDetail: string = 'maximum upload size is {0}.';
+
+ @Input() invalidFileTypeMessageSummary: string = '{0}: Invalid file type, ';
+
+ @Input() invalidFileTypeMessageDetail: string = 'allowed file types: {0}.';
+
+ @Input() style: string;
+
+ @Input() styleClass: string;
+
+ @Input() previewWidth: number = 50;
+
+ @Input() chooseLabel: string = 'Choose';
+
+ @Input() uploadLabel: string = 'Upload';
+
+ @Input() cancelLabel: string = 'Cancel';
+
+ @Output() onBeforeUpload: EventEmitter = new EventEmitter();
+
+ @Output() onBeforeSend: EventEmitter = new EventEmitter();
+
+ @Output() onUpload: EventEmitter = new EventEmitter();
+
+ @Output() onError: EventEmitter = new EventEmitter();
+
+ @Output() onClear: EventEmitter = new EventEmitter();
+
+ @Output() onRemove: EventEmitter = new EventEmitter();
+
+ @Output() onSelect: EventEmitter = new EventEmitter();
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public files: File[];
+
+ public progress: number = 0;
+
+ public dragHighlight: boolean;
+
+ public msgs: Message[];
+
+ public fileTemplate: TemplateRef;
+
+ public contentTemplate: TemplateRef;
+
+ public toolbarTemplate: TemplateRef;
+
+ constructor(private sanitizer: DomSanitizer){}
+
+ ngOnInit() {
+ this.files = [];
+ }
+
+ ngAfterContentInit():void {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'file':
+ this.fileTemplate = item.template;
+ break;
+
+ case 'content':
+ this.contentTemplate = item.template;
+ break;
+
+ case 'toolbar':
+ this.toolbarTemplate = item.template;
+ break;
+
+ default:
+ this.fileTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ onChooseClick(event, fileInput) {
+ fileInput.value = null;
+ fileInput.click();
+ }
+
+ onFileSelect(event) {
+ this.msgs = [];
+ if(!this.multiple) {
+ this.files = [];
+ }
+
+ let files = event.dataTransfer ? event.dataTransfer.files : event.target.files;
+ for(let i = 0; i < files.length; i++) {
+ let file = files[i];
+ if(this.validate(file)) {
+ if(this.isImage(file)) {
+ file.objectURL = this.sanitizer.bypassSecurityTrustUrl((window.URL.createObjectURL(files[i])));
+ }
+
+ this.files.push(files[i]);
+ }
+ }
+
+ this.onSelect.emit({originalEvent: event, files: files});
+
+ if(this.hasFiles() && this.auto) {
+ this.upload();
+ }
+ }
+
+ validate(file: File): boolean {
+ if(this.accept && !this.isFileTypeValid(file)) {
+ this.msgs.push({
+ severity: 'error',
+ summary: this.invalidFileTypeMessageSummary.replace('{0}', file.name),
+ detail: this.invalidFileTypeMessageDetail.replace('{0}', this.accept)
+ });
+ return false;
+ }
+
+ if(this.maxFileSize && file.size > this.maxFileSize) {
+ this.msgs.push({
+ severity: 'error',
+ summary: this.invalidFileSizeMessageSummary.replace('{0}', file.name),
+ detail: this.invalidFileSizeMessageDetail.replace('{0}', this.formatSize(this.maxFileSize))
+ });
+ return false;
+ }
+
+ return true;
+ }
+
+ private isFileTypeValid(file: File): boolean {
+ let acceptableTypes = this.accept.split(',');
+ for(let type of acceptableTypes) {
+ let acceptable = this.isWildcard(type) ? this.getTypeClass(file.type) === this.getTypeClass(type)
+ : file.type == type || this.getFileExtension(file) === type;
+
+ if(acceptable) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ getTypeClass(fileType: string): string {
+ return fileType.substring(0, fileType.indexOf('/'));
+ }
+
+ isWildcard(fileType: string): boolean {
+ return fileType.indexOf('*') !== -1;
+ }
+
+ getFileExtension(file: File): string {
+ return '.' + file.name.split('.').pop();
+ }
+
+ isImage(file: File): boolean {
+ return /^image\//.test(file.type);
+ }
+
+ onImageLoad(img: any) {
+ window.URL.revokeObjectURL(img.src);
+ }
+
+ upload() {
+ this.msgs = [];
+ let xhr = new XMLHttpRequest(),
+ formData = new FormData();
+
+ this.onBeforeUpload.emit({
+ 'xhr': xhr,
+ 'formData': formData
+ });
+
+ for(let i = 0; i < this.files.length; i++) {
+ formData.append(this.name, this.files[i], this.files[i].name);
+ }
+
+ xhr.upload.addEventListener('progress', (e: ProgressEvent) => {
+ if(e.lengthComputable) {
+ this.progress = Math.round((e.loaded * 100) / e.total);
+ }
+ }, false);
+
+ xhr.onreadystatechange = () => {
+ if(xhr.readyState == 4) {
+ this.progress = 0;
+
+ if(xhr.status >= 200 && xhr.status < 300)
+ this.onUpload.emit({xhr: xhr, files: this.files});
+ else
+ this.onError.emit({xhr: xhr, files: this.files});
+
+ this.clear();
+ }
+ };
+
+ xhr.open(this.method, this.url, true);
+
+ this.onBeforeSend.emit({
+ 'xhr': xhr,
+ 'formData': formData
+ });
+
+ xhr.withCredentials = this.withCredentials;
+
+ xhr.send(formData);
+ }
+
+ clear() {
+ this.files = [];
+ this.onClear.emit();
+ }
+
+ remove(index: number) {
+ this.onRemove.emit({originalEvent: event, file: this.files[index]});
+ this.files.splice(index, 1);
+ }
+
+ hasFiles(): boolean {
+ return this.files && this.files.length > 0;
+ }
+
+ onDragEnter(e) {
+ if(!this.disabled) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ onDragOver(e) {
+ if(!this.disabled) {
+ this.dragHighlight = true;
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ onDragLeave(e) {
+ if(!this.disabled) {
+ this.dragHighlight = false;
+ }
+ }
+
+ onDrop(e) {
+ if(!this.disabled) {
+ this.dragHighlight = false;
+ e.stopPropagation();
+ e.preventDefault();
+
+ this.onFileSelect(e);
+ }
+ }
+
+ formatSize(bytes) {
+ if(bytes == 0) {
+ return '0 B';
+ }
+ let k = 1000,
+ dm = 3,
+ sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
+ i = Math.floor(Math.log(bytes) / Math.log(k));
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,SharedModule,ButtonModule,ProgressBarModule,MessagesModule],
+ exports: [FileUpload,SharedModule,ButtonModule,ProgressBarModule,MessagesModule],
+ declarations: [FileUpload]
+})
+export class FileUploadModule { }
\ No newline at end of file
diff --git a/src/app/components/galleria/galleria.css b/src/app/components/galleria/galleria.css
new file mode 100644
index 00000000000..8226a610b85
--- /dev/null
+++ b/src/app/components/galleria/galleria.css
@@ -0,0 +1,81 @@
+.ui-galleria {
+ overflow: hidden;
+ visibility: hidden;
+ position: relative;
+}
+
+.ui-galleria-panel-wrapper {
+ position: relative;
+ padding: 0;
+ margin: 0;
+}
+
+.ui-galleria-panel {
+ filter: inherit;
+ position: absolute;
+ top: 0;
+ left: 0;
+ list-style-type: none;
+}
+
+.ui-galleria-filmstrip-wrapper {
+ overflow: hidden;
+ margin: .25em auto;
+ position: relative;
+}
+
+.ui-galleria-filmstrip {
+ list-style: none outside none;
+ margin: 0;
+ padding: 0;
+ width: 2340px;
+ z-index: 900;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.ui-galleria-frame {
+ float:left;
+ margin-right: 5px;
+ opacity: 0.3;
+ cursor: pointer;
+}
+
+.ui-galleria-frame-active {
+ opacity: 1;
+}
+
+.ui-galleria-frame-content {
+ overflow: hidden;
+}
+
+.ui-galleria-nav-next, .ui-galleria-nav-prev {
+ cursor: pointer;
+ position: absolute;
+}
+
+.ui-galleria-nav-prev {
+ left: 5px;
+}
+
+.ui-galleria-nav-next {
+ right: 5px;
+}
+
+.ui-galleria-caption {
+ position: absolute;
+ left:1px;
+ background-color: rgba(0,0,0,0.5);
+ display: none;
+ color: #ededed;
+ padding: 0.2em 1em;
+}
+
+.ui-galleria-caption h4 {
+ color: #ededed;
+}
+
+.ui-galleria-panel-content {
+ padding: 1em 1.4em;
+}
\ No newline at end of file
diff --git a/src/app/components/galleria/galleria.ts b/src/app/components/galleria/galleria.ts
new file mode 100644
index 00000000000..cda4fb5c8cf
--- /dev/null
+++ b/src/app/components/galleria/galleria.ts
@@ -0,0 +1,242 @@
+import {NgModule,Component,ElementRef,AfterViewChecked,AfterViewInit,OnDestroy,Input,Output,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+
+@Component({
+ selector: 'p-galleria',
+ template: `
+
+
+ -
+
+
+
+
+
+ -
+
+
![]()
+
+
+
+
+
+
+
+
{{images[activeIndex]?.title}}
{{images[activeIndex]?.alt}}
+
+
+ `,
+ providers: [DomHandler]
+})
+export class Galleria implements AfterViewChecked,AfterViewInit,OnDestroy {
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() panelWidth: number = 600;
+
+ @Input() panelHeight: number = 400;
+
+ @Input() frameWidth: number = 60;
+
+ @Input() frameHeight: number = 40;
+
+ @Input() activeIndex: number = 0;
+
+ @Input() showFilmstrip: boolean = true;
+
+ @Input() autoPlay: boolean = true;
+
+ @Input() transitionInterval: number = 4000;
+
+ @Input() showCaption: boolean = true;
+
+ @Output() onImageClicked = new EventEmitter();
+
+ _images: any[];
+
+ slideshowActive: boolean;
+
+ public container: any;
+
+ public panelWrapper: any;
+
+ public panels: any;
+
+ public caption: any;
+
+ public stripWrapper: any;
+
+ public strip: any;
+
+ public frames: any;
+
+ public interval: any;
+
+ public stripLeft: number = 0;
+
+ public imagesChanged: boolean;
+
+ public initialized: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ ngAfterViewChecked() {
+ if(this.imagesChanged) {
+ this.stopSlideshow();
+ this.render();
+ this.imagesChanged = false;
+ }
+ }
+
+ @Input() get images(): any[] {
+ return this._images;
+ }
+
+ set images(value:any[]) {
+ this._images = value;
+ this.activeIndex = 0;
+ this.imagesChanged = true;
+ }
+
+ ngAfterViewInit() {
+ this.container = this.el.nativeElement.children[0];
+ this.panelWrapper = this.domHandler.findSingle(this.el.nativeElement, 'ul.ui-galleria-panel-wrapper');
+ this.initialized = true;
+
+ if(this.showFilmstrip) {
+ this.stripWrapper = this.domHandler.findSingle(this.container,'div.ui-galleria-filmstrip-wrapper');
+ this.strip = this.domHandler.findSingle(this.stripWrapper,'ul.ui-galleria-filmstrip');
+ }
+
+ if(this.images && this.images.length) {
+ this.render();
+ }
+ }
+
+ render() {
+ this.panels = this.domHandler.find(this.panelWrapper, 'li.ui-galleria-panel');
+
+ if(this.showFilmstrip) {
+ this.frames = this.domHandler.find(this.strip,'li.ui-galleria-frame');
+ this.stripWrapper.style.width = this.domHandler.width(this.panelWrapper) - 50 + 'px';
+ this.stripWrapper.style.height = this.frameHeight + 'px';
+ }
+
+ if(this.showCaption) {
+ this.caption = this.domHandler.findSingle(this.container,'div.ui-galleria-caption');
+ this.caption.style.bottom = this.showFilmstrip ? this.domHandler.getOuterHeight(this.stripWrapper,true) + 'px' : 0 + 'px';
+ this.caption.style.width = this.domHandler.width(this.panelWrapper) + 'px';
+ }
+
+ if(this.autoPlay) {
+ this.startSlideshow();
+ }
+
+ this.container.style.visibility = 'visible';
+ }
+
+ startSlideshow() {
+ this.interval = setInterval(() => {
+ this.next();
+ }, this.transitionInterval);
+
+ this.slideshowActive = true;
+ }
+
+ stopSlideshow() {
+ if(this.interval) {
+ clearInterval(this.interval);
+ }
+
+ this.slideshowActive = false;
+ }
+
+ clickNavRight() {
+ if(this.slideshowActive) {
+ this.stopSlideshow();
+ }
+ this.next();
+ }
+
+ clickNavLeft() {
+ if(this.slideshowActive) {
+ this.stopSlideshow();
+ }
+ this.prev();
+ }
+
+ frameClick(frame) {
+ if(this.slideshowActive) {
+ this.stopSlideshow();
+ }
+
+ this.select(this.domHandler.index(frame), false);
+ }
+
+ prev() {
+ if(this.activeIndex !== 0) {
+ this.select(this.activeIndex - 1, true);
+ }
+ }
+
+ next() {
+ if(this.activeIndex !== (this.panels.length-1)) {
+ this.select(this.activeIndex + 1, true);
+ }
+ else {
+ this.select(0, false);
+ this.stripLeft = 0;
+ }
+ }
+
+ select(index, reposition) {
+ if(index !== this.activeIndex) {
+ let oldPanel = this.panels[this.activeIndex],
+ newPanel = this.panels[index];
+
+ this.domHandler.fadeIn(newPanel, 500);
+
+ if(this.showFilmstrip) {
+ let oldFrame = this.frames[this.activeIndex],
+ newFrame = this.frames[index];
+
+ if(reposition === undefined || reposition === true) {
+ let frameLeft = newFrame.offsetLeft,
+ stepFactor = this.frameWidth + parseInt(getComputedStyle(newFrame)['margin-right'], 10),
+ stripLeft = this.strip.offsetLeft,
+ frameViewportLeft = frameLeft + stripLeft,
+ frameViewportRight = frameViewportLeft + this.frameWidth;
+
+ if(frameViewportRight > this.domHandler.width(this.stripWrapper))
+ this.stripLeft -= stepFactor;
+ else if(frameViewportLeft < 0)
+ this.stripLeft += stepFactor;
+ }
+ }
+
+ this.activeIndex = index;
+ }
+ }
+
+ clickImage(event, image, i) {
+ this.onImageClicked.emit({originalEvent: event, image: image, index: i})
+ }
+
+ ngOnDestroy() {
+ this.stopSlideshow();
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Galleria],
+ declarations: [Galleria]
+})
+export class GalleriaModule { }
\ No newline at end of file
diff --git a/src/app/components/gmap/gmap.ts b/src/app/components/gmap/gmap.ts
new file mode 100644
index 00000000000..88d3dae32b6
--- /dev/null
+++ b/src/app/components/gmap/gmap.ts
@@ -0,0 +1,143 @@
+import {NgModule,Component,ElementRef,OnInit,AfterViewInit,DoCheck,OnDestroy,Input,Output,EventEmitter,IterableDiffers,ChangeDetectorRef,NgZone} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+declare var google: any;
+
+@Component({
+ selector: 'p-gmap',
+ template: ``
+})
+export class GMap implements AfterViewInit,DoCheck {
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() options: any;
+
+ @Input() overlays: any[];
+
+ @Output() onMapClick: EventEmitter = new EventEmitter();
+
+ @Output() onOverlayClick: EventEmitter = new EventEmitter();
+
+ @Output() onOverlayDragStart: EventEmitter = new EventEmitter();
+
+ @Output() onOverlayDrag: EventEmitter = new EventEmitter();
+
+ @Output() onOverlayDragEnd: EventEmitter = new EventEmitter();
+
+ @Output() onMapReady: EventEmitter = new EventEmitter();
+
+ differ: any;
+
+ map: any;
+
+ constructor(public el: ElementRef,differs: IterableDiffers, public cd: ChangeDetectorRef, public zone:NgZone) {
+ this.differ = differs.find([]).create(null);
+ }
+
+ ngAfterViewInit() {
+ this.map = new google.maps.Map(this.el.nativeElement.children[0], this.options);
+ this.onMapReady.emit({
+ map: this.map
+ });
+
+ if(this.overlays) {
+ for(let overlay of this.overlays) {
+ overlay.setMap(this.map);
+ this.bindOverlayEvents(overlay);
+ }
+ }
+
+ this.map.addListener('click', (event) => {
+ this.zone.run(() => {
+ this.onMapClick.emit(event);
+ });
+ });
+ }
+
+ bindOverlayEvents(overlay: any) {
+ overlay.addListener('click', (event) => {
+ this.zone.run(() => {
+ this.onOverlayClick.emit({
+ originalEvent: event,
+ 'overlay': overlay,
+ map: this.map
+ });
+ });
+ });
+
+ if(overlay.getDraggable()) {
+ this.bindDragEvents(overlay);
+ }
+ }
+
+ ngDoCheck() {
+ let changes = this.differ.diff(this.overlays);
+
+ if(changes && this.map) {
+ changes.forEachRemovedItem((record) => {record.item.setMap(null)});
+ changes.forEachAddedItem((record) => {
+ record.item.setMap(this.map);
+ record.item.addListener('click', (event) => {
+ this.zone.run(() => {
+ this.onOverlayClick.emit({
+ originalEvent: event,
+ overlay: record.item,
+ map: this.map
+ });
+ });
+ });
+
+ if(record.item.getDraggable()) {
+ this.bindDragEvents(record.item);
+ }
+ });
+ }
+ }
+
+ bindDragEvents(overlay) {
+ overlay.addListener('dragstart', (event) => {
+ this.zone.run(() => {
+ this.onOverlayDragStart.emit({
+ originalEvent: event,
+ overlay: overlay,
+ map: this.map
+ });
+ });
+ });
+
+ overlay.addListener('drag', (event) => {
+ this.zone.run(() => {
+ this.onOverlayDrag.emit({
+ originalEvent: event,
+ overlay: overlay,
+ map: this.map
+ });
+ });
+ });
+
+ overlay.addListener('dragend', (event) => {
+ this.zone.run(() => {
+ this.onOverlayDragEnd.emit({
+ originalEvent: event,
+ overlay: overlay,
+ map: this.map
+ });
+ });
+
+ });
+ }
+
+ getMap() {
+ return this.map;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [GMap],
+ declarations: [GMap]
+})
+export class GMapModule { }
\ No newline at end of file
diff --git a/src/app/components/grid/grid.css b/src/app/components/grid/grid.css
new file mode 100644
index 00000000000..0106d227f9c
--- /dev/null
+++ b/src/app/components/grid/grid.css
@@ -0,0 +1,514 @@
+/* Deprecated Grid CSS */
+.ui-grid {
+ clear: both;
+ padding: 0;
+ margin: 0;
+}
+
+.ui-grid:before,
+.ui-grid:after {
+ content:"";
+ display:table;
+}
+
+.ui-grid:after {
+ clear:both;
+}
+
+.ui-grid .ui-grid-row {
+ display: -webkit-box;
+ display: -moz-box;
+ display: -ms-flexbox;
+ display: -webkit-flex;
+ display: flex;
+ clear:both;
+}
+
+.ui-grid-row:after {
+ clear: both;
+ content: "";
+ display: table;
+}
+
+.ui-grid-col-1,
+.ui-grid-col-2,
+.ui-grid-col-3,
+.ui-grid-col-4,
+.ui-grid-col-5,
+.ui-grid-col-6,
+.ui-grid-col-7,
+.ui-grid-col-8,
+.ui-grid-col-9,
+.ui-grid-col-10,
+.ui-grid-col-11,
+.ui-grid-col-12 {
+ float: left;
+ box-sizing: border-box;
+}
+
+.ui-grid-col-1 {
+ width: 8.33333%;
+}
+
+.ui-grid-col-2 {
+ width: 16.66666%;
+}
+
+.ui-grid-col-3 {
+ width: 25%;
+}
+
+.ui-grid-col-4 {
+ width: 33.33333%;
+}
+
+.ui-grid-col-5 {
+ width: 41.66666%;
+}
+
+.ui-grid-col-6 {
+ width: 50%;
+}
+
+.ui-grid-col-7 {
+ width: 58.33333%;
+}
+
+.ui-grid-col-8 {
+ width: 66.66666%;
+}
+
+.ui-grid-col-9 {
+ width: 75%;
+}
+
+.ui-grid-col-10 {
+ width: 83.33333%;
+}
+
+.ui-grid-col-11 {
+ width: 91.66666%;
+}
+
+.ui-grid-col-12 {
+ width: 100%;
+}
+
+@media (min-width: 480px) {
+ .ui-grid-fixed {
+ width: 480px;
+ }
+}
+
+@media (min-width: 768px) {
+ .ui-grid-fixed {
+ width: 768px;
+ }
+}
+
+@media (min-width: 960px) {
+ .ui-grid-fixed {
+ width: 960px;
+ }
+}
+
+@media (min-width: 1024px) {
+ .ui-grid-fixed {
+ width: 1024px;
+ }
+}
+
+/* Responsive */
+@media (max-width: 640px) {
+ .ui-grid-responsive .ui-grid-row {
+ display: block;
+ }
+
+ .ui-grid-responsive .ui-grid-col-1,
+ .ui-grid-responsive .ui-grid-col-2,
+ .ui-grid-responsive .ui-grid-col-3,
+ .ui-grid-responsive .ui-grid-col-4,
+ .ui-grid-responsive .ui-grid-col-5,
+ .ui-grid-responsive .ui-grid-col-6,
+ .ui-grid-responsive .ui-grid-col-7,
+ .ui-grid-responsive .ui-grid-col-8,
+ .ui-grid-responsive .ui-grid-col-9,
+ .ui-grid-responsive .ui-grid-col-10,
+ .ui-grid-responsive .ui-grid-col-11,
+ .ui-grid-responsive .ui-grid-col-12 {
+ width: 100%;
+ float: none;
+ }
+}
+
+.ui-grid.ui-grid-pad > .ui-grid-row > div {
+ padding: .25em .5em;
+}
+
+/* Responsive */
+@media (max-width: 640px) {
+ .ui-grid-responsive .ui-grid-row {
+ display: block;
+ }
+
+ .ui-grid-responsive .ui-grid-col-1,
+ .ui-grid-responsive .ui-grid-col-2,
+ .ui-grid-responsive .ui-grid-col-3,
+ .ui-grid-responsive .ui-grid-col-4,
+ .ui-grid-responsive .ui-grid-col-5,
+ .ui-grid-responsive .ui-grid-col-6,
+ .ui-grid-responsive .ui-grid-col-7,
+ .ui-grid-responsive .ui-grid-col-8,
+ .ui-grid-responsive .ui-grid-col-9,
+ .ui-grid-responsive .ui-grid-col-10,
+ .ui-grid-responsive .ui-grid-col-11,
+ .ui-grid-responsive .ui-grid-col-12 {
+ width: 100%;
+ float: none;
+ }
+}
+
+/* New Grid CSS */
+.ui-g {
+ display: -webkit-box;
+ display: -moz-box;
+ display: -ms-flexbox;
+ display: -webkit-flex;
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.ui-g:after {
+ clear: both;
+ content: "";
+ display: table;
+}
+
+.ui-g-1,
+.ui-g-2,
+.ui-g-3,
+.ui-g-4,
+.ui-g-5,
+.ui-g-6,
+.ui-g-7,
+.ui-g-8,
+.ui-g-9,
+.ui-g-10,
+.ui-g-11,
+.ui-g-12 {
+ float: left;
+ box-sizing: border-box;
+ padding: 0.5em;
+}
+
+.ui-g-1 {
+ width: 8.3333%;
+}
+
+.ui-g-2 {
+ width: 16.6667%;
+}
+
+.ui-g-3 {
+ width: 25%;
+}
+
+.ui-g-4 {
+ width: 33.3333%;
+}
+
+.ui-g-5 {
+ width: 41.6667%;
+}
+
+.ui-g-6 {
+ width: 50%;
+}
+
+.ui-g-7 {
+ width: 58.3333%;
+}
+
+.ui-g-8 {
+ width: 66.6667%;
+}
+
+.ui-g-9 {
+ width: 75%;
+}
+
+.ui-g-10 {
+ width: 83.3333%;
+}
+
+.ui-g-11 {
+ width: 91.6667%;
+}
+
+.ui-g-12 {
+ width: 100%;
+}
+
+@media screen and (max-width: 40em) {
+ .ui-sm-1,
+ .ui-sm-2,
+ .ui-sm-3,
+ .ui-sm-4,
+ .ui-sm-5,
+ .ui-sm-6,
+ .ui-sm-7,
+ .ui-sm-8,
+ .ui-sm-9,
+ .ui-sm-10,
+ .ui-sm-11,
+ .ui-sm-12 {
+ padding: 0.5em;
+ }
+
+ .ui-sm-1 {
+ width: 8.3333%;
+ }
+
+ .ui-sm-2 {
+ width: 16.6667%;
+ }
+
+ .ui-sm-3 {
+ width: 25%;
+ }
+
+ .ui-sm-4 {
+ width: 33.3333%;
+ }
+
+ .ui-sm-5 {
+ width: 41.6667%;
+ }
+
+ .ui-sm-6 {
+ width: 50%;
+ }
+
+ .ui-sm-7 {
+ width: 58.3333%;
+ }
+
+ .ui-sm-8 {
+ width: 66.6667%;
+ }
+
+ .ui-sm-9 {
+ width: 75%;
+ }
+
+ .ui-sm-10 {
+ width: 83.3333%;
+ }
+
+ .ui-sm-11 {
+ width: 91.6667%;
+ }
+
+ .ui-sm-12 {
+ width: 100%;
+ }
+}
+
+@media screen and (min-width: 40.063em) {
+ .ui-md-1,
+ .ui-md-2,
+ .ui-md-3,
+ .ui-md-4,
+ .ui-md-5,
+ .ui-md-6,
+ .ui-md-7,
+ .ui-md-8,
+ .ui-md-9,
+ .ui-md-10,
+ .ui-md-11,
+ .ui-md-12 {
+ padding: 0.5em;
+ }
+
+ .ui-md-1 {
+ width: 8.3333%;
+ }
+
+ .ui-md-2 {
+ width: 16.6667%;
+ }
+
+ .ui-md-3 {
+ width: 25%;
+ }
+
+ .ui-md-4 {
+ width: 33.3333%;
+ }
+
+ .ui-md-5 {
+ width: 41.6667%;
+ }
+
+ .ui-md-6 {
+ width: 50%;
+ }
+
+ .ui-md-7 {
+ width: 58.3333%;
+ }
+
+ .ui-md-8 {
+ width: 66.6667%;
+ }
+
+ .ui-md-9 {
+ width: 75%;
+ }
+
+ .ui-md-10 {
+ width: 83.3333%;
+ }
+
+ .ui-md-11 {
+ width: 91.6667%;
+ }
+
+ .ui-md-12 {
+ width: 100%;
+ }
+}
+
+@media screen and (min-width: 64.063em) {
+ .ui-lg-1,
+ .ui-lg-2,
+ .ui-lg-3,
+ .ui-lg-4,
+ .ui-lg-5,
+ .ui-lg-6,
+ .ui-lg-7,
+ .ui-lg-8,
+ .ui-lg-9,
+ .ui-lg-10,
+ .ui-lg-11,
+ .ui-lg-12 {
+ padding: 0.5em;
+ }
+
+ .ui-lg-1 {
+ width: 8.3333%;
+ }
+
+ .ui-lg-2 {
+ width: 16.6667%;
+ }
+
+ .ui-lg-3 {
+ width: 25%;
+ }
+
+ .ui-lg-4 {
+ width: 33.3333%;
+ }
+
+ .ui-lg-5 {
+ width: 41.6667%;
+ }
+
+ .ui-lg-6 {
+ width: 50%;
+ }
+
+ .ui-lg-7 {
+ width: 58.3333%;
+ }
+
+ .ui-lg-8 {
+ width: 66.6667%;
+ }
+
+ .ui-lg-9 {
+ width: 75%;
+ }
+
+ .ui-lg-10 {
+ width: 83.3333%;
+ }
+
+ .ui-lg-11 {
+ width: 91.6667%;
+ }
+
+ .ui-lg-12 {
+ width: 100%;
+ }
+}
+
+@media screen and (min-width: 90.063em) {
+ .ui-xl-1,
+ .ui-xl-2,
+ .ui-xl-3,
+ .ui-xl-4,
+ .ui-xl-5,
+ .ui-xl-6,
+ .ui-xl-7,
+ .ui-xl-8,
+ .ui-xl-9,
+ .ui-xl-10,
+ .ui-xl-11,
+ .ui-xl-12 {
+ padding: 0.5em;
+ }
+
+ .ui-xl-1 {
+ width: 8.3333%;
+ }
+
+ .ui-xl-2 {
+ width: 16.6667%;
+ }
+
+ .ui-xl-3 {
+ width: 25%;
+ }
+
+ .ui-xl-4 {
+ width: 33.3333%;
+ }
+
+ .ui-xl-5 {
+ width: 41.6667%;
+ }
+
+ .ui-xl-6 {
+ width: 50%;
+ }
+
+ .ui-xl-7 {
+ width: 58.3333%;
+ }
+
+ .ui-xl-8 {
+ width: 66.6667%;
+ }
+
+ .ui-xl-9 {
+ width: 75%;
+ }
+
+ .ui-xl-10 {
+ width: 83.3333%;
+ }
+
+ .ui-xl-11 {
+ width: 91.6667%;
+ }
+
+ .ui-xl-12 {
+ width: 100%;
+ }
+}
+
+.ui-g-nopad {
+ padding: 0;
+}
diff --git a/src/app/components/growl/growl.css b/src/app/components/growl/growl.css
new file mode 100644
index 00000000000..228b722bd97
--- /dev/null
+++ b/src/app/components/growl/growl.css
@@ -0,0 +1,54 @@
+.ui-growl {
+ position:fixed;
+ top: 20px;
+ right: 20px;
+ width: 20em;
+}
+
+.ui-growl-item-container {
+ position:relative;
+ margin:0 0 10px 0;
+ opacity:0.95;
+ filter:alpha(opacity=95);
+}
+
+.ui-growl-item {
+ position: relative;
+ display: block;
+ padding: .5em 1em;
+}
+
+.ui-growl-item p {
+ padding: 0;
+ margin: 0;
+}
+
+.ui-growl-icon-close {
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ cursor: pointer;
+}
+
+.ui-growl-title {
+ font-weight: bold;
+ padding: 0 0 .5em 0;
+ display: block;
+}
+
+.ui-growl-image {
+ position: absolute;
+ display: inline-block;
+ left: .5em;
+ top: .25em;
+ padding: 0;
+}
+
+.ui-growl-message {
+ padding: 0 0 .25em 0;
+ margin-left: 2.5em;
+}
+
+.ui-growl-message p {
+ font-weight: normal;
+}
\ No newline at end of file
diff --git a/src/app/components/growl/growl.ts b/src/app/components/growl/growl.ts
new file mode 100644
index 00000000000..3bcf7679375
--- /dev/null
+++ b/src/app/components/growl/growl.ts
@@ -0,0 +1,129 @@
+import {NgModule,Component,ElementRef,AfterViewInit,OnDestroy,Input,Output,ViewChild,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {Message} from '../common/message';
+import {DomHandler} from '../dom/domhandler';
+
+@Component({
+ selector: 'p-growl',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class Growl implements AfterViewInit,OnDestroy {
+
+ @Input() sticky: boolean = false;
+
+ @Input() life: number = 3000;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Output() onClose: EventEmitter = new EventEmitter();
+
+ @Output() valueChange: EventEmitter = new EventEmitter();
+
+ @ViewChild('container') containerViewChild: ElementRef;
+
+ _value: Message[];
+
+ zIndex: number;
+
+ container: HTMLDivElement;
+
+ timeout: any;
+
+ preventRerender: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {
+ this.zIndex = DomHandler.zindex;
+ }
+
+ ngAfterViewInit() {
+ this.container = this.containerViewChild.nativeElement;
+ }
+
+ @Input() get value(): Message[] {
+ return this._value;
+ }
+
+ set value(val:Message[]) {
+ this._value = val;
+ if(this.container) {
+ this.handleValueChange();
+ }
+ }
+
+ handleValueChange() {
+ if(this.preventRerender) {
+ this.preventRerender = false;
+ return;
+ }
+
+ this.zIndex = ++DomHandler.zindex;
+ this.domHandler.fadeIn(this.container, 250);
+
+ if(!this.sticky) {
+ if(this.timeout) {
+ clearTimeout(this.timeout);
+ }
+ this.timeout = setTimeout(() => {
+ this.removeAll();
+ }, this.life);
+ }
+ }
+
+ remove(index: number, msgel: any) {
+ this.domHandler.fadeOut(msgel, 250);
+
+ setTimeout(() => {
+ this.preventRerender = true;
+ this.onClose.emit({message:this.value[index]});
+ this._value = this.value.filter((val,i) => i!=index);
+ this.valueChange.emit(this._value);
+ }, 250);
+ }
+
+ removeAll() {
+ if(this.value && this.value.length) {
+ this.domHandler.fadeOut(this.container, 250);
+
+ setTimeout(() => {
+ this.value.forEach((msg,index) => this.onClose.emit({message:this.value[index]}));
+ this.value = [];
+ this.valueChange.emit(this.value);
+ }, 250);
+ }
+ }
+
+ ngOnDestroy() {
+ if(!this.sticky) {
+ clearTimeout(this.timeout);
+ }
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Growl],
+ declarations: [Growl]
+})
+export class GrowlModule { }
\ No newline at end of file
diff --git a/src/app/components/inplace/inplace.css b/src/app/components/inplace/inplace.css
new file mode 100644
index 00000000000..05ed40149f6
--- /dev/null
+++ b/src/app/components/inplace/inplace.css
@@ -0,0 +1,11 @@
+.ui-inplace .ui-inplace-display {
+ display: inline;
+ cursor: pointer;
+ border: 0 none;
+ padding: .25em;
+ font-weight: normal;
+}
+
+.ui-inplace .ui-inplace-content {
+ display: inline;
+}
\ No newline at end of file
diff --git a/src/app/components/inplace/inplace.ts b/src/app/components/inplace/inplace.ts
new file mode 100644
index 00000000000..8270f3e1800
--- /dev/null
+++ b/src/app/components/inplace/inplace.ts
@@ -0,0 +1,71 @@
+import {NgModule,Component,Input,Output,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {ButtonModule} from '../button/button';
+
+@Component({
+ selector: 'p-inplaceDisplay',
+ template: ''
+})
+export class InplaceDisplay {}
+
+@Component({
+ selector: 'p-inplaceContent',
+ template: ''
+})
+export class InplaceContent {}
+
+@Component({
+ selector: 'p-inplace',
+ template: `
+
+ `
+})
+export class Inplace {
+
+ @Input() active: boolean;
+
+ @Input() closable: boolean;
+
+ @Input() disabled: boolean;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Output() onActivate: EventEmitter = new EventEmitter();
+
+ @Output() onDeactivate: EventEmitter = new EventEmitter();
+
+ hover: boolean;
+
+ activate(event) {
+ if(!this.disabled) {
+ this.active = true;
+ this.onActivate.emit(event);
+ }
+ }
+
+ deactivate(event) {
+ if(!this.disabled) {
+ this.active = false;
+ this.hover = false;
+ this.onDeactivate.emit(event);
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,ButtonModule],
+ exports: [Inplace,InplaceDisplay,InplaceContent,ButtonModule],
+ declarations: [Inplace,InplaceDisplay,InplaceContent]
+})
+export class InplaceModule { }
\ No newline at end of file
diff --git a/src/app/components/inputmask/inputmask.ts b/src/app/components/inputmask/inputmask.ts
new file mode 100644
index 00000000000..a86a3e09853
--- /dev/null
+++ b/src/app/components/inputmask/inputmask.ts
@@ -0,0 +1,590 @@
+/*
+ Port of jQuery MaskedInput by DigitalBush as a Native Angular2 Component in Typescript without jQuery
+ https://github.com/digitalBush/jquery.maskedinput/
+
+ Copyright (c) 2007-2014 Josh Bush (digitalbush.com)
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+*/
+import {NgModule,Component,ElementRef,OnInit,OnDestroy,HostBinding,HostListener,Input,forwardRef,Output,EventEmitter,ViewChild} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {InputTextModule} from '../inputtext/inputtext';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const INPUTMASK_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => InputMask),
+ multi: true
+};
+
+@Component({
+ selector: 'p-inputMask',
+ template: ``,
+ host: {
+ '[class.ui-inputwrapper-filled]': 'filled',
+ '[class.ui-inputwrapper-focus]': 'focus'
+ },
+ providers: [INPUTMASK_VALUE_ACCESSOR,DomHandler]
+})
+export class InputMask implements OnInit,OnDestroy,ControlValueAccessor {
+
+ @Input() mask: string;
+
+ @Input() type: string = 'text';
+
+ @Input() slotChar: string = '_';
+
+ @Input() autoClear: boolean = true;
+
+ @Input() style: string;
+
+ @Input() inputId: string;
+
+ @Input() styleClass: string;
+
+ @Input() placeholder: string;
+
+ @Input() size: number;
+
+ @Input() maxlength: number;
+
+ @Input() tabindex: string;
+
+ @Input() disabled: boolean;
+
+ @Input() readonly: boolean;
+
+ @Input() unmask: boolean;
+
+ @Input() name: string;
+
+ @ViewChild('input') inputViewChild: ElementRef;
+
+ @Output() onComplete: EventEmitter = new EventEmitter();
+
+ @Output() onBlur: EventEmitter = new EventEmitter();
+
+ value: any;
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ input: HTMLInputElement;
+
+ filled: boolean;
+
+ defs: any;
+
+ tests: any[];
+
+ partialPosition: any;
+
+ firstNonMaskPos: number;
+
+ lastRequiredNonMaskPos: any;
+
+ len: number;
+
+ oldVal: string;
+
+ buffer: any;
+
+ defaultBuffer: string;
+
+ focusText: string;
+
+ caretTimeoutId: any;
+
+ androidChrome: boolean;
+
+ focus: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ ngOnInit() {
+ this.tests = [];
+ this.partialPosition = this.mask.length;
+ this.len = this.mask.length;
+ this.firstNonMaskPos = null;
+ this.defs = {
+ '9': '[0-9]',
+ 'a': '[A-Za-z]',
+ '*': '[A-Za-z0-9]'
+ };
+
+ let ua = this.domHandler.getUserAgent();
+ this.androidChrome = /chrome/i.test(ua) && /android/i.test(ua);
+
+ let maskTokens = this.mask.split('');
+ for(let i = 0; i < maskTokens.length; i++) {
+ let c = maskTokens[i];
+ if (c == '?') {
+ this.len--;
+ this.partialPosition = i;
+ }
+ else if (this.defs[c]) {
+ this.tests.push(new RegExp(this.defs[c]));
+ if(this.firstNonMaskPos === null) {
+ this.firstNonMaskPos = this.tests.length - 1;
+ }
+ if(i < this.partialPosition){
+ this.lastRequiredNonMaskPos = this.tests.length - 1;
+ }
+ }
+ else {
+ this.tests.push(null);
+ }
+ }
+
+ this.buffer = [];
+ for(let i = 0; i < maskTokens.length; i++) {
+ let c = maskTokens[i];
+ if(c != '?') {
+ if(this.defs[c])
+ this.buffer.push(this.getPlaceholder(i));
+ else
+ this.buffer.push(c);
+ }
+ }
+ this.defaultBuffer = this.buffer.join('');
+ }
+
+ writeValue(value: any) : void {
+ this.value = value;
+
+ if(this.inputViewChild.nativeElement) {
+ if(this.value == undefined || this.value == null)
+ this.inputViewChild.nativeElement.value = '';
+ else
+ this.inputViewChild.nativeElement.value = this.value;
+
+ this.checkVal();
+ this.focusText = this.inputViewChild.nativeElement.value;
+ this.updateFilledState();
+ }
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ caret(first?: number, last?: number) {
+ let range, begin, end;
+
+ if(!this.inputViewChild.nativeElement.offsetParent||this.inputViewChild.nativeElement !== document.activeElement) {
+ return;
+ }
+
+ if(typeof first == 'number') {
+ begin = first;
+ end = (typeof last === 'number') ? last : begin;
+ if(this.inputViewChild.nativeElement.setSelectionRange) {
+ this.inputViewChild.nativeElement.setSelectionRange(begin, end);
+ }
+ else if(this.inputViewChild.nativeElement['createTextRange']) {
+ range = this.inputViewChild.nativeElement['createTextRange']();
+ range.collapse(true);
+ range.moveEnd('character', end);
+ range.moveStart('character', begin);
+ range.select();
+ }
+ }
+ else {
+ if (this.inputViewChild.nativeElement.setSelectionRange) {
+ begin = this.inputViewChild.nativeElement.selectionStart;
+ end = this.inputViewChild.nativeElement.selectionEnd;
+ }
+ else if (document['selection'] && document['selection'].createRange) {
+ range = document['selection'].createRange();
+ begin = 0 - range.duplicate().moveStart('character', -100000);
+ end = begin + range.text.length;
+ }
+
+ return {begin: begin, end: end};
+ }
+ }
+
+ isCompleted(): boolean {
+ let completed: boolean;
+ for (let i = this.firstNonMaskPos; i <= this.lastRequiredNonMaskPos; i++) {
+ if (this.tests[i] && this.buffer[i] === this.getPlaceholder(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ getPlaceholder(i: number) {
+ if(i < this.slotChar.length) {
+ return this.slotChar.charAt(i);
+ }
+ return this.slotChar.charAt(0);
+ }
+
+ seekNext(pos) {
+ while (++pos < this.len && !this.tests[pos]);
+ return pos;
+ }
+
+ seekPrev(pos) {
+ while (--pos >= 0 && !this.tests[pos]);
+ return pos;
+ }
+
+ shiftL(begin:number,end:number) {
+ let i, j;
+
+ if (begin<0) {
+ return;
+ }
+
+ for (i = begin, j = this.seekNext(end); i < this.len; i++) {
+ if (this.tests[i]) {
+ if (j < this.len && this.tests[i].test(this.buffer[j])) {
+ this.buffer[i] = this.buffer[j];
+ this.buffer[j] = this.getPlaceholder(j);
+ } else {
+ break;
+ }
+
+ j = this.seekNext(j);
+ }
+ }
+ this.writeBuffer();
+ this.caret(Math.max(this.firstNonMaskPos, begin));
+ }
+
+ shiftR(pos) {
+ let i, c, j, t;
+
+ for (i = pos, c = this.getPlaceholder(pos); i < this.len; i++) {
+ if (this.tests[i]) {
+ j = this.seekNext(i);
+ t = this.buffer[i];
+ this.buffer[i] = c;
+ if (j < this.len && this.tests[j].test(t)) {
+ c = t;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ handleAndroidInput(e) {
+ var curVal = this.inputViewChild.nativeElement.value;
+ var pos = this.caret();
+ if (this.oldVal && this.oldVal.length && this.oldVal.length > curVal.length ) {
+ // a deletion or backspace happened
+ this.checkVal(true);
+ while (pos.begin > 0 && !this.tests[pos.begin-1])
+ pos.begin--;
+ if (pos.begin === 0)
+ {
+ while (pos.begin < this.firstNonMaskPos && !this.tests[pos.begin])
+ pos.begin++;
+ }
+ this.caret(pos.begin,pos.begin);
+ } else {
+ this.checkVal(true);
+ while (pos.begin < this.len && !this.tests[pos.begin - 1])
+ pos.begin++;
+
+ setTimeout(() => this.caret(pos.begin, pos.begin));
+ }
+
+ if(this.isCompleted()) {
+ this.onComplete.emit();
+ }
+ }
+
+ onInputBlur(e) {
+ this.focus = false;
+ this.onModelTouched();
+ this.checkVal();
+ this.updateModel(e);
+ this.updateFilledState();
+ this.onBlur.emit(e);
+
+ if (this.inputViewChild.nativeElement.value != this.focusText) {
+ let event = document.createEvent('HTMLEvents');
+ event.initEvent('change', true, false);
+ this.inputViewChild.nativeElement.dispatchEvent(event);
+ }
+ }
+
+ onKeyDown(e) {
+ if (this.readonly) {
+ return;
+ }
+
+ let k = e.which||e.keyCode,
+ pos,
+ begin,
+ end;
+ let iPhone = /iphone/i.test(this.domHandler.getUserAgent());
+ this.oldVal = this.inputViewChild.nativeElement.value;
+
+ //backspace, delete, and escape get special treatment
+ if (k === 8 || k === 46 || (iPhone && k === 127)) {
+ pos = this.caret();
+ begin = pos.begin;
+ end = pos.end;
+
+
+ if (end - begin === 0) {
+ begin=k!==46?this.seekPrev(begin):(end=this.seekNext(begin-1));
+ end=k===46?this.seekNext(end):end;
+ }
+
+ this.clearBuffer(begin, end);
+ this.shiftL(begin, end - 1);
+ this.updateModel(e);
+
+ e.preventDefault();
+ } else if( k === 13 ) { // enter
+ this.onInputBlur(e);
+ this.updateModel(e);
+ } else if (k === 27) { // escape
+ this.inputViewChild.nativeElement.value = this.focusText;
+ this.caret(0, this.checkVal());
+ this.updateModel(e);
+ e.preventDefault();
+ }
+ }
+
+ onKeyPress(e) {
+ if (this.readonly){
+ return;
+ }
+
+ var k = e.which || e.keyCode,
+ pos = this.caret(),
+ p,
+ c,
+ next,
+ completed;
+
+ if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore
+ return;
+ } else if ( k && k !== 13 ) {
+ if (pos.end - pos.begin !== 0){
+ this.clearBuffer(pos.begin, pos.end);
+ this.shiftL(pos.begin, pos.end-1);
+ }
+
+ p = this.seekNext(pos.begin - 1);
+ if (p < this.len) {
+ c = String.fromCharCode(k);
+ if (this.tests[p].test(c)) {
+ this.shiftR(p);
+
+ this.buffer[p] = c;
+ this.writeBuffer();
+ next = this.seekNext(p);
+
+ if(/android/i.test(this.domHandler.getUserAgent())){
+ //Path for CSP Violation on FireFox OS 1.1
+ let proxy = () => {
+ this.caret(next);
+ };
+
+ setTimeout(proxy,0);
+ }else{
+ this.caret(next);
+ }
+ if(pos.begin <= this.lastRequiredNonMaskPos){
+ completed = this.isCompleted();
+ }
+ }
+ }
+ e.preventDefault();
+ }
+
+ this.updateModel(e);
+
+ this.updateFilledState();
+
+ if(completed) {
+ this.onComplete.emit();
+ }
+ }
+
+ clearBuffer(start, end) {
+ let i;
+ for (i = start; i < end && i < this.len; i++) {
+ if (this.tests[i]) {
+ this.buffer[i] = this.getPlaceholder(i);
+ }
+ }
+ }
+
+ writeBuffer() {
+ this.inputViewChild.nativeElement.value = this.buffer.join('');
+ }
+
+ checkVal(allow?: boolean) {
+ //try to place characters where they belong
+ let test = this.inputViewChild.nativeElement.value,
+ lastMatch = -1,
+ i,
+ c,
+ pos;
+
+ for (i = 0, pos = 0; i < this.len; i++) {
+ if (this.tests[i]) {
+ this.buffer[i] = this.getPlaceholder(i);
+ while (pos++ < test.length) {
+ c = test.charAt(pos - 1);
+ if (this.tests[i].test(c)) {
+ this.buffer[i] = c;
+ lastMatch = i;
+ break;
+ }
+ }
+ if (pos > test.length) {
+ this.clearBuffer(i + 1, this.len);
+ break;
+ }
+ } else {
+ if (this.buffer[i] === test.charAt(pos)) {
+ pos++;
+ }
+ if( i < this.partialPosition){
+ lastMatch = i;
+ }
+ }
+ }
+ if (allow) {
+ this.writeBuffer();
+ } else if (lastMatch + 1 < this.partialPosition) {
+ if (this.autoClear || this.buffer.join('') === this.defaultBuffer) {
+ // Invalid value. Remove it and replace it with the
+ // mask, which is the default behavior.
+ if(this.inputViewChild.nativeElement.value) this.inputViewChild.nativeElement.value = '';
+ this.clearBuffer(0, this.len);
+ } else {
+ // Invalid value, but we opt to show the value to the
+ // user and allow them to correct their mistake.
+ this.writeBuffer();
+ }
+ } else {
+ this.writeBuffer();
+ this.inputViewChild.nativeElement.value = this.inputViewChild.nativeElement.value.substring(0, lastMatch + 1);
+ }
+ return (this.partialPosition ? i : this.firstNonMaskPos);
+ }
+
+ onFocus(event) {
+ if (this.readonly){
+ return;
+ }
+
+ this.focus = true;
+
+ clearTimeout(this.caretTimeoutId);
+ let pos;
+
+ this.focusText = this.inputViewChild.nativeElement.value;
+
+ pos = this.checkVal();
+
+ this.caretTimeoutId = setTimeout(() => {
+ if(this.inputViewChild.nativeElement !== document.activeElement){
+ return;
+ }
+ this.writeBuffer();
+ if (pos == this.mask.replace("?","").length) {
+ this.caret(0, pos);
+ } else {
+ this.caret(pos);
+ }
+ }, 10);
+ }
+
+ onInput(event) {
+ if (this.androidChrome)
+ this.handleAndroidInput(event);
+ else
+ this.handleInputChange(event);
+ }
+
+ handleInputChange(event) {
+ if (this.readonly){
+ return;
+ }
+
+ setTimeout(() => {
+ var pos = this.checkVal(true);
+ this.caret(pos);
+ this.updateModel(event);
+ if(this.isCompleted()) {
+ this.onComplete.emit();
+ }
+ }, 0);
+ }
+
+ getUnmaskedValue() {
+ let unmaskedBuffer = [];
+ for(let i = 0; i < this.buffer.length; i++) {
+ let c = this.buffer[i];
+ if(this.tests[i] && c != this.getPlaceholder(i)) {
+ unmaskedBuffer.push(c);
+ }
+ }
+
+ return unmaskedBuffer.join('');
+ }
+
+ updateModel(e) {
+ this.onModelChange(this.unmask ? this.getUnmaskedValue() : e.target.value);
+ }
+
+ updateFilledState() {
+ this.filled = this.inputViewChild.nativeElement && this.inputViewChild.nativeElement.value != '';
+ }
+
+ ngOnDestroy() {
+
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,InputTextModule],
+ exports: [InputMask],
+ declarations: [InputMask]
+})
+export class InputMaskModule { }
\ No newline at end of file
diff --git a/src/app/components/inputswitch/inputswitch.css b/src/app/components/inputswitch/inputswitch.css
new file mode 100644
index 00000000000..da01d4378cc
--- /dev/null
+++ b/src/app/components/inputswitch/inputswitch.css
@@ -0,0 +1,58 @@
+.ui-inputswitch {
+ display: inline-block;
+ padding: 0;
+ position: relative;
+ overflow: hidden;
+ cursor: pointer;
+ user-select: none;
+ -moz-user-select: none;
+ -khtml-user-select: none;
+ -webkit-user-select: none;
+ height: 1.5em;
+}
+
+.ui-inputswitch .ui-inputswitch-on,
+.ui-inputswitch .ui-inputswitch-off {
+ white-space: nowrap;
+ display: inline-block;
+ position: absolute;
+ top: 0;
+ width: auto;
+ overflow: hidden;
+ user-select: none;
+ -moz-user-select: none;
+ -khtml-user-select: none;
+ -webkit-user-select: none;
+ font-weight: bold;
+ height: 100%;
+ line-height: 1.5em;
+}
+
+.ui-inputswitch .ui-inputswitch-on {
+ left: 0;
+ border: 0 none;
+}
+
+.ui-inputswitch .ui-inputswitch-off {
+ right: 0;
+ text-align: right;
+}
+
+.ui-inputswitch .ui-inputswitch-on span,
+.ui-inputswitch .ui-inputswitch-off span {
+ display: inline-block;
+ text-align: center;
+ height: 100%;
+ line-height: inherit;
+}
+
+.ui-inputswitch .ui-inputswitch-handle {
+ display: block;
+ width: 0;
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ border-top: 0 none;
+ border-bottom: 0 none;
+}
\ No newline at end of file
diff --git a/src/app/components/inputswitch/inputswitch.ts b/src/app/components/inputswitch/inputswitch.ts
new file mode 100644
index 00000000000..52ab91305ee
--- /dev/null
+++ b/src/app/components/inputswitch/inputswitch.ts
@@ -0,0 +1,200 @@
+import {NgModule,Component,ElementRef,AfterViewInit,AfterViewChecked,OnChanges,Input,forwardRef,EventEmitter,Output} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {NG_VALUE_ACCESSOR,ControlValueAccessor} from '@angular/forms';
+import {DomHandler} from '../dom/domhandler';
+
+export const INPUTSWITCH_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => InputSwitch),
+ multi: true
+};
+
+@Component({
+ selector: 'p-inputSwitch',
+ template: `
+
+
+ {{offLabel}}
+
+
+ {{onLabel}}
+
+
+
+
+
+
+ `,
+ providers: [INPUTSWITCH_VALUE_ACCESSOR,DomHandler]
+})
+export class InputSwitch implements ControlValueAccessor,AfterViewInit,AfterViewChecked {
+
+ @Input() onLabel: string = 'On';
+
+ @Input() offLabel: string = 'Off';
+
+ @Input() disabled: boolean;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() tabindex: number;
+
+ @Input() inputId: string;
+
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ checked: boolean = false;
+
+ focused: boolean = false;
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ public container: any;
+
+ public handle: any;
+
+ public onContainer: any;
+
+ public offContainer: any;
+
+ public onLabelChild: any;
+
+ public offLabelChild: any;
+
+ public offset: any;
+
+ initialized: boolean = false;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ ngAfterViewInit() {
+ this.container = this.el.nativeElement.children[0];
+ this.handle = this.domHandler.findSingle(this.el.nativeElement, 'div.ui-inputswitch-handle');
+ this.onContainer = this.domHandler.findSingle(this.container,'div.ui-inputswitch-on');
+ this.offContainer = this.domHandler.findSingle(this.container,'div.ui-inputswitch-off');
+ this.onLabelChild = this.domHandler.findSingle(this.onContainer,'span.ui-inputswitch-onlabel');
+ this.offLabelChild = this.domHandler.findSingle(this.offContainer,'span.ui-inputswitch-offlabel');
+ }
+
+ ngAfterViewChecked() {
+ if(this.container.offsetParent && !this.initialized) {
+ this.render();
+ }
+ }
+
+ render() {
+ let onContainerWidth = this.domHandler.width(this.onContainer),
+ offContainerWidth = this.domHandler.width(this.offContainer),
+ spanPadding = this.domHandler.innerWidth(this.offLabelChild) - this.domHandler.width(this.offLabelChild),
+ handleMargins = this.domHandler.getOuterWidth(this.handle) - this.domHandler.innerWidth(this.handle);
+
+ var containerWidth = (onContainerWidth > offContainerWidth) ? onContainerWidth : offContainerWidth,
+ handleWidth = containerWidth;
+
+ this.handle.style.width = handleWidth + 'px';
+ handleWidth = this.domHandler.width(this.handle);
+ containerWidth = containerWidth + handleWidth + 6;
+
+ var labelWidth = containerWidth - handleWidth - spanPadding - handleMargins;
+
+ this.container.style.width = containerWidth + 'px';
+ this.onLabelChild.style.width = labelWidth + 'px';
+ this.offLabelChild.style.width = labelWidth + 'px';
+
+ //position
+ this.offContainer.style.width = (this.domHandler.width(this.container) - 5) + 'px';
+ this.offset = this.domHandler.width(this.container) - this.domHandler.getOuterWidth(this.handle);
+
+ //default value
+ if(this.checked) {
+ this.handle.style.left = this.offset + 'px';
+ this.onContainer.style.width = this.offset + 'px';
+ this.offLabelChild.style.marginRight = -this.offset + 'px';
+ }
+ else {
+ this.onContainer.style.width = 0 + 'px';
+ this.onLabelChild.style.marginLeft = -this.offset + 'px';
+ }
+
+ this.initialized = true;
+ }
+
+ toggle(event,checkbox) {
+ if(!this.disabled) {
+ if(this.checked) {
+ this.checked = false;
+ this.uncheckUI()
+ }
+ else {
+ this.checked = true;
+ this.checkUI();
+ }
+
+ this.onModelChange(this.checked);
+ this.onChange.emit({
+ originalEvent: event,
+ checked: this.checked
+ });
+ checkbox.focus();
+ }
+ }
+
+ checkUI() {
+ this.onContainer.style.width = this.offset + 'px';
+ this.onLabelChild.style.marginLeft = 0 + 'px';
+ this.offLabelChild.style.marginRight = -this.offset + 'px';
+ this.handle.style.left = this.offset + 'px';
+ }
+
+ uncheckUI() {
+ this.onContainer.style.width = 0 + 'px';
+ this.onLabelChild.style.marginLeft = -this.offset + 'px';
+ this.offLabelChild.style.marginRight = 0 + 'px';
+ this.handle.style.left = 0 + 'px';
+ }
+
+ onFocus(event) {
+ this.focused = true;
+ }
+
+ onBlur(event) {
+ this.focused = false;
+ this.onModelTouched();
+ }
+
+ writeValue(checked: any) : void {
+ this.checked = checked;
+
+ if(this.initialized) {
+ if(this.checked === true)
+ this.checkUI();
+ else
+ this.uncheckUI();
+ }
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [InputSwitch],
+ declarations: [InputSwitch]
+})
+export class InputSwitchModule { }
diff --git a/src/app/components/inputtext/inputtext.css b/src/app/components/inputtext/inputtext.css
new file mode 100644
index 00000000000..1163fe103eb
--- /dev/null
+++ b/src/app/components/inputtext/inputtext.css
@@ -0,0 +1,18 @@
+.ui-inputtext {
+ margin: 0;
+ outline: medium none;
+ padding: .25em;
+ font-weight: normal;
+}
+
+.ui-widget-header .ui-inputtext,
+.ui-widget-content .ui-inputtext {
+ font-weight: normal;
+}
+
+.ui-fluid .ui-inputtext {
+ width: 100%;
+ box-sizing: border-box;
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing: border-box;
+}
\ No newline at end of file
diff --git a/src/app/components/inputtext/inputtext.ts b/src/app/components/inputtext/inputtext.ts
new file mode 100644
index 00000000000..6acba8eefb7
--- /dev/null
+++ b/src/app/components/inputtext/inputtext.ts
@@ -0,0 +1,40 @@
+import {NgModule,Directive,ElementRef,HostListener,Input,DoCheck} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+@Directive({
+ selector: '[pInputText]',
+ host: {
+ '[class.ui-inputtext]': 'true',
+ '[class.ui-corner-all]': 'true',
+ '[class.ui-state-default]': 'true',
+ '[class.ui-widget]': 'true',
+ '[class.ui-state-filled]': 'filled'
+ }
+})
+export class InputText implements DoCheck {
+
+ filled: boolean;
+
+ constructor(public el: ElementRef) {}
+
+ ngDoCheck() {
+ this.updateFilledState();
+ }
+
+ //To trigger change detection to manage ui-state-filled for material labels when there is no value binding
+ @HostListener('input', ['$event'])
+ onInput(e) {
+ this.updateFilledState();
+ }
+
+ updateFilledState() {
+ this.filled = this.el.nativeElement.value && this.el.nativeElement.value.length;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [InputText],
+ declarations: [InputText]
+})
+export class InputTextModule { }
\ No newline at end of file
diff --git a/src/app/components/inputtextarea/inputtextarea.css b/src/app/components/inputtextarea/inputtextarea.css
new file mode 100644
index 00000000000..f7ee7d87e0a
--- /dev/null
+++ b/src/app/components/inputtextarea/inputtextarea.css
@@ -0,0 +1,8 @@
+.ui-inputtextarea-resizable {
+ overflow: hidden;
+ resize:none;
+}
+
+.ui-fluid .ui-inputtextarea {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/src/app/components/inputtextarea/inputtextarea.ts b/src/app/components/inputtextarea/inputtextarea.ts
new file mode 100644
index 00000000000..28c8b0ae92b
--- /dev/null
+++ b/src/app/components/inputtextarea/inputtextarea.ts
@@ -0,0 +1,89 @@
+import {NgModule,Directive,ElementRef,HostListener,Input,OnInit,DoCheck} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+@Directive({
+ selector: '[pInputTextarea]',
+ host: {
+ '[class.ui-inputtext]': 'true',
+ '[class.ui-corner-all]': 'true',
+ '[class.ui-state-default]': 'true',
+ '[class.ui-widget]': 'true',
+ '[class.ui-state-filled]': 'filled',
+ '[attr.rows]': 'rows',
+ '[attr.cols]': 'cols'
+ }
+})
+export class InputTextarea implements OnInit,DoCheck {
+
+ @Input() autoResize: boolean;
+
+ @Input() rows: number;
+
+ @Input() cols: number;
+
+ rowsDefault: number;
+
+ colsDefault: number;
+
+ filled: boolean;
+
+ constructor(public el: ElementRef) {}
+
+ ngOnInit() {
+ this.rowsDefault = this.rows;
+ this.colsDefault = this.cols;
+ }
+
+ ngDoCheck() {
+ this.updateFilledState();
+ }
+
+ //To trigger change detection to manage ui-state-filled for material labels when there is no value binding
+ @HostListener('input', ['$event'])
+ onInput(e) {
+ this.updateFilledState();
+ }
+
+ updateFilledState() {
+ this.filled = this.el.nativeElement.value && this.el.nativeElement.value.length;
+ }
+
+ @HostListener('focus', ['$event'])
+ onFocus(e) {
+ if(this.autoResize) {
+ this.resize();
+ }
+ }
+
+ @HostListener('blur', ['$event'])
+ onBlur(e) {
+ if(this.autoResize) {
+ this.resize();
+ }
+ }
+
+ @HostListener('keyup', ['$event'])
+ onKeyup(e) {
+ if(this.autoResize) {
+ this.resize();
+ }
+ }
+
+ resize () {
+ let linesCount = 0,
+ lines = this.el.nativeElement.value.split('\n');
+
+ for(let i = lines.length-1; i >= 0 ; --i) {
+ linesCount += Math.floor((lines[i].length / this.colsDefault) + 1);
+ }
+
+ this.rows = (linesCount >= this.rowsDefault) ? (linesCount + 1) : this.rowsDefault;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [InputTextarea],
+ declarations: [InputTextarea]
+})
+export class InputTextareaModule { }
\ No newline at end of file
diff --git a/src/app/components/lightbox/images/loading.gif b/src/app/components/lightbox/images/loading.gif
new file mode 100644
index 00000000000..19c67bbd040
Binary files /dev/null and b/src/app/components/lightbox/images/loading.gif differ
diff --git a/src/app/components/lightbox/lightbox.css b/src/app/components/lightbox/lightbox.css
new file mode 100644
index 00000000000..6e53a3c74d9
--- /dev/null
+++ b/src/app/components/lightbox/lightbox.css
@@ -0,0 +1,60 @@
+.ui-lightbox {
+ position: fixed;
+}
+
+.ui-lightbox-content-wrapper {
+ position: relative;
+}
+
+.ui-lightbox-content {
+ position: relative;
+ margin: 0;
+ padding: 0;
+ background-color: #000000;
+}
+
+.ui-lightbox-nav-right, .ui-lightbox-nav-left {
+ position: absolute;
+ top: 50%;
+ cursor: pointer;
+}
+
+.ui-lightbox-nav-left {
+ left: 0;
+}
+
+.ui-lightbox-nav-right {
+ right: 0;
+}
+
+.ui-lightbox-loading {
+ background: url("./images/loading.gif") #000000 center center no-repeat;
+}
+
+.ui-lightbox-caption {
+ padding: 0.2em 0.4em;
+ display: none;
+}
+
+.ui-lightbox-caption-text {
+ margin: 0.3em 0 0.1em 0;
+ float:left;
+}
+
+.ui-lightbox-close {
+ float:right;
+ margin: 0;
+ padding: .125em;
+}
+
+.ui-lightbox-close.ui-state-hover {
+ padding: 0;
+}
+
+.ui-lightbox-nav-left, .ui-lightbox-nav-right {
+ opacity: .5;
+}
+
+.ui-lightbox-nav-left:hover, .ui-lightbox-nav-right:hover{
+ opacity: 1;
+}
\ No newline at end of file
diff --git a/src/app/components/lightbox/lightbox.ts b/src/app/components/lightbox/lightbox.ts
new file mode 100644
index 00000000000..afdd1422792
--- /dev/null
+++ b/src/app/components/lightbox/lightbox.ts
@@ -0,0 +1,229 @@
+import {NgModule,Component,ElementRef,Input,Output,Renderer2,AfterViewInit,OnDestroy} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+
+@Component({
+ selector: 'p-lightbox',
+ template: `
+
+
+
+
+
+ `,
+ providers: [DomHandler]
+})
+export class Lightbox implements AfterViewInit,OnDestroy{
+
+ @Input() images: any[];
+
+ @Input() type: string = 'image';
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() appendTo: any;
+
+ @Input() easing: 'ease-out';
+
+ @Input() effectDuration: any = '500ms';
+
+ public visible: boolean;
+
+ public loading: boolean;
+
+ public currentImage: any;
+
+ public captionText: string;
+
+ public zindex: any;
+
+ public panel: any;
+
+ public index: number;
+
+ public mask: any;
+
+ public preventDocumentClickListener: boolean;
+
+ public documentClickListener: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2) {}
+
+ onImageClick(event,image,i,content) {
+ this.index = i;
+ this.loading = true;
+ content.style.width = 32 + 'px';
+ content.style.height = 32 + 'px';
+ this.show();
+ this.displayImage(image);
+
+ this.preventDocumentClickListener = true;
+ event.preventDefault();
+ }
+
+ ngAfterViewInit() {
+ this.panel = this.domHandler.findSingle(this.el.nativeElement, '.ui-lightbox ');
+
+ if(this.appendTo) {
+ if(this.appendTo === 'body')
+ document.body.appendChild(this.panel);
+ else
+ this.domHandler.appendChild(this.panel, this.appendTo);
+ }
+
+ this.documentClickListener = this.renderer.listen('document', 'click', (event) => {
+ if(!this.preventDocumentClickListener&&this.visible) {
+ this.hide(event);
+ }
+ this.preventDocumentClickListener = false;
+ });
+ }
+
+ onLinkClick(event,content) {
+ this.show();
+ this.preventDocumentClickListener = true;
+ event.preventDefault();
+ }
+
+ displayImage(image) {
+ setTimeout(() => {
+ this.currentImage = image;
+ this.captionText = image.title;
+ }, 1000);
+ }
+
+ show() {
+ this.mask = document.createElement('div');
+ this.mask.style.zIndex = ++DomHandler.zindex;
+ this.domHandler.addMultipleClasses(this.mask, 'ui-widget-overlay ui-dialog-mask');
+ document.body.appendChild(this.mask);
+
+ this.zindex = ++DomHandler.zindex;
+ this.center();
+ this.visible = true;
+ }
+
+ hide(event) {
+ this.captionText = null;
+ this.index = null;
+ this.currentImage = null;
+ this.visible = false;
+ this.panel.style.left = 'auto';
+ this.panel.style.top = 'auto';
+
+ if(this.mask) {
+ document.body.removeChild(this.mask);
+ this.mask = null;
+ }
+
+ event.preventDefault();
+ }
+
+ center() {
+ let elementWidth = this.domHandler.getOuterWidth(this.panel);
+ let elementHeight = this.domHandler.getOuterHeight(this.panel);
+ if(elementWidth == 0 && elementHeight == 0) {
+ this.panel.style.visibility = 'hidden';
+ this.panel.style.display = 'block';
+ elementWidth = this.domHandler.getOuterWidth(this.panel);
+ elementHeight = this.domHandler.getOuterHeight(this.panel);
+ this.panel.style.display = 'none';
+ this.panel.style.visibility = 'visible';
+ }
+ let viewport = this.domHandler.getViewport();
+ let x = (viewport.width - elementWidth) / 2;
+ let y = (viewport.height - elementHeight) / 2;
+
+ this.panel.style.left = x + 'px';
+ this.panel.style.top = y + 'px';
+ }
+
+ onImageLoad(event,content) {
+ let image = event.target;
+ image.style.visibility = 'hidden';
+ image.style.display = 'block';
+ let imageWidth = this.domHandler.getOuterWidth(image);
+ let imageHeight = this.domHandler.getOuterHeight(image);
+ image.style.display = 'none';
+ image.style.visibility = 'visible';
+
+ content.style.width = imageWidth + 'px';
+ content.style.height = imageHeight + 'px';
+ this.panel.style.left = parseInt(this.panel.style.left) + (this.domHandler.getOuterWidth(this.panel) - imageWidth) / 2 + 'px';
+ this.panel.style.top = parseInt(this.panel.style.top) + (this.domHandler.getOuterHeight(this.panel) - imageHeight) / 2 + 'px';
+
+ setTimeout(() => {
+ this.domHandler.fadeIn(image, 500);
+ image.style.display = 'block';
+ //this.captionText = this.currentImage.title;
+ this.loading = false;
+ }, parseInt(this.effectDuration));
+ }
+
+ prev(placeholder: any) {
+ this.captionText = null;
+ this.loading = true;
+ placeholder.style.display = 'none';
+ if(this.index > 0) {
+ this.displayImage(this.images[--this.index]);
+ }
+ }
+
+ next(placeholder: any) {
+ this.captionText = null;
+ this.loading = true;
+ placeholder.style.display = 'none';
+ if(this.index <= (this.images.length - 1)) {
+ this.displayImage(this.images[++this.index]);
+ }
+ }
+
+ get leftVisible():boolean {
+ return this.images && this.images.length && this.index != 0 && !this.loading;
+ }
+
+ get rightVisible():boolean {
+ return this.images && this.images.length && this.index < (this.images.length - 1) && !this.loading;
+ }
+
+ ngOnDestroy() {
+ if(this.documentClickListener) {
+ this.documentClickListener();
+ }
+
+ if(this.appendTo) {
+ this.el.nativeElement.appendChild(this.panel);
+ }
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Lightbox],
+ declarations: [Lightbox]
+})
+export class LightboxModule { }
diff --git a/src/app/components/listbox/listbox.css b/src/app/components/listbox/listbox.css
new file mode 100644
index 00000000000..cda0cd5a501
--- /dev/null
+++ b/src/app/components/listbox/listbox.css
@@ -0,0 +1,67 @@
+.ui-listbox {
+ overflow:auto;
+ padding: .25em;
+ width: 10em;
+}
+
+.ui-listbox .ui-listbox-list {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+.ui-listbox .ui-listbox-item {
+ padding: .25em;
+ border: 0 none;
+ cursor: pointer;
+ font-weight: normal;
+ margin-bottom: 1px;
+}
+
+.ui-listbox .ui-listbox-item > span {
+ vertical-align: middle;
+}
+
+.ui-listbox .ui-listbox-item:last-child {
+ margin-bottom: 0;
+}
+
+.ui-listbox.ui-state-disabled .ui-listbox-item {
+ cursor: default;
+}
+
+.ui-listbox-header {
+ margin-bottom: 0.3em;
+ padding: .125em .2em;
+ position: relative;
+}
+
+.ui-listbox-header .ui-chkbox {
+ display: inline-block;
+ vertical-align: middle;
+ cursor: pointer;
+}
+
+.ui-listbox-header .ui-listbox-filter-container {
+ display: inline-block;
+ vertical-align: middle;
+ position: relative;
+ width: 7em;
+}
+
+.ui-listbox-header .ui-listbox-filter-container .fa {
+ position: absolute;
+ top: .25em;
+ left: .25em;
+}
+
+.ui-listbox-header .ui-inputtext {
+ padding: .125em .125em .125em 1.25em;
+ width: 100%;
+}
+
+/* Fluid */
+.ui-fluid .ui-listbox .ui-listbox-filter-container,
+.ui-fluid .ui-listbox .ui-listbox-filter-container input {
+ width: calc(100% - 32px);
+}
\ No newline at end of file
diff --git a/src/app/components/listbox/listbox.ts b/src/app/components/listbox/listbox.ts
new file mode 100644
index 00000000000..c15598fee5d
--- /dev/null
+++ b/src/app/components/listbox/listbox.ts
@@ -0,0 +1,371 @@
+import {NgModule,Component,ElementRef,Input,Output,EventEmitter,AfterContentInit,ContentChildren,QueryList,TemplateRef,IterableDiffers,forwardRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {SelectItem} from '../common/selectitem';
+import {SharedModule,PrimeTemplate} from '../common/shared';
+import {DomHandler} from '../dom/domhandler';
+import {ObjectUtils} from '../utils/ObjectUtils';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const LISTBOX_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => Listbox),
+ multi: true
+};
+
+@Component({
+ selector: 'p-listbox',
+ template: `
+
+
+
+ -
+
+ {{option.label}}
+
+
+
+
+ `,
+ providers: [DomHandler,ObjectUtils,LISTBOX_VALUE_ACCESSOR]
+})
+export class Listbox implements AfterContentInit,ControlValueAccessor {
+
+ @Input() options: SelectItem[];
+
+ @Input() multiple: boolean;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() disabled: boolean;
+
+ @Input() checkbox: boolean = false;
+
+ @Input() filter: boolean = false;
+
+ @Input() metaKeySelection: boolean = true;
+
+ @Input() dataKey: string;
+
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ @Output() onDblClick: EventEmitter = new EventEmitter();
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public itemTemplate: TemplateRef;
+
+ public filterValue: string;
+
+ public visibleOptions: SelectItem[];
+
+ public filtered: boolean;
+
+ public value: any;
+
+ public onModelChange: Function = () => { };
+
+ public onModelTouched: Function = () => { };
+
+ public checkboxClick: boolean;
+
+ public optionTouched: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public objectUtils: ObjectUtils) {}
+
+ ngAfterContentInit() {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'item':
+ this.itemTemplate = item.template;
+ break;
+
+ default:
+ this.itemTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ writeValue(value: any): void {
+ this.value = value;
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ onOptionClick(event, option) {
+ if(this.disabled) {
+ return;
+ }
+
+ if(!this.checkboxClick) {
+ if(this.multiple)
+ this.onOptionClickMultiple(event, option);
+ else
+ this.onOptionClickSingle(event, option);
+ }
+ else {
+ this.checkboxClick = false;
+ }
+
+ this.optionTouched = false;
+ }
+
+ onOptionTouchEnd(event, option) {
+ if(this.disabled) {
+ return;
+ }
+
+ this.optionTouched = true;
+ }
+
+ onOptionClickSingle(event, option) {
+ let selected = this.isSelected(option);
+ let valueChanged = false;
+ let metaSelection = this.optionTouched ? false : this.metaKeySelection;
+
+ if(metaSelection) {
+ let metaKey = (event.metaKey || event.ctrlKey);
+
+ if(selected) {
+ if(metaKey) {
+ this.value = null;
+ valueChanged = true;
+ }
+ }
+ else {
+ this.value = option.value;
+ valueChanged = true;
+ }
+ }
+ else {
+ this.value = selected ? null : option.value;
+ valueChanged = true;
+ }
+
+ if(valueChanged) {
+ this.onModelChange(this.value);
+ this.onChange.emit({
+ originalEvent: event,
+ value: this.value
+ });
+ }
+ }
+
+ onOptionClickMultiple(event, option) {
+ let selected = this.isSelected(option);
+ let valueChanged = false;
+ let metaSelection = this.optionTouched ? false : this.metaKeySelection;
+
+ if(metaSelection) {
+ let metaKey = (event.metaKey || event.ctrlKey);
+
+ if(selected) {
+ if(metaKey) {
+ this.removeOption(option);
+ }
+ else {
+ this.value = [option.value];
+ }
+ valueChanged = true;
+ }
+ else {
+ this.value = (metaKey) ? this.value || [] : [];
+ this.value = [...this.value, option.value];
+ valueChanged = true;
+ }
+ }
+ else {
+ if(selected) {
+ this.removeOption(option);
+ }
+ else {
+ this.value = [...this.value||[],option.value];
+ }
+
+ valueChanged = true;
+ }
+
+ if (valueChanged) {
+ this.onModelChange(this.value);
+ this.onChange.emit({
+ originalEvent: event,
+ value: this.value
+ });
+ }
+ }
+
+ removeOption(option: any): void {
+ this.value = this.value.filter(val => !this.objectUtils.equals(val, option.value, this.dataKey));
+ }
+
+ isSelected(option: SelectItem) {
+ let selected = false;
+
+ if(this.multiple) {
+ if(this.value) {
+ for(let val of this.value) {
+ if(this.objectUtils.equals(val, option.value, this.dataKey)) {
+ selected = true;
+ break;
+ }
+ }
+ }
+ }
+ else {
+ selected = this.objectUtils.equals(this.value, option.value, this.dataKey);
+ }
+
+ return selected;
+ }
+
+ get allChecked(): boolean {
+ if(this.filterValue && this.filterValue.trim().length)
+ return this.allFilteredSelected();
+ else
+ return this.value&&this.options&&(this.value.length == this.options.length);
+ }
+
+ allFilteredSelected(): boolean {
+ let allSelected: boolean;
+ if(this.value && this.visibleOptions && this.visibleOptions.length) {
+ allSelected = true;
+ for(let opt of this.visibleOptions) {
+ let selected: boolean;
+ for(let val of this.value) {
+ if(this.objectUtils.equals(val, opt.value, this.dataKey)) {
+ selected = true;
+ }
+ }
+
+ if(!selected) {
+ allSelected = false;
+ break;
+ }
+ }
+ }
+
+ return allSelected;
+ }
+
+ onFilter(event) {
+ this.filterValue = event.target.value.trim().toLowerCase();
+ this.visibleOptions = [];
+ for(let i = 0; i < this.options.length; i++) {
+ let option = this.options[i];
+ if(option.label.toLowerCase().indexOf(this.filterValue.toLowerCase()) > -1) {
+ this.visibleOptions.push(option);
+ }
+ }
+ this.filtered = true;
+ }
+
+ toggleAll(event, checkbox) {
+ if(this.disabled || (this.filterValue && this.filterValue.trim().length && (!this.visibleOptions || this.visibleOptions.length === 0))) {
+ return;
+ }
+
+ if(checkbox.checked) {
+ this.value = [];
+ }
+ else {
+ let opts = (this.visibleOptions&&this.visibleOptions.length) ? this.visibleOptions : this.options;
+ if(opts) {
+ this.value = [];
+ for(let i = 0; i < opts.length; i++) {
+ this.value.push(opts[i].value);
+ }
+ }
+ }
+ checkbox.checked = !checkbox.checked;
+ this.onModelChange(this.value);
+ this.onChange.emit({originalEvent: event, value: this.value});
+ }
+
+ isItemVisible(option: SelectItem): boolean {
+ if(this.filterValue && this.filterValue.trim().length) {
+ for(let i = 0; i < this.visibleOptions.length; i++) {
+ if(this.visibleOptions[i].value == option.value) {
+ return true;
+ }
+ }
+ }
+ else {
+ return true;
+ }
+ }
+
+ onDoubleClick(event: Event, option: SelectItem): any {
+ if(this.disabled) {
+ return;
+ }
+
+ this.onDblClick.emit({
+ originalEvent: event,
+ value: this.value
+ })
+ }
+
+ onCheckboxClick(event: Event, option: SelectItem) {
+ if(this.disabled) {
+ return;
+ }
+
+ this.checkboxClick = true;
+ let selected = this.isSelected(option);
+
+ if(selected) {
+ this.removeOption(option);
+ }
+ else {
+ this.value = this.value ? this.value : [];
+ this.value = [...this.value,option.value];
+ }
+
+ this.onModelChange(this.value);
+ this.onChange.emit({
+ originalEvent: event,
+ value: this.value
+ });
+ }
+}
+
+@NgModule({
+ imports: [CommonModule, SharedModule],
+ exports: [Listbox, SharedModule],
+ declarations: [Listbox]
+})
+export class ListboxModule { }
diff --git a/src/app/components/megamenu/megamenu.ts b/src/app/components/megamenu/megamenu.ts
new file mode 100644
index 00000000000..6d8b0b7a168
--- /dev/null
+++ b/src/app/components/megamenu/megamenu.ts
@@ -0,0 +1,172 @@
+import {NgModule,Component,ElementRef,AfterViewInit,OnDestroy,Input,Output,Renderer2,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {MenuItem} from '../common/menuitem';
+import {Location} from '@angular/common';
+import {RouterModule} from '@angular/router';
+
+@Component({
+ selector: 'p-megaMenu',
+ template: `
+
+
+
+ `,
+ providers: [DomHandler]
+})
+export class MegaMenu implements OnDestroy {
+
+ @Input() model: MenuItem[];
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() orientation: string = 'horizontal';
+
+ activeItem: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2) {}
+
+ onItemMouseEnter(event, item, menuitem: MenuItem) {
+ if(menuitem.disabled) {
+ return;
+ }
+
+ this.activeItem = item;
+ let submenu = item.children[0].nextElementSibling;
+ if(submenu) {
+ submenu.style.zIndex = ++DomHandler.zindex;
+
+ if(this.orientation === 'horizontal') {
+ submenu.style.top = this.domHandler.getOuterHeight(item.children[0]) + 'px';
+ submenu.style.left = '0px';
+ }
+ else if(this.orientation === 'vertical') {
+ submenu.style.top = '0px';
+ submenu.style.left = this.domHandler.getOuterWidth(item.children[0]) + 'px';
+ }
+ }
+ }
+
+ onItemMouseLeave(event, link) {
+ this.activeItem = null;
+ }
+
+ itemClick(event, item: MenuItem) {
+ if(item.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if(!item.url) {
+ event.preventDefault();
+ }
+
+ if(item.command) {
+ if(!item.eventEmitter) {
+ item.eventEmitter = new EventEmitter();
+ item.eventEmitter.subscribe(item.command);
+ }
+
+ item.eventEmitter.emit({
+ originalEvent: event,
+ item: item
+ });
+ }
+
+ this.activeItem = null;
+ }
+
+ unsubscribe(item: any) {
+ if(item.eventEmitter) {
+ item.eventEmitter.unsubscribe();
+ }
+
+ if(item.items) {
+ for(let childItem of item.items) {
+ this.unsubscribe(childItem);
+ }
+ }
+ }
+
+ ngOnDestroy() {
+ if(this.model) {
+ for(let item of this.model) {
+ this.unsubscribe(item);
+ }
+ }
+ }
+
+ getColumnClass(menuitem: MenuItem) {
+ let length = menuitem.items ? menuitem.items.length: 0;
+ let columnClass;
+ switch(length) {
+ case 2:
+ columnClass= 'ui-g-6';
+ break;
+
+ case 3:
+ columnClass= 'ui-g-4';
+ break;
+
+ case 4:
+ columnClass= 'ui-g-3';
+ break;
+
+ case 6:
+ columnClass= 'ui-g-2';
+ break;
+
+ default:
+ columnClass= 'ui-g-12';
+ break;
+ }
+
+ return columnClass;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,RouterModule],
+ exports: [MegaMenu,RouterModule],
+ declarations: [MegaMenu]
+})
+export class MegaMenuModule { }
\ No newline at end of file
diff --git a/src/app/components/menu/menu.css b/src/app/components/menu/menu.css
new file mode 100644
index 00000000000..e7a74dbc9f4
--- /dev/null
+++ b/src/app/components/menu/menu.css
@@ -0,0 +1,302 @@
+.ui-menu {
+ width: 12.5em;
+ padding: .25em;
+ position:relative;
+}
+
+.ui-menu.ui-menu-dynamic {
+ position: absolute;
+ display: none;
+ z-index: 100000;
+}
+
+.ui-menu-list {
+ position: static;
+}
+
+.ui-menu .ui-menu-list .ui-menuitem {
+ border: none;
+}
+
+.ui-menu .ui-menu-list .ui-widget-header {
+ clear:both;
+ float:left;
+ width: 100%;
+ margin: .125em 0;
+ padding: .25em .5em;
+}
+
+.ui-menu .ui-menuitem-parent,
+.ui-menu .ui-menuitem {
+ width: 100%;
+ clear: both;
+ margin: .125em 0;
+ padding: 0;
+}
+
+.ui-menu .ui-menuitem-link {
+ display: block;
+ width: 100%;
+ outline: none;
+ text-decoration: none;
+ font-weight: normal;
+ border: 1px solid transparent;
+ line-height: 1em;
+ padding: .25em;
+ cursor: pointer;
+}
+
+.ui-menu .ui-menuitem-link .ui-menuitem-icon {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ui-menu .ui-menuitem-text {
+ vertical-align: middle;
+}
+
+.ui-menu .ui-widget-header h1,
+.ui-menu .ui-widget-header h2,
+.ui-menu .ui-widget-header h3,
+.ui-menu .ui-widget-header h4,
+.ui-menu .ui-widget-header h5,
+.ui-menu .ui-widget-header h6 {
+ font-size: 1em;
+ margin: 0 auto;
+}
+
+/* Tiered Menu */
+.ui-menu .ui-menu-parent .ui-menu-child {
+ display: none;
+ width: 12.5em;
+ padding: .25em;
+ position:absolute;
+ margin: 0;
+ outline: 0;
+ text-decoration:none;
+ list-style:none;
+}
+
+.ui-menu .ui-menu-parent {
+ position: relative;
+}
+
+.ui-menu .ui-menu-parent .ui-submenu-icon {
+ float: right;
+ margin-right: -.25em;
+}
+
+/** MenuButton **/
+.ui-menubutton {
+ padding: 0;
+}
+
+.ui-menubutton .ui-button {
+ margin: 0;
+}
+
+/** Menubar **/
+.ui-menu.ui-menubar .ui-menubar-root-list > li > a > .ui-submenu-icon {
+ float: none;
+}
+
+.ui-menubar {
+ width:auto;
+}
+
+.ui-menubar .ui-menubar-root-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.ui-menubar .ui-menubar-root-list > .ui-menuitem {
+ display: inline-block;
+ width: auto;
+}
+
+.ui-menubar .ui-menu-child .ui-menuitem {
+ width: 100%;
+}
+
+.ui-menubar .ui-menubar-options {
+ float: right;
+}
+
+/** SlideMenu **/
+.ui-slidemenu .ui-slidemenu-wrapper {
+ position: relative;
+}
+
+.ui-slidemenu .ui-slidemenu-content {
+ overflow-x: hidden;
+ overflow-y: auto;
+ position: relative;
+}
+
+.ui-slidemenu .ui-menu-list {
+ position: absolute;
+ top: 0;
+}
+
+.ui-slidemenu .ui-menu-parent {
+ position: static;
+}
+
+.ui-slidemenu .ui-menu-child {
+ box-shadow : none;
+ border: 0 none;
+ background: none repeat scroll 0 0 transparent;
+}
+
+.ui-slidemenu-backward {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ padding: 0.2em;
+ cursor: pointer;
+ display: none;
+}
+
+.ui-slidemenu-backward .fa {
+ vertical-align: middle;
+}
+
+.ui-slidemenu-backward span {
+ vertical-align: middle;
+}
+
+.ui-slidemenu .ui-slidemenuitem-active > .ui-submenu > ul {
+ display: block !important;
+}
+
+/** MegaMenu **/
+.ui-megamenu .ui-g {
+ flex-wrap: nowrap;
+}
+
+.ui-megamenu .ui-megamenu-panel.ui-menu-child {
+ width: auto;
+}
+
+.ui-megamenu .ui-megamenu-panel .ui-menu-list {
+ width: 12.5em;
+}
+
+.ui-megamenu-vertical {
+ width: 12.5em;
+}
+
+.ui-megamenu-vertical .ui-menuitem-link,
+.ui-megamenu-vertical .ui-menu-list .ui-menuitem {
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.ui-megamenu-vertical > .ui-menubar-root-list > .ui-menuitem > .ui-menuitem-link > .ui-submenu-icon {
+ float: right;
+}
+
+/** PanelMenu **/
+.ui-panelmenu {
+ width: auto;
+}
+
+.ui-panelmenu .ui-panelmenu-panel {
+ padding: 0;
+ margin: 0;
+}
+
+.ui-panelmenu .ui-panelmenu-header {
+ cursor: pointer;
+ position: relative;
+ margin: -1px 0 0 0;
+ zoom: 1;
+}
+
+.ui-panelmenu .ui-panelmenu-header a {
+ display: block;
+ padding: .25em .5em;
+}
+
+.ui-panelmenu span {
+ vertical-align: middle;
+}
+
+.ui-panelmenu .fa {
+ width: 1em;
+ text-align: center;
+ vertical-align: middle;
+ margin-right: .25em;
+}
+
+.ui-panelmenu .ui-menuitem-text {
+ margin-left: .125em;
+}
+
+.ui-panelmenu span {
+ vertical-align: middle;
+}
+
+.ui-panelmenu .ui-panelmenu-content {
+ padding: 0.2em 0;
+ border-top: 0;
+ overflow: auto;
+ zoom: 1;
+ outline: none;
+ margin-bottom: 1px;
+}
+
+.ui-panelmenu .ui-panelmenu-content-wrapper {
+ box-sizing: border-box;
+}
+
+.ui-panelmenu .ui-panelmenu-content-wrapper-overflown {
+ overflow: hidden;
+}
+
+.ui-panelmenu .ui-panelmenu-header.ui-state-disabled,
+.ui-panelmenu .ui-panelmenu-header.ui-state-disabled a {
+ cursor: default;
+}
+
+.ui-panelmenu .ui-menu-list {
+ position: static;
+}
+
+.ui-panelmenu .ui-menuitem {
+ margin: 1px 0;
+ padding: 0;
+}
+
+.ui-panelmenu .ui-menuitem-link {
+ display: block;
+ outline: none;
+ text-decoration: none;
+ font-weight: normal;
+ border: 1px solid transparent;
+ line-height: 1em;
+ cursor: pointer;
+ position: relative;
+ padding: .25em .5em;
+}
+
+.ui-panelmenu .ui-menu-parent .ui-menu-list {
+ margin-left: 1.5em;
+}
+
+/** MegaMenu and TieredMenus **/
+.ui-menuitem-active > .ui-submenu > ul,
+.ui-menuitem-active > .ui-megamenu-panel {
+ display: block !important;
+}
+
+.ui-menuitem-outline {
+ outline: 1px dotted;
+ z-index: 1;
+}
+
+/** Fluid **/
+.ui-fluid .ui-menu {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/src/app/components/menu/menu.ts b/src/app/components/menu/menu.ts
new file mode 100644
index 00000000000..268115e0f92
--- /dev/null
+++ b/src/app/components/menu/menu.ts
@@ -0,0 +1,194 @@
+import {NgModule,Component,ElementRef,AfterViewInit,OnDestroy,Input,Output,Renderer2,HostListener,EventEmitter,ViewChild} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {MenuItem} from '../common/menuitem';
+import {RouterModule} from '@angular/router';
+
+@Component({
+ selector: 'p-menu',
+ template: `
+
+
+
+ `,
+ providers: [DomHandler],
+ host: {'(window:resize)': 'onResize($event)'}
+})
+export class Menu implements AfterViewInit,OnDestroy {
+
+ @Input() model: MenuItem[];
+
+ @Input() popup: boolean;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() appendTo: any;
+
+ @ViewChild('container') containerViewChild: ElementRef;
+
+ container: HTMLDivElement;
+
+ documentClickListener: any;
+
+ preventDocumentDefault: any;
+
+ onResizeTarget: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2) {}
+
+ ngAfterViewInit() {
+ this.container = this.containerViewChild.nativeElement;
+
+ if(this.popup) {
+ if(this.appendTo) {
+ if(this.appendTo === 'body')
+ document.body.appendChild(this.container);
+ else
+ this.domHandler.appendChild(this.container, this.appendTo);
+ }
+
+ this.documentClickListener = this.renderer.listen('document', 'click', () => {
+ if(!this.preventDocumentDefault) {
+ this.hide();
+ }
+ this.preventDocumentDefault = false;
+ });
+ }
+ }
+
+ toggle(event) {
+ if(this.container.offsetParent)
+ this.hide();
+ else
+ this.show(event);
+
+ this.preventDocumentDefault = true;
+ }
+
+ onResize(event) {
+ if(this.onResizeTarget && this.container.offsetParent) {
+ this.domHandler.absolutePosition(this.container, this.onResizeTarget);
+ }
+ }
+
+ show(event) {
+ let target = event.currentTarget;
+ this.onResizeTarget = event.currentTarget;
+ this.container.style.display = 'block';
+ this.domHandler.absolutePosition(this.container, target);
+ this.domHandler.fadeIn(this.container, 250);
+ this.preventDocumentDefault = true;
+ }
+
+ hide() {
+ this.container.style.display = 'none';
+ }
+
+ itemClick(event, item: MenuItem) {
+ if(item.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if(!item.url) {
+ event.preventDefault();
+ }
+
+ if(item.command) {
+ if(!item.eventEmitter) {
+ item.eventEmitter = new EventEmitter();
+ item.eventEmitter.subscribe(item.command);
+ }
+
+ item.eventEmitter.emit({
+ originalEvent: event,
+ item: item
+ });
+ }
+
+ if(this.popup) {
+ this.hide();
+ }
+ }
+
+ ngOnDestroy() {
+ if(this.popup) {
+ if(this.documentClickListener) {
+ this.documentClickListener();
+ }
+
+ if(this.appendTo) {
+ this.el.nativeElement.appendChild(this.container);
+ }
+ }
+
+ if(this.model) {
+ for(let item of this.model) {
+ this.unsubscribe(item);
+ }
+ }
+ }
+
+ hasSubMenu(): boolean {
+ if(this.model) {
+ for(var item of this.model) {
+ if(item.items) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ unsubscribe(item: any) {
+ if(item.eventEmitter) {
+ item.eventEmitter.unsubscribe();
+ }
+
+ if(item.items) {
+ for(let childItem of item.items) {
+ this.unsubscribe(childItem);
+ }
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,RouterModule],
+ exports: [Menu,RouterModule],
+ declarations: [Menu]
+})
+export class MenuModule { }
\ No newline at end of file
diff --git a/src/app/components/menubar/menubar.ts b/src/app/components/menubar/menubar.ts
new file mode 100644
index 00000000000..f41ece4e3e3
--- /dev/null
+++ b/src/app/components/menubar/menubar.ts
@@ -0,0 +1,149 @@
+import {NgModule,Component,ElementRef,AfterViewInit,OnDestroy,Input,Output,Renderer2,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {MenuItem} from '../common/menuitem';
+import {Location} from '@angular/common';
+import {RouterModule} from '@angular/router';
+
+@Component({
+ selector: 'p-menubarSub',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class MenubarSub {
+
+ @Input() item: MenuItem;
+
+ @Input() root: boolean;
+
+ constructor(public domHandler: DomHandler) {}
+
+ activeItem: any;
+
+ onItemMouseEnter(event: Event, item: HTMLLIElement, menuitem: MenuItem) {
+ if(menuitem.disabled) {
+ return;
+ }
+
+ this.activeItem = item;
+ let nextElement = item.children[0].nextElementSibling;
+ if(nextElement) {
+ let sublist = nextElement.children[0];
+ sublist.style.zIndex = String(++DomHandler.zindex);
+
+ if(this.root) {
+ sublist.style.top = this.domHandler.getOuterHeight(item.children[0]) + 'px';
+ sublist.style.left = '0px'
+ }
+ else {
+ sublist.style.top = '0px';
+ sublist.style.left = this.domHandler.getOuterWidth(item.children[0]) + 'px';
+ }
+ }
+ }
+
+ onItemMouseLeave(event: Event) {
+ this.activeItem = null;
+ }
+
+ itemClick(event, item: MenuItem) {
+ if(item.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if(!item.url) {
+ event.preventDefault();
+ }
+
+ if(item.command) {
+ if(!item.eventEmitter) {
+ item.eventEmitter = new EventEmitter();
+ item.eventEmitter.subscribe(item.command);
+ }
+
+ item.eventEmitter.emit({
+ originalEvent: event,
+ item: item
+ });
+ }
+
+ this.activeItem = null;
+ }
+
+ listClick(event) {
+ this.activeItem = null;
+ }
+
+}
+
+@Component({
+ selector: 'p-menubar',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class Menubar implements OnDestroy {
+
+ @Input() model: MenuItem[];
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2) {}
+
+ unsubscribe(item: any) {
+ if(item.eventEmitter) {
+ item.eventEmitter.unsubscribe();
+ }
+
+ if(item.items) {
+ for(let childItem of item.items) {
+ this.unsubscribe(childItem);
+ }
+ }
+ }
+
+ ngOnDestroy() {
+ if(this.model) {
+ for(let item of this.model) {
+ this.unsubscribe(item);
+ }
+ }
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule,RouterModule],
+ exports: [Menubar,RouterModule],
+ declarations: [Menubar,MenubarSub]
+})
+export class MenubarModule { }
\ No newline at end of file
diff --git a/src/app/components/messages/messages.css b/src/app/components/messages/messages.css
new file mode 100644
index 00000000000..d1b03b80f38
--- /dev/null
+++ b/src/app/components/messages/messages.css
@@ -0,0 +1,73 @@
+.ui-messages {
+ border: 1px solid;
+ margin: .5em 0;
+ padding: 1em 1em 1em .5em;
+ display: none;
+ position: relative;
+}
+
+.ui-messages-icon {
+ display:inline-block;
+ padding: 0;
+ vertical-align: middle;
+}
+
+.ui-messages-summary {
+ font-weight: bold;
+ margin-left: .25em;
+}
+
+.ui-messages-detail {
+ margin-left: .25em;
+}
+
+.ui-messages-success {
+ color: #ffffff;
+ background-color: #4CAF50;
+ border-color: #4CAF50;
+}
+
+.ui-messages-info {
+ color: #ffffff;
+ background-color: #2196f3;
+ border-color: #2196f3;
+}
+
+.ui-messages-warn {
+ color: #ffffff;
+ background-color: #FFB300;
+ border-color: #FFB300;
+}
+
+.ui-messages-error {
+ color: #ffffff;
+ background-color: #f44336;
+ border-color: #f44336;
+}
+
+.ui-messages ul {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ui-messages.ui-messages-noicon ul {
+ margin: 0 1.5em 0 0;
+}
+
+.ui-messages .ui-messages-close {
+ color: #ffffff;
+ cursor: pointer;
+ position: absolute;
+ top: 5px;
+ right: 5px;
+}
+
+/* Message */
+.ui-message {
+ border: 1px solid;
+ margin: 0px .25em;
+ padding: .125em .25em;
+}
\ No newline at end of file
diff --git a/src/app/components/messages/messages.ts b/src/app/components/messages/messages.ts
new file mode 100644
index 00000000000..2f01b9909f2
--- /dev/null
+++ b/src/app/components/messages/messages.ts
@@ -0,0 +1,89 @@
+import {NgModule,Component,Input,Output,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {Message} from '../common/message';
+
+@Component({
+ selector: 'p-messages',
+ template: `
+
+ `
+})
+export class Messages {
+
+ @Input() value: Message[];
+
+ @Input() closable: boolean = true;
+
+ @Output() valueChange: EventEmitter = new EventEmitter();
+
+ hasMessages() {
+ return this.value && this.value.length > 0;
+ }
+
+ getSeverityClass() {
+ return this.value[0].severity;
+ }
+
+ clear(event) {
+ this.value = [];
+ this.valueChange.emit(this.value);
+
+ event.preventDefault();
+ }
+
+ get icon(): string {
+ let icon: string = null;
+ if(this.hasMessages()) {
+ let msg = this.value[0];
+ switch(msg.severity) {
+ case 'success':
+ icon = 'fa-check';
+ break;
+
+ case 'info':
+ icon = 'fa-info-circle';
+ break;
+
+ case 'error':
+ icon = 'fa-close';
+ break;
+
+ case 'warn':
+ icon = 'fa-warning';
+ break;
+
+ case 'success':
+ icon = 'fa-check';
+ break;
+
+ default:
+ icon = 'fa-info-circle';
+ break;
+ }
+ }
+
+ return icon;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Messages],
+ declarations: [Messages]
+})
+export class MessagesModule { }
\ No newline at end of file
diff --git a/src/app/components/multiselect/multiselect.css b/src/app/components/multiselect/multiselect.css
new file mode 100644
index 00000000000..9742b18f40d
--- /dev/null
+++ b/src/app/components/multiselect/multiselect.css
@@ -0,0 +1,138 @@
+/** MultiSelect **/
+.ui-multiselect {
+ display: inline-block;
+ position: relative;
+ width: auto;
+ cursor: pointer;
+}
+
+.ui-multiselect .ui-multiselect-trigger {
+ border-right: none;
+ border-top: none;
+ border-bottom: none;
+ cursor: pointer;
+ width: 1.5em;
+ height: 100%;
+ position: absolute;
+ right: 0;
+ top: 0;
+ padding: 0 .25em;
+}
+
+.ui-multiselect .ui-multiselect-trigger .fa {
+ margin-top: .4em;
+ margin-left: -.125em;
+}
+
+.ui-multiselect .ui-multiselect-label-container {
+ overflow: hidden;
+}
+
+.ui-multiselect .ui-multiselect-label {
+ display: block;
+ padding: .25em 2em .25em .25em;
+ width: auto;
+ border: none;
+ cursor: pointer;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.ui-multiselect.ui-state-disabled .ui-multiselect-trigger,
+.ui-multiselect.ui-state-disabled .ui-multiselect-label {
+ cursor: auto
+}
+
+.ui-multiselect-panel {
+ padding: 0.2em;
+ position: absolute;
+ min-width: 10em;
+}
+
+.ui-multiselect-panel .ui-multiselect-items-wrapper {
+ overflow: auto;
+ position: relative;
+ padding: 0.2em 0;
+}
+
+.ui-multiselect-panel .ui-multiselect-list {
+ border: 0 none;
+}
+
+.ui-multiselect-panel .ui-multiselect-item {
+ border: 0 none;
+ cursor: pointer;
+ font-weight: normal;
+ margin: 1px 0;
+ padding: .125em .25em;
+ text-align: left;
+ white-space: nowrap;
+ display: block;
+ position: relative;
+}
+
+.ui-multiselect-panel .ui-multiselect-item .ui-chkbox {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ui-multiselect-panel .ui-multiselect-item label {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ui-multiselect-header {
+ margin-bottom: 0.3em;
+ padding: .25em;
+ position: relative;
+ text-align: left;
+}
+
+.ui-multiselect-header .ui-chkbox {
+ display: inline-block;
+ vertical-align: middle;
+ cursor:pointer;
+}
+
+.ui-multiselect-header .ui-multiselect-filter-container {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ width: 65%;
+}
+
+.ui-multiselect-header .ui-multiselect-filter-container .fa {
+ position: absolute;
+ top: .25em;
+ left: .125em;
+}
+
+.ui-multiselect-header .ui-inputtext {
+ padding: .125em .125em .125em 1.25em;
+ width: 100%;
+}
+
+.ui-multiselect-header .ui-multiselect-close {
+ position: absolute;
+ right: .375em;
+ top: .375em;
+ display: block;
+ font-size: 1em;
+ border: 0 none;
+}
+
+.ui-multiselect-header a.ui-multiselect-all,
+.ui-multiselect-header a.ui-multiselect-none {
+ float:left;
+ margin-right: 10px;
+ display: block;
+}
+
+.ui-multiselect-header .ui-multiselect-close.ui-state-hover {
+ padding:0px;
+}
+
+.ui-fluid .ui-multiselect {
+ width: 100%;
+ box-sizing: border-box;
+}
diff --git a/src/app/components/multiselect/multiselect.ts b/src/app/components/multiselect/multiselect.ts
new file mode 100644
index 00000000000..175692f75dd
--- /dev/null
+++ b/src/app/components/multiselect/multiselect.ts
@@ -0,0 +1,408 @@
+import {NgModule,Component,ElementRef,OnInit,AfterViewInit,AfterViewChecked,DoCheck,OnDestroy,Input,Output,Renderer2,EventEmitter,IterableDiffers,forwardRef,ViewChild} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {SelectItem} from '../common/selectitem';
+import {DomHandler} from '../dom/domhandler';
+import {ObjectUtils} from '../utils/ObjectUtils';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const MULTISELECT_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => MultiSelect),
+ multi: true
+};
+
+@Component({
+ selector: 'p-multiSelect',
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ `,
+ providers: [DomHandler,ObjectUtils,MULTISELECT_VALUE_ACCESSOR]
+})
+export class MultiSelect implements OnInit,AfterViewInit,AfterViewChecked,DoCheck,OnDestroy,ControlValueAccessor {
+
+ @Input() options: SelectItem[];
+
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ @Output() onBlur: EventEmitter = new EventEmitter();
+
+ @Input() scrollHeight: string = '200px';
+
+ @Input() defaultLabel: string = 'Choose';
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() inputId: string;
+
+ @Input() disabled: boolean;
+
+ @Input() filter: boolean = true;
+
+ @Input() overlayVisible: boolean;
+
+ @Input() tabindex: number;
+
+ @Input() appendTo: any;
+
+ @Input() dataKey: string;
+
+ @Input() displaySelectedLabel: boolean = true;
+
+ @Input() maxSelectedLabels: number = 3;
+
+ @Input() selectedItemsLabel: string = '{0} items selected';
+
+ @ViewChild('container') containerViewChild: ElementRef;
+
+ @ViewChild('panel') panelViewChild: ElementRef;
+
+ public value: any[];
+
+ public onModelChange: Function = () => {};
+
+ public onModelTouched: Function = () => {};
+
+ public valuesAsString: string;
+
+ public focus: boolean;
+
+ public documentClickListener: any;
+
+ public container: HTMLDivElement;
+
+ public panel: HTMLDivElement;
+
+ public selfClick: boolean;
+
+ public panelClick: boolean;
+
+ public filterValue: string;
+
+ public visibleOptions: SelectItem[];
+
+ public filtered: boolean;
+
+ public valueDiffer: any;
+
+ public optionsDiffer: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2, differs: IterableDiffers, public objectUtils: ObjectUtils) {
+ this.valueDiffer = differs.find([]).create(null);
+ this.optionsDiffer = differs.find([]).create(null);
+ }
+
+ ngOnInit() {
+ this.updateLabel();
+
+ this.documentClickListener = this.renderer.listen('document', 'click', () => {
+ if(!this.selfClick && !this.panelClick && this.overlayVisible) {
+ this.hide();
+ }
+
+ this.selfClick = false;
+ this.panelClick = false;
+ });
+ }
+
+ ngAfterViewInit() {
+ this.container = this.containerViewChild.nativeElement;
+ this.panel = this.panelViewChild.nativeElement;
+
+ if(this.appendTo) {
+ if(this.appendTo === 'body')
+ document.body.appendChild(this.panel);
+ else
+ this.domHandler.appendChild(this.panel, this.appendTo);
+ }
+
+ if(this.overlayVisible) {
+ this.show();
+ }
+ }
+
+ ngAfterViewChecked() {
+ if(this.filtered) {
+ if(this.appendTo)
+ this.domHandler.absolutePosition(this.panel, this.container);
+ else
+ this.domHandler.relativePosition(this.panel, this.container);
+
+ this.filtered = false;
+ }
+ }
+
+ ngDoCheck() {
+ let valueChanges = this.valueDiffer.diff(this.value);
+ let optionChanges = this.optionsDiffer.diff(this.options);
+
+ if(valueChanges||optionChanges) {
+ this.updateLabel();
+ }
+ }
+
+ writeValue(value: any) : void {
+ this.value = value;
+ this.updateLabel();
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ onItemClick(event, value) {
+ let selectionIndex = this.findSelectionIndex(value);
+ if(selectionIndex != -1)
+ this.value = this.value.filter((val,i) => i!=selectionIndex);
+ else
+ this.value = [...this.value||[],value];
+
+ this.onModelChange(this.value);
+ this.onChange.emit({originalEvent: event, value: this.value});
+ }
+
+ isSelected(value) {
+ return this.findSelectionIndex(value) != -1;
+ }
+
+ findSelectionIndex(val: any): number {
+ let index = -1;
+
+ if(this.value) {
+ for(let i = 0; i < this.value.length; i++) {
+ if(this.objectUtils.equals(this.value[i], val, this.dataKey)) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ return index;
+ }
+
+ toggleAll(event, checkbox) {
+ if(checkbox.checked) {
+ this.value = [];
+ }
+ else {
+ let opts = this.getVisibleOptions();
+ if(opts) {
+ this.value = [];
+ for(let i = 0; i < opts.length; i++) {
+ this.value.push(opts[i].value);
+ }
+ }
+ }
+ checkbox.checked = !checkbox.checked;
+ this.onModelChange(this.value);
+ this.onChange.emit({originalEvent: event, value: this.value});
+ }
+
+ isAllChecked() {
+ if(this.filterValue && this.filterValue.trim().length)
+ return this.value&&this.visibleOptions&&this.visibleOptions.length&&(this.value.length == this.visibleOptions.length);
+ else
+ return this.value&&this.options&&(this.value.length == this.options.length);
+ }
+
+ show() {
+ this.overlayVisible = true;
+ this.panel.style.zIndex = String(++DomHandler.zindex);
+
+ if(this.appendTo)
+ this.domHandler.absolutePosition(this.panel, this.container);
+ else
+ this.domHandler.relativePosition(this.panel, this.container);
+
+ this.domHandler.fadeIn(this.panel, 250);
+ }
+
+ hide() {
+ this.overlayVisible = false;
+ }
+
+ close(event) {
+ this.hide();
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ onMouseclick(event,input) {
+ if(this.disabled) {
+ return;
+ }
+
+ if(!this.panelClick) {
+ if(this.overlayVisible) {
+ this.hide();
+ }
+ else {
+ input.focus();
+ this.show();
+ }
+ }
+
+ this.selfClick = true;
+ }
+
+ onFocus(event) {
+ this.focus = true;
+ }
+
+ onInputBlur(event) {
+ this.focus = false;
+ this.onBlur.emit({originalEvent: event});
+ this.onModelTouched();
+ }
+
+ updateLabel() {
+ if(this.value && this.options && this.value.length && this.displaySelectedLabel) {
+ let label = '';
+ for(let i = 0; i < this.value.length; i++) {
+ if(i != 0) {
+ label = label + ', ';
+ }
+ label = label + this.findLabelByValue(this.value[i]);
+ }
+
+ if(this.value.length <= this.maxSelectedLabels) {
+ this.valuesAsString = label;
+ }
+ else {
+ let pattern = /{(.*?)}/,
+ newSelectedItemsLabel = this.selectedItemsLabel.replace(this.selectedItemsLabel.match(pattern)[0], this.value.length + '');
+ this.valuesAsString = newSelectedItemsLabel;
+ }
+ }
+ else {
+ this.valuesAsString = this.defaultLabel;
+ }
+ }
+
+ findLabelByValue(val: any): string {
+ let label = null;
+ for(let i = 0; i < this.options.length; i++) {
+ let option = this.options[i];
+ if(option.value == val) {
+ label = option.label;
+ break;
+ }
+ }
+ return label;
+ }
+
+ onFilter(event) {
+ this.filterValue = event.target.value.trim().toLowerCase();
+ this.visibleOptions = [];
+ for(let i = 0; i < this.options.length; i++) {
+ let option = this.options[i];
+ if(option.label.toLowerCase().indexOf(this.filterValue.toLowerCase()) > -1) {
+ this.visibleOptions.push(option);
+ }
+ }
+ this.filtered = true;
+ }
+
+ isItemVisible(option: SelectItem): boolean {
+ if(this.filterValue && this.filterValue.trim().length) {
+ for(let i = 0; i < this.visibleOptions.length; i++) {
+ if(this.visibleOptions[i].value == option.value) {
+ return true;
+ }
+ }
+ }
+ else {
+ return true;
+ }
+ }
+
+ getVisibleOptions(): SelectItem[] {
+ if(this.filterValue && this.filterValue.trim().length) {
+ let items = [];
+ for(let i = 0; i < this.options.length; i++) {
+ let option = this.options[i];
+ if(option.label.toLowerCase().includes(this.filterValue.toLowerCase())) {
+ items.push(option);
+ }
+ }
+ return items;
+ }
+ else {
+ return this.options;
+ }
+ }
+
+ ngOnDestroy() {
+ if(this.documentClickListener) {
+ this.documentClickListener();
+ }
+
+ if(this.appendTo) {
+ this.container.appendChild(this.panel);
+ }
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [MultiSelect],
+ declarations: [MultiSelect]
+})
+export class MultiSelectModule { }
diff --git a/src/app/components/orderlist/orderlist.css b/src/app/components/orderlist/orderlist.css
new file mode 100644
index 00000000000..e223d039018
--- /dev/null
+++ b/src/app/components/orderlist/orderlist.css
@@ -0,0 +1,86 @@
+.ui-orderlist {
+ display: table;
+}
+
+.ui-orderlist .ui-orderlist-caption {
+ width: 12.5em;
+}
+
+.ui-orderlist .ui-orderlist-list {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ overflow: auto;
+ height: 12.5em;
+ width: 12.5em;
+}
+
+.ui-orderlist .ui-orderlist-list li {
+ margin: 1px;
+ padding: .125em;
+}
+
+.ui-orderlist .ui-button {
+ display: block;
+ margin-bottom: 0.3em;
+}
+
+.ui-orderlist .ui-orderlist-button.ui-button-text-icon-primary {
+ width: 100%;
+}
+
+.ui-orderlist .ui-orderlist-item {
+ cursor: pointer;
+ border: 0 none;
+ font-weight: inherit;
+}
+
+.ui-orderlist .ui-orderlist-caption {
+ text-align: center;
+ padding: .5em .75em;
+ border-bottom: 0 none;
+}
+
+.ui-orderlist table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.ui-orderlist.ui-state-disabled .ui-orderlist-item,
+.ui-orderlist.ui-state-disabled .ui-button {
+ cursor: default;
+}
+
+.ui-orderlist.ui-state-disabled .ui-orderlist-list {
+ overflow:hidden;
+}
+
+/* Responsive */
+.ui-orderlist.ui-grid-responsive {
+ display: block;
+ width: 100%;
+}
+
+.ui-orderlist.ui-grid-responsive .ui-orderlist-controls {
+ margin-right: .5em;
+}
+
+.ui-orderlist.ui-grid-responsive .ui-orderlist-list,
+.ui-orderlist.ui-grid-responsive .ui-orderlist-caption {
+ width: 100%;
+}
+
+.ui-orderlist.ui-grid-responsive .ui-orderlist-controls .ui-button {
+ width: 100%;
+}
+
+@media (max-width: 40em) {
+ .ui-orderlist.ui-grid-responsive .ui-orderlist-controls {
+ text-align: center;
+ }
+
+ .ui-orderlist.ui-grid-responsive .ui-orderlist-controls .ui-button {
+ display: inline;
+ width: 20%;
+ }
+}
\ No newline at end of file
diff --git a/src/app/components/orderlist/orderlist.ts b/src/app/components/orderlist/orderlist.ts
new file mode 100644
index 00000000000..e28044eed17
--- /dev/null
+++ b/src/app/components/orderlist/orderlist.ts
@@ -0,0 +1,247 @@
+import {NgModule,Component,ElementRef,AfterViewChecked,AfterContentInit,Input,Output,ContentChildren,QueryList,TemplateRef,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {ButtonModule} from '../button/button';
+import {SharedModule,PrimeTemplate} from '../common/shared';
+import {DomHandler} from '../dom/domhandler';
+
+@Component({
+ selector: 'p-orderList',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class OrderList implements AfterViewChecked,AfterContentInit {
+
+ @Input() value: any[];
+
+ @Input() header: string;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() listStyle: any;
+
+ @Input() responsive: boolean;
+
+ @Input() metaKeySelection: boolean = true;
+
+ @Output() onReorder: EventEmitter = new EventEmitter();
+
+ @Output() onSelectionChange: EventEmitter = new EventEmitter();
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public itemTemplate: TemplateRef;
+
+ selectedItems: any[];
+
+ movedUp: boolean;
+
+ movedDown: boolean;
+
+ listContainer: any;
+
+ itemTouched: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ ngAfterViewInit() {
+ this.listContainer = this.domHandler.findSingle(this.el.nativeElement, 'ul.ui-orderlist-list');
+ }
+
+ ngAfterContentInit() {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'item':
+ this.itemTemplate = item.template;
+ break;
+
+ default:
+ this.itemTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ ngAfterViewChecked() {
+ if(this.movedUp||this.movedDown) {
+ let listItems = this.domHandler.find(this.listContainer, 'li.ui-state-highlight');
+ let listItem;
+
+ if(this.movedUp)
+ listItem = listItems[0];
+ else
+ listItem = listItems[listItems.length - 1];
+
+ this.domHandler.scrollInView(this.listContainer, listItem);
+ this.movedUp = false;
+ this.movedDown = false;
+ }
+ }
+
+ onItemClick(event, item) {
+ let index = this.findIndexInList(item, this.selectedItems);
+ let selected = (index != -1);
+ let metaSelection = this.itemTouched ? false : this.metaKeySelection;
+
+ if(metaSelection) {
+ let metaKey = (event.metaKey||event.ctrlKey);
+
+ if(selected && metaKey) {
+ this.selectedItems.splice(index, 1);
+ }
+ else {
+ this.selectedItems = (metaKey) ? this.selectedItems||[] : [];
+ this.selectedItems.push(item);
+ }
+ }
+ else {
+ if(selected) {
+ this.selectedItems.splice(index, 1);
+ }
+ else {
+ this.selectedItems = this.selectedItems||[];
+ this.selectedItems.push(item);
+ }
+ }
+
+ this.onSelectionChange.emit({originalEvent:event, value:this.selectedItems});
+ this.itemTouched = false;
+ }
+
+ onItemTouchEnd(event) {
+ this.itemTouched = true;
+ }
+
+ isSelected(item: any) {
+ return this.findIndexInList(item, this.selectedItems) != -1;
+ }
+
+ findIndexInList(item: any, list: any): number {
+ let index: number = -1;
+
+ if(list) {
+ for(let i = 0; i < list.length; i++) {
+ if(list[i] == item) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ return index;
+ }
+
+ moveUp(event,listElement) {
+ if(this.selectedItems) {
+ for(let i = 0; i < this.selectedItems.length; i++) {
+ let selectedItem = this.selectedItems[i];
+ let selectedItemIndex: number = this.findIndexInList(selectedItem, this.value);
+
+ if(selectedItemIndex != 0) {
+ let movedItem = this.value[selectedItemIndex];
+ let temp = this.value[selectedItemIndex-1];
+ this.value[selectedItemIndex-1] = movedItem;
+ this.value[selectedItemIndex] = temp;
+ }
+ else {
+ break;
+ }
+ }
+
+ this.movedUp = true;
+ this.onReorder.emit(event);
+ }
+ }
+
+ moveTop(event,listElement) {
+ if(this.selectedItems) {
+ for(let i = 0; i < this.selectedItems.length; i++) {
+ let selectedItem = this.selectedItems[i];
+ let selectedItemIndex: number = this.findIndexInList(selectedItem, this.value);
+
+ if(selectedItemIndex != 0) {
+ let movedItem = this.value.splice(selectedItemIndex,1)[0];
+ this.value.unshift(movedItem);
+ listElement.scrollTop = 0;
+ }
+ else {
+ break;
+ }
+ }
+
+ this.onReorder.emit(event);
+ listElement.scrollTop = 0;
+ }
+ }
+
+ moveDown(event,listElement) {
+ if(this.selectedItems) {
+ for(let i = this.selectedItems.length - 1; i >= 0; i--) {
+ let selectedItem = this.selectedItems[i];
+ let selectedItemIndex: number = this.findIndexInList(selectedItem, this.value);
+
+ if(selectedItemIndex != (this.value.length - 1)) {
+ let movedItem = this.value[selectedItemIndex];
+ let temp = this.value[selectedItemIndex+1];
+ this.value[selectedItemIndex+1] = movedItem;
+ this.value[selectedItemIndex] = temp;
+ }
+ else {
+ break;
+ }
+ }
+
+ this.movedDown = true;
+ this.onReorder.emit(event);
+ }
+ }
+
+ moveBottom(event,listElement) {
+ if(this.selectedItems) {
+ for(let i = this.selectedItems.length - 1; i >= 0; i--) {
+ let selectedItem = this.selectedItems[i];
+ let selectedItemIndex: number = this.findIndexInList(selectedItem, this.value);
+
+ if(selectedItemIndex != (this.value.length - 1)) {
+ let movedItem = this.value.splice(selectedItemIndex,1)[0];
+ this.value.push(movedItem);
+ }
+ else {
+ break;
+ }
+ }
+
+ this.onReorder.emit(event);
+ listElement.scrollTop = listElement.scrollHeight;
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,ButtonModule,SharedModule],
+ exports: [OrderList,SharedModule],
+ declarations: [OrderList]
+})
+export class OrderListModule { }
\ No newline at end of file
diff --git a/src/app/components/overlaypanel/overlaypanel.css b/src/app/components/overlaypanel/overlaypanel.css
new file mode 100644
index 00000000000..b4955daf115
--- /dev/null
+++ b/src/app/components/overlaypanel/overlaypanel.css
@@ -0,0 +1,18 @@
+.ui-overlaypanel {
+ padding: 0;
+ margin: 0;
+ position: absolute;
+}
+
+.ui-overlaypanel-content {
+ padding: 0.5em 1em;
+}
+
+.ui-overlaypanel-close {
+ position: absolute;
+ top: -.5em;
+ right: -.5em;
+ -moz-border-radius: 100%;
+ -webkit-border-radius: 100%;
+ border-radius: 100%;
+}
\ No newline at end of file
diff --git a/src/app/components/overlaypanel/overlaypanel.ts b/src/app/components/overlaypanel/overlaypanel.ts
new file mode 100644
index 00000000000..0c742ffbca0
--- /dev/null
+++ b/src/app/components/overlaypanel/overlaypanel.ts
@@ -0,0 +1,159 @@
+import {NgModule,Component,Input,Output,OnInit,AfterViewInit,OnDestroy,EventEmitter,Renderer2,ElementRef,ChangeDetectorRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+
+@Component({
+ selector: 'p-overlayPanel',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class OverlayPanel implements OnInit,AfterViewInit,OnDestroy {
+
+ @Input() dismissable: boolean = true;
+
+ @Input() showCloseIcon: boolean;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() appendTo: any;
+
+ @Output() onBeforeShow: EventEmitter = new EventEmitter();
+
+ @Output() onAfterShow: EventEmitter = new EventEmitter();
+
+ @Output() onBeforeHide: EventEmitter = new EventEmitter();
+
+ @Output() onAfterHide: EventEmitter = new EventEmitter();
+
+ container: any;
+
+ visible: boolean = false;
+
+ documentClickListener: any;
+
+ selfClick: boolean;
+
+ targetEvent: boolean;
+
+ target: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2, private cd: ChangeDetectorRef) {}
+
+ ngOnInit() {
+ if(this.dismissable) {
+ this.documentClickListener = this.renderer.listen('document', 'click', () => {
+ if(!this.selfClick&&!this.targetEvent) {
+ this.hide();
+ }
+ this.selfClick = false;
+ this.targetEvent = false;
+ this.cd.markForCheck();
+ });
+ }
+ }
+
+ ngAfterViewInit() {
+ this.container = this.el.nativeElement.children[0];
+ if(this.appendTo) {
+ if(this.appendTo === 'body')
+ document.body.appendChild(this.container);
+ else
+ this.domHandler.appendChild(this.container, this.appendTo);
+ }
+ }
+
+ toggle(event,target?) {
+ let currentTarget = (target||event.currentTarget||event.target);
+
+ if(!this.target||this.target == currentTarget) {
+ if(this.visible)
+ this.hide();
+ else
+ this.show(event, target);
+ }
+ else {
+ this.show(event, target);
+ }
+
+ if(this.dismissable) {
+ this.targetEvent = true;
+ }
+
+ this.target = currentTarget;
+ }
+
+ show(event,target?) {
+ if(this.dismissable) {
+ this.targetEvent = true;
+ }
+
+ this.onBeforeShow.emit(null);
+ let elementTarget = target||event.currentTarget||event.target;
+ this.container.style.zIndex = ++DomHandler.zindex;
+
+ if(this.visible) {
+ this.domHandler.absolutePosition(this.container, elementTarget);
+ }
+ else {
+ this.visible = true;
+ this.domHandler.absolutePosition(this.container, elementTarget);
+ this.domHandler.fadeIn(this.container, 250);
+ }
+ this.onAfterShow.emit(null);
+ }
+
+ hide() {
+ if(this.visible) {
+ this.onBeforeHide.emit(null);
+ this.visible = false;
+ this.onAfterHide.emit(null);
+ }
+ }
+
+ onPanelClick() {
+ if(this.dismissable) {
+ this.selfClick = true;
+ }
+ }
+
+ onCloseClick(event) {
+ this.hide();
+
+ if(this.dismissable) {
+ this.selfClick = true;
+ }
+
+ event.preventDefault();
+ }
+
+ ngOnDestroy() {
+ if(this.documentClickListener) {
+ this.documentClickListener();
+ }
+
+ if(this.appendTo) {
+ this.el.nativeElement.appendChild(this.container);
+ }
+
+ this.target = null;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [OverlayPanel],
+ declarations: [OverlayPanel]
+})
+export class OverlayPanelModule { }
diff --git a/src/app/components/paginator/paginator.css b/src/app/components/paginator/paginator.css
new file mode 100644
index 00000000000..6d8573bfbf9
--- /dev/null
+++ b/src/app/components/paginator/paginator.css
@@ -0,0 +1,56 @@
+.ui-paginator {
+ margin: 0;
+ text-align: center;
+ padding: .125em;
+}
+
+.ui-paginator .ui-paginator-top {
+ border-bottom: 0 none;
+}
+
+.ui-paginator .ui-paginator-bottom {
+ border-top:0 none;
+}
+
+.ui-paginator .ui-paginator-page,
+.ui-paginator .ui-paginator-pages,
+.ui-paginator .ui-paginator-next,
+.ui-paginator .ui-paginator-last,
+.ui-paginator .ui-paginator-first,
+.ui-paginator .ui-paginator-prev,
+.ui-paginator .ui-paginator-current {
+ display: inline-block;
+ padding: .125em .375em;
+ zoom: 1;
+ margin-left: .063em;
+ margin-right: .063em;
+ text-decoration: none;
+}
+
+.ui-paginator .ui-paginator-page,
+.ui-paginator .ui-paginator-next,
+.ui-paginator .ui-paginator-last,
+.ui-paginator .ui-paginator-first,
+.ui-paginator .ui-paginator-prev{
+ cursor: pointer;
+}
+
+.ui-paginator .ui-paginator-current,
+.ui-paginator .ui-paginator-rpp-options {
+ margin-left: 1em;
+ margin-right: 1em;
+ background-image: none;
+}
+
+.ui-paginator .ui-paginator-jtp-select option,
+.ui-paginator .ui-paginator-rpp-options option {
+ background-image: none;
+ border: 0 none;
+ box-shadow: none;
+ -moz-box-shadow: none;
+ -webkit-box-shadow: none;
+}
+
+.ui-paginator a.ui-state-disabled {
+ outline: 0 none;
+}
\ No newline at end of file
diff --git a/src/app/components/paginator/paginator.ts b/src/app/components/paginator/paginator.ts
new file mode 100644
index 00000000000..e88862980b0
--- /dev/null
+++ b/src/app/components/paginator/paginator.ts
@@ -0,0 +1,174 @@
+import {NgModule,Component,ElementRef,Input,Output,SimpleChange,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+@Component({
+ selector: 'p-paginator',
+ template: `
+
+ `
+})
+export class Paginator {
+
+ @Input() pageLinkSize: number = 5;
+
+ @Output() onPageChange: EventEmitter = new EventEmitter();
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() rowsPerPageOptions: number[];
+
+ @Input() alwaysShow: boolean = true;
+
+ public pageLinks: number[];
+
+ public _totalRecords: number = 0;
+
+ public _first: number = 0;
+
+ public _rows: number = 0;
+
+ @Input() get totalRecords(): number {
+ return this._totalRecords;
+ }
+
+ set totalRecords(val:number) {
+ this._totalRecords = val;
+ this.updatePageLinks();
+ }
+
+ @Input() get first(): number {
+ return this._first;
+ }
+
+ set first(val:number) {
+ this._first = val;
+ this.updatePageLinks();
+ }
+
+ @Input() get rows(): number {
+ return this._rows;
+ }
+
+ set rows(val:number) {
+ this._rows = val;
+ this.updatePageLinks();
+ }
+
+ isFirstPage() {
+ return this.getPage() === 0;
+ }
+
+ isLastPage() {
+ return this.getPage() === this.getPageCount() - 1;
+ }
+
+ getPageCount() {
+ return Math.ceil(this.totalRecords/this.rows)||1;
+ }
+
+ calculatePageLinkBoundaries() {
+ let numberOfPages = this.getPageCount(),
+ visiblePages = Math.min(this.pageLinkSize, numberOfPages);
+
+ //calculate range, keep current in middle if necessary
+ let start = Math.max(0, Math.ceil(this.getPage() - ((visiblePages) / 2))),
+ end = Math.min(numberOfPages - 1, start + visiblePages - 1);
+
+ //check when approaching to last page
+ var delta = this.pageLinkSize - (end - start + 1);
+ start = Math.max(0, start - delta);
+
+ return [start, end];
+ }
+
+ updatePageLinks() {
+ this.pageLinks = [];
+ let boundaries = this.calculatePageLinkBoundaries(),
+ start = boundaries[0],
+ end = boundaries[1];
+
+ for(let i = start; i <= end; i++) {
+ this.pageLinks.push(i + 1);
+ }
+ }
+
+ changePage(p :number, event) {
+ var pc = this.getPageCount();
+
+ if(p >= 0 && p < pc) {
+ this.first = this.rows * p;
+ var state = {
+ page: p,
+ first: this.first,
+ rows: this.rows,
+ pageCount: pc
+ };
+ this.updatePageLinks();
+
+ this.onPageChange.emit(state);
+ }
+
+ if(event) {
+ event.preventDefault();
+ }
+ }
+
+ getPage(): number {
+ return Math.floor(this.first / this.rows);
+ }
+
+ changePageToFirst(event) {
+ this.changePage(0, event);
+ }
+
+ changePageToPrev(event) {
+ this.changePage(this.getPage() - 1, event);
+ }
+
+ changePageToNext(event) {
+ this.changePage(this.getPage() + 1, event);
+ }
+
+ changePageToLast(event) {
+ this.changePage(this.getPageCount() - 1, event);
+ }
+
+ onRppChange(event) {
+ this.rows = this.rowsPerPageOptions[event.target.selectedIndex];
+ this.changePageToFirst(event);
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Paginator],
+ declarations: [Paginator]
+})
+export class PaginatorModule { }
diff --git a/src/app/components/panel/panel.css b/src/app/components/panel/panel.css
new file mode 100644
index 00000000000..d295228671d
--- /dev/null
+++ b/src/app/components/panel/panel.css
@@ -0,0 +1,33 @@
+.ui-panel {
+ padding: 0.2em;
+}
+
+.ui-panel .ui-panel-titlebar {
+ padding: .5em .75em;
+}
+
+.ui-panel .ui-panel-titlebar-icon {
+ float: right;
+ cursor: pointer;
+}
+
+.ui-panel .ui-panel-titlebar-icon {
+ margin-left: 0.2em;
+ margin-top: -0.1em;
+}
+
+.ui-panel .ui-panel-content {
+ border: 0;
+ background: none;
+ padding: .5em .75em;
+}
+
+.ui-panel .ui-panel-footer {
+ border-width: 1px 0 0;
+ padding: .25em .5em;
+ text-align:left;
+}
+
+.ui-panel-content-wrapper-overflown {
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/src/app/components/panel/panel.ts b/src/app/components/panel/panel.ts
new file mode 100644
index 00000000000..8c61febff4a
--- /dev/null
+++ b/src/app/components/panel/panel.ts
@@ -0,0 +1,110 @@
+import {NgModule,Component,Input,Output,EventEmitter,ElementRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {SharedModule} from '../common/shared';
+import {BlockableUI} from '../common/blockableui';
+import {trigger,state,style,transition,animate} from '@angular/animations';
+
+@Component({
+ selector: 'p-panel',
+ template: `
+
+ `,
+ animations: [
+ trigger('panelContent', [
+ state('hidden', style({
+ height: '0'
+ })),
+ state('visible', style({
+ height: '*'
+ })),
+ transition('visible <=> hidden', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
+ ])
+ ]
+})
+export class Panel implements BlockableUI {
+
+ @Input() toggleable: boolean;
+
+ @Input() header: string;
+
+ @Input() collapsed: boolean = false;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() expandIcon: string = 'fa-plus';
+
+ @Input() collapseIcon: string = 'fa-minus';
+
+ @Output() collapsedChange: EventEmitter = new EventEmitter();
+
+ @Output() onBeforeToggle: EventEmitter = new EventEmitter();
+
+ @Output() onAfterToggle: EventEmitter = new EventEmitter();
+
+ public animating: boolean;
+
+ constructor(private el: ElementRef) {}
+
+ toggle(event) {
+ if(this.animating) {
+ return false;
+ }
+
+ this.animating = true;
+ this.onBeforeToggle.emit({originalEvent: event, collapsed: this.collapsed});
+
+ if(this.toggleable) {
+ if(this.collapsed)
+ this.expand(event);
+ else
+ this.collapse(event);
+ }
+
+ this.onAfterToggle.emit({originalEvent: event, collapsed: this.collapsed});
+
+ event.preventDefault();
+ }
+
+ expand(event) {
+ this.collapsed = false;
+ this.collapsedChange.emit(this.collapsed);
+ }
+
+ collapse(event) {
+ this.collapsed = true;
+ this.collapsedChange.emit(this.collapsed);
+ }
+
+ getBlockableElement(): HTMLElement {
+ return this.el.nativeElement.children[0];
+ }
+
+ onToggleDone(event: Event) {
+ this.animating = false;
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Panel,SharedModule],
+ declarations: [Panel]
+})
+export class PanelModule { }
\ No newline at end of file
diff --git a/src/app/components/panelmenu/panelmenu.ts b/src/app/components/panelmenu/panelmenu.ts
new file mode 100644
index 00000000000..4c17b069a14
--- /dev/null
+++ b/src/app/components/panelmenu/panelmenu.ts
@@ -0,0 +1,154 @@
+import {NgModule,Component,ElementRef,OnDestroy,Input,EventEmitter} from '@angular/core';
+import {trigger,state,style,transition,animate} from '@angular/animations';
+import {CommonModule} from '@angular/common';
+import {MenuItem} from '../common/menuitem';
+import {RouterModule} from '@angular/router';
+
+export class BasePanelMenuItem {
+
+ handleClick(event, item) {
+ if(item.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ item.expanded = !item.expanded;
+
+ if(!item.url) {
+ event.preventDefault();
+ }
+
+ if(item.command) {
+ if(!item.eventEmitter) {
+ item.eventEmitter = new EventEmitter();
+ item.eventEmitter.subscribe(item.command);
+ }
+
+ item.eventEmitter.emit({
+ originalEvent: event,
+ item: item
+ });
+ }
+ }
+}
+
+@Component({
+ selector: 'p-panelMenuSub',
+ template: `
+
+ `
+})
+export class PanelMenuSub extends BasePanelMenuItem {
+
+ @Input() item: MenuItem;
+
+ @Input() expanded: boolean;
+}
+
+@Component({
+ selector: 'p-panelMenu',
+ template: `
+
+
+
+ `,
+ animations: [
+ trigger('rootItem', [
+ state('hidden', style({
+ height: '0px'
+ })),
+ state('visible', style({
+ height: '*'
+ })),
+ transition('visible => hidden', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)')),
+ transition('hidden => visible', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
+ ])
+ ]
+})
+export class PanelMenu extends BasePanelMenuItem {
+
+ @Input() model: MenuItem[];
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ public animating: boolean;
+
+ unsubscribe(item: any) {
+ if(item.eventEmitter) {
+ item.eventEmitter.unsubscribe();
+ }
+
+ if(item.items) {
+ for(let childItem of item.items) {
+ this.unsubscribe(childItem);
+ }
+ }
+ }
+
+ ngOnDestroy() {
+ if(this.model) {
+ for(let item of this.model) {
+ this.unsubscribe(item);
+ }
+ }
+ }
+
+ handleClick(event, item) {
+ this.animating = true;
+ super.handleClick(event, item);
+ //TODO: Use onDone of animate callback instead with RC6
+ setTimeout(() => {
+ this.animating = false;
+ }, 400);
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule,RouterModule],
+ exports: [PanelMenu,RouterModule],
+ declarations: [PanelMenu,PanelMenuSub]
+})
+export class PanelMenuModule { }
diff --git a/src/app/components/password/images/password-meter.png b/src/app/components/password/images/password-meter.png
new file mode 100644
index 00000000000..eec05cfb434
Binary files /dev/null and b/src/app/components/password/images/password-meter.png differ
diff --git a/src/app/components/password/password.css b/src/app/components/password/password.css
new file mode 100644
index 00000000000..f72f179a337
--- /dev/null
+++ b/src/app/components/password/password.css
@@ -0,0 +1,20 @@
+.ui-password-panel {
+ padding: .25em .5em;
+ width: 10em;
+ margin-top: 2px;
+}
+
+.ui-password-panel .ui-password-meter {
+ height: 10px;
+ background:transparent url("./images/password-meter.png") no-repeat left top;
+ padding: 0;
+ margin: 0;
+}
+
+.ui-password-info {
+ margin-top: .25em;
+}
+
+.ui-password-panel-overlay {
+ position: absolute;
+}
\ No newline at end of file
diff --git a/src/app/components/password/password.ts b/src/app/components/password/password.ts
new file mode 100644
index 00000000000..5a1bcbc7c6f
--- /dev/null
+++ b/src/app/components/password/password.ts
@@ -0,0 +1,164 @@
+import {NgModule,Directive,ElementRef,HostListener,Input,AfterViewInit,OnDestroy,DoCheck} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+
+@Directive({
+ selector: '[pPassword]',
+ host: {
+ '[class.ui-inputtext]': 'true',
+ '[class.ui-corner-all]': 'true',
+ '[class.ui-state-default]': 'true',
+ '[class.ui-widget]': 'true',
+ '[class.ui-state-filled]': 'filled'
+ },
+ providers: [DomHandler]
+})
+export class Password implements AfterViewInit,OnDestroy,DoCheck {
+
+ @Input() promptLabel: string = 'Please enter a password';
+
+ @Input() weakLabel: string = 'Weak';
+
+ @Input() mediumLabel: string = 'Medium';
+
+ @Input() strongLabel: string = 'Strong';
+
+ @Input() feedback: boolean = true;
+
+ panel: any;
+
+ meter: any;
+
+ info: any;
+
+ filled: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ ngAfterViewInit() {
+ this.panel = document.createElement('div');
+ this.panel.className = 'ui-password-panel ui-widget ui-state-highlight ui-corner-all ui-helper-hidden ui-password-panel-overlay';
+ this.meter = document.createElement('div');
+ this.meter.className = 'ui-password-meter';
+ this.info = document.createElement('div');
+ this.info.className = 'ui-password-info';
+ this.info.textContent = this.promptLabel;
+
+ if(this.feedback) {
+ this.panel.appendChild(this.meter);
+ this.panel.appendChild(this.info);
+ document.body.appendChild(this.panel);
+ }
+ }
+
+ ngDoCheck() {
+ this.updateFilledState();
+ }
+
+ //To trigger change detection to manage ui-state-filled for material labels when there is no value binding
+ @HostListener('input', ['$event'])
+ onInput(e) {
+ this.updateFilledState();
+ }
+
+ updateFilledState() {
+ this.filled = this.el.nativeElement.value && this.el.nativeElement.value.length;
+ }
+
+ @HostListener('focus', ['$event'])
+ onFocus(e) {
+ this.panel.style.zIndex = String(++DomHandler.zindex);
+ this.domHandler.removeClass(this.panel, 'ui-helper-hidden');
+ this.domHandler.absolutePosition(this.panel, this.el.nativeElement);
+ this.domHandler.fadeIn(this.panel, 250);
+ }
+
+ @HostListener('blur', ['$event'])
+ onBlur(e) {
+ this.domHandler.addClass(this.panel, 'ui-helper-hidden');
+ }
+
+ @HostListener('keyup', ['$event'])
+ onKeyup(e) {
+ let value = e.target.value,
+ label = null,
+ meterPos = null;
+
+ if(value.length === 0) {
+ label = this.promptLabel;
+ meterPos = '0px 0px';
+ }
+ else {
+ var score = this.testStrength(value);
+
+ if(score < 30) {
+ label = this.weakLabel;
+ meterPos = '0px -10px';
+ }
+ else if(score >= 30 && score < 80) {
+ label = this.mediumLabel;
+ meterPos = '0px -20px';
+ }
+ else if(score >= 80) {
+ label = this.strongLabel;
+ meterPos = '0px -30px';
+ }
+ }
+
+ this.meter.style.backgroundPosition = meterPos;
+ this.info.textContent = label;
+ }
+
+ testStrength(str: string) {
+ let grade: number = 0;
+ let val;
+
+ val = str.match('[0-9]');
+ grade += this.normalize(val ? val.length : 1/4, 1) * 25;
+
+ val = str.match('[a-zA-Z]');
+ grade += this.normalize(val ? val.length : 1/2, 3) * 10;
+
+ val = str.match('[!@#$%^&*?_~.,;=]');
+ grade += this.normalize(val ? val.length : 1/6, 1) * 35;
+
+ val = str.match('[A-Z]');
+ grade += this.normalize(val ? val.length : 1/6, 1) * 30;
+
+ grade *= str.length / 8;
+
+ return grade > 100 ? 100 : grade;
+ }
+
+ normalize(x, y) {
+ let diff = x - y;
+
+ if(diff <= 0)
+ return x / y;
+ else
+ return 1 + 0.5 * (x / (x + y/4));
+ }
+
+ get disabled(): boolean {
+ return this.el.nativeElement.disabled;
+ }
+
+ ngOnDestroy() {
+ if (!this.feedback)
+ return;
+
+ this.panel.removeChild(this.meter);
+ this.panel.removeChild(this.info);
+ document.body.removeChild(this.panel);
+ this.panel = null;
+ this.meter = null;
+ this.info = null;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Password],
+ declarations: [Password]
+})
+export class PasswordModule { }
diff --git a/src/app/components/picklist/picklist.css b/src/app/components/picklist/picklist.css
new file mode 100644
index 00000000000..c6a12aaf38f
--- /dev/null
+++ b/src/app/components/picklist/picklist.css
@@ -0,0 +1,191 @@
+.ui-picklist > div {
+ float: left;
+}
+
+.ui-picklist .ui-picklist-buttons {
+ height: 12.5em;
+ padding: 0 .25em;
+}
+
+.ui-picklist .ui-picklist-list {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ overflow:auto;
+ height: 12.5em;
+ width: 12.5em;
+}
+
+.ui-picklist .ui-picklist-list li {
+ margin: 1px;
+ padding: .125em;
+}
+
+.ui-picklist .ui-button {
+ display:block;
+ margin-bottom: 0.25em;
+}
+
+.ui-picklist .ui-button-text-icon-left {
+ width: 100%;
+}
+
+.ui-picklist .ui-picklist-item {
+ cursor: pointer;
+ border: 0 none;
+ font-weight: inherit;
+}
+
+.ui-picklist .ui-picklist-caption {
+ text-align: center;
+ padding: .5em .75em;
+ border-bottom:0 none;
+}
+
+.ui-picklist table {
+ width: 100%;
+ border-collapse:collapse;
+}
+
+.ui-picklist .ui-picklist-filter {
+ padding-right: 1em;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.ui-picklist .ui-picklist-filter-container {
+ position: relative;
+ margin: 0;
+ padding: 0;
+}
+
+.ui-picklist .ui-picklist-filter-container .fa {
+ position: absolute;
+ top: .25em;
+ right: .125em;
+}
+
+.ui-picklist {
+ display: table;
+}
+
+.ui-picklist > div {
+ float: none;
+ display: table-cell;
+ vertical-align: top;
+}
+
+.ui-picklist .ui-picklist-buttons {
+ vertical-align: middle;
+}
+
+/* Vertical */
+.ui-picklist.ui-picklist-vertical {
+ display: table;
+}
+
+.ui-picklist.ui-picklist-vertical > div {
+ float: none;
+ display: table-row;
+ vertical-align: top;
+}
+
+.ui-picklist.ui-picklist-vertical .ui-picklist-buttons {
+ text-align:center;
+ height: auto;
+}
+
+.ui-picklist.ui-picklist-vertical .ui-picklist-buttons .ui-button {
+ display: inline-block;
+}
+
+.ui-picklist.ui-picklist-vertical .ui-button {
+ margin-top: 0.25em;
+}
+
+.ui-picklist-outline {
+ outline: 1px dotted black;
+ z-index: 1;
+}
+
+.ui-picklist-list.ui-picklist-source,
+.ui-picklist-list.ui-picklist-target {
+ outline: none;
+}
+
+/* Responsive */
+.ui-picklist.ui-picklist-responsive * {
+ box-sizing: border-box;
+}
+
+.ui-picklist.ui-picklist-responsive {
+ width: 100%;
+}
+
+.ui-picklist.ui-picklist-responsive .ui-picklist-listwrapper {
+ width: 35%;
+}
+
+.ui-picklist.ui-picklist-responsive .ui-picklist-listwrapper.ui-picklist-listwrapper-nocontrols {
+ width: 45%;
+}
+
+.ui-picklist.ui-picklist-responsive .ui-picklist-buttons {
+ width: 10%;
+}
+
+.ui-picklist.ui-picklist-responsive .ui-picklist-buttons button {
+ width: 100%;
+}
+
+.ui-picklist.ui-picklist-responsive .ui-picklist-list {
+ width: auto;
+}
+
+/* Responsive */
+@media (max-width: 40em) {
+ .ui-picklist.ui-picklist-responsive {
+ display: block;
+ }
+
+ .ui-picklist.ui-picklist-responsive > div {
+ display: block;
+ width: 100% !important;
+ }
+
+ .ui-picklist.ui-picklist-responsive .ui-picklist-buttons {
+ text-align: center;
+ height: auto;
+ padding: .4em 0;
+ }
+
+ .ui-picklist.ui-picklist-responsive .ui-picklist-buttons button {
+ display: inline;
+ width: 20%;
+ margin-bottom: 0;
+ }
+
+ .ui-picklist.ui-picklist-responsive .ui-picklist-source-controls.ui-picklist-buttons {
+ padding-bottom: .4em;
+ }
+
+ .ui-picklist.ui-picklist-responsive .ui-picklist-target-controls.ui-picklist-buttons {
+ padding-top: .4em;
+ }
+
+ .ui-picklist.ui-picklist-responsive .ui-picklist-buttons .fa-angle-right:before {
+ content: "\f107";
+ }
+
+ .ui-picklist.ui-picklist-responsive .ui-picklist-buttons .fa-angle-double-right:before {
+ content: "\f103";
+ }
+
+ .ui-picklist.ui-picklist-responsive .ui-picklist-buttons .fa-angle-left:before {
+ content: "\f106";
+ }
+
+ .ui-picklist.ui-picklist-responsive .ui-picklist-buttons .fa-angle-double-left:before {
+ content: "\f102";
+ }
+}
diff --git a/src/app/components/picklist/picklist.ts b/src/app/components/picklist/picklist.ts
new file mode 100644
index 00000000000..ff21e600edd
--- /dev/null
+++ b/src/app/components/picklist/picklist.ts
@@ -0,0 +1,348 @@
+import {NgModule,Component,ElementRef,OnDestroy,AfterViewInit,AfterContentInit,AfterViewChecked,DoCheck,Input,Output,ContentChildren,QueryList,TemplateRef,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {ButtonModule} from '../button/button';
+import {SharedModule,PrimeTemplate} from '../common/shared';
+import {DomHandler} from '../dom/domhandler';
+
+@Component({
+ selector: 'p-pickList',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class PickList implements OnDestroy,AfterViewChecked,AfterContentInit {
+
+ @Input() source: any[];
+
+ @Input() target: any[];
+
+ @Input() sourceHeader: string;
+
+ @Input() targetHeader: string;
+
+ @Input() responsive: boolean;
+
+ @Input() metaKeySelection: boolean = true;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() sourceStyle: any;
+
+ @Input() targetStyle: any;
+
+ @Input() showSourceControls: boolean = true;
+
+ @Input() showTargetControls: boolean = true;
+
+ @Output() onMoveToSource: EventEmitter = new EventEmitter();
+
+ @Output() onMoveToTarget: EventEmitter = new EventEmitter();
+
+ @Output() onSourceReorder: EventEmitter = new EventEmitter();
+
+ @Output() onTargetReorder: EventEmitter = new EventEmitter();
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public itemTemplate: TemplateRef;
+
+ selectedItemsSource: any[] = [];
+
+ selectedItemsTarget: any[] = [];
+
+ reorderedListElement: any;
+
+ movedUp: boolean;
+
+ movedDown: boolean;
+
+ itemTouched: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ ngAfterContentInit() {
+ this.templates.forEach((item) => {
+ switch(item.getType()) {
+ case 'item':
+ this.itemTemplate = item.template;
+ break;
+
+ default:
+ this.itemTemplate = item.template;
+ break;
+ }
+ });
+ }
+
+ ngAfterViewChecked() {
+ if(this.movedUp||this.movedDown) {
+ let listItems = this.domHandler.find(this.reorderedListElement, 'li.ui-state-highlight');
+ let listItem;
+
+ if(this.movedUp)
+ listItem = listItems[0];
+ else
+ listItem = listItems[listItems.length - 1];
+
+ this.domHandler.scrollInView(this.reorderedListElement, listItem);
+ this.movedUp = false;
+ this.movedDown = false;
+ this.reorderedListElement = null;
+ }
+ }
+
+ onItemClick(event, item: any, selectedItems: any[]) {
+ let index = this.findIndexInSelection(item,selectedItems);
+ let selected = (index != -1);
+ let metaSelection = this.itemTouched ? false : this.metaKeySelection;
+
+ if(metaSelection) {
+ let metaKey = (event.metaKey||event.ctrlKey);
+
+ if(selected && metaKey) {
+ selectedItems.splice(index, 1);
+ }
+ else {
+ if(!metaKey) {
+ selectedItems.length = 0;
+ }
+ selectedItems.push(item);
+ }
+ }
+ else {
+ if(selected)
+ selectedItems.splice(index, 1);
+ else
+ selectedItems.push(item);
+ }
+
+
+ this.itemTouched = false;
+ }
+
+ onItemTouchEnd(event) {
+ this.itemTouched = true;
+ }
+
+ moveUp(listElement, list, selectedItems, callback) {
+ if(selectedItems && selectedItems.length) {
+ for(let i = 0; i < selectedItems.length; i++) {
+ let selectedItem = selectedItems[i];
+ let selectedItemIndex: number = this.findIndexInList(selectedItem, list);
+
+ if(selectedItemIndex != 0) {
+ let movedItem = list[selectedItemIndex];
+ let temp = list[selectedItemIndex-1];
+ list[selectedItemIndex-1] = movedItem;
+ list[selectedItemIndex] = temp;
+ }
+ else {
+ break;
+ }
+ }
+
+ this.movedUp = true;
+ this.reorderedListElement = listElement;
+ callback.emit({items: selectedItems});
+ }
+ }
+
+ moveTop(listElement, list, selectedItems, callback) {
+ if(selectedItems && selectedItems.length) {
+ for(let i = 0; i < selectedItems.length; i++) {
+ let selectedItem = selectedItems[i];
+ let selectedItemIndex: number = this.findIndexInList(selectedItem, list);
+
+ if(selectedItemIndex != 0) {
+ let movedItem = list.splice(selectedItemIndex,1)[0];
+ list.unshift(movedItem);
+ }
+ else {
+ break;
+ }
+ }
+
+ listElement.scrollTop = 0;
+ callback.emit({items: selectedItems});
+ }
+ }
+
+ moveDown(listElement, list, selectedItems, callback) {
+ if(selectedItems && selectedItems.length) {
+ for(let i = selectedItems.length - 1; i >= 0; i--) {
+ let selectedItem = selectedItems[i];
+ let selectedItemIndex: number = this.findIndexInList(selectedItem, list);
+
+ if(selectedItemIndex != (list.length - 1)) {
+ let movedItem = list[selectedItemIndex];
+ let temp = list[selectedItemIndex+1];
+ list[selectedItemIndex+1] = movedItem;
+ list[selectedItemIndex] = temp;
+ }
+ else {
+ break;
+ }
+ }
+
+ this.movedDown = true;
+ this.reorderedListElement = listElement;
+ callback.emit({items: selectedItems});
+ }
+ }
+
+ moveBottom(listElement, list, selectedItems, callback) {
+ if(selectedItems && selectedItems.length) {
+ for(let i = selectedItems.length - 1; i >= 0; i--) {
+ let selectedItem = selectedItems[i];
+ let selectedItemIndex: number = this.findIndexInList(selectedItem, list);
+
+ if(selectedItemIndex != (list.length - 1)) {
+ let movedItem = list.splice(selectedItemIndex,1)[0];
+ list.push(movedItem);
+ }
+ else {
+ break;
+ }
+ }
+
+ listElement.scrollTop = listElement.scrollHeight;
+ callback.emit({items: selectedItems});
+ }
+ }
+
+ moveRight(targetListElement) {
+ if(this.selectedItemsSource && this.selectedItemsSource.length) {
+ for(let i = 0; i < this.selectedItemsSource.length; i++) {
+ let selectedItem = this.selectedItemsSource[i];
+ if(this.findIndexInList(selectedItem, this.target) == -1) {
+ this.target.push(this.source.splice(this.findIndexInList(selectedItem, this.source),1)[0]);
+ }
+ }
+ this.onMoveToTarget.emit({
+ items: this.selectedItemsSource
+ });
+ this.selectedItemsSource = [];
+ }
+ }
+
+ moveAllRight() {
+ if(this.source) {
+ for(let i = 0; i < this.source.length; i++) {
+ this.target.push(this.source[i]);
+ }
+ this.onMoveToTarget.emit({
+ items: this.source
+ });
+ this.source.splice(0, this.source.length);
+ this.selectedItemsSource = [];
+ }
+ }
+
+ moveLeft(sourceListElement) {
+ if(this.selectedItemsTarget && this.selectedItemsTarget.length) {
+ for(let i = 0; i < this.selectedItemsTarget.length; i++) {
+ let selectedItem = this.selectedItemsTarget[i];
+ if(this.findIndexInList(selectedItem, this.source) == -1) {
+ this.source.push(this.target.splice(this.findIndexInList(selectedItem, this.target),1)[0]);
+ }
+ }
+ this.onMoveToSource.emit({
+ items: this.selectedItemsTarget
+ });
+ this.selectedItemsTarget = [];
+ }
+ }
+
+ moveAllLeft() {
+ if(this.target) {
+ for(let i = 0; i < this.target.length; i++) {
+ this.source.push(this.target[i]);
+ }
+ this.onMoveToSource.emit({
+ items: this.target
+ });
+ this.target.splice(0, this.target.length);
+ this.selectedItemsTarget = [];
+ }
+ }
+
+ isSelected(item: any, selectedItems: any[]) {
+ return this.findIndexInSelection(item, selectedItems) != -1;
+ }
+
+ findIndexInSelection(item: any, selectedItems: any[]): number {
+ return this.findIndexInList(item, selectedItems);
+ }
+
+ findIndexInList(item: any, list: any): number {
+ let index: number = -1;
+
+ if(list) {
+ for(let i = 0; i < list.length; i++) {
+ if(list[i] == item) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ return index;
+ }
+
+ ngOnDestroy() {
+
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,ButtonModule,SharedModule],
+ exports: [PickList,SharedModule],
+ declarations: [PickList]
+})
+export class PickListModule { }
diff --git a/src/app/components/progressbar/progressbar.css b/src/app/components/progressbar/progressbar.css
new file mode 100644
index 00000000000..017bb5c94e6
--- /dev/null
+++ b/src/app/components/progressbar/progressbar.css
@@ -0,0 +1,29 @@
+.ui-progressbar {
+ height: 1.2em;
+ text-align: left;
+ position: relative;
+}
+
+.ui-progressbar .ui-progressbar-value {
+ height: 100%;
+ width: 0%;
+ position: absolute;
+ display: none;
+ border: 0 none;
+}
+
+.ui-progressbar .ui-progressbar-value-animate {
+ -webkit-transition: width 1s ease-in-out;
+ -moz-transition: width 1s ease-in-out;
+ -o-transition: width 1s ease-in-out;
+ transition: width 1s ease-in-out;
+}
+
+.ui-progressbar .ui-progressbar-label {
+ text-align: center;
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ display: none;
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/src/app/components/progressbar/progressbar.ts b/src/app/components/progressbar/progressbar.ts
new file mode 100644
index 00000000000..a9b533716cb
--- /dev/null
+++ b/src/app/components/progressbar/progressbar.ts
@@ -0,0 +1,28 @@
+import {NgModule,Component,Input} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+@Component({
+ selector: 'p-progressBar',
+ template: `
+
+ `
+})
+export class ProgressBar {
+
+ @Input() value: any;
+
+ @Input() showValue: boolean = true;
+
+ @Input() unit: string = '%';
+
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [ProgressBar],
+ declarations: [ProgressBar]
+})
+export class ProgressBarModule { }
\ No newline at end of file
diff --git a/src/app/components/radiobutton/radiobutton.css b/src/app/components/radiobutton/radiobutton.css
new file mode 100644
index 00000000000..0a2480bafdd
--- /dev/null
+++ b/src/app/components/radiobutton/radiobutton.css
@@ -0,0 +1,27 @@
+.ui-radiobutton {
+ display:inline-block;
+ cursor: pointer;
+ vertical-align: middle;
+ margin-right: .25em;
+}
+
+.ui-radiobutton-box {
+ width: 1.125em;
+ height: 1.125em;
+ line-height: 1.125em;
+ -moz-border-radius: 100%;
+ -webkit-border-radius: 100%;
+ border-radius: 100%;
+ text-align: center;
+}
+
+.ui-radiobutton-icon {
+ display: block;
+ font-size: .6em;
+ line-height: inherit;
+}
+
+.ui-radiobutton, .ui-radiobutton-label {
+ vertical-align: middle;
+ display: inline-block;
+}
\ No newline at end of file
diff --git a/src/app/components/radiobutton/radiobutton.ts b/src/app/components/radiobutton/radiobutton.ts
new file mode 100644
index 00000000000..ece45c2cc6c
--- /dev/null
+++ b/src/app/components/radiobutton/radiobutton.ts
@@ -0,0 +1,123 @@
+import {NgModule,Component,Input,Output,AfterViewInit,ElementRef,EventEmitter,forwardRef,ViewChild,ChangeDetectorRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const RADIO_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => RadioButton),
+ multi: true
+};
+
+@Component({
+ selector: 'p-radioButton',
+ template: `
+
+
+ `,
+ providers: [RADIO_VALUE_ACCESSOR]
+})
+export class RadioButton implements ControlValueAccessor,AfterViewInit {
+
+ @Input() value: any;
+
+ @Input() name: string;
+
+ @Input() disabled: boolean;
+
+ @Input() label: string;
+
+ @Input() tabindex: number;
+
+ @Input() inputId: string;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Output() onClick: EventEmitter = new EventEmitter();
+
+ @ViewChild('rb') inputViewChild: ElementRef;
+
+ public input: HTMLInputElement;
+
+ public onModelChange: Function = () => {};
+
+ public onModelTouched: Function = () => {};
+
+ public checked: boolean;
+
+ public focused: boolean;
+
+ constructor(private cd: ChangeDetectorRef) {}
+
+ ngAfterViewInit() {
+ this.input = this.inputViewChild.nativeElement;
+ }
+
+ handleClick() {
+ if(!this.disabled) {
+ this.onClick.emit(null);
+ this.select();
+ }
+ }
+
+ select() {
+ if(!this.disabled) {
+ this.input.checked = true;
+ this.checked = true;
+ this.onModelChange(this.value);
+ }
+ }
+
+ writeValue(value: any) : void {
+ this.checked = (value == this.value);
+
+ if(this.input) {
+ this.input.checked = this.checked;
+ }
+
+ this.cd.markForCheck();
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ onFocus(event) {
+ this.focused = true;
+ }
+
+ onBlur(event) {
+ this.focused = false;
+ this.onModelTouched();
+ }
+
+ onChange(event) {
+ this.select();
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [RadioButton],
+ declarations: [RadioButton]
+})
+export class RadioButtonModule { }
\ No newline at end of file
diff --git a/src/app/components/rating/rating.ts b/src/app/components/rating/rating.ts
new file mode 100644
index 00000000000..44692c967d0
--- /dev/null
+++ b/src/app/components/rating/rating.ts
@@ -0,0 +1,99 @@
+import {NgModule,Component,ElementRef,OnInit,Input,Output,EventEmitter,forwardRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const RATING_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => Rating),
+ multi: true
+};
+
+@Component({
+ selector: 'p-rating',
+ template: `
+
+ `,
+ providers: [RATING_VALUE_ACCESSOR]
+})
+export class Rating implements ControlValueAccessor {
+
+ @Input() disabled: boolean;
+
+ @Input() readonly: boolean;
+
+ @Input() stars: number = 5;
+
+ @Input() cancel: boolean = true;
+
+ @Output() onRate: EventEmitter = new EventEmitter();
+
+ @Output() onCancel: EventEmitter = new EventEmitter();
+
+ value: number;
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ public starsArray: number[];
+
+ ngOnInit() {
+ this.starsArray = [];
+ for(let i = 0; i < this.stars; i++) {
+ this.starsArray[i] = i;
+ }
+ }
+
+ rate(event, i: number): void {
+ if(!this.readonly&&!this.disabled) {
+ this.value = (i + 1);
+ this.onModelChange(this.value);
+ this.onModelTouched();
+ this.onRate.emit({
+ originalEvent: event,
+ value: (i+1)
+ });
+ }
+ event.preventDefault();
+ }
+
+ clear(event): void {
+ if(!this.readonly&&!this.disabled) {
+ this.value = null;
+ this.onModelChange(this.value);
+ this.onModelTouched();
+ this.onCancel.emit(event);
+ }
+ event.preventDefault();
+ }
+
+ writeValue(value: any) : void {
+ this.value = value;
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Rating],
+ declarations: [Rating]
+})
+export class RatingModule { }
\ No newline at end of file
diff --git a/src/app/components/schedule/schedule.css b/src/app/components/schedule/schedule.css
new file mode 100644
index 00000000000..838a51d449d
--- /dev/null
+++ b/src/app/components/schedule/schedule.css
@@ -0,0 +1,3 @@
+.ui-fluid .fc .ui-button {
+ width: auto;
+}
\ No newline at end of file
diff --git a/src/app/components/schedule/schedule.ts b/src/app/components/schedule/schedule.ts
new file mode 100644
index 00000000000..ec4bd4e99c6
--- /dev/null
+++ b/src/app/components/schedule/schedule.ts
@@ -0,0 +1,382 @@
+import {NgModule,Component,ElementRef,OnDestroy,DoCheck,OnChanges,Input,Output,EventEmitter,IterableDiffers,OnInit,AfterViewChecked,SimpleChanges} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+declare var jQuery: any;
+
+@Component({
+ selector: 'p-schedule',
+ template: ''
+})
+export class Schedule implements DoCheck,OnDestroy,OnInit,OnChanges,AfterViewChecked {
+
+ @Input() events: any[];
+
+ @Input() header: any;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() rtl: boolean;
+
+ @Input() weekends: boolean;
+
+ @Input() hiddenDays: number[];
+
+ @Input() fixedWeekCount: boolean;
+
+ @Input() weekNumbers: boolean;
+
+ @Input() businessHours: any;
+
+ @Input() height: any;
+
+ @Input() contentHeight: any;
+
+ @Input() aspectRatio: number = 1.35;
+
+ @Input() eventLimit: any;
+
+ @Input() defaultDate: any;
+
+ @Input() editable: boolean;
+
+ @Input() droppable: boolean;
+
+ @Input() eventStartEditable: boolean;
+
+ @Input() eventDurationEditable: boolean;
+
+ @Input() defaultView: string = 'month';
+
+ @Input() allDaySlot: boolean = true;
+
+ @Input() allDayText: string = 'all-day';
+
+ @Input() slotDuration: any = '00:30:00';
+
+ @Input() slotLabelInterval: any;
+
+ @Input() snapDuration: any;
+
+ @Input() scrollTime: any = '06:00:00';
+
+ @Input() minTime: any = '00:00:00';
+
+ @Input() maxTime: any = '24:00:00';
+
+ @Input() slotEventOverlap: boolean = true;
+
+ @Input() nowIndicator: boolean;
+
+ @Input() dragRevertDuration: number = 500;
+
+ @Input() dragOpacity: number = .75;
+
+ @Input() dragScroll: boolean = true;
+
+ @Input() eventOverlap: any;
+
+ @Input() eventConstraint: any;
+
+ @Input() locale: string;
+
+ @Input() timezone: boolean | string = false;
+
+ @Input() timeFormat:string | null = null;
+
+ @Input() eventRender: Function;
+
+ @Input() dayRender: Function;
+
+ @Input() options: any;
+
+ @Output() onDayClick: EventEmitter = new EventEmitter();
+
+ @Output() onDrop: EventEmitter = new EventEmitter();
+
+ @Output() onEventClick: EventEmitter = new EventEmitter();
+
+ @Output() onEventMouseover: EventEmitter = new EventEmitter();
+
+ @Output() onEventMouseout: EventEmitter = new EventEmitter();
+
+ @Output() onEventDragStart: EventEmitter = new EventEmitter();
+
+ @Output() onEventDragStop: EventEmitter = new EventEmitter();
+
+ @Output() onEventDrop: EventEmitter = new EventEmitter();
+
+ @Output() onEventResizeStart: EventEmitter = new EventEmitter();
+
+ @Output() onEventResizeStop: EventEmitter = new EventEmitter();
+
+ @Output() onEventResize: EventEmitter = new EventEmitter();
+
+ @Output() onViewRender: EventEmitter = new EventEmitter();
+
+ initialized: boolean;
+
+ stopNgOnChangesPropagation: boolean;
+
+ differ: any;
+
+ schedule: any;
+
+ config: any;
+
+ constructor(public el: ElementRef, differs: IterableDiffers) {
+ this.differ = differs.find([]).create(null);
+ this.initialized = false;
+ }
+
+ ngOnInit() {
+ this.config = {
+ theme: true,
+ header: this.header,
+ isRTL: this.rtl,
+ weekends: this.weekends,
+ hiddenDays: this.hiddenDays,
+ fixedWeekCount: this.fixedWeekCount,
+ weekNumbers: this.weekNumbers,
+ businessHours: this.businessHours,
+ height: this.height,
+ contentHeight: this.contentHeight,
+ aspectRatio: this.aspectRatio,
+ eventLimit: this.eventLimit,
+ defaultDate: this.defaultDate,
+ locale: this.locale,
+ timezone: this.timezone,
+ timeFormat: this.timeFormat,
+ editable: this.editable,
+ droppable: this.droppable,
+ eventStartEditable: this.eventStartEditable,
+ eventDurationEditable: this.eventDurationEditable,
+ defaultView: this.defaultView,
+ allDaySlot: this.allDaySlot,
+ allDayText: this.allDayText,
+ slotDuration: this.slotDuration,
+ slotLabelInterval: this.slotLabelInterval,
+ snapDuration: this.snapDuration,
+ scrollTime: this.scrollTime,
+ minTime: this.minTime,
+ maxTime: this.maxTime,
+ slotEventOverlap: this.slotEventOverlap,
+ nowIndicator: this.nowIndicator,
+ dragRevertDuration: this.dragRevertDuration,
+ dragOpacity: this.dragOpacity,
+ dragScroll: this.dragScroll,
+ eventOverlap: this.eventOverlap,
+ eventConstraint: this.eventConstraint,
+ eventRender: this.eventRender,
+ dayRender: this.dayRender,
+ dayClick: (date, jsEvent, view) => {
+ this.onDayClick.emit({
+ 'date': date,
+ 'jsEvent': jsEvent,
+ 'view': view
+ });
+ },
+ drop: (date, jsEvent, ui, resourceId) => {
+ this.onDrop.emit({
+ 'date': date,
+ 'jsEvent': jsEvent,
+ 'ui': ui,
+ 'resourceId': resourceId
+ });
+ },
+ eventClick: (calEvent, jsEvent, view) => {
+ this.onEventClick.emit({
+ 'calEvent': calEvent,
+ 'jsEvent': jsEvent,
+ 'view': view
+ });
+ },
+ eventMouseover: (calEvent, jsEvent, view) => {
+ this.onEventMouseover.emit({
+ 'calEvent': calEvent,
+ 'jsEvent': jsEvent,
+ 'view': view
+ });
+ },
+ eventMouseout: (calEvent, jsEvent, view) => {
+ this.onEventMouseout.emit({
+ 'calEvent': calEvent,
+ 'jsEvent': jsEvent,
+ 'view': view
+ });
+ },
+ eventDragStart: (event, jsEvent, ui, view) => {
+ this.onEventDragStart.emit({
+ 'event': event,
+ 'jsEvent': jsEvent,
+ 'view': view
+ });
+ },
+ eventDragStop: (event, jsEvent, ui, view) => {
+ this.onEventDragStop.emit({
+ 'event': event,
+ 'jsEvent': jsEvent,
+ 'view': view
+ });
+ },
+ eventDrop: (event, delta, revertFunc, jsEvent, ui, view) => {
+ this.updateEvent(event);
+
+ this.onEventDrop.emit({
+ 'event': event,
+ 'delta': delta,
+ 'revertFunc': revertFunc,
+ 'jsEvent': jsEvent,
+ 'view': view
+ });
+ },
+ eventResizeStart: (event, jsEvent, ui, view) => {
+ this.onEventResizeStart.emit({
+ 'event': event,
+ 'jsEvent': jsEvent,
+ 'view': view
+ });
+ },
+ eventResizeStop: (event, jsEvent, ui, view) => {
+ this.onEventResizeStop.emit({
+ 'event': event,
+ 'jsEvent': jsEvent,
+ 'view': view
+ });
+ },
+ eventResize: (event, delta, revertFunc, jsEvent, ui, view) => {
+ this.updateEvent(event);
+
+ this.onEventResize.emit({
+ 'event': event,
+ 'delta': delta,
+ 'revertFunc': revertFunc,
+ 'jsEvent': jsEvent,
+ 'view': view
+ });
+ },
+ viewRender: (view, element) => {
+ this.onViewRender.emit({
+ 'view': view,
+ 'element': element
+ });
+ }
+ };
+
+ if(this.options) {
+ for(let prop in this.options) {
+ this.config[prop] = this.options[prop];
+ }
+ }
+ }
+
+ ngAfterViewChecked() {
+ if(!this.initialized && this.el.nativeElement.offsetParent) {
+ this.initialize();
+ }
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if(this.schedule) {
+ let options = {};
+ for(let change in changes) {
+ if(change !== 'events') {
+ options[change] = changes[change].currentValue;
+ }
+ }
+
+ if(Object.keys(options).length) {
+ this.schedule.fullCalendar('option', options);
+ }
+ }
+ }
+
+ initialize() {
+ this.schedule = jQuery(this.el.nativeElement.children[0]);
+ this.schedule.fullCalendar(this.config);
+ this.schedule.fullCalendar('addEventSource', this.events);
+ this.initialized = true;
+ }
+
+ ngDoCheck() {
+ let changes = this.differ.diff(this.events);
+
+ if(this.schedule && changes) {
+ this.schedule.fullCalendar('removeEventSources');
+ this.schedule.fullCalendar('addEventSource', this.events);
+ }
+ }
+
+ ngOnDestroy() {
+ jQuery(this.el.nativeElement.children[0]).fullCalendar('destroy');
+ this.initialized = false;
+ this.schedule = null;
+ }
+
+ gotoDate(date: any) {
+ this.schedule.fullCalendar('gotoDate', date);
+ }
+
+ prev() {
+ this.schedule.fullCalendar('prev');
+ }
+
+ next() {
+ this.schedule.fullCalendar('next');
+ }
+
+ prevYear() {
+ this.schedule.fullCalendar('prevYear');
+ }
+
+ nextYear() {
+ this.schedule.fullCalendar('nextYear');
+ }
+
+ today() {
+ this.schedule.fullCalendar('today');
+ }
+
+ incrementDate(duration: any) {
+ this.schedule.fullCalendar('incrementDate', duration);
+ }
+
+ changeView(viewName: string) {
+ this.schedule.fullCalendar('changeView', viewName);
+ }
+
+ getDate() {
+ return this.schedule.fullCalendar('getDate');
+ }
+
+ findEvent(id: string) {
+ let event;
+ if(this.events) {
+ for(let e of this.events) {
+ if(e.id === id) {
+ event = e;
+ break;
+ }
+ }
+ }
+ return event;
+ }
+
+ updateEvent(event: any) {
+ let sourceEvent = this.findEvent(event.id);
+ if(sourceEvent) {
+ sourceEvent.start = event.start.format();
+ if(event.end) {
+ sourceEvent.end = event.end.format();
+ }
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Schedule],
+ declarations: [Schedule]
+})
+export class ScheduleModule { }
diff --git a/src/app/components/selectbutton/selectbutton.css b/src/app/components/selectbutton/selectbutton.css
new file mode 100644
index 00000000000..b2b6650d3bf
--- /dev/null
+++ b/src/app/components/selectbutton/selectbutton.css
@@ -0,0 +1,11 @@
+.ui-selectbutton{
+ display: inline-block;
+}
+
+.ui-selectbutton.ui-state-error {
+ padding: 0;
+}
+
+.ui-selectbutton .ui-button.ui-state-focus{
+ outline: none;
+}
\ No newline at end of file
diff --git a/src/app/components/selectbutton/selectbutton.ts b/src/app/components/selectbutton/selectbutton.ts
new file mode 100644
index 00000000000..dd101e27434
--- /dev/null
+++ b/src/app/components/selectbutton/selectbutton.ts
@@ -0,0 +1,128 @@
+import {NgModule,Component,Input,Output,EventEmitter,forwardRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {SelectItem} from '../common/selectitem';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const SELECTBUTTON_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => SelectButton),
+ multi: true
+};
+
+@Component({
+ selector: 'p-selectButton',
+ template: `
+
+
+
{{option.label}}
+
+
+
+
+
+ `,
+ providers: [SELECTBUTTON_VALUE_ACCESSOR]
+})
+export class SelectButton implements ControlValueAccessor {
+
+ @Input() options: SelectItem[];
+
+ @Input() tabindex: number;
+
+ @Input() multiple: boolean;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() disabled: boolean;
+
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ value: any;
+
+ focusedItem: HTMLInputElement;
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ writeValue(value: any) : void {
+ this.value = value;
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ onItemClick(event, option: SelectItem, checkbox: HTMLInputElement) {
+ if(this.disabled) {
+ return;
+ }
+
+ checkbox.focus();
+
+ if(this.multiple) {
+ let itemIndex = this.findItemIndex(option);
+ if(itemIndex != -1)
+ this.value = this.value.filter((val,i) => i!=itemIndex);
+ else
+ this.value = [...this.value||[], option.value];
+ }
+ else {
+ this.value = option.value;
+ }
+
+ this.onModelChange(this.value);
+
+ this.onChange.emit({
+ originalEvent: event,
+ value: this.value
+ });
+ }
+
+ onFocus(event: Event) {
+ this.focusedItem = event.target;
+ }
+
+ onBlur(event) {
+ this.focusedItem = null;
+ this.onModelTouched();
+ }
+
+ isSelected(option: SelectItem) {
+ if(this.multiple)
+ return this.findItemIndex(option) != -1;
+ else
+ return option.value == this.value;
+ }
+
+ findItemIndex(option: SelectItem) {
+ let index = -1;
+ if(this.value) {
+ for(let i = 0; i < this.value.length; i++) {
+ if(this.value[i] == option.value) {
+ index = i;
+ break;
+ }
+ }
+ }
+ return index;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [SelectButton],
+ declarations: [SelectButton]
+})
+export class SelectButtonModule { }
diff --git a/src/app/components/slidemenu/slidemenu.ts b/src/app/components/slidemenu/slidemenu.ts
new file mode 100644
index 00000000000..6ec0f22d85e
--- /dev/null
+++ b/src/app/components/slidemenu/slidemenu.ts
@@ -0,0 +1,221 @@
+import {NgModule,Component,ElementRef,AfterViewInit,OnDestroy,Input,Output,Renderer2,EventEmitter,Inject,forwardRef,ViewChild} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {MenuItem} from '../common/menuitem';
+import {Location} from '@angular/common';
+import {RouterModule} from '@angular/router';
+
+@Component({
+ selector: 'p-slideMenuSub',
+ template: `
+
+ `
+})
+export class SlideMenuSub implements OnDestroy {
+
+ @Input() item: MenuItem;
+
+ @Input() root: boolean;
+
+ @Input() backLabel: string = 'Back';
+
+ @Input() menuWidth: string;
+
+ @Input() effectDuration: any;
+
+ @Input() easing: string = 'ease-out';
+
+ constructor(@Inject(forwardRef(() => SlideMenu)) public slideMenu: SlideMenu) {}
+
+ activeItem: any;
+
+ itemClick(event, item: MenuItem, listitem: any) {
+ if(item.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if(!item.url) {
+ event.preventDefault();
+ }
+
+ if(item.command) {
+ if(!item.eventEmitter && item.command) {
+ item.eventEmitter = new EventEmitter();
+ item.eventEmitter.subscribe(item.command);
+ }
+
+ item.eventEmitter.emit({
+ originalEvent: event,
+ item: item
+ });
+ }
+
+ if(item.items && !this.slideMenu.animating) {
+ this.slideMenu.left -= this.slideMenu.menuWidth;
+ this.activeItem = listitem;
+ this.slideMenu.animating = true;
+ setTimeout(() => this.slideMenu.animating = false, this.effectDuration);
+ }
+ }
+
+ ngOnDestroy() {
+ this.activeItem = null;
+ }
+}
+
+@Component({
+ selector: 'p-slideMenu',
+ template: `
+
+
+
+ `,
+ providers: [DomHandler]
+})
+export class SlideMenu implements AfterViewInit,OnDestroy {
+
+ @Input() model: MenuItem[];
+
+ @Input() popup: boolean;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() menuWidth: number = 190;
+
+ @Input() viewportHeight: number = 175;
+
+ @Input() effectDuration: any = 500;
+
+ @Input() easing: string = 'ease-out';
+
+ @Input() backLabel: string = 'Back';
+
+ @ViewChild('container') containerViewChild: ElementRef;
+
+ @ViewChild('backward') backwardViewChild: ElementRef;
+
+ @ViewChild('slideMenuContent') slideMenuContentViewChild: ElementRef;
+
+ public container: HTMLDivElement;
+
+ public backwardElement: HTMLDivElement;
+
+ public slideMenuContentElement: HTMLDivElement;
+
+ public documentClickListener: any;
+
+ public preventDocumentDefault: any;
+
+ public left: number = 0;
+
+ public animating: boolean = false;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2) {}
+
+ ngAfterViewInit() {
+ this.container = this.containerViewChild.nativeElement;
+ this.backwardElement = this.backwardViewChild.nativeElement;
+ this.slideMenuContentElement = this.slideMenuContentViewChild.nativeElement;
+ this.slideMenuContentElement.style.height = this.viewportHeight - this.domHandler.getHiddenElementOuterHeight(this.backwardElement) + 'px';
+
+ if(this.popup) {
+ this.documentClickListener = this.renderer.listen('document', 'click', () => {
+ if(!this.preventDocumentDefault) {
+ this.hide();
+ }
+ this.preventDocumentDefault = false;
+ });
+ }
+ }
+
+ toggle(event) {
+ if(this.container.offsetParent)
+ this.hide();
+ else
+ this.show(event);
+ }
+
+ show(event) {
+ this.preventDocumentDefault = true;
+ this.container.style.display = 'block';
+ this.domHandler.absolutePosition(this.container, event.target);
+ this.domHandler.fadeIn(this.container, 250);
+ }
+
+ hide() {
+ this.container.style.display = 'none';
+ }
+
+ unsubscribe(item: any) {
+ if(item.eventEmitter) {
+ item.eventEmitter.unsubscribe();
+ }
+
+ if(item.items) {
+ for(let childItem of item.items) {
+ this.unsubscribe(childItem);
+ }
+ }
+ }
+
+ onClick(event) {
+ this.preventDocumentDefault = true;
+ }
+
+ goBack() {
+ this.left += this.menuWidth;
+ }
+
+ ngOnDestroy() {
+ if(this.documentClickListener) {
+ this.documentClickListener();
+ }
+
+ if(this.model) {
+ for(let item of this.model) {
+ this.unsubscribe(item);
+ }
+ }
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule,RouterModule],
+ exports: [SlideMenu,RouterModule],
+ declarations: [SlideMenu,SlideMenuSub]
+})
+export class SlideMenuModule { }
diff --git a/src/app/components/slider/slider.css b/src/app/components/slider/slider.css
new file mode 100644
index 00000000000..00dafd7306a
--- /dev/null
+++ b/src/app/components/slider/slider.css
@@ -0,0 +1,65 @@
+.ui-slider {
+ position: relative;
+ text-align: left;
+}
+.ui-slider .ui-slider-handle {
+ position: absolute;
+ width: 1.2em;
+ height: 1.2em;
+ cursor: default;
+ -ms-touch-action: none;
+ touch-action: none;
+ z-index: 1;
+}
+.ui-slider .ui-slider-handle.ui-slider-handle-active {
+ z-index: 2;
+}
+.ui-slider .ui-slider-range {
+ position: absolute;
+ font-size: .7em;
+ display: block;
+ border: 0;
+ background-position: 0 0;
+}
+
+.ui-slider-horizontal {
+ height: .8em;
+}
+.ui-slider-horizontal .ui-slider-handle {
+ top: -.25em;
+ margin-left: -.6em;
+}
+.ui-slider-horizontal .ui-slider-range {
+ top: 0;
+ height: 100%;
+}
+.ui-slider-horizontal .ui-slider-range-min {
+ left: 0;
+}
+.ui-slider-horizontal .ui-slider-range-max {
+ right: 0;
+}
+
+.ui-slider-vertical {
+ width: .8em;
+ height: 100px;
+}
+.ui-slider-vertical .ui-slider-handle {
+ left: -.25em;
+ margin-left: 0;
+ margin-bottom: -.6em;
+}
+.ui-slider-vertical .ui-slider-range {
+ left: 0;
+ width: 100%;
+}
+.ui-slider-vertical .ui-slider-range-min {
+ bottom: 0;
+}
+.ui-slider-vertical .ui-slider-range-max {
+ top: 0;
+}
+
+.ui-slider-animate .ui-slider-handle {
+ transition: left .3s;
+}
\ No newline at end of file
diff --git a/src/app/components/slider/slider.ts b/src/app/components/slider/slider.ts
new file mode 100644
index 00000000000..82d24aa7236
--- /dev/null
+++ b/src/app/components/slider/slider.ts
@@ -0,0 +1,332 @@
+import {NgModule,Component, ElementRef,AfterViewInit,OnDestroy,Input,Output,SimpleChange,EventEmitter,forwardRef,Renderer2} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const SLIDER_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => Slider),
+ multi: true
+};
+
+@Component({
+ selector: 'p-slider',
+ template: `
+
+
+
+
+
+
+
+ `,
+ providers: [SLIDER_VALUE_ACCESSOR,DomHandler]
+})
+export class Slider implements AfterViewInit,OnDestroy,ControlValueAccessor {
+
+ @Input() animate: boolean;
+
+ @Input() disabled: boolean;
+
+ @Input() min: number = 0;
+
+ @Input() max: number = 100;
+
+ @Input() orientation: string = 'horizontal';
+
+ @Input() step: number;
+
+ @Input() range: boolean;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ @Output() onSlideEnd: EventEmitter = new EventEmitter();
+
+ public value: number;
+
+ public values: number;
+
+ public handleValue: number;
+
+ public handleValues: number[] = [];
+
+ public onModelChange: Function = () => {};
+
+ public onModelTouched: Function = () => {};
+
+ public dragging: boolean;
+
+ public dragListener: any;
+
+ public mouseupListener: any;
+
+ public initX: number;
+
+ public initY: number;
+
+ public barWidth: number;
+
+ public barHeight: number;
+
+ public sliderHandleClick: boolean;
+
+ public handleIndex: number;
+
+ public startHandleValue: any;
+
+ public startx: number;
+
+ public starty: number;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2) {}
+
+ onMouseDown(event:Event,index?:number) {
+ if(this.disabled) {
+ return;
+ }
+
+ this.dragging = true;
+ this.updateDomData();
+ this.sliderHandleClick = true;
+ this.handleIndex = index;
+ }
+
+ onTouchStart(event, index?:number) {
+ var touchobj = event.changedTouches[0];
+ this.startHandleValue = (this.range) ? this.handleValues[index] : this.handleValue;
+ this.dragging = true;
+ this.handleIndex = index;
+
+ if (this.orientation === 'horizontal') {
+ this.startx = parseInt(touchobj.clientX, 10);
+ this.barWidth = this.el.nativeElement.children[0].offsetWidth;
+ }
+ else {
+ this.starty = parseInt(touchobj.clientY, 10);
+ this.barHeight = this.el.nativeElement.children[0].offsetHeight;
+ }
+
+ event.preventDefault();
+ }
+
+ onTouchMove(event, index?:number) {
+ var touchobj = event.changedTouches[0],
+ handleValue = 0;
+
+ if (this.orientation === 'horizontal') {
+ handleValue = Math.floor(((parseInt(touchobj.clientX, 10) - this.startx) * 100) / (this.barWidth)) + this.startHandleValue;
+ }
+ else {
+ handleValue = Math.floor(((this.starty - parseInt(touchobj.clientY, 10)) * 100) / (this.barHeight)) + this.startHandleValue;
+ }
+
+ this.setValueFromHandle(event, handleValue);
+
+ event.preventDefault();
+ }
+
+ onBarClick(event) {
+ if(this.disabled) {
+ return;
+ }
+
+ if(!this.sliderHandleClick) {
+ this.updateDomData();
+ this.handleChange(event);
+ }
+
+ this.sliderHandleClick = false;
+ }
+
+ ngAfterViewInit() {
+ if(this.disabled) {
+ return;
+ }
+
+ this.dragListener = this.renderer.listen('document', 'mousemove', (event) => {
+ if(this.dragging) {
+ this.handleChange(event);
+ }
+ });
+
+ this.mouseupListener = this.renderer.listen('document', 'mouseup', (event) => {
+ if(this.dragging) {
+ this.dragging = false;
+ if(this.range) {
+ this.onSlideEnd.emit({originalEvent: event, values: this.values});
+ } else {
+ this.onSlideEnd.emit({originalEvent: event, value: this.value});
+ }
+ }
+ });
+ }
+
+ handleChange(event: Event) {
+ let handleValue = this.calculateHandleValue(event);
+ this.setValueFromHandle(event, handleValue);
+ }
+
+ setValueFromHandle(event: Event, handleValue: any) {
+ let newValue = this.getValueFromHandle(handleValue);
+
+ if(this.range) {
+ if(this.step) {
+ this.handleStepChange(newValue, this.values[this.handleIndex]);
+ }
+ else {
+ this.handleValues[this.handleIndex] = handleValue;
+ this.updateValue(newValue, event);
+ }
+ }
+ else {
+ if(this.step) {
+ this.handleStepChange(newValue, this.value);
+ }
+ else {
+ this.handleValue = handleValue;
+ this.updateValue(newValue, event);
+ }
+ }
+ }
+
+ handleStepChange(newValue: number, oldValue: number) {
+ let diff = (newValue - oldValue);
+
+ if(diff < 0 && (-1 * diff) >= this.step / 2) {
+ newValue = oldValue - this.step;
+ this.updateValue(newValue);
+ this.updateHandleValue();
+ }
+ else if(diff > 0 && diff >= this.step / 2) {
+ newValue = oldValue + this.step;
+ this.updateValue(newValue);
+ this.updateHandleValue();
+ }
+ }
+
+ writeValue(value: any) : void {
+ if(this.range)
+ this.values = value||[0,0];
+ else
+ this.value = value||0;
+
+ this.updateHandleValue();
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ updateDomData(): void {
+ let rect = this.el.nativeElement.children[0].getBoundingClientRect();
+ this.initX = rect.left + this.domHandler.getWindowScrollLeft();
+ this.initY = rect.top + this.domHandler.getWindowScrollTop();
+ this.barWidth = this.el.nativeElement.children[0].offsetWidth;
+ this.barHeight = this.el.nativeElement.children[0].offsetHeight;
+ }
+
+ calculateHandleValue(event): number {
+ if(this.orientation === 'horizontal')
+ return Math.floor(((event.pageX - this.initX) * 100) / (this.barWidth));
+ else
+ return Math.floor((((this.initY + this.barHeight) - event.pageY) * 100) / (this.barHeight));
+ }
+
+ updateHandleValue(): void {
+ if(this.range) {
+ this.handleValues[0] = (this.values[0] < this.min ? 0 : this.values[0] - this.min) * 100 / (this.max - this.min);
+ this.handleValues[1] = (this.values[1] > this.max ? 100 : this.values[1] - this.min) * 100 / (this.max - this.min);
+ }
+ else {
+ if(this.value < this.min)
+ this.handleValue = 0;
+ else if(this.value > this.max)
+ this.handleValue = 100;
+ else
+ this.handleValue = (this.value - this.min) * 100 / (this.max - this.min);
+ }
+ }
+
+ updateValue(val: number, event?: Event): void {
+ if(this.range) {
+ let value = val;
+
+ if(this.handleIndex == 0) {
+ if(value < this.min) {
+ value = this.min;
+ this.handleValues[0] = 0;
+ }
+ else if (value > this.values[1]) {
+ value = this.values[1];
+ this.handleValues[0] = this.handleValues[1];
+ }
+ }
+ else {
+ if(value > this.max) {
+ value = this.max;
+ this.handleValues[1] = 100;
+ }
+ else if (value < this.values[0]) {
+ value = this.values[0];
+ this.handleValues[1] = this.handleValues[0];
+ }
+ }
+
+ this.values[this.handleIndex] = Math.floor(value);
+ this.onModelChange(this.values);
+ this.onChange.emit({event: event, values: this.values});
+ }
+ else {
+ if(val < this.min) {
+ val = this.min;
+ this.handleValue = 0;
+ }
+ else if (val > this.max) {
+ val = this.max;
+ this.handleValue = 100;
+ }
+
+ this.value = Math.floor(val);
+ this.onModelChange(this.value);
+ this.onChange.emit({event: event, value: this.value});
+ }
+ }
+
+ getValueFromHandle(handleValue: number): number {
+ return (this.max - this.min) * (handleValue / 100) + this.min;
+ }
+
+ ngOnDestroy() {
+ if(this.dragListener) {
+ this.dragListener();
+ }
+
+ if(this.mouseupListener) {
+ this.mouseupListener();
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Slider],
+ declarations: [Slider]
+})
+export class SliderModule { }
diff --git a/src/app/components/spinner/spinner.css b/src/app/components/spinner/spinner.css
new file mode 100644
index 00000000000..9b25a7170e4
--- /dev/null
+++ b/src/app/components/spinner/spinner.css
@@ -0,0 +1,61 @@
+.ui-spinner {
+ display: inline-block;
+ overflow: visible;
+ padding: 0;
+ position: relative;
+ vertical-align: middle;
+}
+
+.ui-spinner-input {
+ vertical-align: middle;
+ padding-right: 1.5em;
+}
+
+.ui-spinner-button {
+ cursor: default;
+ display: block;
+ height: 50%;
+ margin: 0;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ right: 0;
+ text-align: center;
+ vertical-align: middle;
+ width: 1.5em;
+}
+
+.ui-spinner .fa {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ margin-top: -.5em;
+ margin-left: -.5em;
+ width: 1em;
+}
+
+.ui-spinner-up {
+ top: 0;
+}
+
+.ui-spinner-down {
+ bottom: 0;
+}
+
+/* Fluid */
+.ui-fluid .ui-spinner {
+ width: 100%;
+}
+
+.ui-fluid .ui-spinner .ui-spinner-input {
+ padding-right: 2em;
+ width: 100%;
+}
+
+.ui-fluid .ui-spinner .ui-spinner-button {
+ width: 1.5em;
+}
+
+.ui-fluid .ui-spinner .ui-spinner-button .fa {
+ left: .7em;
+}
\ No newline at end of file
diff --git a/src/app/components/spinner/spinner.ts b/src/app/components/spinner/spinner.ts
new file mode 100644
index 00000000000..65eb5748549
--- /dev/null
+++ b/src/app/components/spinner/spinner.ts
@@ -0,0 +1,300 @@
+import {NgModule,Component,ElementRef,OnInit,Input,Output,EventEmitter,forwardRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {InputTextModule} from '../inputtext/inputtext';
+import {DomHandler} from '../dom/domhandler';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const SPINNER_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => Spinner),
+ multi: true
+};
+
+@Component({
+ selector: 'p-spinner',
+ template: `
+
+
+
+
+
+ `,
+ host: {
+ '[class.ui-inputwrapper-filled]': 'filled',
+ '[class.ui-inputwrapper-focus]': 'focus'
+ },
+ providers: [DomHandler,SPINNER_VALUE_ACCESSOR],
+})
+export class Spinner implements OnInit,ControlValueAccessor {
+
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ @Output() onBlur: EventEmitter = new EventEmitter();
+
+ @Input() step: number = 1;
+
+ @Input() min: number;
+
+ @Input() max: number;
+
+ @Input() maxlength: number;
+
+ @Input() size: number;
+
+ @Input() placeholder: string;
+
+ @Input() inputId: string;
+
+ @Input() disabled: boolean;
+
+ @Input() readonly: boolean;
+
+ @Input() decimalSeparator: string = '.';
+
+ @Input() thousandSeparator: string = ',';
+
+ @Input() tabindex: number;
+
+ @Input() formatInput: boolean = true;
+
+ value: number;
+
+ valueAsString: string = '';
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ keyPattern: RegExp = /[0-9\+\-]/;
+
+ public precision: number;
+
+ public timer: any;
+
+ public focus: boolean;
+
+ public filled: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ ngOnInit() {
+ if(Math.floor(this.step) === 0) {
+ this.precision = this.step.toString().split(/[,]|[.]/)[1].length;
+ }
+ }
+
+ repeat(interval: number, dir: number) {
+ let i = interval||500;
+
+ this.clearTimer();
+ this.timer = setTimeout(() => {
+ this.repeat(40, dir);
+ }, i);
+
+ this.spin(dir);
+ }
+
+ spin(dir: number) {
+ let step = this.step * dir;
+ let currentValue = this.value||0;
+ let newValue: number = null;
+
+ if(this.precision)
+ this.value = parseFloat(this.toFixed(currentValue + step, this.precision));
+ else
+ this.value = currentValue + step;
+
+ if(this.maxlength !== undefined && this.value.toString().length > this.maxlength) {
+ this.value = currentValue;
+ }
+
+ if(this.min !== undefined && this.value < this.min) {
+ this.value = this.min;
+ }
+
+ if(this.max !== undefined && this.value > this.max) {
+ this.value = this.max;
+ }
+
+ this.formatValue();
+ this.onModelChange(this.value);
+ this.onChange.emit();
+ }
+
+ toFixed(value: number, precision: number) {
+ let power = Math.pow(10, precision||0);
+ return String(Math.round(value * power) / power);
+ }
+
+ onUpButtonMousedown(event: Event,input: HTMLInputElement) {
+ if(!this.disabled) {
+ input.focus();
+ this.repeat(null, 1);
+ this.updateFilledState();
+ }
+ }
+
+ onUpButtonMouseup(event: Event) {
+ if(!this.disabled) {
+ this.clearTimer();
+ }
+ }
+
+ onUpButtonMouseleave(event: Event) {
+ if(!this.disabled) {
+ this.clearTimer();
+ }
+ }
+
+ onDownButtonMousedown(event: Event, input: HTMLInputElement) {
+ if(!this.disabled) {
+ input.focus();
+ this.repeat(null, -1);
+ this.updateFilledState();
+ }
+ }
+
+ onDownButtonMouseup(event: Event) {
+ if(!this.disabled) {
+ this.clearTimer();
+ }
+ }
+
+ onDownButtonMouseleave(event: Event) {
+ if(!this.disabled) {
+ this.clearTimer();
+ }
+ }
+
+ onInputKeydown(event: KeyboardEvent) {
+ if(event.which == 38) {
+ this.spin(1);
+ event.preventDefault();
+ }
+ else if(event.which == 40) {
+ this.spin(-1);
+ event.preventDefault();
+ }
+ }
+
+ onInputKeyPress(event: KeyboardEvent) {
+ let inputChar = String.fromCharCode(event.charCode);
+ if(!this.keyPattern.test(inputChar) && inputChar != this.decimalSeparator && event.keyCode != 9 && event.keyCode != 8 && event.keyCode != 37 && event.keyCode != 39 && event.keyCode != 46) {
+ event.preventDefault();
+ }
+ }
+
+ onInput(event: Event, inputValue: string) {
+ this.value = this.parseValue(inputValue);
+ this.formatValue();
+ this.onModelChange(this.value);
+ this.updateFilledState();
+ }
+
+ onInputBlur(event) {
+ this.focus = false;
+ this.onModelTouched();
+ this.onBlur.emit(event);
+ }
+
+ onFocus() {
+ this.focus = true;
+ }
+
+ parseValue(val: string): number {
+ let value: number;
+
+ if(this.formatInput) {
+ val = val.split(this.thousandSeparator).join('');
+ }
+
+ if(val.trim() === '') {
+ value= this.min !== undefined ? this.min : null;
+ }
+ else {
+ if(this.precision) {
+ value = parseFloat(val.replace(',','.'));
+ }
+ else {
+ value = parseInt(val);
+ }
+
+ if(!isNaN(value)) {
+ if(this.max !== undefined && value > this.max) {
+ value = this.max;
+ }
+
+ if(this.min !== undefined && value < this.min) {
+ value = this.min;
+ }
+ }
+ else {
+ value = null;
+ }
+ }
+
+ return value;
+ }
+
+ formatValue(): void {
+ if(this.value !== null && this.value !== undefined) {
+ let textValue = String(this.value).replace('.', this.decimalSeparator);
+
+ if(this.formatInput) {
+ textValue = textValue.replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
+ }
+ this.valueAsString = textValue;
+ }
+ else {
+ this.valueAsString = '';
+ }
+ }
+
+ handleChange(event: Event) {
+ this.onChange.emit(event);
+ }
+
+ clearTimer() {
+ if(this.timer) {
+ clearInterval(this.timer);
+ }
+ }
+
+ writeValue(value: any) : void {
+ this.value = value;
+ this.formatValue();
+ this.updateFilledState();
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ updateFilledState() {
+ this.filled = (this.value !== undefined && this.value != null);
+ }
+}
+
+
+@NgModule({
+ imports: [CommonModule,InputTextModule],
+ exports: [Spinner],
+ declarations: [Spinner]
+})
+export class SpinnerModule { }
diff --git a/src/app/components/splitbutton/splitbutton.css b/src/app/components/splitbutton/splitbutton.css
new file mode 100644
index 00000000000..3ccc9c24926
--- /dev/null
+++ b/src/app/components/splitbutton/splitbutton.css
@@ -0,0 +1,31 @@
+.ui-splitbutton {
+ position: relative;
+ display: inline-block;
+ zoom: 1;
+}
+
+.ui-splitbutton .ui-button.ui-splitbutton-menubutton {
+ width: 2em;
+}
+
+.ui-splitbutton.ui-state-disabled button {
+ cursor: default;
+}
+
+.ui-fluid .ui-splitbutton {
+ width: 100%;
+ box-sizing: border-box;
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing: border-box;
+}
+
+.ui-fluid .ui-splitbutton .ui-button:first-child {
+ width: calc(100% - 2em);
+}
+
+.ui-fluid .ui-splitbutton .ui-button.ui-splitbutton-menubutton {
+ width: 2em;
+ box-sizing: border-box;
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing: border-box;
+}
\ No newline at end of file
diff --git a/src/app/components/splitbutton/splitbutton.ts b/src/app/components/splitbutton/splitbutton.ts
new file mode 100644
index 00000000000..4a7690f45c4
--- /dev/null
+++ b/src/app/components/splitbutton/splitbutton.ts
@@ -0,0 +1,125 @@
+import {NgModule,Component,ElementRef,OnInit,OnDestroy,Input,Output,EventEmitter,ContentChildren,QueryList,Renderer2,ChangeDetectorRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {MenuItem} from '../common/menuitem';
+import {ButtonModule} from '../button/button';
+import {Router} from '@angular/router';
+import {RouterModule} from '@angular/router';
+
+@Component({
+ selector: 'p-splitButton',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class SplitButton implements OnInit,OnDestroy {
+
+ @Input() model: MenuItem[];
+
+ @Input() icon: string;
+
+ @Input() iconPos: string = 'left';
+
+ @Input() label: string;
+
+ @Output() onClick: EventEmitter = new EventEmitter();
+
+ @Output() onDropdownClick: EventEmitter = new EventEmitter();
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() menuStyle: any;
+
+ @Input() menuStyleClass: string;
+
+ @Input() disabled: boolean;
+
+ @Input() tabindex: number;
+
+ public menuVisible: boolean = false;
+
+ public documentClickListener: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2, public router: Router, public cd: ChangeDetectorRef) {}
+
+ ngOnInit() {
+ this.documentClickListener = this.renderer.listen('document', 'click', () => {
+ this.menuVisible = false;
+ this.cd.markForCheck();
+ });
+ }
+
+ onDefaultButtonClick(event: Event) {
+ this.onClick.emit(event);
+ }
+
+ itemClick(event: Event, item: MenuItem) {
+ if(item.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if(!item.url) {
+ event.preventDefault();
+ }
+
+ if(item.command) {
+ if(!item.eventEmitter) {
+ item.eventEmitter = new EventEmitter();
+ item.eventEmitter.subscribe(item.command);
+ }
+
+ item.eventEmitter.emit({
+ originalEvent: event,
+ item: item
+ });
+ }
+
+ this.menuVisible = false;
+ }
+
+ onDropdownButtonClick(event: Event, menu: HTMLDivElement, container: Element) {
+ this.menuVisible= !this.menuVisible;
+ this.domHandler.relativePosition(menu, container);
+ this.domHandler.fadeIn(menu,25);
+ menu.style.zIndex = String(++DomHandler.zindex);
+ this.onDropdownClick.emit(event);
+ event.stopPropagation();
+ }
+
+ ngOnDestroy() {
+ if(this.documentClickListener) {
+ this.documentClickListener();
+ }
+ }
+}
+
+@NgModule({
+ imports: [CommonModule,ButtonModule,RouterModule],
+ exports: [SplitButton,ButtonModule,RouterModule],
+ declarations: [SplitButton]
+})
+export class SplitButtonModule { }
diff --git a/src/app/components/steps/steps.css b/src/app/components/steps/steps.css
new file mode 100644
index 00000000000..0c6039c87b4
--- /dev/null
+++ b/src/app/components/steps/steps.css
@@ -0,0 +1,49 @@
+.ui-steps ul {
+ list-style-type: none;
+ padding: 0;
+ margin: 0;
+}
+
+.ui-steps .ui-steps-item {
+ float: left;
+ box-sizing: border-box;
+ cursor: pointer;
+}
+
+.ui-steps.ui-steps-readonly .ui-steps-item {
+ cursor: auto;
+}
+
+.ui-steps .ui-steps-item .ui-menuitem-link {
+ text-decoration: none;
+ display: block;
+ padding: 1em;
+ position: relative;
+ text-align: center;
+}
+
+.ui-steps .ui-steps-item.ui-state-highlight .ui-menuitem-link,
+.ui-steps .ui-steps-item.ui-state-disabled .ui-menuitem-link {
+ cursor: default;
+}
+
+.ui-steps .ui-steps-number {
+ font-size: 200%;
+ display: block;
+}
+
+.ui-steps .ui-steps-title {
+ display: block;
+ white-space: nowrap;
+}
+
+/* Responsive */
+@media (max-width: 40em) {
+ .ui-steps .ui-steps-item .ui-menuitem-link {
+ padding: 0.5em;
+ }
+
+ .ui-steps .ui-steps-item .ui-steps-title {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/src/app/components/steps/steps.ts b/src/app/components/steps/steps.ts
new file mode 100644
index 00000000000..65d04ae75e2
--- /dev/null
+++ b/src/app/components/steps/steps.ts
@@ -0,0 +1,74 @@
+import {NgModule,Component,Input,Output,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {MenuItem} from '../common/menuitem';
+import {RouterModule} from '@angular/router';
+
+@Component({
+ selector: 'p-steps',
+ template: `
+
+ `
+})
+export class Steps {
+
+ @Input() activeIndex: number = 0;
+
+ @Input() model: MenuItem[];
+
+ @Input() readonly: boolean = true;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Output() activeIndexChange: EventEmitter = new EventEmitter();
+
+ itemClick(event: Event, item: MenuItem, i: number) {
+ if(this.readonly || item.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ this.activeIndexChange.emit(i);
+
+ if(!item.url) {
+ event.preventDefault();
+ }
+
+ if(item.command) {
+ if(!item.eventEmitter) {
+ item.eventEmitter = new EventEmitter();
+ item.eventEmitter.subscribe(item.command);
+ }
+
+ item.eventEmitter.emit({
+ originalEvent: event,
+ item: item,
+ index: i
+ });
+ }
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule,RouterModule],
+ exports: [Steps,RouterModule],
+ declarations: [Steps]
+})
+export class StepsModule { }
\ No newline at end of file
diff --git a/src/app/components/tabmenu/tabmenu.css b/src/app/components/tabmenu/tabmenu.css
new file mode 100644
index 00000000000..1ccd8c4603e
--- /dev/null
+++ b/src/app/components/tabmenu/tabmenu.css
@@ -0,0 +1,39 @@
+/** TabMenu **/
+.ui-tabmenu {
+
+}
+
+.ui-tabmenu .ui-tabmenu-nav {
+ margin: 0;
+ padding: .25em .5em 0 .25em;
+}
+
+.ui-tabmenu .ui-tabmenu-nav .ui-tabmenuitem {
+ list-style: none;
+ float: left;
+ position: relative;
+ margin: 0 .2em 1px 0;
+ padding: 0;
+ white-space: nowrap;
+ display: block;
+ border-bottom: 0;
+ top: 1px;
+}
+
+.ui-tabmenu .ui-tabmenu-nav .ui-tabmenuitem a {
+ float: left;
+ padding: 0.5em 1em;
+ text-decoration: none;
+}
+
+.ui-tabmenu .ui-tabmenu-nav a {
+ padding: 0.5em 1em;
+}
+
+.ui-tabmenu .ui-tabmenu-nav .ui-tabmenuitem .ui-icon {
+ float: left;
+}
+
+.ui-tabmenu .ui-tabmenu-nav .ui-tabmenuitem.ui-state-disabled a {
+ cursor: default;
+}
\ No newline at end of file
diff --git a/src/app/components/tabmenu/tabmenu.ts b/src/app/components/tabmenu/tabmenu.ts
new file mode 100644
index 00000000000..a9b72347506
--- /dev/null
+++ b/src/app/components/tabmenu/tabmenu.ts
@@ -0,0 +1,102 @@
+import {NgModule,Component,ElementRef,OnDestroy,Input,Output,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {MenuItem} from '../common/menuitem';
+import {Location} from '@angular/common';
+import {RouterModule} from '@angular/router';
+
+@Component({
+ selector: 'p-tabMenu',
+ template: `
+
+
+
+ `,
+ providers: [DomHandler]
+})
+export class TabMenu implements OnDestroy {
+
+ @Input() model: MenuItem[];
+
+ @Input() activeItem: MenuItem;
+
+ @Input() popup: boolean;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ ngOnInit() {
+ if(!this.activeItem && this.model && this.model.length) {
+ this.activeItem = this.model[0];
+ }
+ }
+
+ itemClick(event: Event, item: MenuItem) {
+ if(item.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if(!item.url) {
+ event.preventDefault();
+ }
+
+ if(item.command) {
+ if(!item.eventEmitter) {
+ item.eventEmitter = new EventEmitter();
+ item.eventEmitter.subscribe(item.command);
+ }
+
+ item.eventEmitter.emit({
+ originalEvent: event,
+ item: item
+ });
+ }
+
+ this.activeItem = item;
+ }
+
+ ngOnDestroy() {
+ if(this.model) {
+ for(let item of this.model) {
+ this.unsubscribe(item);
+ }
+ }
+ }
+
+ unsubscribe(item: any) {
+ if(item.eventEmitter) {
+ item.eventEmitter.unsubscribe();
+ }
+
+ if(item.items) {
+ for(let childItem of item.items) {
+ this.unsubscribe(childItem);
+ }
+ }
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule,RouterModule],
+ exports: [TabMenu,RouterModule],
+ declarations: [TabMenu]
+})
+export class TabMenuModule { }
\ No newline at end of file
diff --git a/src/app/components/tabview/tabview.css b/src/app/components/tabview/tabview.css
new file mode 100644
index 00000000000..0e94ce0f54c
--- /dev/null
+++ b/src/app/components/tabview/tabview.css
@@ -0,0 +1,132 @@
+.ui-tabview {
+ padding: .25em;
+}
+
+.ui-tabview .ui-tabview-nav {
+ margin: 0;
+}
+
+.ui-tabview .ui-tabview-nav li {
+ list-style: none;
+ float: left;
+ position: relative;
+ margin: 0 .125em 1px 0;
+ padding: 0;
+ white-space: nowrap;
+}
+
+.ui-tabview .ui-tabview-nav li a {
+ float: left;
+ padding: .5em 1em;
+ text-decoration: none;
+}
+
+.ui-tabview .ui-tabview-nav li.ui-tabview-selected a,
+.ui-tabview .ui-tabview-nav li.ui-state-disabled a,
+.ui-tabview .ui-tabview-nav li.ui-state-processing a {
+ cursor: text;
+}
+
+.ui-tabview .ui-tabview-nav li a,
+.ui-tabview.ui-tabview-collapsible .ui-tabview-nav li.ui-tabview-selected a {
+ cursor: pointer;
+}
+
+.ui-tabview .ui-tabview-panel {
+ border-width: 0;
+ padding: 1em;
+ background: none;
+}
+
+.ui-tabview .ui-tabview-nav li {
+ display: block;
+}
+
+.ui-tabview .ui-tabview-nav li .ui-tabview-left-icon,
+.ui-tabview .ui-tabview-nav li .ui-tabview-right-icon,
+.ui-tabview .ui-tabview-nav li .ui-tabview-title {
+ vertical-align: middle;
+}
+
+.ui-tabview .ui-tabview-nav li .ui-tabview-close {
+ margin: 0.5em 0.3em 0 0;
+ cursor: pointer;
+}
+
+/* per orientation settings */
+/* top and bottom */
+.ui-tabview.ui-tabview-top > .ui-tabview-nav li {
+ border-bottom: 0;
+ top: 1px;
+}
+
+.ui-tabview.ui-tabview-top > .ui-tabview-nav {
+ padding: .2em .2em 0;
+}
+
+.ui-tabview.ui-tabview-bottom > .ui-tabview-nav {
+ padding: 0 .2em .2em;
+}
+
+.ui-tabview.ui-tabview-bottom > .ui-tabview-nav li {
+ border-top: 0;
+}
+
+/* left and right*/
+.ui-tabview-left:after,
+.ui-tabview-right:after {
+ clear:both;
+ content: ".";
+ display: block;
+ height: 0;
+ visibility: hidden;
+}
+
+.ui-tabview-left > .ui-tabview-nav {
+ float:left;
+ width: 25%;
+ height: 300px;
+ background-image: none;
+ padding-top: 1px;
+}
+
+.ui-tabview-left > .ui-tabview-panels {
+ float:right;
+ width: 75%;
+}
+
+.ui-tabview.ui-tabview-left > .ui-tabview-nav li,
+.ui-tabview.ui-tabview-right > .ui-tabview-nav li{
+ display: block;
+ float: right;
+ white-space: normal;
+ width: 99%;
+}
+
+.ui-tabview.ui-tabview-left > .ui-tabview-nav li {
+ margin: 0 0 1px 0;
+ border-right:0 none;
+}
+
+.ui-tabview.ui-tabview-right > .ui-tabview-nav {
+ float:right;
+ width: 25%;
+ height: 300px;
+ background-image: none;
+ padding-top: 1px;
+}
+
+.ui-tabview.ui-tabview-right > .ui-tabview-panels {
+ float:left;
+ width: 75%;
+}
+
+.ui-tabview.ui-tabview-right > .ui-tabview-nav li {
+ margin: 0 0 1px 0;
+ border-left:0 none;
+}
+
+/* RTL */
+.ui-rtl .ui-tabview .ui-tabview-nav li {
+ float: right;
+}
diff --git a/src/app/components/tabview/tabview.ts b/src/app/components/tabview/tabview.ts
new file mode 100644
index 00000000000..b8222b5229b
--- /dev/null
+++ b/src/app/components/tabview/tabview.ts
@@ -0,0 +1,260 @@
+import {NgModule,Component,ElementRef,Input,Output,EventEmitter,HostListener,AfterContentInit,ContentChildren,QueryList} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {BlockableUI} from '../common/blockableui';
+
+@Component({
+ selector: '[p-tabViewNav]',
+ host:{
+ '[class.ui-tabview-nav]': 'true',
+ '[class.ui-helper-reset]': 'true',
+ '[class.ui-helper-clearfix]': 'true',
+ '[class.ui-widget-header]': 'true',
+ '[class.ui-corner-all]': 'true'
+ },
+ template: `
+
+
+
+
+ {{tab.header}}
+
+
+
+
+
+ `,
+})
+export class TabViewNav {
+
+ @Input() tabs: TabPanel[];
+
+ @Input() orientation: string = 'top';
+
+ @Output() onTabClick: EventEmitter = new EventEmitter();
+
+ @Output() onTabCloseClick: EventEmitter = new EventEmitter();
+
+ getDefaultHeaderClass(tab:TabPanel) {
+ let styleClass = 'ui-state-default ui-corner-' + this.orientation;
+ if(tab.headerStyleClass) {
+ styleClass = styleClass + " " + tab.headerStyleClass;
+ }
+ return styleClass;
+ }
+
+ clickTab(event, tab: TabPanel) {
+ this.onTabClick.emit({
+ originalEvent: event,
+ tab: tab
+ })
+ }
+
+ clickClose(event, tab: TabPanel) {
+ this.onTabCloseClick.emit({
+ originalEvent: event,
+ tab: tab
+ })
+ }
+}
+
+@Component({
+ selector: 'p-tabPanel',
+ template: `
+
+
+
+ `
+})
+export class TabPanel {
+
+ @Input() header: string;
+
+ @Input() selected: boolean;
+
+ @Input() disabled: boolean;
+
+ @Input() closable: boolean;
+
+ @Input() headerStyle: any;
+
+ @Input() headerStyleClass: string;
+
+ @Input() leftIcon: string;
+
+ @Input() rightIcon: string;
+
+ public closed: boolean;
+
+ public lazy: boolean;
+}
+
+@Component({
+ selector: 'p-tabView',
+ template: `
+
+ `,
+})
+export class TabView implements AfterContentInit,BlockableUI {
+
+ @Input() orientation: string = 'top';
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() controlClose: boolean;
+
+ @Input() lazy: boolean;
+
+ @ContentChildren(TabPanel) tabPanels: QueryList;
+
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ @Output() onClose: EventEmitter = new EventEmitter();
+
+ initialized: boolean;
+
+ tabs: TabPanel[];
+
+ private _activeIndex: number;
+
+ constructor(public el: ElementRef) {}
+
+ ngAfterContentInit() {
+ this.initTabs();
+
+ this.tabPanels.changes.subscribe(_ => {
+ this.initTabs();
+ });
+ }
+
+ initTabs(): void {
+ this.tabs = this.tabPanels.toArray();
+ for(let tab of this.tabs) {
+ tab.lazy = this.lazy;
+ }
+
+ let selectedTab: TabPanel = this.findSelectedTab();
+ if(!selectedTab && this.tabs.length) {
+ if(this.activeIndex != null && this.tabs.length > this.activeIndex)
+ this.tabs[this.activeIndex].selected = true;
+ else
+ this.tabs[0].selected = true;
+ }
+ }
+
+ open(event: Event, tab: TabPanel) {
+ if(tab.disabled) {
+ if(event) {
+ event.preventDefault();
+ }
+ return;
+ }
+
+ if(!tab.selected) {
+ let selectedTab: TabPanel = this.findSelectedTab();
+ if(selectedTab) {
+ selectedTab.selected = false
+ }
+ tab.selected = true;
+ this.onChange.emit({originalEvent: event, index: this.findTabIndex(tab)});
+ }
+
+ if(event) {
+ event.preventDefault();
+ }
+ }
+
+ close(event: Event, tab: TabPanel) {
+ if(this.controlClose) {
+ this.onClose.emit({
+ originalEvent: event,
+ index: this.findTabIndex(tab),
+ close: () => {
+ this.closeTab(tab);
+ }}
+ );
+ }
+ else {
+ this.closeTab(tab);
+ this.onClose.emit({
+ originalEvent: event,
+ index: this.findTabIndex(tab)
+ });
+ }
+
+ event.stopPropagation();
+ }
+
+ closeTab(tab: TabPanel) {
+ if(tab.selected) {
+ tab.selected = false;
+ for(let i = 0; i < this.tabs.length; i++) {
+ let tabPanel = this.tabs[i];
+ if(!tabPanel.closed&&!tab.disabled) {
+ tabPanel.selected = true;
+ break;
+ }
+ }
+ }
+
+ tab.closed = true;
+ }
+
+ findSelectedTab() {
+ for(let i = 0; i < this.tabs.length; i++) {
+ if(this.tabs[i].selected) {
+ return this.tabs[i];
+ }
+ }
+ return null;
+ }
+
+ findTabIndex(tab: TabPanel) {
+ let index = -1;
+ for(let i = 0; i < this.tabs.length; i++) {
+ if(this.tabs[i] == tab) {
+ index = i;
+ break;
+ }
+ }
+ return index;
+ }
+
+ getBlockableElement(): HTMLElement {
+ return this.el.nativeElement.children[0];
+ }
+
+ @Input() get activeIndex(): number {
+ return this._activeIndex;
+ }
+
+ set activeIndex(val:number) {
+ this._activeIndex = val;
+
+ if(this.tabs && this.tabs.length && this._activeIndex != null) {
+ this.findSelectedTab().selected = false;
+ this.tabs[this._activeIndex].selected = true;
+ }
+ }
+}
+
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [TabView,TabPanel,TabViewNav],
+ declarations: [TabView,TabPanel,TabViewNav]
+})
+export class TabViewModule { }
\ No newline at end of file
diff --git a/src/app/components/terminal/terminal.css b/src/app/components/terminal/terminal.css
new file mode 100644
index 00000000000..86fe6028e06
--- /dev/null
+++ b/src/app/components/terminal/terminal.css
@@ -0,0 +1,25 @@
+.ui-terminal {
+ height: 18em;
+ overflow: auto;
+ padding: .25em;
+}
+
+.ui-terminal-input {
+ border: 0 none;
+ background-color: transparent;
+ color: inherit;
+ padding: 0;
+ margin: 0 0 0 .125em;
+ width: 75%;
+ outline: none;
+ vertical-align: baseline;
+}
+
+.ui-terminal-command {
+ margin-left: .125em;
+ -moz-margin-start: .125em;
+}
+
+.ui-terminal-input::-ms-clear {
+ display: none;
+}
\ No newline at end of file
diff --git a/src/app/components/terminal/terminal.ts b/src/app/components/terminal/terminal.ts
new file mode 100644
index 00000000000..4c2dacea5bd
--- /dev/null
+++ b/src/app/components/terminal/terminal.ts
@@ -0,0 +1,88 @@
+import {NgModule,Component,AfterViewInit,AfterViewChecked,Input,Output,EventEmitter,ElementRef} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+
+@Component({
+ selector: 'p-terminal',
+ template: `
+
+
{{welcomeMessage}}
+
+
+
{{prompt}}
+
{{command.text}}
+
{{command.response}}
+
+
+
+ {{prompt}}
+
+
+
+ `,
+ providers: [DomHandler]
+})
+export class Terminal implements AfterViewInit,AfterViewChecked {
+
+ @Input() welcomeMessage: string;
+
+ @Input() prompt: string;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Output() responseChange: EventEmitter = new EventEmitter();
+
+ @Output() handler: EventEmitter = new EventEmitter();
+
+ commands: any[] = [];
+
+ command: string;
+
+ container: Element;
+
+ commandProcessed: boolean;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ ngAfterViewInit() {
+ this.container = this.domHandler.find(this.el.nativeElement, '.ui-terminal')[0];
+ }
+
+ ngAfterViewChecked() {
+ if(this.commandProcessed) {
+ this.container.scrollTop = this.container.scrollHeight;
+ this.commandProcessed = false;
+ }
+ }
+
+ @Input()
+ set response(value: string) {
+ if(value) {
+ this.commands[this.commands.length - 1].response = value;
+ this.commandProcessed = true;
+ }
+ }
+
+ handleCommand(event: KeyboardEvent) {
+ if(event.keyCode == 13) {
+ this.commands.push({text: this.command});
+ this.handler.emit({originalEvent: event, command: this.command});
+ this.command = '';
+ }
+ }
+
+ focus(element: HTMLElement) {
+ element.focus();
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule,FormsModule],
+ exports: [Terminal],
+ declarations: [Terminal]
+})
+export class TerminalModule { }
\ No newline at end of file
diff --git a/src/app/components/tieredmenu/tieredmenu.ts b/src/app/components/tieredmenu/tieredmenu.ts
new file mode 100644
index 00000000000..f3125671498
--- /dev/null
+++ b/src/app/components/tieredmenu/tieredmenu.ts
@@ -0,0 +1,182 @@
+import {NgModule,Component,ElementRef,AfterViewInit,OnDestroy,Input,Output,Renderer2,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+import {MenuItem} from '../common/menuitem';
+import {RouterModule} from '@angular/router';
+
+@Component({
+ selector: 'p-tieredMenuSub',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class TieredMenuSub {
+
+ @Input() item: MenuItem;
+
+ @Input() root: boolean;
+
+ constructor(public domHandler: DomHandler) {}
+
+ activeItem: HTMLLIElement;
+
+ onItemMouseEnter(event: Event, item: HTMLLIElement, menuitem: MenuItem) {
+ if(menuitem.disabled) {
+ return;
+ }
+
+ this.activeItem = item;
+ let nextElement: HTMLElement = item.children[0].nextElementSibling;
+ if(nextElement) {
+ let sublist: HTMLElement = nextElement.children[0];
+ sublist.style.zIndex = String(++DomHandler.zindex);
+
+ sublist.style.top = '0px';
+ sublist.style.left = this.domHandler.getOuterWidth(item.children[0]) + 'px';
+ }
+ }
+
+ onItemMouseLeave(event: Event) {
+ this.activeItem = null;
+ }
+
+ itemClick(event: Event, item: MenuItem) {
+ if(item.disabled) {
+ event.preventDefault();
+ return true;
+ }
+
+ if(!item.url) {
+ event.preventDefault();
+ }
+
+ if(item.command) {
+ if(!item.eventEmitter) {
+ item.eventEmitter = new EventEmitter();
+ item.eventEmitter.subscribe(item.command);
+ }
+
+ item.eventEmitter.emit({
+ originalEvent: event,
+ item: item
+ });
+ }
+ }
+
+ listClick(event: Event) {
+ this.activeItem = null;
+ }
+}
+
+@Component({
+ selector: 'p-tieredMenu',
+ template: `
+
+ `,
+ providers: [DomHandler]
+})
+export class TieredMenu implements AfterViewInit,OnDestroy {
+
+ @Input() model: MenuItem[];
+
+ @Input() popup: boolean;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ container: any;
+
+ documentClickListener: any;
+
+ preventDocumentDefault: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2) {}
+
+ ngAfterViewInit() {
+ this.container = this.el.nativeElement.children[0];
+
+ if(this.popup) {
+ this.documentClickListener = this.renderer.listen('document', 'click', () => {
+ if(!this.preventDocumentDefault) {
+ this.hide();
+ }
+ this.preventDocumentDefault = false;
+ });
+ }
+ }
+
+ toggle(event: Event) {
+ if(this.container.offsetParent)
+ this.hide();
+ else
+ this.show(event);
+ }
+
+ show(event: Event) {
+ this.preventDocumentDefault = true;
+ this.container.style.display = 'block';
+ this.domHandler.absolutePosition(this.container, event.target);
+ this.domHandler.fadeIn(this.container, 250);
+ }
+
+ hide() {
+ this.container.style.display = 'none';
+ }
+
+ unsubscribe(item: any) {
+ if(item.eventEmitter) {
+ item.eventEmitter.unsubscribe();
+ }
+
+ if(item.items) {
+ for(let childItem of item.items) {
+ this.unsubscribe(childItem);
+ }
+ }
+ }
+
+ ngOnDestroy() {
+ if(this.popup && this.documentClickListener) {
+ this.documentClickListener();
+ }
+
+ if(this.model) {
+ for(let item of this.model) {
+ this.unsubscribe(item);
+ }
+ }
+ }
+
+}
+
+@NgModule({
+ imports: [CommonModule,RouterModule],
+ exports: [TieredMenu,RouterModule],
+ declarations: [TieredMenu,TieredMenuSub]
+})
+export class TieredMenuModule { }
diff --git a/src/app/components/togglebutton/togglebutton.ts b/src/app/components/togglebutton/togglebutton.ts
new file mode 100644
index 00000000000..0909cf7d19e
--- /dev/null
+++ b/src/app/components/togglebutton/togglebutton.ts
@@ -0,0 +1,122 @@
+import {NgModule,Component,Input,Output,EventEmitter,forwardRef,AfterViewInit,ViewChild,ElementRef} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+export const TOGGLEBUTTON_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => ToggleButton),
+ multi: true
+};
+
+@Component({
+ selector: 'p-toggleButton',
+ template: `
+
+
+
+
+
+
{{checked ? hasOnLabel ? onLabel : 'ui-btn' : hasOffLabel ? offLabel : 'ui-btn'}}
+
+ `,
+ providers: [TOGGLEBUTTON_VALUE_ACCESSOR]
+})
+export class ToggleButton implements ControlValueAccessor,AfterViewInit {
+
+ @Input() onLabel: string = 'Yes';
+
+ @Input() offLabel: string = 'No';
+
+ @Input() onIcon: string;
+
+ @Input() offIcon: string;
+
+ @Input() disabled: boolean;
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() inputId: string;
+
+ @Input() tabindex: number;
+
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ @ViewChild('checkbox') checkboxViewChild: ElementRef;
+
+ checkbox: HTMLInputElement;
+
+ checked: boolean = false;
+
+ focus: boolean = false;
+
+ onModelChange: Function = () => {};
+
+ onModelTouched: Function = () => {};
+
+ ngAfterViewInit() {
+ this.checkbox = this.checkboxViewChild.nativeElement;
+ }
+
+ getIconClass() {
+ let baseClass = 'ui-button-icon-left fa fa-fw';
+ return baseClass + ' ' + (this.checked ? this.onIcon : this.offIcon);
+ }
+
+ toggle(event: Event) {
+ if(!this.disabled) {
+ this.checked = !this.checked;
+ this.onModelChange(this.checked);
+ this.onModelTouched();
+ this.onChange.emit({
+ originalEvent: event,
+ checked: this.checked
+ });
+ this.checkbox.focus();
+ }
+ }
+
+ onFocus() {
+ this.focus = true;
+ }
+
+ onBlur() {
+ this.focus = false;
+ this.onModelTouched();
+ }
+
+ writeValue(value: any) : void {
+ this.checked = value;
+ }
+
+ registerOnChange(fn: Function): void {
+ this.onModelChange = fn;
+ }
+
+ registerOnTouched(fn: Function): void {
+ this.onModelTouched = fn;
+ }
+
+ setDisabledState(val: boolean): void {
+ this.disabled = val;
+ }
+
+ get hasOnLabel():boolean {
+ return this.onLabel && this.onLabel.length > 0;
+ }
+
+ get hasOffLabel():boolean {
+ return this.onLabel && this.onLabel.length > 0;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [ToggleButton],
+ declarations: [ToggleButton]
+})
+export class ToggleButtonModule { }
diff --git a/src/app/components/toolbar/toolbar.css b/src/app/components/toolbar/toolbar.css
new file mode 100644
index 00000000000..0c48db8f5f8
--- /dev/null
+++ b/src/app/components/toolbar/toolbar.css
@@ -0,0 +1,11 @@
+.ui-toolbar {
+ padding: .25em .5em;
+}
+
+.ui-toolbar-group-left {
+ float:left
+}
+
+.ui-toolbar-group-right {
+ float:right
+}
\ No newline at end of file
diff --git a/src/app/components/toolbar/toolbar.ts b/src/app/components/toolbar/toolbar.ts
new file mode 100644
index 00000000000..881896855bc
--- /dev/null
+++ b/src/app/components/toolbar/toolbar.ts
@@ -0,0 +1,25 @@
+import {NgModule,Component,Input,Output,EventEmitter} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+@Component({
+ selector: 'p-toolbar',
+ template: `
+
+
+
+ `
+})
+export class Toolbar {
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Toolbar],
+ declarations: [Toolbar]
+})
+export class ToolbarModule { }
\ No newline at end of file
diff --git a/src/app/components/tooltip/tooltip.css b/src/app/components/tooltip/tooltip.css
new file mode 100644
index 00000000000..e726b45048d
--- /dev/null
+++ b/src/app/components/tooltip/tooltip.css
@@ -0,0 +1,66 @@
+.ui-tooltip {
+ position:absolute;
+ display:none;
+ padding: .25em .5em;
+}
+
+.ui-tooltip.ui-tooltip-right,
+.ui-tooltip.ui-tooltip-left {
+ padding: 0 .25em;
+}
+
+.ui-tooltip.ui-tooltip-top,
+.ui-tooltip.ui-tooltip-bottom {
+ padding:.25em 0;
+}
+
+.ui-tooltip .ui-tooltip-text {
+ padding: .125em .5em;
+ background-color: rgb(76, 76, 76);
+ color: #ffffff;
+ white-space: nowrap;
+}
+
+.ui-tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.ui-tooltip-right .ui-tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -.25em;
+ border-width: .25em .25em .25em 0;
+ border-right-color: rgb(76, 76, 76);
+}
+
+.ui-tooltip-left .ui-tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -.25em;
+ border-width: .25em 0 .25em .25em;
+ border-left-color: rgb(76, 76, 76);
+}
+
+.ui-tooltip.ui-tooltip-top {
+ padding: .25em 0;
+}
+
+.ui-tooltip-top .ui-tooltip-arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -.25em;
+ border-width: .25em .25em 0;
+ border-top-color: rgb(76, 76, 76);
+}
+
+.ui-tooltip-bottom .ui-tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -.25em;
+ border-width: 0 .25em .25em;
+ border-bottom-color: rgb(76, 76, 76);
+}
\ No newline at end of file
diff --git a/src/app/components/tooltip/tooltip.ts b/src/app/components/tooltip/tooltip.ts
new file mode 100644
index 00000000000..da2035d7aef
--- /dev/null
+++ b/src/app/components/tooltip/tooltip.ts
@@ -0,0 +1,160 @@
+import {NgModule,Directive,ElementRef,OnDestroy,HostBinding,HostListener,Input} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {DomHandler} from '../dom/domhandler';
+
+@Directive({
+ selector: '[pTooltip]',
+ host: {
+ },
+ providers: [DomHandler]
+})
+export class Tooltip implements OnDestroy {
+
+ @Input('pTooltip') text: string;
+
+ @Input() tooltipPosition: string = 'right';
+
+ @Input() tooltipEvent: string = 'hover';
+
+ @Input() appendTo: any = 'body';
+
+ @Input() positionStyle: string;
+
+ @Input() tooltipStyleClass: string;
+
+ @Input("tooltipDisabled") disabled: boolean;
+
+ @Input() escape: boolean = true;
+
+ container: any;
+
+ constructor(public el: ElementRef, public domHandler: DomHandler) {}
+
+ @HostListener('mouseenter', ['$event'])
+ onMouseEnter(e: Event) {
+ if(this.tooltipEvent === 'hover') {
+ this.show();
+ }
+ }
+
+ @HostListener('mouseleave', ['$event'])
+ onMouseLeave(e: Event) {
+ if(this.tooltipEvent === 'hover') {
+ this.hide();
+ }
+ }
+
+ @HostListener('focus', ['$event'])
+ onFocus(e: Event) {
+ if(this.tooltipEvent === 'focus') {
+ this.show();
+ }
+ }
+
+ @HostListener('blur', ['$event'])
+ onBlur(e: Event) {
+ if(this.tooltipEvent === 'focus') {
+ this.hide();
+ }
+ }
+
+ show() {
+ if(!this.text || this.disabled) {
+ return;
+ }
+
+ this.create();
+ let offset = this.el.nativeElement.getBoundingClientRect();
+ let targetTop = offset.top + this.domHandler.getWindowScrollTop();
+ let targetLeft = offset.left + this.domHandler.getWindowScrollLeft();
+ let left: number;
+ let top: number;
+
+ this.container.style.display = 'block';
+
+ switch(this.tooltipPosition) {
+ case 'right':
+ left = targetLeft + this.domHandler.getOuterWidth(this.el.nativeElement);
+ top = targetTop + (this.domHandler.getOuterHeight(this.el.nativeElement) - this.domHandler.getOuterHeight(this.container)) / 2;
+ break;
+
+ case 'left':
+ left = targetLeft - this.domHandler.getOuterWidth(this.container);
+ top = targetTop + (this.domHandler.getOuterHeight(this.el.nativeElement) - this.domHandler.getOuterHeight(this.container)) / 2;
+ break;
+
+ case 'top':
+ left = targetLeft + (this.domHandler.getOuterWidth(this.el.nativeElement) - this.domHandler.getOuterWidth(this.container)) / 2;
+ top = targetTop - this.domHandler.getOuterHeight(this.container);
+ break;
+
+ case 'bottom':
+ left = targetLeft + (this.domHandler.getOuterWidth(this.el.nativeElement) - this.domHandler.getOuterWidth(this.container)) / 2;
+ top = targetTop + this.domHandler.getOuterHeight(this.el.nativeElement);
+ break;
+ }
+
+ this.container.style.left = left + 'px';
+ this.container.style.top = top + 'px';
+ this.domHandler.fadeIn(this.container, 250);
+ this.container.style.zIndex = ++DomHandler.zindex;
+ }
+
+ hide() {
+ this.ngOnDestroy();
+ }
+
+ create() {
+ let styleClass = 'ui-widget ui-tooltip ui-tooltip-' + this.tooltipPosition;
+ this.container = document.createElement('div');
+ if(this.tooltipStyleClass) {
+ styleClass += ' ' + this.tooltipStyleClass;
+ }
+
+ this.container.className = styleClass;
+
+ let tooltipArrow = document.createElement('div');
+ tooltipArrow.className = 'ui-tooltip-arrow';
+ this.container.appendChild(tooltipArrow);
+
+ let tooltipText = document.createElement('div');
+ tooltipText.className = 'ui-tooltip-text ui-shadow ui-corner-all';
+
+ if(this.escape)
+ tooltipText.appendChild(document.createTextNode(this.text));
+ else
+ tooltipText.innerHTML = this.text;
+
+ if(this.positionStyle) {
+ this.container.style.position = this.positionStyle;
+ }
+
+ this.container.appendChild(tooltipText);
+
+ if(this.appendTo === 'body')
+ document.body.appendChild(this.container);
+ else if(this.appendTo === 'target')
+ this.domHandler.appendChild(this.container, this.el.nativeElement);
+ else
+ this.domHandler.appendChild(this.container, this.appendTo);
+ }
+
+ ngOnDestroy() {
+ if(this.container && this.container.parentElement) {
+ if(this.appendTo === 'body')
+ document.body.removeChild(this.container);
+ else if(this.appendTo === 'target')
+ this.el.nativeElement.removeChild(this.container);
+ else
+ this.domHandler.removeChild(this.container, this.appendTo);
+ }
+ this.container = null;
+ }
+}
+
+@NgModule({
+ imports: [CommonModule],
+ exports: [Tooltip],
+ declarations: [Tooltip]
+})
+export class TooltipModule { }
\ No newline at end of file
diff --git a/src/app/components/tree/images/line.gif b/src/app/components/tree/images/line.gif
new file mode 100755
index 00000000000..64e2280ec55
Binary files /dev/null and b/src/app/components/tree/images/line.gif differ
diff --git a/src/app/components/tree/tree.css b/src/app/components/tree/tree.css
new file mode 100644
index 00000000000..27617575fc5
--- /dev/null
+++ b/src/app/components/tree/tree.css
@@ -0,0 +1,158 @@
+.ui-tree {
+ width: 18em;
+}
+
+.ui-tree .ui-treenode-selectable.ui-treenode-content {
+ cursor: pointer;
+}
+
+.ui-tree .ui-tree-container {
+ height: 100%;
+ margin: 0;
+ overflow: auto;
+ padding: .25em;
+ white-space: nowrap;
+}
+
+.ui-tree .ui-treenode-children {
+ margin: 0;
+ padding: 0 0 0 1em;
+}
+
+.ui-tree .ui-treenode {
+ background-attachment: scroll;
+ background-color: transparent;
+ background-image: none;
+ background-position: 0 0;
+ background-repeat: repeat-y;
+ list-style: none outside none;
+ margin: 0;
+ padding: .125em 0 0 0;
+}
+
+.ui-tree .ui-treenode-droppoint {
+ height: 4px;
+ list-style-type: none;
+}
+
+.ui-tree .ui-treenode-droppoint-active {
+ border: 0 none;
+}
+
+.ui-tree .ui-tree-toggler {
+ cursor: pointer;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ui-tree .ui-treenode-icon {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ui-tree .ui-treenode-label {
+ display: inline-block;
+ padding: 0 .25em;
+ vertical-align: middle;
+}
+
+.ui-tree .ui-treenode-label.ui-state-hover,
+.ui-tree .ui-treenode-label.ui-state-highlight {
+ font-weight: normal;
+ border: 0 none;
+}
+
+.ui-tree .ui-treenode.ui-treenode-leaf > .ui-treenode-content > .ui-tree-toggler {
+ visibility: hidden;
+}
+
+.ui-tree .ui-chkbox-box {
+ cursor: pointer;
+}
+
+.ui-tree .ui-chkbox {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ui-tree .ui-chkbox .ui-chkbox-icon {
+ margin-left: 1px;
+}
+
+/** Fluid **/
+.ui-fluid .ui-tree {
+ width: 100%;
+}
+
+/** Horizontal Tree **/
+.ui-tree-horizontal {
+ width:auto;
+ padding: .5em 0;
+ overflow:auto;
+}
+
+.ui-tree.ui-tree-horizontal table,
+.ui-tree.ui-tree-horizontal tr,
+.ui-tree.ui-tree-horizontal td {
+ border-collapse: collapse;
+ margin: 0;
+ padding: 0;
+ vertical-align: middle;
+}
+
+.ui-tree.ui-tree-horizontal .ui-tree-toggler {
+ vertical-align: middle;
+ margin: 0;
+}
+
+.ui-tree-horizontal .ui-treenode-content {
+ font-weight: normal;
+ padding: 0.4em 1em 0.4em 0.2em;
+}
+
+.ui-tree.ui-tree-horizontal .ui-tree-node-label {
+ margin: 0;
+}
+
+.ui-tree-horizontal .ui-treenode-parent .ui-treenode-content {
+ font-weight: normal;
+ white-space: nowrap;
+}
+
+.ui-tree.ui-tree-horizontal .ui-treenode {
+ background: url("./images/line.gif") repeat-x scroll center center transparent;
+ padding: .25em 2.5em;
+}
+
+.ui-tree.ui-tree-horizontal .ui-treenode.ui-treenode-leaf,
+.ui-tree.ui-tree-horizontal .ui-treenode.ui-treenode-collapsed {
+ padding-right: 0;
+}
+
+.ui-tree.ui-tree-horizontal .ui-treenode-children {
+ padding: 0;
+ margin: 0;
+}
+
+.ui-tree.ui-tree-horizontal .ui-treenode-connector {
+ width: 1px;
+}
+
+.ui-tree.ui-tree-horizontal .ui-treenode-connector-table {
+ height: 100%;
+ width: 1px;
+}
+
+.ui-tree.ui-tree-horizontal .ui-treenode-connector-line {
+ background: url("./images/line.gif") repeat-y scroll 0 0 transparent;
+ width: 1px;
+}
+
+.ui-tree.ui-tree-horizontal table {
+ height: 0;
+}
+
+.ui-tree.ui-tree-horizontal .ui-chkbox {
+ vertical-align: bottom;
+ margin-right: .25em;
+}
diff --git a/src/app/components/tree/tree.ts b/src/app/components/tree/tree.ts
new file mode 100644
index 00000000000..109e8a08181
--- /dev/null
+++ b/src/app/components/tree/tree.ts
@@ -0,0 +1,770 @@
+import {NgModule,Component,Input,AfterContentInit,OnDestroy,Output,EventEmitter,OnInit,EmbeddedViewRef,ViewContainerRef,
+ ContentChildren,QueryList,TemplateRef,Inject,forwardRef,Host} from '@angular/core';
+import {Optional} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {TreeNode} from '../common/treenode';
+import {SharedModule} from '../common/shared';
+import {PrimeTemplate} from '../common/shared';
+import {TreeDragDropService} from '../common/treedragdropservice';
+import {Subscription} from 'rxjs/Subscription';
+
+@Component({
+ selector: 'p-treeNodeTemplateLoader',
+ template: ``
+})
+export class TreeNodeTemplateLoader implements OnInit, OnDestroy {
+
+ @Input() node: any;
+
+ @Input() template: TemplateRef;
+
+ view: EmbeddedViewRef;
+
+ constructor(public viewContainer: ViewContainerRef) {}
+
+ ngOnInit() {
+ this.view = this.viewContainer.createEmbeddedView(this.template, {
+ '\$implicit': this.node
+ });
+ }
+
+ ngOnDestroy() {
+ this.view.destroy();
+ }
+}
+
+@Component({
+ selector: 'p-treeNode',
+ template: `
+
+
+
+
+
+ {{node.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+ {{node.label}}
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+ `
+})
+export class UITreeNode implements OnInit {
+
+ static ICON_CLASS: string = 'ui-treenode-icon fa fa-fw';
+
+ @Input() node: TreeNode;
+
+ @Input() parentNode: TreeNode;
+
+ @Input() root: boolean;
+
+ @Input() index: number;
+
+ @Input() firstChild: boolean;
+
+ @Input() lastChild: boolean;
+
+ constructor(@Inject(forwardRef(() => Tree)) public tree:Tree) {}
+
+ draghoverPrev: boolean;
+
+ draghoverNext: boolean;
+
+ draghoverNode: boolean
+
+ ngOnInit() {
+ this.node.parent = this.parentNode;
+ }
+
+ getIcon() {
+ let icon: string;
+
+ if(this.node.icon)
+ icon = this.node.icon;
+ else
+ icon = this.node.expanded && this.node.children && this.node.children.length ? this.node.expandedIcon : this.node.collapsedIcon;
+
+ return UITreeNode.ICON_CLASS + ' ' + icon;
+ }
+
+ isLeaf() {
+ return this.node.leaf == false ? false : !(this.node.children&&this.node.children.length);
+ }
+
+ toggle(event: Event) {
+ if(this.node.expanded)
+ this.tree.onNodeCollapse.emit({originalEvent: event, node: this.node});
+ else
+ this.tree.onNodeExpand.emit({originalEvent: event, node: this.node});
+
+ this.node.expanded = !this.node.expanded
+ }
+
+ onNodeClick(event: MouseEvent) {
+ this.tree.onNodeClick(event, this.node);
+ }
+
+ onNodeTouchEnd() {
+ this.tree.onNodeTouchEnd();
+ }
+
+ onNodeRightClick(event: MouseEvent) {
+ this.tree.onNodeRightClick(event, this.node);
+ }
+
+ isSelected() {
+ return this.tree.isSelected(this.node);
+ }
+
+ onDropPoint(event: Event, position: number) {
+ event.preventDefault();
+ let dragNode = this.tree.dragNode;
+ let dragNodeIndex = this.tree.dragNodeIndex;
+ let dragNodeScope = this.tree.dragNodeScope;
+ let isValidDropPointIndex = this.tree.dragNodeTree === this.tree ? (position === 1 || dragNodeIndex !== this.index - 1) : true;
+
+ if(this.tree.allowDrop(dragNode, this.node, dragNodeScope) && isValidDropPointIndex) {
+ let newNodeList = this.node.parent ? this.node.parent.children : this.tree.value;
+ this.tree.dragNodeSubNodes.splice(dragNodeIndex, 1);
+ if(position < 0)
+ newNodeList.splice(this.index, 0, dragNode);
+ else
+ newNodeList.push(dragNode);
+
+ this.tree.dragDropService.stopDrag({
+ node: dragNode,
+ subNodes: this.node.parent ? this.node.parent.children : this.tree.value,
+ index: dragNodeIndex
+ });
+
+ this.tree.onNodeDrop.emit({
+ originalEvent: event,
+ dragNode: dragNode
+ });
+ }
+
+ this.draghoverPrev = false;
+ this.draghoverNext = false;
+ }
+
+ onDropPointDragOver(event) {
+ event.dataTransfer.dropEffect = 'move';
+ event.preventDefault();
+ }
+
+ onDropPointDragEnter(event: Event, position: number) {
+ if(this.tree.allowDrop(this.tree.dragNode, this.node, this.tree.dragNodeScope)) {
+ if(position < 0)
+ this.draghoverPrev = true;
+ else
+ this.draghoverNext = true;
+ }
+ }
+
+ onDropPointDragLeave(event: Event) {
+ this.draghoverPrev = false;
+ this.draghoverNext = false;
+ }
+
+ onDragStart(event) {
+ if(this.tree.draggableNodes && this.node.draggable !== false) {
+ event.dataTransfer.setData("text", "data");
+
+ this.tree.dragDropService.startDrag({
+ tree: this,
+ node: this.node,
+ subNodes: this.node.parent ? this.node.parent.children : this.tree.value,
+ index: this.index,
+ scope: this.tree.draggableScope
+ });
+ }
+ else {
+ event.preventDefault();
+ }
+ }
+
+ onDragStop(event) {
+ this.tree.dragDropService.stopDrag({
+ node: this.node,
+ subNodes: this.node.parent ? this.node.parent.children : this.tree.value,
+ index: this.index
+ });
+ }
+
+ onDropNodeDragOver(event) {
+ event.dataTransfer.dropEffect = 'move';
+ if(this.tree.droppableNodes) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ onDropNode(event) {
+ if(this.tree.droppableNodes && this.node.droppable !== false) {
+ event.preventDefault();
+ event.stopPropagation();
+ let dragNode = this.tree.dragNode;
+ if(this.tree.allowDrop(dragNode, this.node, this.tree.dragNodeScope)) {
+ let dragNodeIndex = this.tree.dragNodeIndex;
+ this.tree.dragNodeSubNodes.splice(dragNodeIndex, 1);
+
+ if(this.node.children)
+ this.node.children.push(dragNode);
+ else
+ this.node.children = [dragNode];
+
+ this.tree.dragDropService.stopDrag({
+ node: dragNode,
+ subNodes: this.node.parent ? this.node.parent.children : this.tree.value,
+ index: this.tree.dragNodeIndex
+ });
+
+ this.tree.onNodeDrop.emit({
+ originalEvent: event,
+ dragNode: dragNode,
+ dropNode: this.node
+ });
+ }
+ }
+
+ this.draghoverNode = false;
+ }
+
+ onDropNodeDragEnter(event) {
+ if(this.tree.droppableNodes && this.node.droppable !== false && this.tree.allowDrop(this.tree.dragNode, this.node, this.tree.dragNodeScope)) {
+ this.draghoverNode = true;
+ }
+ }
+
+ onDropNodeDragLeave(event) {
+ if(this.tree.droppableNodes) {
+ let rect = event.currentTarget.getBoundingClientRect();
+ if(event.x > rect.left + rect.width || event.x < rect.left || event.y >= Math.floor(rect.top + rect.height) || event.y < rect.top) {
+ this.draghoverNode = false;
+ }
+ }
+ }
+}
+
+@Component({
+ selector: 'p-tree',
+ template: `
+
+
+ `
+})
+export class Tree implements OnInit,AfterContentInit,OnDestroy {
+
+ @Input() value: TreeNode[];
+
+ @Input() selectionMode: string;
+
+ @Input() selection: any;
+
+ @Output() selectionChange: EventEmitter = new EventEmitter();
+
+ @Output() onNodeSelect: EventEmitter = new EventEmitter();
+
+ @Output() onNodeUnselect: EventEmitter = new EventEmitter();
+
+ @Output() onNodeExpand: EventEmitter = new EventEmitter();
+
+ @Output() onNodeCollapse: EventEmitter = new EventEmitter();
+
+ @Output() onNodeContextMenuSelect: EventEmitter = new EventEmitter();
+
+ @Output() onNodeDrop: EventEmitter = new EventEmitter();
+
+ @Input() style: any;
+
+ @Input() styleClass: string;
+
+ @Input() contextMenu: any;
+
+ @Input() layout: string = 'vertical';
+
+ @Input() draggableScope: any;
+
+ @Input() droppableScope: any;
+
+ @Input() draggableNodes: boolean;
+
+ @Input() droppableNodes: boolean;
+
+ @Input() metaKeySelection: boolean = true;
+
+ @Input() propagateSelectionUp: boolean = true;
+
+ @Input() propagateSelectionDown: boolean = true;
+
+ @ContentChildren(PrimeTemplate) templates: QueryList;
+
+ public templateMap: any;
+
+ public nodeTouched: boolean;
+
+ public dragNodeTree: Tree;
+
+ public dragNode: TreeNode;
+
+ public dragNodeSubNodes: TreeNode[];
+
+ public dragNodeIndex: number;
+
+ public dragNodeScope: any;
+
+ public dragHover: boolean;
+
+ public dragStartSubscription: Subscription;
+
+ public dragStopSubscription: Subscription;
+
+ constructor(@Optional() public dragDropService: TreeDragDropService) {}
+
+ ngOnInit() {
+ if(this.droppableNodes) {
+ this.dragStartSubscription = this.dragDropService.dragStart$.subscribe(
+ event => {
+ this.dragNodeTree = event.tree;
+ this.dragNode = event.node;
+ this.dragNodeSubNodes = event.subNodes;
+ this.dragNodeIndex = event.index;
+ this.dragNodeScope = event.scope;
+ });
+
+ this.dragStopSubscription = this.dragDropService.dragStop$.subscribe(
+ event => {
+ this.dragNodeTree = null;
+ this.dragNode = null;
+ this.dragNodeSubNodes = null;
+ this.dragNodeIndex = null;
+ this.dragNodeScope = null;
+ this.dragHover = false;
+ });
+ }
+ }
+
+ get horizontal(): boolean {
+ return this.layout == 'horizontal';
+ }
+
+ ngAfterContentInit() {
+ if(this.templates.length) {
+ this.templateMap = {};
+ }
+
+ this.templates.forEach((item) => {
+ this.templateMap[item.getType()] = item.template;
+ });
+ }
+
+ onNodeClick(event: MouseEvent, node: TreeNode) {
+ let eventTarget = ( event.target);
+
+ if(eventTarget.className && eventTarget.className.indexOf('ui-tree-toggler') === 0) {
+ return;
+ }
+ else if(this.selectionMode) {
+ if(node.selectable === false) {
+ return;
+ }
+
+ let index = this.findIndexInSelection(node);
+ let selected = (index >= 0);
+
+ if(this.isCheckboxSelectionMode()) {
+ if(selected) {
+ if(this.propagateSelectionDown)
+ this.propagateDown(node, false);
+ else
+ this.selection = this.selection.filter((val,i) => i!=index);
+
+ if(this.propagateSelectionUp && node.parent) {
+ this.propagateUp(node.parent, false);
+ }
+
+ this.selectionChange.emit(this.selection);
+ this.onNodeUnselect.emit({originalEvent: event, node: node});
+ }
+ else {
+ if(this.propagateSelectionDown)
+ this.propagateDown(node, true);
+ else
+ this.selection = [...this.selection||[],node];
+
+ if(this.propagateSelectionUp && node.parent) {
+ this.propagateUp(node.parent, true);
+ }
+
+ this.selectionChange.emit(this.selection);
+ this.onNodeSelect.emit({originalEvent: event, node: node});
+ }
+ }
+ else {
+ let metaSelection = this.nodeTouched ? false : this.metaKeySelection;
+
+ if(metaSelection) {
+ let metaKey = (event.metaKey||event.ctrlKey);
+
+ if(selected && metaKey) {
+ if(this.isSingleSelectionMode()) {
+ this.selectionChange.emit(null);
+ }
+ else {
+ this.selection = this.selection.filter((val,i) => i!=index);
+ this.selectionChange.emit(this.selection);
+ }
+
+ this.onNodeUnselect.emit({originalEvent: event, node: node});
+ }
+ else {
+ if(this.isSingleSelectionMode()) {
+ this.selectionChange.emit(node);
+ }
+ else if(this.isMultipleSelectionMode()) {
+ this.selection = (!metaKey) ? [] : this.selection||[];
+ this.selection = [...this.selection,node];
+ this.selectionChange.emit(this.selection);
+ }
+
+ this.onNodeSelect.emit({originalEvent: event, node: node});
+ }
+ }
+ else {
+ if(this.isSingleSelectionMode()) {
+ if(selected) {
+ this.selection = null;
+ this.onNodeUnselect.emit({originalEvent: event, node: node});
+ }
+ else {
+ this.selection = node;
+ this.onNodeSelect.emit({originalEvent: event, node: node});
+ }
+ }
+ else {
+ if(selected) {
+ this.selection = this.selection.filter((val,i) => i!=index);
+ this.onNodeUnselect.emit({originalEvent: event, node: node});
+ }
+ else {
+ this.selection = [...this.selection||[],node];
+ this.onNodeSelect.emit({originalEvent: event, node: node});
+ }
+ }
+
+ this.selectionChange.emit(this.selection);
+ }
+ }
+ }
+
+ this.nodeTouched = false;
+ }
+
+ onNodeTouchEnd() {
+ this.nodeTouched = true;
+ }
+
+ onNodeRightClick(event: MouseEvent, node: TreeNode) {
+ if(this.contextMenu) {
+ let eventTarget = ( event.target);
+
+ if(eventTarget.className && eventTarget.className.indexOf('ui-tree-toggler') === 0) {
+ return;
+ }
+ else {
+ let index = this.findIndexInSelection(node);
+ let selected = (index >= 0);
+
+ if(!selected) {
+ if(this.isSingleSelectionMode())
+ this.selectionChange.emit(node);
+ else
+ this.selectionChange.emit([node]);
+ }
+
+ this.contextMenu.show(event);
+ this.onNodeContextMenuSelect.emit({originalEvent: event, node: node});
+ }
+ }
+ }
+
+ findIndexInSelection(node: TreeNode) {
+ let index: number = -1;
+
+ if(this.selectionMode && this.selection) {
+ if(this.isSingleSelectionMode()) {
+ index = (this.selection == node) ? 0 : - 1;
+ }
+ else {
+ for(let i = 0; i < this.selection.length; i++) {
+ if(this.selection[i] == node) {
+ index = i;
+ break;
+ }
+ }
+ }
+ }
+
+ return index;
+ }
+
+ propagateUp(node: TreeNode, select: boolean) {
+ if(node.children && node.children.length) {
+ let selectedCount: number = 0;
+ let childPartialSelected: boolean = false;
+ for(let child of node.children) {
+ if(this.isSelected(child)) {
+ selectedCount++;
+ }
+ else if(child.partialSelected) {
+ childPartialSelected = true;
+ }
+ }
+
+ if(select && selectedCount == node.children.length) {
+ this.selection = [...this.selection||[],node];
+ node.partialSelected = false;
+ }
+ else {
+ if(!select) {
+ let index = this.findIndexInSelection(node);
+ if(index >= 0) {
+ this.selection = this.selection.filter((val,i) => i!=index);
+ }
+ }
+
+ if(childPartialSelected || selectedCount > 0 && selectedCount != node.children.length)
+ node.partialSelected = true;
+ else
+ node.partialSelected = false;
+ }
+ }
+
+ let parent = node.parent;
+ if(parent) {
+ this.propagateUp(parent, select);
+ }
+ }
+
+ propagateDown(node: TreeNode, select: boolean) {
+ let index = this.findIndexInSelection(node);
+
+ if(select && index == -1) {
+ this.selection = [...this.selection||[],node];
+ }
+ else if(!select && index > -1) {
+ this.selection = this.selection.filter((val,i) => i!=index);
+ }
+
+ node.partialSelected = false;
+
+ if(node.children && node.children.length) {
+ for(let child of node.children) {
+ this.propagateDown(child, select);
+ }
+ }
+ }
+
+ isSelected(node: TreeNode) {
+ return this.findIndexInSelection(node) != -1;
+ }
+
+ isSingleSelectionMode() {
+ return this.selectionMode && this.selectionMode == 'single';
+ }
+
+ isMultipleSelectionMode() {
+ return this.selectionMode && this.selectionMode == 'multiple';
+ }
+
+ isCheckboxSelectionMode() {
+ return this.selectionMode && this.selectionMode == 'checkbox';
+ }
+
+ getTemplateForNode(node: TreeNode): TemplateRef {
+ if(this.templateMap)
+ return node.type ? this.templateMap[node.type] : this.templateMap['default'];
+ else
+ return null;
+ }
+
+ onDragOver(event) {
+ if(this.droppableNodes && (!this.value || this.value.length === 0)) {
+ event.dataTransfer.dropEffect = 'move';
+ event.preventDefault();
+ }
+ }
+
+ onDrop(event) {
+ if(this.droppableNodes && (!this.value || this.value.length === 0)) {
+ event.preventDefault();
+ let dragNode = this.dragNode;
+ if(this.allowDrop(dragNode, null, this.dragNodeScope)) {
+ let dragNodeIndex = this.dragNodeIndex;
+ this.dragNodeSubNodes.splice(dragNodeIndex, 1);
+ this.value = this.value||[];
+ this.value.push(dragNode);
+
+ this.dragDropService.stopDrag({
+ node: dragNode
+ });
+ }
+ }
+ }
+
+ onDragEnter(event) {
+ if(this.droppableNodes && this.allowDrop(this.dragNode, null, this.dragNodeScope)) {
+ this.dragHover = true;
+ }
+ }
+
+ onDragLeave(event) {
+ if(this.droppableNodes) {
+ let rect = event.currentTarget.getBoundingClientRect();
+ if(event.x > rect.left + rect.width || event.x < rect.left || event.y > rect.top + rect.height || event.y < rect.top) {
+ this.dragHover = false;
+ }
+ }
+ }
+
+ allowDrop(dragNode: TreeNode, dropNode: TreeNode, dragNodeScope: any): boolean {
+ if(this.isValidDragScope(dragNodeScope)) {
+ let allow: boolean = true;
+ if(dropNode) {
+ if(dragNode === dropNode) {
+ allow = false;
+ }
+ else {
+ let parent = dropNode.parent;
+ while(parent != null) {
+ if(parent === dragNode) {
+ allow = false;
+ break;
+ }
+ parent = parent.parent;
+ }
+ }
+ }
+
+ return allow;
+ }
+ else {
+ return false;
+ }
+ }
+
+ isValidDragScope(dragScope: any): boolean {
+ let dropScope = this.droppableScope;
+
+ if(dropScope) {
+ if(typeof dropScope === 'string') {
+ if(typeof dragScope === 'string')
+ return dropScope === dragScope;
+ else if(dragScope instanceof Array)
+ return (>dragScope).indexOf(dropScope) != -1;
+ }
+ else if(dropScope instanceof Array) {
+ if(typeof dragScope === 'string') {
+ return (>dropScope).indexOf(dragScope) != -1;
+ }
+ else if(dragScope instanceof Array) {
+ for(let s of dropScope) {
+ for(let ds of dragScope) {
+ if(s === ds) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+ else {
+ return true;
+ }
+ }
+
+ ngOnDestroy() {
+ if(this.dragStartSubscription) {
+ this.dragStartSubscription.unsubscribe();
+ }
+
+ if(this.dragStopSubscription) {
+ this.dragStopSubscription.unsubscribe();
+ }
+ }
+}
+@NgModule({
+ imports: [CommonModule],
+ exports: [Tree,SharedModule],
+ declarations: [Tree,UITreeNode,TreeNodeTemplateLoader]
+})
+export class TreeModule { }
diff --git a/src/app/components/treetable/treetable.css b/src/app/components/treetable/treetable.css
new file mode 100644
index 00000000000..bfdf1fcdcd1
--- /dev/null
+++ b/src/app/components/treetable/treetable.css
@@ -0,0 +1,93 @@
+.ui-treetable table {
+ border-collapse:collapse;
+ width: 100%;
+ table-layout: fixed;
+}
+
+.ui-treetable .ui-treetable-header,
+.ui-treetable .ui-treetable-footer {
+ text-align:center;
+ padding: .5em .75em;
+}
+
+.ui-treetable .ui-treetable-header {
+ border-bottom: 0 none;
+}
+
+.ui-treetable .ui-treetable-footer {
+ border-top: 0 none;
+}
+
+.ui-treetable th, .ui-treetable tfoot td {
+ text-align: center;
+}
+
+.ui-treetable thead th,
+.ui-treetable tbody td,
+.ui-treetable tfoot td {
+ padding: .25em .5em;
+ overflow: hidden;
+ white-space: nowrap;
+ border-width: 1px;
+ border-style: solid;
+}
+
+.ui-treetable tbody td {
+ border-color: inherit;
+}
+
+.ui-treetable tbody td:first-child span {
+ vertical-align: middle;
+}
+
+.ui-treetable .ui-treetable-toggler {
+ vertical-align: middle;
+ cursor: pointer;
+ text-decoration: none;
+}
+
+.ui-treetable .ui-treetable-checkbox {
+ margin-right: .5em;
+}
+
+.ui-treetable .ui-treetable-checkbox .ui-chkbox-icon {
+ margin-left: 1px;
+}
+
+.ui-treetable .ui-treetable-row.ui-treetable-row-selectable {
+ cursor: pointer;
+}
+
+.ui-treetable .ui-treetable-row.ui-state-highlight {
+ border: 0 none;
+}
+
+.ui-treetable tr.ui-state-hover {
+ border-color: inherit;
+ font-weight: inherit;
+}
+
+.ui-treetable .ui-treetable-indent {
+ width: 1em;
+ height: 1em;
+ float: left;
+}
+
+/* PrimeNG */
+.ui-treetable td.ui-treetable-child-table-container {
+ padding: 0;
+ border: 0 none;
+}
+
+.ui-treetable .ui-treetable-row {
+ display: table-row;
+ border-bottom: 0 transparent
+}
+
+.ui-treetable tbody .ui-treetable-row td {
+ border: 0 none;
+}
+
+.ui-treetable tbody .ui-treetable-row td input {
+ outline: 0 none;
+}
\ No newline at end of file
diff --git a/src/app/components/treetable/treetable.ts b/src/app/components/treetable/treetable.ts
new file mode 100644
index 00000000000..ef4c611bf1d
--- /dev/null
+++ b/src/app/components/treetable/treetable.ts
@@ -0,0 +1,409 @@
+import {NgModule,Component,Input,Output,EventEmitter,ElementRef,ContentChild,IterableDiffers,ContentChildren,QueryList,Inject,forwardRef,OnInit} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {TreeNode} from '../common/treenode';
+import {Header,Footer,Column} from '../common/shared';
+import {SharedModule} from '../common/shared';
+import {DomHandler} from '../dom/domhandler';
+
+@Component({
+ selector: '[pTreeRow]',
+ template: `
+