diff --git a/modules/site/public/meta.json b/modules/site/public/meta.json index 24f49ad4..dd7527c0 100644 --- a/modules/site/public/meta.json +++ b/modules/site/public/meta.json @@ -48,6 +48,32 @@ "readme": "
This component is experimental.\n
", "documentation": "

MdDialog

\n

Dialogs allow prompting the user to make a decision or take action that must be completed before normal operation may continue.

\n

<md-dialog>

\n

Properties

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NameTypeDescription
configOverlayConfigUsed to override dialog positioning
\n

Events

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NameValue TypeDescription
onCloseanyEmitted when the dialog closes with a user specified value
onCancelanyEmitted when the dialog closes because of an escape action
onShowMdDialogEmitted when the dialog has been presented to the user
\n

Examples

\n

Basic dialog with one action

\n
<md-dialog #alert>\n  Clicking the button will close this dialog\n  <md-dialog-actions ok="Got It"></md-dialog-actions>\n</md-dialog>\n\n<button md-button (click)="alert.show()">Open Dialog</button>\n
\n

<md-dialog-title>

\n

Properties

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NameTypeDescription
titlestringSpecify dialog title with a binding
\n

Examples

\n

Dialog with title and body

\n
<md-dialog #rentMovie>\n  <md-dialog-title text="Confirm Rental"></md-dialog-title>\n  Your account will be charged $4.99.\n  <md-dialog-actions ok="Purchase" cancel="Cancel"></md-dialog-actions>\n</md-dialog>\n\n<button md-button (click)="rentMovie.show()">Rent Movie</button>\n
\n

<md-dialog-actions>

\n

Properties

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NameTypeDescription
okstringThe label to use for an acceptance action button
cancelstringThe label to use for a cancel action button
dialogMdDialogThe dialog to take action on. Set by the owning dialog, but could be overridden for custom behavior.
\n

Examples

\n

Confirmation dialog with a yes/no decision

\n
<md-dialog #confirm (onClose)="confirmClose($event)">\n  <md-dialog-title>Are you sure?</md-dialog-title>\n  This decision will change your life.\n  <md-dialog-actions ok="Yep" cancel="Nope"></md-dialog-actions>\n</md-dialog>\n\n<button md-button (click)="confirm.show()">Confirm</button>\n
\n

Dialog with custom action buttons

\n
<md-dialog #custom>\n  <md-dialog-title>Did you like it?</md-dialog-title>\n  Scott Pilgrim vs. the World\n  <md-dialog-actions>\n    <a md-button href="https://en.wikipedia.org/wiki/Scott_Pilgrim_vs._the_World">\n      <span>What's that?</span>\n    </a>\n    <span flex></span>\n    <button md-button (click)="custom.close(false)">\n      <span>It was awesome!</span>\n    </button>\n    <button md-button class="md-primary" (click)="custom.close(true)">\n      <span>It was trying too hard...</span>\n    </button>\n  </md-dialog-actions>\n</md-dialog>\n\n<button md-button (click)="custom.show()">Feedback</button>\n
\n" }, + { + "name": "Pagination", + "sources": [ + "src/components/pagination/index.ts", + "src/components/pagination/pagination_service.ts", + "src/components/pagination/pagination_spec.ts", + "src/components/pagination/pagination.ts" + ], + "id": "pagination", + "examples": [ + { + "template": "\n\n\n \n \n Material\n Quantity\n \n \n \n \n {{ material.name }}\n {{ material.quantity }}\n \n \n\n\n\n", + "source": "import { Component } from '@angular/core';\nimport {MATERIAL_DIRECTIVES} from \"ng2-material\";\n\nimport {tableDatas} from './pagination-datas';\n\n@Component({\n moduleId: module.id,\n selector: 'pagination-basic-usage',\n templateUrl: 'pagination-basic-usage.component.html',\n directives: [MATERIAL_DIRECTIVES]\n})\nexport class PaginationBasicUsageComponent {\n materials: Array = tableDatas;\n\n pagination = {\n currentPage: 1,\n itemsPerPage: 5,\n totalItems: 24\n };\n\n availableLength: Array = [5, 10, 20];\n\n pagedMaterials: Array = [];\n\n constructor() {\n this.refreshMaterials();\n }\n\n refreshMaterials() {\n let start = (this.pagination.currentPage - 1) * this.pagination.itemsPerPage,\n end = start + this.pagination.itemsPerPage;\n this.pagedMaterials = this.materials.slice(start, end);\n }\n\n detectChange(event) {\n if (event !== undefined && event.name === 'pagination_changed' && event.pagination !== undefined) {\n this.pagination = event.pagination;\n this.refreshMaterials();\n }\n }\n}\n", + "component": "pagination-basic-usage", + "name": "Pagination Basic Usage" + }, + { + "template": "\n \n {{ page }}\n \n\n \n \n \n \n\n\n", + "styles": "@import \"../../../ng2-material/core/style/variables\";\n@import \"../../../ng2-material/core/style/default-theme\";\n@import \"../../../ng2-material/components/whiteframe/whiteframe\";\n\npagination-split-usage {\n .page-container {\n position: relative;\n align-items: stretch;\n }\n\n md-card {\n margin: 0;\n\n md-card-content {\n padding: 30px 30px 40px;\n }\n }\n\n .controls {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n\n .md-pagination-control {\n position: absolute;\n top: 0;\n bottom: 0;\n margin: auto;\n width: 35px;\n height: 35px;\n vertical-align: middle;\n }\n\n .md-pagination-control-previous {\n left: 5px;\n }\n\n .md-pagination-control-next {\n right: 5px;\n }\n\n .material-icons {\n @extend .md-whiteframe-1dp;\n width: 35px;\n height: 35px;\n background: #FFF;\n }\n }\n\n .md-pagination-range {\n position: absolute;\n right: 0;\n bottom: 20px;\n left: 0;\n }\n\n .page-number {\n padding: 0 10px;\n font-weight: bold;\n }\n}\n", + "source": "import {Component, ViewEncapsulation, OnInit, OnDestroy} from '@angular/core';\nimport {MATERIAL_DIRECTIVES, Media, MediaListener} from \"ng2-material\";\n\nimport {bookDatas} from './pagination-datas';\n\n@Component({\n moduleId: module.id,\n selector: 'pagination-split-usage',\n templateUrl: 'pagination-split-usage.component.html',\n styleUrls: ['pagination-split-usage.component.css'],\n directives: [MATERIAL_DIRECTIVES],\n encapsulation: ViewEncapsulation.None\n})\nexport class PaginationSplitUsageComponent implements OnInit, OnDestroy {\n pages: Array = bookDatas;\n\n pagination: any = {\n currentPage: 1,\n itemsPerPage: 2,\n totalItems: 6\n };\n\n rangeFormat: string;\n\n displayedPages: Array = [];\n\n mediaListener: MediaListener;\n\n constructor(private media: Media) {}\n\n refreshPageBySize() {\n if (this.media.hasMedia('xs')) {\n this.pagination.itemsPerPage = 1;\n this.rangeFormat = `{start}`;\n } else {\n this.pagination.itemsPerPage = 2;\n this.rangeFormat = ` \n {start}\n {end}\n `;\n }\n\n this.pagination.currentPage = 1;\n\n this.refreshPages();\n }\n\n getFlexSize() {\n return Math.round(100 / this.pagination.itemsPerPage);\n }\n\n refreshPages() {\n let start = (this.pagination.currentPage - 1) * this.pagination.itemsPerPage,\n end = start + this.pagination.itemsPerPage;\n this.displayedPages = this.pages.slice(start, end);\n }\n\n detectChange(event) {\n if (event !== undefined && event.name === 'pagination_changed' && event.pagination !== undefined) {\n this.pagination = event.pagination;\n this.refreshPages();\n }\n }\n\n ngOnInit() {\n this.mediaListener = this.media.listen(Media.getQuery('xs'));\n this.mediaListener.onMatched.subscribe(this.refreshPageBySize.bind(this));\n }\n\n ngOnDestroy() {\n this.mediaListener.destroy();\n }\n}\n", + "component": "pagination-split-usage", + "name": "Pagination Split Usage" + } + ], + "documentation": "

MdPagination

\n

MdPagination allow you to use pagination related to your datas. There's any real recommandations about pagination \nbut this component follow this example

\n

<md-pagination>

\n

Main component of the pagination

\n

Properties

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NameTypeDescription
namestringGroup pagination by name to enable multiple instance of MdPagination on the same page
modelIPaginationModelModel that include pagination informations: currentPage, itemsPerPage, totalItems
controlsbooleanToggle the display of controls
rangebooleanToggle the display of range
rangeFormatstringOverride the default format of the range by using {start}, {end}, {total} keys
itemsPerPagebooleanToggle the display of items per page selector
itemsPerPageBeforestringPrepend items per page selector with a string
itemsPerPageAfterstringAppend items per page selector with a string
itemsPerPageOptionsArray<number>available lengths to display in the combobox. If you don't provide choices, the selector won't be displayed
\n

Events

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NameTypeDescription
onPaginationChangeEventEmitter<IPaginationChange>Emitted when something change on the model: currentPage, itemsPerPage, totalItems
\n

<md-pagination-items-per-page>

\n

Length selector component

\n

Properties

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NameTypeDescription
namestringGroup pagination by name to enable multiple instance of MdPagination on the same page
modelIPaginationModelModel that include pagination informations: currentPage, itemsPerPage, totalItems
itemsPerPageBeforestringPrepend items per page selector with a string
itemsPerPageAfterstringAppend items per page selector with a string
itemsPerPageOptionsArray<number>available lengths to display in the combobox. If you don't provide choices, the selector won't be displayed
\n

<md-pagination-controls>

\n

Pagination controls component

\n

Properties

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NameTypeDescription
namestringGroup pagination by name to enable multiple instance of MdPagination on the same page
modelIPaginationModelModel that include pagination informations: currentPage, itemsPerPage, totalItems
\n

<md-pagination-range>

\n

Range display component

\n

Properties

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NameTypeDescription
namestringGroup pagination by name to enable multiple instance of MdPagination on the same page
modelIPaginationModelModel that include pagination informations: currentPage, itemsPerPage, totalItems
rangeFormatstringOverride the default format of the range by using {start}, {end}, {total} keys
\n

PaginationService

\n

It's the service that help pagination components to communicate between them. You can subscribe to the onChange property or push changes via change function of this service \nbut you should avoid this and prefer subscribing to onPaginationChange output of MdPagination.

\n" + }, { "name": "Checkbox", "sources": [], diff --git a/modules/site/src/app/examples/pagination/pagination-basic-usage.component.html b/modules/site/src/app/examples/pagination/pagination-basic-usage.component.html new file mode 100644 index 00000000..df21e532 --- /dev/null +++ b/modules/site/src/app/examples/pagination/pagination-basic-usage.component.html @@ -0,0 +1,18 @@ + + + + + + Material + Quantity + + + + + {{ material.name }} + {{ material.quantity }} + + + + + diff --git a/modules/site/src/app/examples/pagination/pagination-basic-usage.component.spec.ts b/modules/site/src/app/examples/pagination/pagination-basic-usage.component.spec.ts new file mode 100644 index 00000000..96df5dc9 --- /dev/null +++ b/modules/site/src/app/examples/pagination/pagination-basic-usage.component.spec.ts @@ -0,0 +1,46 @@ +import { + beforeEach, + beforeEachProviders, + describe, + expect, + it, + inject, +} from '@angular/core/testing'; +import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing'; +import { Component } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { PaginationBasicUsageComponent } from './pagination-basic-usage.component'; + +describe('Component: PaginationBasicUsage', () => { + let builder: TestComponentBuilder; + + beforeEachProviders(() => [PaginationBasicUsageComponent]); + beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) { + builder = tcb; + })); + + it('should inject the component', inject([PaginationBasicUsageComponent], + (component: PaginationBasicUsageComponent) => { + expect(component).toBeTruthy(); + })); + + it('should create the component', inject([], () => { + return builder.createAsync(PaginationBasicUsageComponentTestController) + .then((fixture: ComponentFixture) => { + let query = fixture.debugElement.query(By.directive(PaginationBasicUsageComponent)); + expect(query).toBeTruthy(); + expect(query.componentInstance).toBeTruthy(); + }); + })); +}); + +@Component({ + selector: 'test', + template: ` + + `, + directives: [PaginationBasicUsageComponent] +}) +class PaginationBasicUsageComponentTestController { +} + diff --git a/modules/site/src/app/examples/pagination/pagination-basic-usage.component.ts b/modules/site/src/app/examples/pagination/pagination-basic-usage.component.ts new file mode 100644 index 00000000..14c3f50a --- /dev/null +++ b/modules/site/src/app/examples/pagination/pagination-basic-usage.component.ts @@ -0,0 +1,41 @@ +import { Component } from '@angular/core'; +import {MATERIAL_DIRECTIVES} from "ng2-material"; + +import {tableDatas} from './pagination-datas'; + +@Component({ + moduleId: module.id, + selector: 'pagination-basic-usage', + templateUrl: 'pagination-basic-usage.component.html', + directives: [MATERIAL_DIRECTIVES] +}) +export class PaginationBasicUsageComponent { + materials: Array = tableDatas; + + pagination = { + currentPage: 1, + itemsPerPage: 5, + totalItems: 24 + }; + + availableLength: Array = [5, 10, 20]; + + pagedMaterials: Array = []; + + constructor() { + this.refreshMaterials(); + } + + refreshMaterials() { + let start = (this.pagination.currentPage - 1) * this.pagination.itemsPerPage, + end = start + this.pagination.itemsPerPage; + this.pagedMaterials = this.materials.slice(start, end); + } + + detectChange(event) { + if (event !== undefined && event.name === 'pagination_changed' && event.pagination !== undefined) { + this.pagination = event.pagination; + this.refreshMaterials(); + } + } +} diff --git a/modules/site/src/app/examples/pagination/pagination-datas.ts b/modules/site/src/app/examples/pagination/pagination-datas.ts new file mode 100644 index 00000000..d31719c7 --- /dev/null +++ b/modules/site/src/app/examples/pagination/pagination-datas.ts @@ -0,0 +1,35 @@ +export let tableDatas = [ + {'name': 'Hydrogen', 'quantity': '25', 'price': '$2.90'}, + {'name': 'Helium', 'quantity': '50', 'price': '$3.10'}, + {'name': 'Lithium', 'quantity': '10', 'price': '$1.99'}, + {'name': 'Beryllium', 'quantity': '42', 'price': '$2.60'}, + {'name': 'Boron', 'quantity': '11', 'price': '$2.00'}, + {'name': 'Carbon', 'quantity': '5', 'price': '$3.65'}, + {'name': 'Nitrogen', 'quantity': '3', 'price': '$2.20'}, + {'name': 'Oxygen', 'quantity': '0', 'price': '$2.25'}, + {'name': 'Fluorine', 'quantity': '12', 'price': '$2.90'}, + {'name': 'Neon', 'quantity': '22', 'price': '$2.80'}, + {'name': 'Sodium', 'quantity': '15', 'price': '$2.85'}, + {'name': 'Magnesium', 'quantity': '41', 'price': '$2.05'}, + {'name': 'Aluminum', 'quantity': '33', 'price': '$1.75'}, + {'name': 'Silicon', 'quantity': '8', 'price': '$2.12'}, + {'name': 'Phosphorus', 'quantity': '2', 'price': '$2.15'}, + {'name': 'Sulfur', 'quantity': '9', 'price': '$2.00'}, + {'name': 'Chlorine', 'quantity': '3', 'price': '$2.60'}, + {'name': 'Argon', 'quantity': '13', 'price': '$2.49'}, + {'name': 'Potassium', 'quantity': '11', 'price': '$1.99'}, + {'name': 'Calcium', 'quantity': '1', 'price': '$2.08'}, + {'name': 'Scandium', 'quantity': '4', 'price': '$2.80'}, + {'name': 'Titanium', 'quantity': '6', 'price': '$2.40'}, + {'name': 'Vanadium', 'quantity': '5', 'price': '$2.50'}, + {'name': 'Chromium', 'quantity': '12', 'price': '$2.00'} +]; + +export let bookDatas = [ + `Vivamus laoreet tincidunt sapien, ut efficitur magna rutrum eget. Aliquam lacinia iaculis ligula, vel volutpat tellus malesuada a. Mauris consequat, orci ut laoreet aliquam, sapien ex lacinia nisi, congue euismod massa erat nec orci. Nulla scelerisque lobortis orci, eu egestas libero scelerisque sed. Cras eleifend urna in felis vulputate vestibulum. Integer pellentesque, quam quis lacinia ultrices, odio massa pharetra quam, eget vehicula turpis odio eget magna. Sed viverra venenatis risus. Maecenas iaculis ultricies nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque iaculis malesuada arcu ut vestibulum. Mauris iaculis nisl id odio vehicula, ac venenatis arcu elementum. Donec faucibus pretium est et dictum. Mauris sed erat vitae lectus facilisis molestie a laoreet risus. Nam pulvinar commodo facilisis. Aenean laoreet nec arcu sed ultricies. Etiam ut ultrices urna, sed convallis est. In sem quam, rutrum molestie risus non, fermentum feugiat velit. Morbi ut tincidunt felis. Vivamus aliquam nec enim eget consectetur. Suspendisse odio neque, vulputate quis viverra eu, sagittis a tellus. Donec pulvinar eleifend pulvinar.`, + `Mauris gravida commodo ex nec sagittis. Proin feugiat dui odio, et efficitur lacus pharetra a. In non ligula sed turpis malesuada placerat et et massa. Curabitur sit amet sagittis ipsum, vitae aliquet magna. Praesent venenatis lorem vel diam finibus, vel sollicitudin eros interdum. In lacinia nunc nulla, et congue turpis convallis et. Nam vestibulum magna id ante consectetur aliquet. Donec bibendum, neque nec lobortis gravida, sem odio viverra urna, vel vestibulum dolor nisi a elit. Proin arcu justo, pharetra in pharetra vitae, semper vitae dui. Pellentesque rutrum nec odio at luctus. Proin eleifend posuere elit id condimentum. Etiam eleifend augue ultrices enim ornare, et pretium quam sollicitudin. Morbi ante nunc, malesuada nec risus non, sagittis iaculis velit. Donec eget dui non dolor accumsan porttitor. Aliquam vel eleifend dui. Vestibulum dignissim consectetur neque, vel ornare nulla eleifend ac. Morbi a fermentum justo. Vivamus vitae quam molestie, hendrerit purus non, luctus nisi. Morbi posuere, justo non sodales semper, mauris orci bibendum magna, quis suscipit magna velit id dolor. Quisque consequat eros eu malesuada iaculis. Donec vel vehicula felis, sit amet.`, + `Etiam ut ultrices urna, sed convallis est. In sem quam, rutrum molestie risus non, fermentum feugiat velit. Morbi ut tincidunt felis. Vivamus aliquam nec enim eget consectetur. Suspendisse odio neque, vulputate quis viverra eu, sagittis a tellus. Donec pulvinar eleifend pulvinar. Donec in felis commodo, vehicula est sed, viverra velit. Nullam aliquam urna sed massa convallis maximus. Quisque pharetra ipsum nec congue sollicitudin. Quisque posuere nec massa in iaculis. Etiam rhoncus accumsan nulla vitae fermentum. Pellentesque faucibus, purus iaculis fermentum placerat, quam massa eleifend felis, ut lobortis eros orci at nulla. Nam consequat metus nec orci lobortis, sit amet lacinia lectus euismod. Integer vel condimentum eros. Nunc sed feugiat libero. Ut ultricies erat in metus hendrerit viverra. Maecenas tincidunt a justo in pharetra. Phasellus rutrum turpis ac odio convallis posuere eget sed sapien. Nulla placerat laoreet risus, non iaculis nibh viverra sed. Pellentesque hendrerit eros at turpis posuere consequat. Proin pellentesque tincidunt magna a eleifend. Aliquam eleifend at tortor sit amet hendrerit. Vivamus egestas, turpis id dignissim interdum, est odio mattis.`, + `Nunc sed feugiat libero. Ut ultricies erat in metus hendrerit viverra. Maecenas tincidunt a justo in pharetra. Phasellus rutrum turpis ac odio convallis posuere eget sed sapien. Nulla placerat laoreet risus, non iaculis nibh viverra sed. Pellentesque hendrerit eros at turpis posuere consequat. Proin pellentesque tincidunt magna a eleifend. Aliquam eleifend at tortor sit amet hendrerit. Vivamus egestas, turpis id dignissim interdum, est odio mattis odio, eu volutpat libero mi vel elit. Ut condimentum aliquam magna eget maximus. Nullam eleifend risus non posuere dapibus. Pellentesque rhoncus orci eu cursus auctor. In dui tortor, dignissim porta pulvinar ut, posuere id odio. Aliquam facilisis nisi a elit interdum iaculis. Fusce et nibh posuere, blandit turpis a, accumsan tortor. Proin posuere quam vel laoreet dictum. Nulla fringilla orci quis turpis laoreet, in rhoncus lectus malesuada. Sed nec fermentum turpis. Donec in purus at dolor gravida gravida sit amet vitae lacus. Phasellus aliquam libero nisi, eu consequat nulla molestie nec. `, + `Aliquam erat volutpat. Nam volutpat fermentum porttitor. Nulla facilisi. Nullam feugiat enim in urna aliquam porttitor. Maecenas non lectus elit. Duis vitae dolor in nisi egestas posuere. Cras placerat laoreet dolor, nec dapibus ex pulvinar in. Nam volutpat dignissim maximus. Etiam sit amet lacus quis lectus hendrerit placerat. Quisque scelerisque vitae risus vitae pretium. Vivamus malesuada dui ac interdum luctus. Duis vitae justo tincidunt, aliquet tortor a, rutrum libero. Suspendisse ultrices condimentum tempor. In sit amet convallis odio. Curabitur lectus nisi, porttitor a auctor eu, maximus sed ligula. Maecenas aliquam eros ut nibh aliquam, eget porttitor odio gravida. Proin posuere quam vel laoreet dictum. Nulla fringilla orci quis turpis laoreet, in rhoncus lectus malesuada. Sed nec fermentum turpis. Donec in purus at dolor gravida gravida sit amet vitae lacus. Phasellus aliquam libero nisi, eu consequat nulla molestie nec. `, + `Aliquam vel eleifend dui. Vestibulum dignissim consectetur neque, vel ornare nulla eleifend ac. Morbi a fermentum justo. Vivamus vitae quam molestie, hendrerit purus non, luctus nisi. Morbi posuere, justo non sodales semper, mauris orci bibendum magna, quis suscipit magna velit id dolor. Quisque consequat eros eu malesuada iaculis. Donec vel vehicula felis, sit amet viverra quam. Proin posuere quam vel laoreet dictum. Nulla fringilla orci quis turpis laoreet, in rhoncus lectus malesuada. Sed nec fermentum turpis. Donec in purus at dolor gravida gravida sit amet vitae lacus. Phasellus aliquam libero nisi, eu consequat nulla molestie nec. Ut sodales nisl eget odio finibus dictum. Mauris nec convallis massa. Nulla venenatis nunc non massa fermentum interdum.` +]; diff --git a/modules/site/src/app/examples/pagination/pagination-split-usage.component.html b/modules/site/src/app/examples/pagination/pagination-split-usage.component.html new file mode 100644 index 00000000..6efc80cb --- /dev/null +++ b/modules/site/src/app/examples/pagination/pagination-split-usage.component.html @@ -0,0 +1,27 @@ + + + {{ page }} + + + + + + + + diff --git a/modules/site/src/app/examples/pagination/pagination-split-usage.component.scss b/modules/site/src/app/examples/pagination/pagination-split-usage.component.scss new file mode 100644 index 00000000..b913c98a --- /dev/null +++ b/modules/site/src/app/examples/pagination/pagination-split-usage.component.scss @@ -0,0 +1,63 @@ +@import "../../../ng2-material/core/style/variables"; +@import "../../../ng2-material/core/style/default-theme"; +@import "../../../ng2-material/components/whiteframe/whiteframe"; + +pagination-split-usage { + .page-container { + position: relative; + align-items: stretch; + } + + md-card { + margin: 0; + + md-card-content { + padding: 30px 30px 40px; + } + } + + .controls { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + + .md-pagination-control { + position: absolute; + top: 0; + bottom: 0; + margin: auto; + width: 35px; + height: 35px; + vertical-align: middle; + } + + .md-pagination-control-previous { + left: 0; + } + + .md-pagination-control-next { + right: 0; + } + + .material-icons { + @extend .md-whiteframe-1dp; + width: 35px; + height: 35px; + background: #FFF; + } + } + + .md-pagination-range { + position: absolute; + right: 0; + bottom: 20px; + left: 0; + } + + .page-number { + padding: 0 10px; + font-weight: bold; + } +} diff --git a/modules/site/src/app/examples/pagination/pagination-split-usage.component.spec.ts b/modules/site/src/app/examples/pagination/pagination-split-usage.component.spec.ts new file mode 100644 index 00000000..57017f07 --- /dev/null +++ b/modules/site/src/app/examples/pagination/pagination-split-usage.component.spec.ts @@ -0,0 +1,46 @@ +import { + beforeEach, + beforeEachProviders, + describe, + expect, + it, + inject, +} from '@angular/core/testing'; +import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing'; +import { Component } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { PaginationSplitUsageComponent } from './pagination-split-usage.component'; + +describe('Component: PaginationSplitUsage', () => { + let builder: TestComponentBuilder; + + beforeEachProviders(() => [PaginationSplitUsageComponent]); + beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) { + builder = tcb; + })); + + it('should inject the component', inject([PaginationSplitUsageComponent], + (component: PaginationSplitUsageComponent) => { + expect(component).toBeTruthy(); + })); + + it('should create the component', inject([], () => { + return builder.createAsync(PaginationSplitUsageComponentTestController) + .then((fixture: ComponentFixture) => { + let query = fixture.debugElement.query(By.directive(PaginationSplitUsageComponent)); + expect(query).toBeTruthy(); + expect(query.componentInstance).toBeTruthy(); + }); + })); +}); + +@Component({ + selector: 'test', + template: ` + + `, + directives: [PaginationSplitUsageComponent] +}) +class PaginationSplitUsageComponentTestController { +} + diff --git a/modules/site/src/app/examples/pagination/pagination-split-usage.component.ts b/modules/site/src/app/examples/pagination/pagination-split-usage.component.ts new file mode 100644 index 00000000..41bfde94 --- /dev/null +++ b/modules/site/src/app/examples/pagination/pagination-split-usage.component.ts @@ -0,0 +1,73 @@ +import {Component, ViewEncapsulation, OnInit, OnDestroy} from '@angular/core'; +import {MATERIAL_DIRECTIVES, Media, MediaListener} from "ng2-material"; + +import {bookDatas} from './pagination-datas'; + +@Component({ + moduleId: module.id, + selector: 'pagination-split-usage', + templateUrl: 'pagination-split-usage.component.html', + styleUrls: ['pagination-split-usage.component.css'], + directives: [MATERIAL_DIRECTIVES], + encapsulation: ViewEncapsulation.None +}) +export class PaginationSplitUsageComponent implements OnInit, OnDestroy { + pages: Array = bookDatas; + + pagination: any = { + currentPage: 1, + itemsPerPage: 2, + totalItems: 6 + }; + + rangeFormat: string; + + displayedPages: Array = []; + + mediaListener: MediaListener; + + constructor(private media: Media) {} + + refreshPageBySize() { + if (this.media.hasMedia('xs')) { + this.pagination.itemsPerPage = 1; + this.rangeFormat = `{start}`; + } else { + this.pagination.itemsPerPage = 2; + this.rangeFormat = ` + {start} + {end} + `; + } + + this.pagination.currentPage = 1; + + this.refreshPages(); + } + + getFlexSize() { + return Math.round(100 / this.pagination.itemsPerPage); + } + + refreshPages() { + let start = (this.pagination.currentPage - 1) * this.pagination.itemsPerPage, + end = start + this.pagination.itemsPerPage; + this.displayedPages = this.pages.slice(start, end); + } + + detectChange(event) { + if (event !== undefined && event.name === 'pagination_changed' && event.pagination !== undefined) { + this.pagination = event.pagination; + this.refreshPages(); + } + } + + ngOnInit() { + this.mediaListener = this.media.listen(Media.getQuery('xs')); + this.mediaListener.onMatched.subscribe(this.refreshPageBySize.bind(this)); + } + + ngOnDestroy() { + this.mediaListener.destroy(); + } +} diff --git a/modules/site/src/app/index.ts b/modules/site/src/app/index.ts index 1f37b7a3..80a6f328 100644 --- a/modules/site/src/app/index.ts +++ b/modules/site/src/app/index.ts @@ -9,6 +9,8 @@ import {DialogBasicUsageComponent} from './examples/dialog/dialog-basic-usage.co import {ElevationBasicUsageComponent} from './examples/elevation/elevation-basic-usage.component'; import {InputBasicUsageComponent} from './examples/input/input-basic-usage.component'; import {ListBasicUsageComponent} from './examples/list/list-basic-usage.component'; +import {PaginationBasicUsageComponent} from './examples/pagination/pagination-basic-usage.component'; +import {PaginationSplitUsageComponent} from './examples/pagination/pagination-split-usage.component'; import {ProgressBarBasicUsageComponent} from './examples/progress-bar/progress-bar-basic-usage.component'; import {ProgressCircleBasicUsageComponent} from './examples/progress-circle/progress-circle-basic-usage.component'; import {RadioBasicUsageComponent} from './examples/radio/radio-basic-usage.component'; @@ -30,8 +32,8 @@ export const DEMO_DIRECTIVES: any[] = [ ElevationBasicUsageComponent, ButtonBasicUsageComponent, CardActionButtonsComponent, CardBasicUsageComponent, CardInlineActionsComponent, CheckboxBasicUsageComponent, DataTableBasicUsageComponent, DataTableSelectableRowsComponent, DialogBasicUsageComponent, - InputBasicUsageComponent, ListBasicUsageComponent, ProgressBarBasicUsageComponent, - ProgressCircleBasicUsageComponent, RadioBasicUsageComponent, SidenavBasicUsageComponent, + InputBasicUsageComponent, ListBasicUsageComponent, PaginationBasicUsageComponent, PaginationSplitUsageComponent, + ProgressBarBasicUsageComponent, ProgressCircleBasicUsageComponent, RadioBasicUsageComponent, SidenavBasicUsageComponent, SwitchBasicUsageComponent, TabsDynamicHeightComponent, TabsDynamicTabsComponent, ToolbarBasicUsageComponent ]; diff --git a/src/components.scss b/src/components.scss index f72eecf9..dfed2054 100644 --- a/src/components.scss +++ b/src/components.scss @@ -8,6 +8,7 @@ @import "components/icon/icon"; @import "components/list/list"; @import "components/form/messages"; +@import "components/pagination/pagination"; @import "components/peekaboo/peekaboo"; @import "components/subheader/subheader"; @import "components/switch/switch"; diff --git a/src/components/pagination/README.md b/src/components/pagination/README.md new file mode 100644 index 00000000..110bad8f --- /dev/null +++ b/src/components/pagination/README.md @@ -0,0 +1,70 @@ +# MdPagination +Use pagination components when you need to display large numbers of items. Configure the number of items to display +in a page, control which page is active, and format the display of the current page state. + +The pagination model is a plain javascript object that contains number values for *currentPage*, *itemsPerPage*, and *totalItems*. + +This set of components follows the general guidelines provided [here](http://www.google.com/design/spec/components/data-tables.html#data-tables-interaction) + +## `` + +### Properties +| Name | Type | Description | +| --- | --- | --- | +| name | string | Shared name for the pagination model | +| model | IPaginationModel | Pagination configuration model | +| controls | boolean | Toggle the display of next/back controls | +| range | boolean | Toggle the display of range | +| rangeFormat | string | Range format string with `{start}`, `{end}`, `{total}` | +| itemsPerPage | boolean | Toggle the display of items per page selector | +| itemsPerPageBefore | string | Prepend items per page selector with a string | +| itemsPerPageAfter | string | Append items per page selector with a string | +| itemsPerPageOptions | number[] | Page size options as an array of numbers | + +### Events +| Name | Type | Description | +| --- | --- | --- | +| onPaginationChange | EventEmitter | Emitted when the model changes | + + + +## `` + +Length selector component that controls the number of items shown on each page. + +### Properties +| Name | Type | Description | +| --- | --- | --- | +| name | string | Shared name for the pagination model | +| model | IPaginationModel | Pagination configuration model | +| itemsPerPageBefore | string | Prepend items per page selector with a string | +| itemsPerPageAfter | string | Append items per page selector with a string | +| itemsPerPageOptions | number[] | Page size options as an array of numbers | + + +## `` + +Pagination controls component that move back and forth between pages. + +### Properties +| Name | Type | Description | +| --- | --- | --- | +| name | string | Shared name for the pagination model | +| model | IPaginationModel | Pagination configuration model | + + +## `` + +Range display component that displays the current state of the pagination model. + +### Properties +| Name | Type | Description | +| --- | --- | --- | +| name | string | Shared name for the pagination model | +| model | IPaginationModel | Pagination configuration model | +| rangeFormat | string | Range format string with `{start}`, `{end}`, `{total}` | + +## PaginationService + +Data service that coordinate state between various components. Prefer the `onPaginationChange` output of `MdPagination`. +If needed, subscribe to the `onChange` property or push changes via `change` on the service. diff --git a/src/components/pagination/index.ts b/src/components/pagination/index.ts new file mode 100644 index 00000000..39ce2d73 --- /dev/null +++ b/src/components/pagination/index.ts @@ -0,0 +1,3 @@ +export * from './pagination'; +export {IPaginationModel} from './pagination'; +export * from './pagination_service'; diff --git a/src/components/pagination/pagination.scss b/src/components/pagination/pagination.scss new file mode 100644 index 00000000..1d096185 --- /dev/null +++ b/src/components/pagination/pagination.scss @@ -0,0 +1,69 @@ +@import "../../core/style/variables"; +@import "../../core/style/default-theme"; + +$pagination-padding: rem(1.4) !default; +$pagination-min-height: 28px !default; +$pagination-font-size: rem(1.3) !default; + +$pagination-controls-margin: rem(.6) !default; + +$pagination-control-margin: rem(.600) !default; +$pagination-control-icon-width: rem(3.600) !default; +$pagination-control-icon-height: rem(2.800) !default; +$pagination-control-icon-background: transparent; + +$pagination-range-margin: rem(2) !default; + +$pagination-items-per-page-options-margin: rem(.6) !default; +$pagination-items-per-page-options-select-margin: rem(.6) !default; +$pagination-items-per-page-options-label-margin: rem(.6) !default; +$pagination-items-per-page-options-label-font-size: rem(1.1) !default; + +md-pagination, .md-pagination { + display: block; + padding: $pagination-padding 0; + min-height: $pagination-min-height; + font-size: $pagination-font-size; + color: md-color($md-foreground, text); + text-align: right; + + md-pagination-controls, .md-pagination-controls { + margin: 0 $pagination-controls-margin; + } + + .md-pagination-control { + display: inline-block; + margin: 0 $pagination-control-margin; + width: $pagination-control-icon-width; + height: $pagination-control-icon-height; + vertical-align: middle; + + button { + display: none; + width: $pagination-control-icon-width; + height: $pagination-control-icon-height; + border: 0; + outline: 0; + background: $pagination-control-icon-background; + } + } + .md-pagination-control-active button { + display: block; + } + + md-pagination-range, .md-pagination-range { + margin: 0 $pagination-range-margin; + } + + md-pagination-items-per-page, .md-pagination-items-per-page { + margin: 0 $pagination-items-per-page-options-margin; + } + + .md-pagination-length-select { + margin: 0 $pagination-items-per-page-options-select-margin; + } + .md-pagination-items-per-page-label { + margin: 0 $pagination-items-per-page-options-label-margin; + font-size: $pagination-items-per-page-options-label-font-size; + } +} diff --git a/src/components/pagination/pagination.ts b/src/components/pagination/pagination.ts new file mode 100644 index 00000000..9abdc2a6 --- /dev/null +++ b/src/components/pagination/pagination.ts @@ -0,0 +1,276 @@ +import { + Component, + Input, + Output, + HostBinding, + EventEmitter, + ElementRef, + AfterViewInit, + AfterContentInit +} from '@angular/core'; +import {isPresent} from '@angular/core/src/facade/lang'; +import 'rxjs/add/operator/filter'; +import {PaginationService} from './pagination_service'; + +export interface IPaginationModel { + currentPage: number; + itemsPerPage: number; + totalItems: number; +} + +export abstract class AbstractPaginationSubComponent { + + @Input() name: string; + + @Input() model: IPaginationModel = { currentPage: 0, + itemsPerPage: 0, + totalItems: 0 + }; + + constructor(protected service: PaginationService) { + this.service.onChange + .filter(event => isPresent(event) && isPresent(event.name)) + .filter(event => event.target === this.name) + .subscribe(event => { + this.model = event.pagination; + }); + } + +} + +@Component({ + selector: 'md-pagination-range', + template: '', +}) +export class MdPaginationRange extends AbstractPaginationSubComponent { + + @HostBinding('class.md-pagination-range') mdPaginationRange: boolean = true; + + @HostBinding('innerHTML') get html() { return this.getRange() }; + + @Input() name: string = 'default'; + + @Input() model: IPaginationModel = { + currentPage: 0, + itemsPerPage: 0, + totalItems: 0 + }; + + @Input('range-format') rangeFormat: string = '{start}-{end} of {total}'; + + public value: string = ''; + + constructor(protected service: PaginationService) { + super(service); + } + + /** + * tranform format into an readable string + * + * @returns {string} + */ + getFormattedValue(rangeStart: number, rangeStop: number, totalItems: number) { + let result: string = this.rangeFormat; + + result = result.replace(/\{start\}/gi, rangeStart.toString()); + result = result.replace(/\{end\}/gi, rangeStop.toString()); + result = result.replace(/\{total\}/gi, totalItems.toString()); + + return result; + } + + /** + * calculate range depending via model parameters + * + * @param {IPaginationModel} model + * @private + */ + getRange() { + if (isPresent(this.model)) { + let rangeStart = (this.model.currentPage - 1) * this.model.itemsPerPage + 1; + + let rest = this.model.totalItems - rangeStart, + rangeStop = rest < this.model.itemsPerPage ? this.model.totalItems : rangeStart + this.model.itemsPerPage - 1; + + return this.getFormattedValue(rangeStart, rangeStop, this.model.totalItems); + } + + return; + } +} + +@Component({ + selector: 'md-pagination-controls', + template: ` + + + + + + + ` +}) +export class MdPaginationControls extends AbstractPaginationSubComponent { + + @HostBinding('class.md-pagination-controls') mdPaginationControls: boolean = true; + + @Input() name: string = 'default'; + + @Input() model: IPaginationModel = { + currentPage: 0, + itemsPerPage: 0, + totalItems: 0 + }; + + constructor(protected service: PaginationService) { + super(service); + } + + isFirstPage() { + return isPresent(this.model) && this.model.currentPage == 1; + } + + isLastPage() { + return isPresent(this.model) && this.model.totalItems <= this.model.currentPage * this.model.itemsPerPage; + } + + previousPage() { + if (isPresent(this.model)) { + this.changePage(this.model.currentPage - 1); + } + } + + nextPage() { + if (isPresent(this.model)) { + this.changePage(this.model.currentPage + 1); + } + } + + changePage(newPage: number) { + let model = JSON.parse(JSON.stringify(this.model)); + model.currentPage = newPage; + this.service.change(this.name, model); + } +} + +@Component({ + selector: 'md-pagination-items-per-page', + template: ` + {{itemsPerPageBefore}} + + {{itemsPerPageAfter}} + ` +}) +export class MdPaginationItemsPerPage extends AbstractPaginationSubComponent { + + @HostBinding('class.md-pagination-items-per-page') mdPaginationItemsPerPage: boolean = true; + + @HostBinding() get hidden() { return !this.itemsPerPageOptions || !this.itemsPerPageOptions.length }; + + @Input() name: string = 'default'; + + @Input() model: IPaginationModel = { + currentPage: 0, + itemsPerPage: 0, + totalItems: 0 + }; + + @Input('items-per-page-before') itemsPerPageBefore: string = 'Rows per page:'; + + @Input('items-per-page-after') itemsPerPageAfter: string; + + @Input('items-per-page-options') itemsPerPageOptions: Array = []; + + constructor(protected service: PaginationService) { + super(service); + } + + changePaginationLength(value) { + let model = JSON.parse(JSON.stringify(this.model)); + model.currentPage = 1; + model.itemsPerPage = parseInt(value); + this.service.change(this.name, model); + } + +} + +export interface IPaginationChange { + name: string; + target: string; + pagination: IPaginationModel; +} + +@Component({ + selector: 'md-pagination', + template: ` + + + + + `, + directives: [MdPaginationRange, MdPaginationControls, MdPaginationItemsPerPage], +}) +export class MdPagination implements AfterContentInit, AfterViewInit { + + @HostBinding('class.md-pagination') mdPagination: boolean = true; + + @Input() name: string = 'default'; + + @Input() model: IPaginationModel = { + currentPage: 0, + itemsPerPage: 0, + totalItems: 0 + }; + + @Input() range: boolean = true; + + @Input('range-format') rangeFormat: string; + + @Input() controls: boolean = true; + + @Input('items-per-page') itemsPerPage: boolean = true; + + @Input('items-per-page-before') itemsPerPageBefore: string; + + @Input('items-per-page-after') itemsPerPageAfter: string; + + @Input('items-per-page-options') itemsPerPageOptions: Array; + + @Output('on-pagination-change') onPaginationChange: EventEmitter = new EventEmitter(false); + + defaultDisplay: boolean = true; + + constructor(private service: PaginationService, private element: ElementRef) { + this.service.onChange + .filter(event => isPresent(event) && isPresent(event.name)) + .filter(event => event.target === this.name) + .subscribe(event => this.onPaginationChange.emit(event)); + + } + + ngAfterContentInit() { + this.defaultDisplay = this.element.nativeElement.childElementCount === 0; + } + + ngAfterViewInit() { + this.service.change(this.name, this.model); + } +} diff --git a/src/components/pagination/pagination_service.ts b/src/components/pagination/pagination_service.ts new file mode 100644 index 00000000..6da66196 --- /dev/null +++ b/src/components/pagination/pagination_service.ts @@ -0,0 +1,23 @@ +import {Injectable} from '@angular/core'; +import {Subject} from 'rxjs/Subject'; +import {IPaginationModel, IPaginationChange} from './pagination'; + + +@Injectable() +export class PaginationService { + public onChange: Subject; + + constructor() { + this.onChange = new Subject(null); + } + + change(name: string, pagination: IPaginationModel) { + let newEvent: IPaginationChange = { + name: 'pagination_changed', + target: name, + pagination: pagination + }; + + this.onChange.next(newEvent); + } +} diff --git a/src/components/pagination/pagination_spec.ts b/src/components/pagination/pagination_spec.ts new file mode 100644 index 00000000..d5096bd1 --- /dev/null +++ b/src/components/pagination/pagination_spec.ts @@ -0,0 +1,803 @@ +import {componentSanityCheck} from '../../platform/testing/util'; +import { + beforeEach, + describe, + expect, + inject, + it, + async +} from '@angular/core/testing'; +import {ComponentFixture, TestComponentBuilder} from "@angular/compiler/testing"; +import {Component, DebugElement} from '@angular/core'; +import {CORE_DIRECTIVES} from '@angular/common'; +import {By} from "@angular/platform-browser"; +import { + MdPagination, MdPaginationControls, MdPaginationItemsPerPage, + MdPaginationRange, IPaginationModel, IPaginationChange, PaginationService +} from './index'; + +export function main() { + + describe('Pagination', () => { + let builder: TestComponentBuilder; + + beforeEach(inject([TestComponentBuilder], (tcb) => { + builder = tcb; + })); + + componentSanityCheck('MdPagination', 'md-pagination', ``); + + describe('MdPagination', () => { + + interface IPaginationFixture { + fixture: ComponentFixture; + comp: MdPagination; + debug: DebugElement; + } + @Component({ + selector: 'test-app', + directives: [CORE_DIRECTIVES, MdPagination, MdPaginationControls, MdPaginationItemsPerPage, MdPaginationRange], + template: `` + }) + class TestComponent { + defaultModel: IPaginationModel = { + currentPage: 1, + itemsPerPage: 5, + totalItems: 24 + }; + + defaultItemsPerPageOptions: Array = [10, 50, 100]; + } + + function setup(template: string = null): Promise { + let prep = template === null ? + builder.createAsync(TestComponent) : + builder.overrideTemplate(TestComponent, template).createAsync(TestComponent); + return prep.then((fixture: ComponentFixture) => { + let debug = fixture.debugElement.query(By.css('md-pagination')); + let comp: MdPagination = debug.componentInstance; + fixture.detectChanges(); + return { + fixture: fixture, + comp: comp, + debug: debug + }; + }).catch(console.error.bind(console)); + } + + describe('default values', () => { + + it('should have a default model', inject([], () => { + return setup().then((api: IPaginationFixture) => { + expect(api.comp.model.currentPage).toEqual(0); + expect(api.comp.model.itemsPerPage).toEqual(0); + expect(api.comp.model.totalItems).toEqual(0); + }); + })); + + it('should accept custom model', inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + expect(api.comp.model.currentPage).toEqual(1); + expect(api.comp.model.itemsPerPage).toEqual(5); + expect(api.comp.model.totalItems).toEqual(24); + }); + })); + + it('should have a default name', inject([], () => { + return setup().then((api: IPaginationFixture) => { + expect(api.comp.name).toEqual('default'); + }); + })); + + it('should accept a custom name', inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + expect(api.comp.name).toEqual('book'); + }); + })); + + it('should display range by default', inject([], () => { + return setup().then((api: IPaginationFixture) => { + expect(api.comp.range).toBeTruthy(); + }); + })); + + it('should accept a custom display for range', inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + expect(api.comp.range).toBeFalsy(); + }); + })); + + it('should not have a default rangeFormat', inject([], () => { + return setup().then((api: IPaginationFixture) => { + expect(api.comp.rangeFormat).toBeUndefined(); + }); + })); + + it('should accept a custom rangeFormat', inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + expect(api.comp.rangeFormat).toEqual('{start}/{total}'); + }); + })); + + it('should display controls by default', inject([], () => { + return setup().then((api: IPaginationFixture) => { + expect(api.comp.controls).toBeTruthy(); + }); + })); + + it('should accept a custom display for controls', inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + expect(api.comp.controls).toBeFalsy(); + }); + })); + + it('should display items per page options by default', inject([], () => { + return setup().then((api: IPaginationFixture) => { + expect(api.comp.itemsPerPage).toBeTruthy(); + }); + })); + + it('should accept a custom display for items per page', inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + expect(api.comp.itemsPerPage).toBeFalsy(); + }); + })); + + it('should not have a default prepended string to items per page', inject([], () => { + return setup().then((api: IPaginationFixture) => { + expect(api.comp.itemsPerPageBefore).toBeUndefined(); + }); + })); + + it('should accept a custom prepended string to items per page', inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + expect(api.comp.itemsPerPageBefore).toEqual('page:'); + }); + })); + + it('should not have a default appended string to items per page', inject([], () => { + return setup().then((api: IPaginationFixture) => { + expect(api.comp.itemsPerPageAfter).toBeUndefined(); + }); + })); + + it('should accept a custom appended string to items per page', inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + expect(api.comp.itemsPerPageAfter).toEqual(' - '); + }); + })); + + it('should not have a default list of options for items per page', inject([], () => { + return setup().then((api: IPaginationFixture) => { + expect(api.comp.itemsPerPageOptions).toBeUndefined(); + }); + })); + + it('should accept a custom list of options for items per page', inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + expect(api.comp.itemsPerPageOptions).not.toContain(5); + expect(api.comp.itemsPerPageOptions).toContain(10); + expect(api.comp.itemsPerPageOptions).toContain(50); + expect(api.comp.itemsPerPageOptions).toContain(100); + }); + })); + + }); + + describe('construct', () => { + let service: PaginationService, + updatedPagination: IPaginationModel = { + currentPage: 2, + itemsPerPage: 30, + totalItems: 65 + }; + + beforeEach(inject([PaginationService], (serv) => { + service = serv; + })); + + it('should listen PaginationService', inject([], () => { + return setup().then((api: IPaginationFixture) => { + api.comp.onPaginationChange.subscribe((event) => { + expect(event.name).toEqual('pagination_changed'); + expect(event.target).toEqual('default'); + expect(event.pagination).toEqual(updatedPagination); + }); + + service.change('default', updatedPagination); + }); + })); + + it('should listen PaginationService only for his reference name', inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + let spy = jasmine.createSpy('spy'); + + api.comp.onPaginationChange.subscribe(spy); + + service.onChange.subscribe(() => { + expect(spy).not.toHaveBeenCalled(); + }); + + service.change('default', updatedPagination); + }); + })); + }); + + describe('ngAfterContentInit', () => { + + it('should init default components', async(inject([], () => { + return setup().then((api: IPaginationFixture) => { + let element = api.debug.nativeElement; + api.fixture.detectChanges(); + expect(element.children.length).toEqual(3); + }); + }))); + + it('should accept custom components as children', async(inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + let element = api.debug.nativeElement; + api.fixture.detectChanges(); + expect(element.children.length).toEqual(1); + }); + }))); + + }); + + describe('ngAfterViewInit', () => { + let service: PaginationService, + defaultModel: IPaginationModel = { + currentPage: 1, + itemsPerPage: 30, + totalItems: 65 + }; + + beforeEach(inject([PaginationService], (serv) => { + service = serv; + spyOn(service, 'change') + })); + + it('should dispatch his model after init', async(inject([], () => { + return setup(``).then((api: IPaginationFixture) => { + expect(service.change).toHaveBeenCalled(); + }); + }))); + + }); + + }); + + componentSanityCheck('MdPaginationRange', 'md-pagination-range', ``); + + describe('MdPaginationRange', () => { + + interface IPaginationRangeFixture { + fixture: ComponentFixture; + comp: MdPaginationRange; + debug: DebugElement; + } + @Component({ + selector: 'test-app', + directives: [CORE_DIRECTIVES, MdPaginationRange], + template: `` + }) + class TestComponent { + page2: IPaginationModel = { + currentPage: 2, + itemsPerPage: 30, + totalItems: 65 + }; + + page3: IPaginationModel = { + currentPage: 3, + itemsPerPage: 30, + totalItems: 65 + }; + + defaultRangeFormat: string = '{start}-{end} / {total}'; + } + + function setup(template: string = null): Promise { + let prep = template === null ? + builder.createAsync(TestComponent) : + builder.overrideTemplate(TestComponent, template).createAsync(TestComponent); + return prep.then((fixture: ComponentFixture) => { + let debug = fixture.debugElement.query(By.css('md-pagination-range')); + let comp: MdPaginationRange = debug.componentInstance; + fixture.detectChanges(); + return { + fixture: fixture, + comp: comp, + debug: debug + }; + }).catch(console.error.bind(console)); + } + + describe('default values', () => { + + it('should have a default model', inject([], () => { + return setup().then((api: IPaginationRangeFixture) => { + expect(api.comp.model.currentPage).toEqual(0); + expect(api.comp.model.itemsPerPage).toEqual(0); + expect(api.comp.model.totalItems).toEqual(0); + }); + })); + + it('should accept custom model', inject([], () => { + return setup(``).then((api: IPaginationRangeFixture) => { + expect(api.comp.model.currentPage).toEqual(2); + expect(api.comp.model.itemsPerPage).toEqual(30); + expect(api.comp.model.totalItems).toEqual(65); + }); + })); + + it('should have a default name', inject([], () => { + return setup().then((api: IPaginationRangeFixture) => { + expect(api.comp.name).toEqual('default'); + }); + })); + + it('should accept a custom name', inject([], () => { + return setup(``).then((api: IPaginationRangeFixture) => { + expect(api.comp.name).toEqual('book'); + }); + })); + + it('should have a default range format', inject([], () => { + return setup().then((api: IPaginationRangeFixture) => { + expect(api.comp.rangeFormat).toEqual('{start}-{end} of {total}'); + }); + })); + + it('should accept a custom range format', inject([], () => { + return setup(``).then((api: IPaginationRangeFixture) => { + expect(api.comp.rangeFormat).toEqual('{start}-{end} / {total}'); + }); + })); + + }); + + describe('construct', () => { + let service: PaginationService, + updatedPagination: IPaginationModel = { + currentPage: 1, + itemsPerPage: 30, + totalItems: 65 + }; + + beforeEach(inject([PaginationService], (serv) => { + service = serv; + })); + + it('should listen PaginationService', inject([], () => { + return setup().then((api: IPaginationRangeFixture) => { + service.onChange.subscribe((event) => { + expect(api.comp.model).toEqual(updatedPagination); + }); + + service.change('default', updatedPagination); + }); + })); + + it('should listen PaginationService only for his reference name', inject([], () => { + return setup(``).then((api: IPaginationRangeFixture) => { + service.onChange.subscribe(() => { + expect(api.comp.model).toEqual({ + currentPage: 0, + itemsPerPage: 0, + totalItems: 0 + }); + }); + + service.change('default', updatedPagination); + }); + })); + }); + + describe('getFormattedValue', () => { + + it('should replace pattern in the range format', inject([], () => { + return setup().then((api: IPaginationRangeFixture) => { + expect(api.comp.getFormattedValue(1, 5, 30)).toEqual('1-5 of 30'); + }); + })); + + }); + + describe('getRange', () => { + + it('should calculate range at the middle', inject([], () => { + return setup(``).then((api: IPaginationRangeFixture) => { + spyOn(api.comp, 'getFormattedValue').and.callThrough(); + let result = api.comp.getRange(); + expect(result).toEqual('31-60 of 65'); + expect(api.comp.getFormattedValue).toHaveBeenCalledWith(31, 60, 65); + }); + })); + + + it('should calculate range at the end', inject([], () => { + return setup(``).then((api: IPaginationRangeFixture) => { + spyOn(api.comp, 'getFormattedValue').and.callThrough(); + let result = api.comp.getRange(); + expect(result).toEqual('61-65 of 65'); + expect(api.comp.getFormattedValue).toHaveBeenCalledWith(61, 65, 65); + }); + })); + + }); + + }); + + componentSanityCheck('MdPaginationControls', 'md-pagination-controls', ``); + + describe('MdPaginationControls', () => { + + interface IPaginationControlsFixture { + fixture: ComponentFixture; + comp: MdPaginationControls; + debug: DebugElement; + } + @Component({ + selector: 'test-app', + directives: [CORE_DIRECTIVES, MdPaginationControls], + template: `` + }) + class TestComponent { + page1: IPaginationModel = { + currentPage: 1, + itemsPerPage: 30, + totalItems: 65 + }; + page2: IPaginationModel = { + currentPage: 2, + itemsPerPage: 30, + totalItems: 65 + }; + page3: IPaginationModel = { + currentPage: 3, + itemsPerPage: 30, + totalItems: 65 + }; + } + + function setup(template: string = null): Promise { + let prep = template === null ? + builder.createAsync(TestComponent) : + builder.overrideTemplate(TestComponent, template).createAsync(TestComponent); + return prep.then((fixture: ComponentFixture) => { + let debug = fixture.debugElement.query(By.css('md-pagination-controls')); + let comp: MdPaginationControls = debug.componentInstance; + fixture.detectChanges(); + return { + fixture: fixture, + comp: comp, + debug: debug + }; + }).catch(console.error.bind(console)); + } + + describe('default values', () => { + + it('should have a default model', inject([], () => { + return setup().then((api: IPaginationControlsFixture) => { + expect(api.comp.model.currentPage).toEqual(0); + expect(api.comp.model.itemsPerPage).toEqual(0); + expect(api.comp.model.totalItems).toEqual(0); + }); + })); + + it('should accept custom model', inject([], () => { + return setup(``).then((api: IPaginationControlsFixture) => { + expect(api.comp.model.currentPage).toEqual(2); + expect(api.comp.model.itemsPerPage).toEqual(30); + expect(api.comp.model.totalItems).toEqual(65); + }); + })); + + it('should have a default name', inject([], () => { + return setup().then((api: IPaginationControlsFixture) => { + expect(api.comp.name).toEqual('default'); + }); + })); + + it('should accept a custom name', inject([], () => { + return setup(``).then((api: IPaginationControlsFixture) => { + expect(api.comp.name).toEqual('book'); + }); + })); + + }); + + describe('construct', () => { + let service: PaginationService, + updatedPagination: IPaginationModel = { + currentPage: 1, + itemsPerPage: 30, + totalItems: 65 + }; + + beforeEach(inject([PaginationService], (serv) => { + service = serv; + })); + + it('should listen PaginationService', inject([], () => { + return setup().then((api: IPaginationControlsFixture) => { + service.onChange.subscribe((event) => { + expect(api.comp.model).toEqual(updatedPagination); + }); + + service.change('default', updatedPagination); + }); + })); + + it('should listen PaginationService only for his reference name', inject([], () => { + return setup(``).then((api: IPaginationControlsFixture) => { + service.onChange.subscribe(() => { + expect(api.comp.model).toEqual({ + currentPage: 0, + itemsPerPage: 0, + totalItems: 0 + }); + }); + + service.change('default', updatedPagination); + }); + })); + }); + + describe('isFirstPage', () => { + + it('should accept first page as first page', inject([], () => { + return setup(``).then((api: IPaginationControlsFixture) => { + expect(api.comp.isFirstPage()).toBeTruthy(); + }); + })); + + it('should not accept second page as first page', inject([], () => { + return setup(``).then((api: IPaginationControlsFixture) => { + expect(api.comp.isFirstPage()).toBeFalsy(); + }); + })); + + }); + + describe('isLastPage', () => { + + it('should accept third page as last page', inject([], () => { + return setup(``).then((api: IPaginationControlsFixture) => { + expect(api.comp.isLastPage()).toBeTruthy(); + }); + })); + + it('should not accept second page as last page', inject([], () => { + return setup(``).then((api: IPaginationControlsFixture) => { + expect(api.comp.isLastPage()).toBeFalsy(); + }); + })); + + }); + + describe('previousPage', () => { + + it('should call change of page to previous one', inject([], () => { + return setup(``).then((api: IPaginationControlsFixture) => { + spyOn(api.comp, 'changePage'); + api.comp.previousPage(); + expect(api.comp.changePage).toHaveBeenCalledWith(1); + }); + })); + + }); + + describe('nextPage', () => { + + it('should call change of page to previous one', inject([], () => { + return setup(``).then((api: IPaginationControlsFixture) => { + spyOn(api.comp, 'changePage'); + api.comp.nextPage(); + expect(api.comp.changePage).toHaveBeenCalledWith(3); + }); + })); + + }); + + describe('changePage', () => { + let service: PaginationService; + + beforeEach(inject([PaginationService], (serv) => { + service = serv; + spyOn(service, 'change'); + })); + + it('should dispatch the new current page to the service', inject([], () => { + return setup(``).then((api: IPaginationControlsFixture) => { + api.comp.changePage(1); + expect(service.change).toHaveBeenCalledWith('default', { + currentPage: 1, + itemsPerPage: 30, + totalItems: 65 + }); + }); + })); + + }); + + }); + + componentSanityCheck('MdPaginationItemsPerPage', 'md-pagination-items-per-page', ``); + + describe('MdPaginationItemsPerPage', () => { + + interface IPaginationItemsPerPageFixture { + fixture: ComponentFixture; + comp: MdPaginationItemsPerPage; + debug: DebugElement; + } + @Component({ + selector: 'test-app', + directives: [CORE_DIRECTIVES, MdPaginationItemsPerPage], + template: `` + }) + class TestComponent { + page1: IPaginationModel = { + currentPage: 1, + itemsPerPage: 30, + totalItems: 65 + }; + page2: IPaginationModel = { + currentPage: 2, + itemsPerPage: 30, + totalItems: 65 + }; + + defaultItemsPerPageOptions: Array = [10, 50, 100]; + } + + function setup(template: string = null): Promise { + let prep = template === null ? + builder.createAsync(TestComponent) : + builder.overrideTemplate(TestComponent, template).createAsync(TestComponent); + return prep.then((fixture: ComponentFixture) => { + let debug = fixture.debugElement.query(By.css('md-pagination-items-per-page')); + let comp: MdPaginationItemsPerPage = debug.componentInstance; + fixture.detectChanges(); + return { + fixture: fixture, + comp: comp, + debug: debug + }; + }).catch(console.error.bind(console)); + } + + describe('default values', () => { + + it('should have a default model', inject([], () => { + return setup().then((api: IPaginationItemsPerPageFixture) => { + expect(api.comp.model.currentPage).toEqual(0); + expect(api.comp.model.itemsPerPage).toEqual(0); + expect(api.comp.model.totalItems).toEqual(0); + }); + })); + + it('should accept custom model', inject([], () => { + return setup(``).then((api: IPaginationItemsPerPageFixture) => { + expect(api.comp.model.currentPage).toEqual(2); + expect(api.comp.model.itemsPerPage).toEqual(30); + expect(api.comp.model.totalItems).toEqual(65); + }); + })); + + it('should have a default name', inject([], () => { + return setup().then((api: IPaginationItemsPerPageFixture) => { + expect(api.comp.name).toEqual('default'); + }); + })); + + it('should accept a custom name', inject([], () => { + return setup(``).then((api: IPaginationItemsPerPageFixture) => { + expect(api.comp.name).toEqual('book'); + }); + })); + + it('should have a default prepended string', inject([], () => { + return setup().then((api: IPaginationItemsPerPageFixture) => { + expect(api.comp.itemsPerPageBefore).toEqual('Rows per page:'); + }); + })); + + it('should accept a custom prepended string', inject([], () => { + return setup(``).then((api: IPaginationItemsPerPageFixture) => { + expect(api.comp.itemsPerPageBefore).toEqual('Items per page:'); + }); + })); + + it('should not have a default appended string', inject([], () => { + return setup().then((api: IPaginationItemsPerPageFixture) => { + expect(api.comp.itemsPerPageAfter).toBeUndefined(); + }); + })); + + it('should accept a custom appended string', inject([], () => { + return setup(``).then((api: IPaginationItemsPerPageFixture) => { + expect(api.comp.itemsPerPageAfter).toEqual(' - '); + }); + })); + + it('should have a empty list of options for items per page', inject([], () => { + return setup().then((api: IPaginationItemsPerPageFixture) => { + expect(api.comp.itemsPerPageOptions).toEqual([]); + }); + })); + + it('should accept a custom list of options for items per page', inject([], () => { + return setup(``).then((api: IPaginationItemsPerPageFixture) => { + expect(api.comp.itemsPerPageOptions).not.toContain(5); + expect(api.comp.itemsPerPageOptions).toContain(10); + expect(api.comp.itemsPerPageOptions).toContain(50); + expect(api.comp.itemsPerPageOptions).toContain(100); + }); + })); + + }); + + describe('construct', () => { + let service: PaginationService, + updatedPagination: IPaginationModel = { + currentPage: 1, + itemsPerPage: 30, + totalItems: 65 + }; + + beforeEach(inject([PaginationService], (serv) => { + service = serv; + })); + + it('should listen PaginationService', inject([], () => { + return setup().then((api: IPaginationItemsPerPageFixture) => { + service.onChange.subscribe((event) => { + expect(api.comp.model).toEqual(updatedPagination); + }); + + service.change('default', updatedPagination); + }); + })); + + it('should listen PaginationService only for his reference name', inject([], () => { + return setup(``).then((api: IPaginationItemsPerPageFixture) => { + service.onChange.subscribe(() => { + expect(api.comp.model.currentPage).toEqual(0); + expect(api.comp.model.itemsPerPage).toEqual(0); + expect(api.comp.model.totalItems).toEqual(0); + }); + + service.change('default', updatedPagination); + }); + })); + }); + + describe('changePaginationLength', () => { + let service: PaginationService; + + beforeEach(inject([PaginationService], (serv) => { + service = serv; + spyOn(service, 'change'); + })); + + it('should dispatch page change to the service and reset to first page', inject([], () => { + return setup(``).then((api: IPaginationItemsPerPageFixture) => { + api.comp.changePaginationLength(50); + expect(service.change).toHaveBeenCalledWith('default', { + currentPage: 1, + itemsPerPage: 50, + totalItems: 65 + }); + }); + })); + + }); + + }); + + }); + +} diff --git a/src/index.ts b/src/index.ts index 88ad8d4e..e91c7172 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,11 @@ import { } from "./components/form/validators"; import {MdMessage, MdMessages} from "./components/form/messages"; import {MdList, MdListItem} from "./components/list/list"; +import {MdPagination, + MdPaginationControls, + MdPaginationItemsPerPage, + MdPaginationRange, + PaginationService} from "./components/pagination/index"; import {MdPeekaboo} from "./components/peekaboo/peekaboo"; import {MdSwitch} from "./components/switch/switch"; import {MdSubheader} from "./components/subheader/subheader"; @@ -48,6 +53,8 @@ export * from './components/form/messages'; export * from './components/list/list'; +export * from './components/pagination/index'; + export * from './components/peekaboo/peekaboo'; export * from './components/switch/switch'; @@ -79,6 +86,7 @@ export const MATERIAL_DIRECTIVES: any[] = [ MdNumberRequiredValidator, MdMessage, MdMessages, MdList, MdListItem, + MdPagination, MdPaginationControls, MdPaginationItemsPerPage, MdPaginationRange, MdPeekaboo, MdSubheader, MdSwitch, @@ -91,6 +99,7 @@ export const MATERIAL_DIRECTIVES: any[] = [ export const MATERIAL_NODE_PROVIDERS: any[] = [ provide(ViewportHelper, {useClass: NodeViewportHelper}), Media, + PaginationService, ...INPUT_VALIDATORS ]; diff --git a/tsconfig.json b/tsconfig.json index d3ea7f8c..8255e026 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -49,6 +49,10 @@ "src/components/ink/ink_spec.ts", "src/components/list/list.ts", "src/components/list/list_spec.ts", + "src/components/pagination/index.ts", + "src/components/pagination/pagination.ts", + "src/components/pagination/pagination_service.ts", + "src/components/pagination/pagination_spec.ts", "src/components/peekaboo/peekaboo.ts", "src/components/peekaboo/peekaboo_spec.ts", "src/components/subheader/subheader.ts",