diff --git a/ng2-components/ng2-alfresco-datatable/.editorconfig b/ng2-components/ng2-alfresco-datatable/.editorconfig new file mode 100644 index 0000000000..8786e6e298 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/ng2-components/ng2-alfresco-datatable/.gitignore b/ng2-components/ng2-alfresco-datatable/.gitignore new file mode 100644 index 0000000000..9bc5b518a8 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/.gitignore @@ -0,0 +1,10 @@ +npm-debug.log +node_modules/ +.idea/ +typings +coverage/ +dist/ +src/**/*.js +src/**/*.js.map +ng2-alfresco-datatable.js +ng2-alfresco-datatable.js.map diff --git a/ng2-components/ng2-alfresco-datatable/.travis.yml b/ng2-components/ng2-alfresco-datatable/.travis.yml new file mode 100755 index 0000000000..37844eeed9 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +node_js: + - '5' +script: npm run coverage +# Send coverage data to Coveralls +after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" diff --git a/ng2-components/ng2-alfresco-datatable/LICENSE b/ng2-components/ng2-alfresco-datatable/LICENSE new file mode 100644 index 0000000000..430d42bbea --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/ng2-components/ng2-alfresco-datatable/README.md b/ng2-components/ng2-alfresco-datatable/README.md new file mode 100644 index 0000000000..9dd73dc6cf --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/README.md @@ -0,0 +1,40 @@ +# DataTable Component for Angular 2 + +

+ + license + + + my blog + +

+ +## Install + +```sh +npm install --save ng2-alfresco-core ng2-alfresco-datatable +``` + +## Build from sources + +Alternatively you can build component from sources with the following commands: + +```sh +npm install +npm run build +``` + +## Running unit tests + +```sh +npm test +``` + +This task rebuilds all the code, runs tslint, license checks and other quality check tools +before performing unit testing. + +## Code coverage + +```sh +npm run coverage +``` diff --git a/ng2-components/ng2-alfresco-datatable/assets/license_header.txt b/ng2-components/ng2-alfresco-datatable/assets/license_header.txt new file mode 100644 index 0000000000..83fd1531a3 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/assets/license_header.txt @@ -0,0 +1,16 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ \ No newline at end of file diff --git a/ng2-components/ng2-alfresco-datatable/karma-test-shim.js b/ng2-components/ng2-alfresco-datatable/karma-test-shim.js new file mode 100644 index 0000000000..3015aa2876 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/karma-test-shim.js @@ -0,0 +1,70 @@ +// Tun on full stack traces in errors to help debugging +Error.stackTraceLimit = Infinity; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; + +// // Cancel Karma's synchronous start, +// // we will call `__karma__.start()` later, once all the specs are loaded. +__karma__.loaded = function() {}; + +System.config({ + packages: { + 'base/dist': { + defaultExtension: 'js', + format: 'register', + map: Object.keys(window.__karma__.files).filter(onlyAppFiles).reduce(createPathRecords, {}) + } + }, + map: { + 'ng2-alfresco-core': '/base/dist/node_modules/ng2-alfresco-core' + } +}); + +System.import('angular2/src/platform/browser/browser_adapter') + .then(function(browser_adapter) { browser_adapter.BrowserDomAdapter.makeCurrent(); }) + .then(function() { return Promise.all(resolveTestFiles()); }) + .then( + function() { + __karma__.start(); + }, + function(error) { + __karma__.error(error.stack || error); + } + ); + +function createPathRecords(pathsMapping, appPath) { + // creates local module name mapping to global path with karma's fingerprint in path, e.g.: + // './vg-player/vg-player': + // '/base/dist/vg-player/vg-player.js?f4523daf879cfb7310ef6242682ccf10b2041b3e' + var moduleName = './' + resolveKeyPathForMapping('base/dist/', appPath); + moduleName = moduleName.replace(/\.js$/, ''); + pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath]; + return pathsMapping; +} + +function onlyAppFiles(filePath) { + return /\/base\/dist\/(?!.*\.spec\.js$).*\.js$/.test(filePath); +} + +function onlySpecFiles(path) { + return /\.spec\.js$/.test(path); +} + +function resolveTestFiles() { + return Object.keys(window.__karma__.files) // All files served by Karma. + .filter(onlySpecFiles) + .map(function(moduleName) { + // loads all spec files via their global module names (e.g. + // 'base/dist/vg-player/vg-player.spec') + return System.import(moduleName); + }); +} + +function resolveKeyPathForMapping(basePathWhereToStart, appPath) { + var location = appPath.indexOf(basePathWhereToStart); + if (location > -1) { + return appPath.substring(basePathWhereToStart.length + 1); + } else { + return appPath; + } +} diff --git a/ng2-components/ng2-alfresco-datatable/karma.conf.js b/ng2-components/ng2-alfresco-datatable/karma.conf.js new file mode 100644 index 0000000000..0459318f82 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/karma.conf.js @@ -0,0 +1,78 @@ +'use strict'; + +module.exports = function (config) { + config.set({ + + basePath: '.', + + frameworks: ['jasmine'], + + files: [ + // paths loaded by Karma + {pattern: 'node_modules/angular2/bundles/angular2-polyfills.js', included: true, watched: true}, + {pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: true}, + {pattern: 'node_modules/rxjs/bundles/Rx.js', included: true, watched: true}, + {pattern: 'node_modules/angular2/bundles/angular2.dev.js', included: true, watched: true}, + {pattern: 'node_modules/angular2/bundles/testing.dev.js', included: true, watched: true}, + {pattern: 'node_modules/angular2/bundles/http.dev.js', included: true, watched: true}, + {pattern: 'node_modules/alfresco-core-rest-api/bundle.js', included: true, watched: false}, + + {pattern: 'karma-test-shim.js', included: true, watched: true}, + + // paths loaded via module imports + {pattern: 'dist/**/*.js', included: false, watched: true}, + {pattern: 'dist/**/*.html', included: true, served: true, watched: true}, + {pattern: 'dist/**/*.css', included: true, served: true, watched: true}, + + // paths to support debugging with source maps in dev tools + {pattern: 'src/**/*.ts', included: false, watched: false}, + {pattern: 'dist/**/*.js.map', included: false, watched: false} + ], + + // proxied base paths + proxies: { + // required for component assets fetched by Angular's compiler + '/src/': '/base/src/' + }, + + port: 9876, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + colors: true, + + autoWatch: true, + + browsers: ['Chrome'], + + // Karma plugins loaded + plugins: [ + 'karma-jasmine', + 'karma-coverage', + 'karma-chrome-launcher', + 'karma-mocha-reporter' + ], + + // Coverage reporter generates the coverage + reporters: ['mocha', 'coverage'], + + // Source files that you wanna generate coverage for. + // Do not include tests or libraries (these files will be instrumented by Istanbul) + preprocessors: { + 'dist/**/!(*spec).js': ['coverage'] + }, + + coverageReporter: { + dir: 'coverage/', + reporters: [ + {type: 'text-summary'}, + {type: 'json', subdir: '.', file: 'coverage-final.json'}, + {type: 'html'} + ] + }, + + singleRun: true + }) +}; diff --git a/ng2-components/ng2-alfresco-datatable/ng2-alfresco-datatable.ts b/ng2-components/ng2-alfresco-datatable/ng2-alfresco-datatable.ts new file mode 100644 index 0000000000..2860c7015b --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/ng2-alfresco-datatable.ts @@ -0,0 +1,58 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DataTableComponent } from './src/components/datatable.component'; +import { DataColumnComponent } from './src/components/data-column.component'; +import { DataColumnListComponent } from './src/components/data-column-list.component'; +import { DataActionComponent } from './src/components/data-action.component'; +import { DataActionListComponent } from './src/components/data-action-list.component'; + +import { FolderActionsService } from './src/services/folder-actions.service'; +import { DocumentActionsService } from './src/services/document-actions.service'; +import { AlfrescoService } from './src/services/alfresco.service'; + +// components +export * from './src/components/datatable.component'; +export * from './src/components/data-column.component'; +export * from './src/components/data-column-list.component'; +export * from './src/components/data-action.component'; +export * from './src/components/data-action-list.component'; + +// models +export * from './src/models/data-action.model'; +export * from './src/models/data-column.model'; +export * from './src/models/column-sorting.model'; + +// services +export * from './src/services/folder-actions.service'; +export * from './src/services/document-actions.service'; +export * from './src/services/alfresco.service'; + + +export const ALFRESCO_DATATABLE_DIRECTIVES: [any] = [ + DataTableComponent, + DataColumnComponent, + DataColumnListComponent, + DataActionComponent, + DataActionListComponent +]; + +export const ALFRESCO_DATATABLE_PROVIDERS: [any] = [ + AlfrescoService, + FolderActionsService, + DocumentActionsService +]; diff --git a/ng2-components/ng2-alfresco-datatable/package.json b/ng2-components/ng2-alfresco-datatable/package.json new file mode 100644 index 0000000000..128c099b65 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/package.json @@ -0,0 +1,89 @@ +{ + "name": "ng2-alfresco-datatable", + "description": "Alfresco Angular2 DataTable Component", + "version": "0.1.0", + "author": "Alfresco Software, Ltd.", + "scripts": { + "postinstall": "npm run typings", + "typings": "typings install", + "start": "npm run test && http-server -c-1 -o -p 8875 .", + "build": "npm run tslint && typings install && rm -rf dist && tsc && npm run copytemplates && license-check", + "tslint": "npm run tslint-test && npm run tslint-src && npm run tslint-root", + "tslint-test": "tslint -c tslint.json test/**/*.ts", + "tslint-src": "tslint -c tslint.json src/**/*.ts", + "tslint-root": "tslint -c tslint.json *.ts", + "copytemplates": "npm run copy-html && npm run copy-css", + "copy-html": "copyfiles './src/**/*.html' dist", + "copy-css": "copyfiles './src/**/*.css' dist", + "licensecheck": "license-check", + "tsc": "tsc", + "pretest": "npm run build", + "test": "karma start karma.conf.js", + "posttest": "node_modules/.bin/remap-istanbul -i coverage/coverage-final.json -o coverage -t html", + "coverage": "http-server -c-1 -o -p 9875 ./coverage" + }, + "repository": { + "type": "git", + "url": "https://github.com/Alfresco/dev-platform-webcomponents.git" + }, + "bugs": { + "url": "https://github.com/Alfresco/dev-platform-webcomponents/issues" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Denys Vuika", + "email": "denis.vuyka@gmail.com" + } + ], + "keywords": [ + "ng2", + "angular", + "angular2", + "alfresco" + ], + "dependencies": { + "angular2": "2.0.0-beta.15", + "systemjs": "0.19.26", + "es6-shim": "^0.35.0", + "reflect-metadata": "0.1.2", + "rxjs": "5.0.0-beta.2", + "zone.js": "^0.6.12", + "es6-module-loader": "^0.17.8" + }, + "peerDependencies": { + "angular2": "2.0.0-beta.15" + }, + "devDependencies": { + "copyfiles": "^0.2.1", + "coveralls": "^2.11.9", + "http-server": "0.8.5", + "jasmine-core": "2.4.1", + "karma": "~0.13.22", + "karma-chrome-launcher": "~1.0.1", + "karma-coverage": "^1.0.0", + "karma-jasmine": "~1.0.2", + "karma-mocha-reporter": "^2.0.3", + "license-check": "^1.0.4", + "remap-istanbul": "^0.6.3", + "traceur": "^0.0.91", + "tslint": "^3.8.1", + "typescript": "^1.8.10", + "typings": "^0.7.12" + }, + "license-check-config": { + "src": [ + "**/*.js", + "**/*.ts", + "!/**/coverage/**/*", + "!/**/demo/**/*", + "!/**/node_modules/**/*", + "!/**/typings/**/*", + "!*.js" + ], + "path": "assets/license_header.txt", + "blocking": false, + "logInfo": false, + "logError": true + } +} diff --git a/ng2-components/ng2-alfresco-datatable/src/assets/alfresco.service.mock.ts b/ng2-components/ng2-alfresco-datatable/src/assets/alfresco.service.mock.ts new file mode 100644 index 0000000000..8cee299e13 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/assets/alfresco.service.mock.ts @@ -0,0 +1,41 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Http} from 'angular2/http'; +import {Observable} from 'rxjs/Observable'; + +import {AlfrescoSettingsService} from 'ng2-alfresco-core/services'; +import {AlfrescoService} from './../../src/services/alfresco.service'; + +export class AlfrescoServiceMock extends AlfrescoService { + + _folderToReturn: any = {}; + + constructor( + http: Http = null, + settings: AlfrescoSettingsService = null + ) { + super(http, settings); + } + + getFolder(folder: string) { + return Observable.create(observer => { + observer.next(this._folderToReturn); + observer.complete(); + }); + } +} diff --git a/ng2-components/ng2-alfresco-datatable/src/components/data-action-list.component.ts b/ng2-components/ng2-alfresco-datatable/src/components/data-action-list.component.ts new file mode 100644 index 0000000000..2f695f055c --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/components/data-action-list.component.ts @@ -0,0 +1,42 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component} from 'angular2/core'; + +import { DataTableComponent } from './datatable.component'; +import { DataActionModel } from './../models/data-action.model'; + +@Component({ + selector: 'data-actions', + template: '' +}) +export class DataActionListComponent { + + constructor( + private dataTable: DataTableComponent) { + } + + /** + * Registers action handler within the parent document list component. + * @param action Action model to register. + */ + registerAction(action: DataActionModel): void { + if (this.dataTable && action) { + this.dataTable.actions.push(action); + } + } +} diff --git a/ng2-components/ng2-alfresco-datatable/src/components/data-action.component.spec.ts b/ng2-components/ng2-alfresco-datatable/src/components/data-action.component.spec.ts new file mode 100644 index 0000000000..279bdb39aa --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/components/data-action.component.spec.ts @@ -0,0 +1,179 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + it, + describe, + expect, + beforeEach +} from 'angular2/testing'; +import {EventEmitter} from 'angular2/core'; + +import { AlfrescoServiceMock } from '../assets/alfresco.service.mock'; +import { DataTableComponent } from './datatable.component'; +import { DataActionListComponent } from './data-action-list.component'; +import { DataActionComponent } from './data-action.component'; +import {DocumentActionsService} from '../services/document-actions.service'; +import {FolderActionsService} from '../services/folder-actions.service'; + +describe('ContentAction', () => { + + let dataTable: DataTableComponent; + let actionList: DataActionListComponent; + + beforeEach(() => { + let alfrescoServiceMock = new AlfrescoServiceMock(); + dataTable = new DataTableComponent(alfrescoServiceMock); + actionList = new DataActionListComponent(dataTable); + }); + + it('should register within parent actions list', () => { + spyOn(actionList, 'registerAction').and.stub(); + + let action = new DataActionComponent(actionList, null, null); + action.ngOnInit(); + + expect(actionList.registerAction).toHaveBeenCalled(); + }); + + it('should setup and register model', () => { + let action = new DataActionComponent(actionList, null, null); + action.type = 'button'; + action.target = 'document'; + action.title = ''; + action.icon = '<icon>'; + + expect(dataTable.actions.length).toBe(0); + action.ngOnInit(); + + expect(dataTable.actions.length).toBe(1); + + let model = dataTable.actions[0]; + expect(model.type).toBe(action.type); + expect(model.target).toBe(action.target); + expect(model.title).toBe(action.title); + expect(model.icon).toBe(action.icon); + }); + + it('should get action handler from document actions service', () => { + + let handler = function() {}; + let documentActions = new DocumentActionsService(null); + spyOn(documentActions, 'getHandler').and.returnValue(handler); + + let action = new DataActionComponent(actionList, documentActions, null); + action.type = 'button'; + action.target = 'document'; + action.handler = '<handler>'; + action.ngOnInit(); + + expect(documentActions.getHandler).toHaveBeenCalledWith(action.handler); + expect(dataTable.actions.length).toBe(1); + + let model = dataTable.actions[0]; + expect(model.handler).toBe(handler); + }); + + it('should get action handler from folder actions service', () => { + let handler = function() {}; + let folderActions = new FolderActionsService(); + spyOn(folderActions, 'getHandler').and.returnValue(handler); + + let action = new DataActionComponent(actionList, null, folderActions); + action.type = 'button'; + action.target = 'folder'; + action.handler = '<handler>'; + action.ngOnInit(); + + expect(folderActions.getHandler).toHaveBeenCalledWith(action.handler); + expect(dataTable.actions.length).toBe(1); + + let model = dataTable.actions[0]; + expect(model.handler).toBe(handler); + }); + + it('should require target to get system handler', () => { + let folderActions = new FolderActionsService(); + spyOn(folderActions, 'getHandler').and.stub(); + + let documentActions = new DocumentActionsService(null); + spyOn(documentActions, 'getHandler').and.stub(); + + let action = new DataActionComponent(actionList, documentActions, folderActions); + action.type = 'button'; + action.handler = '<handler>'; + + action.ngOnInit(); + expect(dataTable.actions.length).toBe(1); + expect(folderActions.getHandler).not.toHaveBeenCalled(); + expect(documentActions.getHandler).not.toHaveBeenCalled(); + + action.target = 'document'; + action.ngOnInit(); + expect(documentActions.getHandler).toHaveBeenCalled(); + + action.target = 'folder'; + action.ngOnInit(); + expect(folderActions.getHandler).toHaveBeenCalled(); + }); + + it('should be case insensitive for document target', () => { + let documentActions = new DocumentActionsService(null); + spyOn(documentActions, 'getHandler').and.stub(); + + let action = new DataActionComponent(actionList, documentActions, null); + action.target = 'DoCuMeNt'; + action.type = 'button'; + action.handler = '<handler>'; + + action.ngOnInit(); + expect(documentActions.getHandler).toHaveBeenCalledWith(action.handler); + }); + + it('should be case insensitive for folder target', () => { + let folderActions = new FolderActionsService(); + spyOn(folderActions, 'getHandler').and.stub(); + + let action = new DataActionComponent(actionList, null, folderActions); + action.target = 'FoLdEr'; + action.type = 'button'; + action.handler = '<handler>'; + + action.ngOnInit(); + expect(folderActions.getHandler).toHaveBeenCalledWith(action.handler); + }); + + it('should use custom "execute" emitter', (done) => { + let emitter = new EventEmitter(); + + emitter.subscribe(e => { + expect(e.value).toBe('<obj>'); + done(); + }); + + let action = new DataActionComponent(actionList, null, null); + action.target = 'document'; + action.type = 'button'; + action.execute = emitter; + + action.ngOnInit(); + expect(dataTable.actions.length).toBe(1); + + let model = dataTable.actions[0]; + model.handler('<obj>'); + }); +}); diff --git a/ng2-components/ng2-alfresco-datatable/src/components/data-action.component.ts b/ng2-components/ng2-alfresco-datatable/src/components/data-action.component.ts new file mode 100644 index 0000000000..4f48742eac --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/components/data-action.component.ts @@ -0,0 +1,101 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Component, + OnInit, + Input, + Output, + EventEmitter +} from 'angular2/core'; + +import { DataActionModel, DataActionHandler } from './../models/data-action.model'; +import { DataActionListComponent } from './data-action-list.component'; +import {DocumentActionsService} from '../services/document-actions.service'; +import {FolderActionsService} from '../services/folder-actions.service'; + +@Component({ + selector: 'data-action', + template: '' +}) +export class DataActionComponent implements OnInit { + + @Input() + title: string = 'Action'; + + @Input() + icon: string; + + @Input() + handler: string; + + @Input() + type: string; + + @Input() + target: string; + + @Output() + execute = new EventEmitter(); + + constructor( + private list: DataActionListComponent, + private documentActions: DocumentActionsService, + private folderActions: FolderActionsService) { + } + + ngOnInit() { + let model = new DataActionModel(); + model.type = this.type; + model.title = this.title; + model.icon = this.icon; + model.target = this.target; + + if (this.handler) { + model.handler = this.getSystemHandler(this.target, this.handler); + } else if (this.execute) { + model.handler = (document: any): void => { + this.execute.emit({ + value: document + }); + }; + } + + this.list.registerAction(model); + } + + private getSystemHandler(target: string, name: string): DataActionHandler { + if (target) { + let ltarget = target.toLowerCase(); + + if (ltarget === 'document') { + if (this.documentActions) { + return this.documentActions.getHandler(name); + } + return null; + } + + if (ltarget === 'folder') { + if (this.folderActions) { + return this.folderActions.getHandler(name); + } + return null; + } + } + return null; + } +} diff --git a/ng2-components/ng2-alfresco-datatable/src/components/data-column-list.component.spec.ts b/ng2-components/ng2-alfresco-datatable/src/components/data-column-list.component.spec.ts new file mode 100644 index 0000000000..4e7d58603a --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/components/data-column-list.component.spec.ts @@ -0,0 +1,49 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + it, + describe, + expect, + beforeEach +} from 'angular2/testing'; + +import { DataTableComponent } from './datatable.component'; +import { AlfrescoServiceMock } from '../assets/alfresco.service.mock'; +import { DataColumnListComponent } from './data-column-list.component'; +import { DataColumnModel } from '../models/data-column.model'; + +describe('ContentColumnList', () => { + + let dataTable: DataTableComponent; + let dataColumnList: DataColumnListComponent; + + beforeEach(() => { + let alfrescoServiceMock = new AlfrescoServiceMock(); + dataTable = new DataTableComponent(alfrescoServiceMock); + dataColumnList = new DataColumnListComponent(dataTable); + }); + + it('should register column within parent document list', () => { + expect(dataTable.columns.length).toBe(0); + + dataColumnList.registerColumn(new DataColumnModel()); + + expect(dataTable.columns.length).toBe(1); + }); + +}); diff --git a/ng2-components/ng2-alfresco-datatable/src/components/data-column-list.component.ts b/ng2-components/ng2-alfresco-datatable/src/components/data-column-list.component.ts new file mode 100644 index 0000000000..de2615e6cd --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/components/data-column-list.component.ts @@ -0,0 +1,41 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component } from 'angular2/core'; +import { DataTableComponent } from './datatable.component'; +import { DataColumnModel } from '../models/data-column.model'; + +@Component({ + selector: 'data-columns', + template: '' +}) +export class DataColumnListComponent { + + constructor( + private dataTable: DataTableComponent) { + } + + /** + * Registers column model within the parent document list component. + * @param column Column definition model to register. + */ + registerColumn(column: DataColumnModel): void { + if (this.dataTable && column) { + this.dataTable.columns.push(column); + } + } +} diff --git a/ng2-components/ng2-alfresco-datatable/src/components/data-column.component.spec.ts b/ng2-components/ng2-alfresco-datatable/src/components/data-column.component.spec.ts new file mode 100644 index 0000000000..49202cbad0 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/components/data-column.component.spec.ts @@ -0,0 +1,80 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + it, + describe, + expect, + beforeEach +} from 'angular2/testing'; + +import {AlfrescoServiceMock} from '../assets/alfresco.service.mock'; +import { DataTableComponent } from './datatable.component'; +import { DataColumnComponent } from './data-column.component'; +import { DataColumnListComponent } from './data-column-list.component'; + +describe('ContentColumn', () => { + + let dataTable: DataTableComponent; + let columnList: DataColumnListComponent; + + beforeEach(() => { + let alfrescoServiceMock = new AlfrescoServiceMock(); + dataTable = new DataTableComponent(alfrescoServiceMock); + columnList = new DataColumnListComponent(dataTable); + }); + + it('should register model within parent column list', () => { + spyOn(columnList, 'registerColumn').and.callThrough(); + + let column = new DataColumnComponent(columnList); + column.ngOnInit(); + + expect(columnList.registerColumn).toHaveBeenCalled(); + }); + + it('should setup model properties during registration', () => { + + let column = new DataColumnComponent(columnList); + column.title = '<title>'; + column.srTitle = '<sr-title>'; + column.source = '<source>'; + column.cssClass = '<css-class>'; + column.ngOnInit(); + + expect(dataTable.columns.length).toBe(1); + + let model = dataTable.columns[0]; + expect(model.title).toBe(column.title); + expect(model.srTitle).toBe(column.srTitle); + expect(model.source).toBe(column.source); + expect(model.cssClass).toBe(column.cssClass); + }); + + it('should setup screen reader title for thumbnail column', () => { + + let column = new DataColumnComponent(columnList); + column.source = '$thumbnail'; + column.ngOnInit(); + + expect(dataTable.columns.length).toBe(1); + + let model = dataTable.columns[0]; + expect(model.srTitle).toBe('Thumbnail'); + }); + +}); diff --git a/ng2-components/ng2-alfresco-datatable/src/components/data-column.component.ts b/ng2-components/ng2-alfresco-datatable/src/components/data-column.component.ts new file mode 100644 index 0000000000..e38fc54756 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/components/data-column.component.ts @@ -0,0 +1,63 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, OnInit, Input } from 'angular2/core'; + +import { DataColumnListComponent } from './data-column-list.component'; +import { DataColumnModel } from './../models/data-column.model'; + +@Component({ + selector: 'data-column', + template: '' +}) +export class DataColumnComponent implements OnInit { + + @Input() + title: string = ''; + + /** + * Title to be used for screen readers. + */ + @Input('sr-title') + srTitle: string; + + @Input() + source: string; + + @Input('class') + cssClass: string; + + constructor( + private columns: DataColumnListComponent) { + } + + ngOnInit() { + let model = new DataColumnModel(); + model.title = this.title; + model.srTitle = this.srTitle; + model.source = this.source; + model.cssClass = this.cssClass; + + if (!model.srTitle && model.source === '$thumbnail') { + model.srTitle = 'Thumbnail'; + } + + if (this.columns) { + this.columns.registerColumn(model); + } + } +} diff --git a/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.css b/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.css new file mode 100644 index 0000000000..9c54596092 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.css @@ -0,0 +1,65 @@ +:host .full-width { width: 100%; } + +:host .folder-thumbnail { + font-size: 48px; + cursor: pointer; +} + +:host .document-thumbnail { + width: 48px; + height: 48px; + cursor: pointer; +} + +:host .column-header { + cursor: pointer; + user-select: none; + -webkit-user-select: none; /* Chrome/Safari/Opera */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE/Edge */ + -webkit-touch-callout: none; /* iOS Safari */ +} + +:host .parent-folder-link { cursor: pointer; } +:host .parent-folder-link > td { text-align: left; } + +:host .folder-row-cell, +:host .document-row-cell { + cursor: pointer; +} + +:host .folder-row-cell.name-column, +:host .document-row-cell.name-column { + font-size: 15px; +} + +:host .folder-row-cell.name-column:hover, +:host .document-row-cell.name-column:hover { + cursor: pointer; +} + +/* breadcrumb */ + +:host .breadcrumb { + text-align: left; + padding: 8px 15px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; + margin: 0 0 4px; +} + +:host .breadcrumb > li { + display: inline-block; + box-sizing: border-box; +} + +:host .breadcrumb > li+li:before { + content: "/\00a0"; + padding: 0 0 0 5px; + color: #ccc; +} + +:host .breadcrumb > .active { + color: #777; +} diff --git a/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.html b/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.html new file mode 100644 index 0000000000..06ef08a953 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.html @@ -0,0 +1,100 @@ +<ol *ngIf="breadcrumb" class="breadcrumb"> + <li *ngFor="#r of route; #last = last" [class.active]="last" [ngSwitch]="last"> + <span *ngSwitchWhen="true">{{r.name}}</span> + <a *ngSwitchDefault href="#" (click)="goToRoute(r, $event)">{{r.name}}</a> + </li> +</ol> +<table *ngIf="folder" class="mdl-data-table mdl-js-data-table mdl-shadow--2dp full-width"> + <thead> + <tr> + <!-- Columns --> + <th class="mdl-data-table__cell--non-numeric {{col.cssClass}}" + *ngFor="#col of columns" + [class.column-header]="col.title" + [class.mdl-data-table__header--sorted-ascending]="sorting.key === col.source && sorting.direction === 'asc'" + [class.mdl-data-table__header--sorted-descending]="sorting.key === col.source && sorting.direction === 'desc'" + (click)="onColumnHeaderClick(col)"> + <span *ngIf="col.srTitle" class="sr-only">{{col.srTitle}}</span> + <span *ngIf="col.title">{{col.title}}</span> + </th> + <!-- Actions --> + <th> + <span class="sr-only">Actions</span> + </th> + </tr> + </thead> + <tbody> + <tr class="parent-folder-link" *ngIf="canNavigateParent()" (click)="onNavigateParentClick($event)"> + <td [attr.colspan]="1 + columns?.length"> + <button class="mdl-button mdl-js-button mdl-button--icon" + (click)="onNavigateParentClick($event)"> + <i class="material-icons">arrow_upward</i> + </button> + </td> + </tr> + + <tr *ngFor="#content of folder.list.entries; #idx = index"> + <!-- Columns --> + <td *ngFor="#col of columns" [ngSwitch]="col.source" + class="mdl-data-table__cell--non-numeric {{content.entry.isFolder ? 'folder-row-cell' : 'document-row-cell'}} {{col.cssClass}}" + (click)="onItemClick(content, $event)"> + <div *ngSwitchWhen="'$thumbnail'"> + <div *ngIf="content.entry.isFolder"> + <i class="material-icons folder-thumbnail">{{folderIcon || 'folder_open'}}</i> + </div> + <div *ngIf="!content.entry.isFolder"> + <img class="document-thumbnail" alt="" src="{{getDocumentThumbnailUrl(content)}}"> + </div> + </div> + <span *ngSwitchDefault> + {{getObjectValue(content.entry, col.source)}} + </span> + </td> + + <!-- Actions: folder --> + <td *ngIf="content.entry.isFolder"> + <!-- action buttons --> + <button class="mdl-button mdl-js-button mdl-button--icon" + *ngFor="#action of getContentActions('folder', 'button')" + (click)="executeContentAction(content, action)"> + <i class="material-icons">{{action.icon}}</i> + </button> + + <!-- action menu --> + <button [id]="'folder_action_menu_' + idx" class="mdl-button mdl-js-button mdl-button--icon"> + <i class="material-icons">more_vert</i> + </button> + <ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect" + [attr.for]="'folder_action_menu_' + idx"> + <li class="mdl-menu__item" + *ngFor="#action of getContentActions('folder', 'menu')" + (click)="executeContentAction(content, action)"> + {{action.title}} + </li> + </ul> + </td> + <!-- Actions: document --> + <td *ngIf="!content.entry.isFolder"> + <!-- action buttons --> + <button class="mdl-button mdl-js-button mdl-button--icon" + *ngFor="#action of getContentActions('document', 'button')" + (click)="executeContentAction(content, action)"> + <i class="material-icons">{{action.icon}}</i> + </button> + + <!-- action menu --> + <button [id]="'document_action_menu_' + idx" class="mdl-button mdl-js-button mdl-button--icon"> + <i class="material-icons">more_vert</i> + </button> + <ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect" + [attr.for]="'document_action_menu_' + idx"> + <li class="mdl-menu__item" + *ngFor="#action of getContentActions('document', 'menu')" + (click)="executeContentAction(content, action)"> + {{action.title}} + </li> + </ul> + </td> + </tr> + </tbody> +</table> diff --git a/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.spec.ts b/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.spec.ts new file mode 100644 index 0000000000..4ce97c17b6 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.spec.ts @@ -0,0 +1,359 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + it, + describe, + expect, + beforeEach +} from 'angular2/testing'; + +import { DataTableComponent } from './datatable.component'; +import { DataColumnModel } from './../models/data-column.model'; +import { AlfrescoServiceMock } from '../assets/alfresco.service.mock'; +import { MinimalNodeEntity, MinimalNodeEntryEntity } from '../models/document-library.model'; +import { DataActionModel } from './../models/data-action.model'; + +describe('DocumentList', () => { + + let alfrescoServiceMock: AlfrescoServiceMock; + let dataTable: DataTableComponent; + let eventMock: any; + + beforeEach(() => { + alfrescoServiceMock = new AlfrescoServiceMock(); + dataTable = new DataTableComponent(alfrescoServiceMock); + + eventMock = { + preventDefault: function () { + console.log('mock preventDefault'); + } + }; + }); + + it('should setup default columns', () => { + spyOn(dataTable, 'setupDefaultColumns').and.callThrough(); + + dataTable.ngAfterContentInit(); + + expect(dataTable.setupDefaultColumns).toHaveBeenCalled(); + expect(dataTable.columns.length).not.toBe(0); + }); + + it('should use custom columns instead of default ones', () => { + let column: DataColumnModel = { + title: 'title', + source: 'source', + cssClass: 'css', + srTitle: '' + }; + dataTable.columns.push(column); + + dataTable.ngAfterContentInit(); + expect(dataTable.columns.length).toBe(1); + expect(dataTable.columns[0]).toBe(column); + }); + + it('should setup default root for breadcrumb', () => { + dataTable.ngOnInit(); + expect(dataTable.route.length).toBe(1); + expect(dataTable.route[0]).toBe(dataTable.rootFolder); + }); + + it('should display custom root path', () => { + spyOn(dataTable, 'displayFolderContent').and.stub(); + + let root = { + name: '<root>', + path: '<path>' + }; + + dataTable.currentFolderPath = root.path; + dataTable.rootFolder = root; + dataTable.ngOnInit(); + expect(dataTable.displayFolderContent).toHaveBeenCalledWith(root.path); + }); + + it('should fetch folder', () => { + let folder = { + 'nodeRef': 'workspace://SpacesStore/8bb36efb-c26d-4d2b-9199-ab6922f53c28' + }; + alfrescoServiceMock._folderToReturn = folder; + dataTable.ngOnInit(); + + expect(dataTable.folder).toBe(folder); + }); + + it('should get content url', () => { + let url = 'URL'; + spyOn(alfrescoServiceMock, 'getContentUrl').and.returnValue(url); + + let result = dataTable.getContentUrl(null); + + expect(result).toBe(url); + expect(alfrescoServiceMock.getContentUrl).toHaveBeenCalled(); + }); + + it('should return no content url without service', () => { + let table = new DataTableComponent(null); + let node = new MinimalNodeEntity(); + expect(table.getContentUrl(node)).toBeNull(); + }); + + it('should get thumbnail url', () => { + let url = 'URL'; + spyOn(alfrescoServiceMock, 'getDocumentThumbnailUrl').and.returnValue(url); + + let result = dataTable.getDocumentThumbnailUrl(null); + + expect(result).toBe(url); + expect(alfrescoServiceMock.getDocumentThumbnailUrl).toHaveBeenCalled(); + }); + + it('should get no thumbnail url without service', () => { + let table = new DataTableComponent(null); + let node = new MinimalNodeEntity(); + expect(table.getDocumentThumbnailUrl(node)).toBeNull(); + }); + + it('should execute action with node', () => { + let node = new MinimalNodeEntity(); + let action = new DataActionModel(); + action.handler = function () { + console.log('mock handler'); + }; + + spyOn(action, 'handler').and.stub(); + + dataTable.executeContentAction(node, action); + expect(action.handler).toHaveBeenCalledWith(node); + + }); + + it('should execute action without node provided', () => { + let action = new DataActionModel(); + action.handler = function () { + console.log('mock handler'); + }; + + spyOn(action, 'handler').and.stub(); + dataTable.executeContentAction(null, action); + expect(action.handler).toHaveBeenCalledWith(null); + }); + + it('should update current folder path', () => { + expect(dataTable.currentFolderPath).toBe(dataTable.rootFolder.path); + + let path = '<path>'; + dataTable.displayFolderContent(path); + + expect(dataTable.currentFolderPath).toBe(path); + }); + + it('should give no content actions for empty target', () => { + let actions = dataTable.getContentActions(null, 'button'); + expect(actions.length).toBe(0); + }); + + it('should give no content actions for empty type', () => { + let actions = dataTable.getContentActions('folder', null); + expect(actions.length).toBe(0); + }); + + it('should filter content actions for various types and targets', () => { + let folderButton = new DataActionModel(); + folderButton.target = 'folder'; + folderButton.type = 'button'; + + let folderMenu = new DataActionModel(); + folderMenu.target = 'folder'; + folderMenu.type = 'menu'; + + let documentButton = new DataActionModel(); + documentButton.target = 'document'; + documentButton.type = 'button'; + + let documentMenu = new DataActionModel(); + documentMenu.target = 'document'; + documentMenu.type = 'menu'; + + dataTable.actions = [ + folderButton, + folderMenu, + documentButton, + documentMenu + ]; + + let actions = dataTable.getContentActions('folder', 'button'); + expect(actions.length).toBe(1); + expect(actions[0]).toBe(folderButton); + + actions = dataTable.getContentActions('folder', 'menu'); + expect(actions.length).toBe(1); + expect(actions[0]).toBe(folderMenu); + + actions = dataTable.getContentActions('document', 'button'); + expect(actions.length).toBe(1); + expect(actions[0]).toBe(documentButton); + + actions = dataTable.getContentActions('document', 'menu'); + expect(actions.length).toBe(1); + expect(actions[0]).toBe(documentMenu); + }); + + it('should be case insensitive when filtering content actions', () => { + let documentButton = new DataActionModel(); + documentButton.target = 'document'; + documentButton.type = 'button'; + + dataTable.actions = [documentButton]; + + let actions = dataTable.getContentActions('DoCuMeNt', 'BUTTON'); + expect(actions.length).toBe(1); + expect(actions[0]).toBe(documentButton); + }); + + it('should find no content actions', () => { + let documentButton = new DataActionModel(); + documentButton.target = 'document'; + documentButton.type = 'button'; + + dataTable.actions = [documentButton]; + + let actions = dataTable.getContentActions('unknown', 'value'); + expect(actions.length).toBe(0); + }); + + it('should emit itemClick event', (done) => { + let node: MinimalNodeEntity = new MinimalNodeEntity(); + dataTable.itemClick.subscribe(e => { + expect(e.value).toBe(node); + done(); + }); + dataTable.onItemClick(node); + }); + + it('should prevent default events for item click', () => { + spyOn(eventMock, 'preventDefault').and.stub(); + + dataTable.onItemClick(null, eventMock); + expect(eventMock.preventDefault).toHaveBeenCalled(); + }); + + it('should display folder content on click', () => { + let path = '/'; + + let node = new MinimalNodeEntity(); + node.entry = new MinimalNodeEntryEntity(); + node.entry.isFolder = true; + node.entry.name = '<display name>'; + + spyOn(dataTable, 'getNodePath').and.returnValue(path); + spyOn(dataTable, 'displayFolderContent').and.stub(); + + dataTable.onItemClick(node); + + expect(dataTable.displayFolderContent).toHaveBeenCalledWith(path); + + let routeEntry = dataTable.route.pop(); + expect(routeEntry.name).toBe(node.entry.name); + expect(routeEntry.path).toBe(path); + }); + + it('should not display folder content when no target node provided', () => { + expect(dataTable.navigate).toBe(true); + spyOn(dataTable, 'displayFolderContent').and.stub(); + + dataTable.onItemClick(null); + expect(dataTable.displayFolderContent).not.toHaveBeenCalled(); + + }); + + it('should display folder content only on folder node click', () => { + expect(dataTable.navigate).toBe(true); + spyOn(dataTable, 'displayFolderContent').and.stub(); + + let node = new MinimalNodeEntity(); + node.entry = new MinimalNodeEntryEntity(); + node.entry.isFolder = false; + + dataTable.onItemClick(node); + + expect(dataTable.displayFolderContent).not.toHaveBeenCalled(); + }); + + it('should not display folder content on click when navigation is off', () => { + spyOn(dataTable, 'displayFolderContent').and.stub(); + + let node = new MinimalNodeEntity(); + node.entry = new MinimalNodeEntryEntity(); + node.entry.isFolder = true; + node.entry.name = '<display name>'; + + dataTable.navigate = false; + dataTable.onItemClick(node); + + expect(dataTable.displayFolderContent).not.toHaveBeenCalled(); + }); + + it('should require node to get path', () => { + expect(dataTable.getNodePath(null)).toBe(null); + }); + + /* + it('should get node path', () => { + let location = new LocationEntity(); + location.site = 'swsdp'; + location.container = 'documentLibrary'; + location.path = '\/'; + + let node = new DocumentEntity(); + node.fileName = 'fileName'; + node.location = location; + + expect(documentList.getNodePath(node)).toBe('swsdp/documentLibrary/fileName'); + }); + */ + + it('should return root object value', () => { + let target = { + key1: 'value1' + }; + + expect(dataTable.getObjectValue(target, 'key1')).toBe('value1'); + }); + + it('should return no object value when key is missing', () => { + let target = { + key1: 'value1' + }; + expect(dataTable.getObjectValue(target, 'missing')).toBeUndefined(); + }); + + it('should return nested object value', () => { + let target = { + key1: { + key2: { + key3: 'value1' + } + } + }; + + expect(dataTable.getObjectValue(target, 'key1.key2.key3')).toBe('value1'); + }); + +}); diff --git a/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.ts b/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.ts new file mode 100644 index 0000000000..9efd23dc18 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/components/datatable.component.ts @@ -0,0 +1,377 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Component, + OnInit, + Input, + Output, + EventEmitter, + AfterContentInit, + AfterViewChecked +} from 'angular2/core'; + +import { AlfrescoService } from './../services/alfresco.service'; +import { MinimalNodeEntity, NodePaging } from './../models/document-library.model'; +import { DataActionModel } from './../models/data-action.model'; +import { DataColumnModel } from './../models/data-column.model'; +import { ColumnSortingModel } from './../models/column-sorting.model'; + +declare var componentHandler; +declare let __moduleName: string; + +@Component({ + moduleId: __moduleName, + selector: 'alfresco-datatable', + styleUrls: ['./datatable.component.css'], + templateUrl: './datatable.component.html', + providers: [AlfrescoService] +}) +export class DataTableComponent implements OnInit, AfterViewChecked, AfterContentInit { + + DEFAULT_ROOT_FOLDER: string = '/Sites/swsdp/documentLibrary'; + + @Input() + navigate: boolean = true; + + @Input() + breadcrumb: boolean = false; + + @Input('folder-icon') + folderIcon: string; + + @Output() + itemClick: EventEmitter<any> = new EventEmitter(); + + @Output() + folderClick: EventEmitter<any> = new EventEmitter(); + + rootFolder = { + name: '', + path: '' + }; + + @Input() + currentFolderPath: string = ''; + + folder: NodePaging; + errorMessage; + + route: any[] = []; + + actions: DataActionModel[] = []; + columns: DataColumnModel[] = []; + + sorting: ColumnSortingModel = { + key: 'name', + direction: 'asc' + }; + + /** + * Determines whether navigation to parent folder is available. + * @returns {boolean} + */ + canNavigateParent(): boolean { + return this.navigate && !this.breadcrumb && + this.currentFolderPath !== this.rootFolder.path; + } + + constructor( + private _alfrescoService: AlfrescoService) { + } + + _createRootFolder(): any { + let folderArray = this.currentFolderPath.split('/'); + let nameFolder = folderArray[folderArray.length - 1]; + return { + name: nameFolder, + path: this.currentFolderPath + }; + } + + ngOnInit() { + this.currentFolderPath = this.currentFolderPath || this.DEFAULT_ROOT_FOLDER; + this.rootFolder = this._createRootFolder(); + this.route.push(this.rootFolder); + this.displayFolderContent(this.rootFolder.path); + } + + ngOnChanges(change) { + this.reload(); + } + + ngAfterContentInit() { + if (!this.columns || this.columns.length === 0) { + this.setupDefaultColumns(); + } + } + + ngAfterViewChecked() { + // workaround for MDL issues with dynamic components + if (componentHandler) { + componentHandler.upgradeAllRegistered(); + } + } + + /** + * Get a list of content actions based on target and type. + * @param target Target to filter actions by. + * @param type Type to filter actions by. + * @returns {ContentActionModel[]} List of actions filtered by target and type. + */ + getContentActions(target: string, type: string): DataActionModel[] { + if (target && type) { + + let ltarget = target.toLowerCase(); + let ltype = type.toLowerCase(); + + return this.actions.filter(entry => { + return entry.target.toLowerCase() === ltarget && + entry.type.toLowerCase() === ltype; + }); + } + return []; + } + + /** + * Invoked when 'parent folder' element is clicked. + * @param e DOM event + */ + onNavigateParentClick(e) { + if (e) { + e.preventDefault(); + } + + if (this.navigate) { + this.route.pop(); + let parent = this.route.length > 0 ? this.route[this.route.length - 1] : this.rootFolder; + if (parent) { + this.folderClick.emit({ + value: parent.path + }); + this.displayFolderContent(parent.path); + } + } + } + + /** + * Invoked when list row is clicked. + * @param item Underlying node item + * @param e DOM event (optional) + */ + onItemClick(item: MinimalNodeEntity, e = null) { + if (e) { + e.preventDefault(); + } + + this.itemClick.emit({ + value: item + }); + + if (this.navigate && item && item.entry) { + if (item.entry.isFolder) { + let path = this.getNodePath(item); + + this.folderClick.emit({ + value: path + }); + + this.route.push({ + name: item.entry.name, + path: path + }); + this.displayFolderContent(path); + } + } + } + + /** + * Invoked when a breadcrumb route is clicked. + * @param r Route to navigate to + * @param e DOM event + */ + goToRoute(r, e) { + if (e) { + e.preventDefault(); + } + + if (this.navigate) { + let idx = this.route.indexOf(r); + if (idx > -1) { + this.route.splice(idx + 1); + this.displayFolderContent(r.path); + } + } + } + + /** + * Gets content URL for the given node. + * @param node Node to get URL for. + * @returns {string} URL address. + */ + getContentUrl(node: MinimalNodeEntity): string { + if (this._alfrescoService) { + return this._alfrescoService.getContentUrl(node); + } + return null; + } + + /** + * Gets thumbnail URL for the given document node. + * @param node Node to get URL for. + * @returns {string} URL address. + */ + getDocumentThumbnailUrl(node: MinimalNodeEntity): string { + if (this._alfrescoService) { + return this._alfrescoService.getDocumentThumbnailUrl(node); + } + return null; + } + + /** + * Invoked when executing content action for a document or folder. + * @param node Node to be the context of the execution. + * @param action Action to be executed against the context. + */ + executeContentAction(node: MinimalNodeEntity, action: DataActionModel) { + if (action) { + action.handler(node); + } + } + + /** + * Loads and displays folder content + * @param path Node path + */ + displayFolderContent(path) { + if (path !== null) { + this.currentFolderPath = path; + this._alfrescoService + .getFolder(path) + .subscribe( + folder => this.folder = this.sort(folder, this.sorting), + error => this.errorMessage = <any>error + ); + } + } + + reload() { + if (this.currentFolderPath) { + this.displayFolderContent(this.currentFolderPath); + } + } + + /** + * Gets a path for a given node. + * @param node + * @returns {string} + */ + getNodePath(node: MinimalNodeEntity): string { + if (node) { + let pathWithCompanyHome = node.entry.path.name; + return pathWithCompanyHome.replace('/Company Home', '') + '/' + node.entry.name; + } + return null; + } + + /** + * Gets a value from an object by composed key + * documentList.getObjectValue({ item: { nodeType: 'cm:folder' }}, 'item.nodeType') ==> 'cm:folder' + * @param target + * @param key + * @returns {string} + */ + getObjectValue(target: any, key: string): any { + let keys = key.split('.'); + key = ''; + + do { + key += keys.shift(); + let value = target[key]; + if (value !== undefined && (typeof value === 'object' || !keys.length)) { + target = value; + key = ''; + } else if (!keys.length) { + target = undefined; + } else { + key += '.'; + } + } while (keys.length); + + return target; + } + + /** + * Creates a set of predefined columns. + */ + setupDefaultColumns(): void { + let thumbnailCol = new DataColumnModel(); + thumbnailCol.source = '$thumbnail'; + + let nameCol = new DataColumnModel(); + nameCol.title = 'Name'; + nameCol.source = 'name'; + nameCol.cssClass = 'full-width name-column'; + + this.columns = [ + thumbnailCol, + nameCol + ]; + } + + onColumnHeaderClick(column: DataColumnModel) { + if (column && this._isSortableColumn(column)) { + if (this.sorting.key === column.source) { + this.sorting.direction = this.sorting.direction === 'asc' ? 'desc' : 'asc'; + } else { + this.sorting = <ColumnSortingModel> { + key: column.source, + direction: 'asc' + }; + } + this.sort(this.folder, this.sorting); + } + } + + sort(node: NodePaging, options: ColumnSortingModel) { + if (this._hasEntries(node)) { + node.list.entries.sort((a: MinimalNodeEntity, b: MinimalNodeEntity) => { + if (a.entry.isFolder !== b.entry.isFolder) { + return options.direction === 'asc' + ? (a.entry.isFolder ? -1 : 1) + : (a.entry.isFolder ? 1 : -1); + } + + let left = this.getObjectValue(a.entry, options.key).toString(); + let right = this.getObjectValue(b.entry, options.key).toString(); + + return options.direction === 'asc' + ? left.localeCompare(right) + : right.localeCompare(left); + }); + } + return node; + } + + private _hasEntries(node: NodePaging): boolean { + return (node && node.list && node.list.entries && node.list.entries.length > 0); + } + + private _isSortableColumn(column: DataColumnModel) { + return column && column.source && !column.source.startsWith('$'); + } +} diff --git a/ng2-components/ng2-alfresco-datatable/src/models/column-sorting.model.ts b/ng2-components/ng2-alfresco-datatable/src/models/column-sorting.model.ts new file mode 100644 index 0000000000..10c0dc9123 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/models/column-sorting.model.ts @@ -0,0 +1,21 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class ColumnSortingModel { + key: string; + direction: string = 'asc'; +} diff --git a/ng2-components/ng2-alfresco-datatable/src/models/data-action.model.ts b/ng2-components/ng2-alfresco-datatable/src/models/data-action.model.ts new file mode 100644 index 0000000000..eada0c502b --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/models/data-action.model.ts @@ -0,0 +1,28 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class DataActionModel { + icon: string; + title: string; + handler: DataActionHandler; + type: string; + target: string; +} + +export interface DataActionHandler { + (obj: any): any; +} diff --git a/ng2-components/ng2-alfresco-datatable/src/models/data-column.model.ts b/ng2-components/ng2-alfresco-datatable/src/models/data-column.model.ts new file mode 100644 index 0000000000..5d3c6772a2 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/models/data-column.model.ts @@ -0,0 +1,23 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class DataColumnModel { + title: string; + srTitle: string; + source: string; + cssClass: string; +} diff --git a/ng2-components/ng2-alfresco-datatable/src/models/document-library.model.ts b/ng2-components/ng2-alfresco-datatable/src/models/document-library.model.ts new file mode 100644 index 0000000000..8859ae1c3b --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/models/document-library.model.ts @@ -0,0 +1,117 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// note: contains only limited subset of available fields + +export class FolderEntity { + items: DocumentEntity[]; +} + +export class DocumentEntity { + nodeRef: string; + nodeType: string; + type: string; + mimetype: string; + isFolder: boolean; + isLink: boolean; + fileName: string; + displayName: string; + status: string; + title: string; + description: string; + author: string; + createdOn: string; + createdBy: string; + createdByUser: string; + modifiedOn: string; + modifiedBy: string; + modifiedByUser: string; + lockedBy: string; + lockedByUser: string; + size: number; + version: string; + contentUrl: string; + webdavUrl: string; + actionSet: string; + tags: string[]; + activeWorkflows: string; + location: LocationEntity; +} + +export class LocationEntity { + repositoryId: string; + site: string; + siteTitle: string; + container: string; + path: string; + file: string; + parent: LocationParentEntity; +} + +export class LocationParentEntity { + nodeRef: string; +} + +export class NodePaging { + list: NodePagingList; +} + +export class NodePagingList { + entries: MinimalNodeEntity[]; +} + +export class MinimalNodeEntity { + entry: MinimalNodeEntryEntity; +} + +export class MinimalNodeEntryEntity { + id: string; + parentId: string; + name: string; + nodeType: string; + isFolder: boolean; + isFile: boolean; + modifiedAt: string; + modifiedByUser: UserInfo; + createdAt: string; + createdByUser: UserInfo; + content: ContentInfo; + path: PathInfoEntity; +} + +export class UserInfo { + displayName: string; + id: string; +} + +export class ContentInfo { + mimeType: string; + mimeTypeName: string; + sizeInBytes: number; + encoding: string; +} + +export class PathInfoEntity { + elements: PathElementEntity; + isComplete: boolean; + name: string; +} + +export class PathElementEntity { + id: string; + name: string; +} diff --git a/ng2-components/ng2-alfresco-datatable/src/services/alfresco.service.ts b/ng2-components/ng2-alfresco-datatable/src/services/alfresco.service.ts new file mode 100644 index 0000000000..743e73281f --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/services/alfresco.service.ts @@ -0,0 +1,120 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from 'angular2/core'; +import { Http, Response } from 'angular2/http'; +import { Observable } from 'rxjs/Observable'; + +import { AlfrescoSettingsService } from 'ng2-alfresco-core/services'; +import { NodePaging, MinimalNodeEntity } from './../models/document-library.model'; + +declare let AlfrescoApi: any; + +/** + * Internal service used by Document List component. + */ +@Injectable() +export class AlfrescoService { + + private _host: string = 'http://127.0.0.1:8080'; + private _baseUrlPath: string = '/alfresco/api/-default-/public/alfresco/versions/1'; + + constructor(private http: Http, + private settings: AlfrescoSettingsService) { + if (settings) { + this._host = settings.host; + } + } + + public get host(): string { + return this._host; + } + + public set host(value: string) { + this._host = value; + } + + private getBaseUrl(): string { + return this.host + this._baseUrlPath; + } + + private getAlfrescoTicket() { + return localStorage.getItem('token'); + } + + private getAlfrescoClient() { + let defaultClient = new AlfrescoApi.ApiClient(); + defaultClient.basePath = this.getBaseUrl(); + + // Configure HTTP basic authorization: basicAuth + let basicAuth = defaultClient.authentications['basicAuth']; + basicAuth.username = 'ROLE_TICKET'; + basicAuth.password = this.getAlfrescoTicket(); + + return defaultClient; + } + + private getNodesPromise(folder: string) { + let alfrescoClient = this.getAlfrescoClient(); + let apiInstance = new AlfrescoApi.NodesApi(alfrescoClient); + let nodeId = '-root-'; + let opts = { + relativePath: folder, + include: ['path'] + }; + return apiInstance.getNodeChildren(nodeId, opts); + } + + /** + * Gets the folder node with the content. + * @param folder Path to folder. + * @returns {Observable<NodePaging>} Folder entity. + */ + getFolder(folder: string) { + return Observable.fromPromise(this.getNodesPromise(folder)) + .map(res => <NodePaging> res) + .do(data => console.log('Node data', data)) // eyeball results in the console + .catch(this.handleError); + } + + /** + * Get thumbnail URL for the given document node. + * @param document Node to get URL for. + * @returns {string} URL address. + */ + getDocumentThumbnailUrl(document: MinimalNodeEntity) { + return this.getContentUrl(document) + '/thumbnails/doclib?c=queue&ph=true&lastModified=1&alf_ticket=' + this.getAlfrescoTicket(); + } + + /** + * Get content URL for the given node. + * @param document Node to get URL for. + * @returns {string} URL address. + */ + getContentUrl(document: MinimalNodeEntity) { + return this._host + + '/alfresco/service/api/node/workspace/SpacesStore/' + + document.entry.id + '/content'; + } + + private handleError(error: Response) { + // in a real world app, we may send the error to some remote logging infrastructure + // instead of just logging it to the console + console.error(error); + return Observable.throw(error || 'Server error'); + } +} diff --git a/ng2-components/ng2-alfresco-datatable/src/services/document-actions.service.spec.ts b/ng2-components/ng2-alfresco-datatable/src/services/document-actions.service.spec.ts new file mode 100644 index 0000000000..98e5f96733 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/services/document-actions.service.spec.ts @@ -0,0 +1,58 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + it, + describe, + expect, + beforeEach +} from 'angular2/testing'; + +import { DataActionHandler } from './../models/data-action.model'; +import { DocumentActionsService } from './document-actions.service'; +import { AlfrescoServiceMock } from '../assets/alfresco.service.mock'; + +describe('DocumentActionsService', () => { + + let service: DocumentActionsService; + + beforeEach(() => { + let alfrescoServiceMock = new AlfrescoServiceMock(); + service = new DocumentActionsService(alfrescoServiceMock); + }); + + it('should register default download action', () => { + expect(service.getHandler('download')).not.toBeNull(); + }); + + it('should register custom action handler', () => { + let handler: DataActionHandler = function (obj: any) {}; + service.setHandler('<key>', handler); + expect(service.getHandler('<key>')).toBe(handler); + }); + + it('should not find handler that is not registered', () => { + expect(service.getHandler('<missing>')).toBeNull(); + }); + + it('should be case insensitive for keys', () => { + let handler: DataActionHandler = function (obj: any) {}; + service.setHandler('<key>', handler); + expect(service.getHandler('<KEY>')).toBe(handler); + + }); +}); diff --git a/ng2-components/ng2-alfresco-datatable/src/services/document-actions.service.ts b/ng2-components/ng2-alfresco-datatable/src/services/document-actions.service.ts new file mode 100644 index 0000000000..e39e6af347 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/services/document-actions.service.ts @@ -0,0 +1,72 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Injectable} from 'angular2/core'; + +import { DataActionHandler } from './../models/data-action.model'; +import { AlfrescoService } from './alfresco.service'; + +@Injectable() +export class DocumentActionsService { + private handlers: { [id: string]: DataActionHandler; } = {}; + + constructor(private _alfrescoService: AlfrescoService) { + this.setupActionHandlers(); + } + + getHandler(key: string): DataActionHandler { + if (key) { + let lkey = key.toLowerCase(); + return this.handlers[lkey] || null; + } + return null; + } + + setHandler(key: string, handler: DataActionHandler): void { + if (key) { + let lkey = key.toLowerCase(); + this.handlers[lkey] = handler; + } + } + + private setupActionHandlers() { + this.handlers['download'] = this.download.bind(this); + + // todo: just for dev/demo purposes, to be replaced with real actions + this.handlers['system1'] = this.handleStandardAction1.bind(this); + this.handlers['system2'] = this.handleStandardAction2.bind(this); + } + + private handleStandardAction1(obj: any) { + window.alert('standard document action 1'); + } + + private handleStandardAction2(obj: any) { + window.alert('standard document action 2'); + } + + private download(obj: any) { + if (this._alfrescoService && obj && !obj.isFolder) { + let link = document.createElement('a'); + document.body.appendChild(link); + link.setAttribute('download', 'download'); + link.href = this._alfrescoService.getContentUrl(obj); + link.click(); + document.body.removeChild(link); + } + } +} diff --git a/ng2-components/ng2-alfresco-datatable/src/services/folder-actions.service.spec.ts b/ng2-components/ng2-alfresco-datatable/src/services/folder-actions.service.spec.ts new file mode 100644 index 0000000000..ed9e1edd90 --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/services/folder-actions.service.spec.ts @@ -0,0 +1,53 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + it, + describe, + expect, + beforeEach +} from 'angular2/testing'; + +import { FolderActionsService } from './folder-actions.service'; +import { DataActionHandler } from './../models/data-action.model'; + +describe('FolderActionsService', () => { + + let service: FolderActionsService; + + beforeEach(() => { + service = new FolderActionsService(); + }); + + it('should register custom action handler', () => { + let handler: DataActionHandler = function (obj: any) {}; + service.setHandler('<key>', handler); + expect(service.getHandler('<key>')).toBe(handler); + }); + + it('should not find handler that is not registered', () => { + expect(service.getHandler('<missing>')).toBeNull(); + }); + + it('should be case insensitive for keys', () => { + let handler: DataActionHandler = function (obj: any) {}; + service.setHandler('<key>', handler); + expect(service.getHandler('<KEY>')).toBe(handler); + + }); + +}); diff --git a/ng2-components/ng2-alfresco-datatable/src/services/folder-actions.service.ts b/ng2-components/ng2-alfresco-datatable/src/services/folder-actions.service.ts new file mode 100644 index 0000000000..6270fc1fcc --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/src/services/folder-actions.service.ts @@ -0,0 +1,53 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Injectable} from 'angular2/core'; +import { DataActionHandler } from './../models/data-action.model'; + +@Injectable() +export class FolderActionsService { + private handlers: { [id: string]: DataActionHandler; } = {}; + + constructor() { + // todo: just for dev/demo purposes, to be replaced with real actions + this.handlers['system1'] = this.handleStandardAction1.bind(this); + this.handlers['system2'] = this.handleStandardAction2.bind(this); + } + + getHandler(key: string): DataActionHandler { + if (key) { + let lkey = key.toLowerCase(); + return this.handlers[lkey] || null; + } + return null; + } + + setHandler(key: string, handler: DataActionHandler): void { + if (key) { + let lkey = key.toLowerCase(); + this.handlers[lkey] = handler; + } + } + + private handleStandardAction1(document: any) { + window.alert('standard folder action 1'); + } + + private handleStandardAction2(document: any) { + window.alert('standard folder action 2'); + } +} diff --git a/ng2-components/ng2-alfresco-datatable/tsconfig.json b/ng2-components/ng2-alfresco-datatable/tsconfig.json new file mode 100644 index 0000000000..1a1d552c0a --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "system", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true, + "removeComments": true, + "declaration": true, + "outDir": "dist" + }, + "exclude": [ + "node_modules", + "typings/main", + "typings/main.d.ts" + ] +} diff --git a/ng2-components/ng2-alfresco-datatable/tslint.json b/ng2-components/ng2-alfresco-datatable/tslint.json new file mode 100644 index 0000000000..e0afb6fc9c --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/tslint.json @@ -0,0 +1,119 @@ +{ + "rules": { + "align": [ + true, + "parameters", + "statements" + ], + "ban": false, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "indent": [ + true, + "spaces" + ], + "interface-name": false, + "jsdoc-format": true, + "label-position": true, + "label-undefined": true, + "max-line-length": [ + true, + 140 + ], + "member-ordering": [ + true, + "static-before-instance", + "variables-before-functions" + ], + "no-any": false, + "no-arg": true, + "no-bitwise": false, + "no-conditional-assignment": true, + "no-consecutive-blank-lines": false, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-constructor-vars": false, + "no-debugger": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-eval": true, + "no-inferrable-types": false, + "no-internal-module": true, + "no-require-imports": true, + "no-shadowed-variable": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unreachable": true, + "no-unused-expression": true, + "no-unused-variable": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "no-var-requires": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [ + true, + "single", + "avoid-escape" + ], + "radix": true, + "semicolon": true, + "switch-default": true, + "trailing-comma": [ + true, + { + "multiline": "never", + "singleline": "never" + } + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef": false, + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "use-strict": false, + "variable-name": [ + true, + "check-format", + "allow-leading-underscore", + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator" + ] + } +} diff --git a/ng2-components/ng2-alfresco-datatable/typings.json b/ng2-components/ng2-alfresco-datatable/typings.json new file mode 100644 index 0000000000..8d5672dbcf --- /dev/null +++ b/ng2-components/ng2-alfresco-datatable/typings.json @@ -0,0 +1,6 @@ +{ + "ambientDependencies": { + "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#7de6c3dd94feaeb21f20054b9f30d5dabc5efabd", + "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#5c182b9af717f73146399c2485f70f1e2ac0ff2b" + } +}