------------------------------------------------------------
commit b3cd4226b0c79abc4b7e19747ea384563d768917
Author: ffff:4.36.34.18 <ffff:4.36.34.18@hub.scroll.pub> Date: Fri Oct 4 03:44:25 2024 +0000 Reverted to 444b71e752c76a616fd36b63cc4751114c12b921 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..74c9e57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.html +*.txt +*.xml \ No newline at end of file diff --git a/datatables.css b/datatables.css new file mode 100644 index 0000000..81812f3 --- /dev/null +++ b/datatables.css @@ -0,0 +1,1742 @@ +/* + * This combined file was created by the DataTables downloader builder: + * https://datatables.net/download + * + * To rebuild or modify this file with the latest versions of the included + * software please visit: + * https://datatables.net/download/#dt/dt-2.1.7/b-3.1.2/b-colvis-3.1.2/b-html5-3.1.2/cr-2.0.4/fc-5.0.2/fh-4.0.1/sb-1.8.0 + * + * Included libraries: + * DataTables 2.1.7, Buttons 3.1.2, Column visibility 3.1.2, HTML5 export 3.1.2, ColReorder 2.0.4, FixedColumns 5.0.2, FixedHeader 4.0.1, SearchBuilder 1.8.0 + */ + +@charset "UTF-8"; +:root { + --dt-row-selected: 13, 110, 253; + --dt-row-selected-text: 255, 255, 255; + --dt-row-selected-link: 9, 10, 11; + --dt-row-stripe: 0, 0, 0; + --dt-row-hover: 0, 0, 0; + --dt-column-ordering: 0, 0, 0; + --dt-html-background: white; +} +:root.dark { + --dt-html-background: rgb(33, 37, 41); +} + +table.dataTable td.dt-control { + text-align: center; + cursor: pointer; +} +table.dataTable td.dt-control:before { + display: inline-block; + box-sizing: border-box; + content: ""; + border-top: 5px solid transparent; + border-left: 10px solid rgba(0, 0, 0, 0.5); + border-bottom: 5px solid transparent; + border-right: 0px solid transparent; +} +table.dataTable tr.dt-hasChild td.dt-control:before { + border-top: 10px solid rgba(0, 0, 0, 0.5); + border-left: 5px solid transparent; + border-bottom: 0px solid transparent; + border-right: 5px solid transparent; +} + +html.dark table.dataTable td.dt-control:before, +:root[data-bs-theme=dark] table.dataTable td.dt-control:before, +:root[data-theme=dark] table.dataTable td.dt-control:before { + border-left-color: rgba(255, 255, 255, 0.5); +} +html.dark table.dataTable tr.dt-hasChild td.dt-control:before, +:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before, +:root[data-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before { + border-top-color: rgba(255, 255, 255, 0.5); + border-left-color: transparent; +} + +div.dt-scroll { + width: 100%; +} + +div.dt-scroll-body thead tr, +div.dt-scroll-body tfoot tr { + height: 0; +} +div.dt-scroll-body thead tr th, div.dt-scroll-body thead tr td, +div.dt-scroll-body tfoot tr th, +div.dt-scroll-body tfoot tr td { + height: 0 !important; + padding-top: 0px !important; + padding-bottom: 0px !important; + border-top-width: 0px !important; + border-bottom-width: 0px !important; +} +div.dt-scroll-body thead tr th div.dt-scroll-sizing, div.dt-scroll-body thead tr td div.dt-scroll-sizing, +div.dt-scroll-body tfoot tr th div.dt-scroll-sizing, +div.dt-scroll-body tfoot tr td div.dt-scroll-sizing { + height: 0 !important; + overflow: hidden !important; +} + +table.dataTable thead > tr > th:active, +table.dataTable thead > tr > td:active { + outline: none; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before { + position: absolute; + display: block; + bottom: 50%; + content: "▲"; + content: "▲"/""; +} +table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { + position: absolute; + display: block; + top: 50%; + content: "▼"; + content: "▼"/""; +} +table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc, +table.dataTable thead > tr > td.dt-orderable-asc, +table.dataTable thead > tr > td.dt-orderable-desc, +table.dataTable thead > tr > td.dt-ordering-asc, +table.dataTable thead > tr > td.dt-ordering-desc { + position: relative; + padding-right: 30px; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order { + position: absolute; + right: 12px; + top: 0; + bottom: 0; + width: 12px; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { + left: 0; + opacity: 0.125; + line-height: 9px; + font-size: 0.8em; +} +table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, +table.dataTable thead > tr > td.dt-orderable-asc, +table.dataTable thead > tr > td.dt-orderable-desc { + cursor: pointer; +} +table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover, +table.dataTable thead > tr > td.dt-orderable-asc:hover, +table.dataTable thead > tr > td.dt-orderable-desc:hover { + outline: 2px solid rgba(0, 0, 0, 0.05); + outline-offset: -2px; +} +table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { + opacity: 0.6; +} +table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before, +table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after, +table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before { + display: none; +} +table.dataTable thead > tr > th:active, +table.dataTable thead > tr > td:active { + outline: none; +} + +div.dt-scroll-body > table.dataTable > thead > tr > th, +div.dt-scroll-body > table.dataTable > thead > tr > td { + overflow: hidden; +} + +:root.dark table.dataTable thead > tr > th.dt-orderable-asc:hover, :root.dark table.dataTable thead > tr > th.dt-orderable-desc:hover, +:root.dark table.dataTable thead > tr > td.dt-orderable-asc:hover, +:root.dark table.dataTable thead > tr > td.dt-orderable-desc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-asc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover { + outline: 2px solid rgba(255, 255, 255, 0.05); +} + +div.dt-processing { + position: absolute; + top: 50%; + left: 50%; + width: 200px; + margin-left: -100px; + margin-top: -22px; + text-align: center; + padding: 2px; + z-index: 10; +} +div.dt-processing > div:last-child { + position: relative; + width: 80px; + height: 15px; + margin: 1em auto; +} +div.dt-processing > div:last-child > div { + position: absolute; + top: 0; + width: 13px; + height: 13px; + border-radius: 50%; + background: rgb(13, 110, 253); + background: rgb(var(--dt-row-selected)); + animation-timing-function: cubic-bezier(0, 1, 1, 0); +} +div.dt-processing > div:last-child > div:nth-child(1) { + left: 8px; + animation: datatables-loader-1 0.6s infinite; +} +div.dt-processing > div:last-child > div:nth-child(2) { + left: 8px; + animation: datatables-loader-2 0.6s infinite; +} +div.dt-processing > div:last-child > div:nth-child(3) { + left: 32px; + animation: datatables-loader-2 0.6s infinite; +} +div.dt-processing > div:last-child > div:nth-child(4) { + left: 56px; + animation: datatables-loader-3 0.6s infinite; +} + +@keyframes datatables-loader-1 { + 0% { + transform: scale(0); + } + 100% { + transform: scale(1); + } +} +@keyframes datatables-loader-3 { + 0% { + transform: scale(1); + } + 100% { + transform: scale(0); + } +} +@keyframes datatables-loader-2 { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(24px, 0); + } +} +table.dataTable.nowrap th, table.dataTable.nowrap td { + white-space: nowrap; +} +table.dataTable th, +table.dataTable td { + box-sizing: border-box; +} +table.dataTable th.dt-left, +table.dataTable td.dt-left { + text-align: left; +} +table.dataTable th.dt-center, +table.dataTable td.dt-center { + text-align: center; +} +table.dataTable th.dt-right, +table.dataTable td.dt-right { + text-align: right; +} +table.dataTable th.dt-justify, +table.dataTable td.dt-justify { + text-align: justify; +} +table.dataTable th.dt-nowrap, +table.dataTable td.dt-nowrap { + white-space: nowrap; +} +table.dataTable th.dt-empty, +table.dataTable td.dt-empty { + text-align: center; + vertical-align: top; +} +table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date, +table.dataTable td.dt-type-numeric, +table.dataTable td.dt-type-date { + text-align: right; +} +table.dataTable thead th, +table.dataTable thead td, +table.dataTable tfoot th, +table.dataTable tfoot td { + text-align: left; +} +table.dataTable thead th.dt-head-left, +table.dataTable thead td.dt-head-left, +table.dataTable tfoot th.dt-head-left, +table.dataTable tfoot td.dt-head-left { + text-align: left; +} +table.dataTable thead th.dt-head-center, +table.dataTable thead td.dt-head-center, +table.dataTable tfoot th.dt-head-center, +table.dataTable tfoot td.dt-head-center { + text-align: center; +} +table.dataTable thead th.dt-head-right, +table.dataTable thead td.dt-head-right, +table.dataTable tfoot th.dt-head-right, +table.dataTable tfoot td.dt-head-right { + text-align: right; +} +table.dataTable thead th.dt-head-justify, +table.dataTable thead td.dt-head-justify, +table.dataTable tfoot th.dt-head-justify, +table.dataTable tfoot td.dt-head-justify { + text-align: justify; +} +table.dataTable thead th.dt-head-nowrap, +table.dataTable thead td.dt-head-nowrap, +table.dataTable tfoot th.dt-head-nowrap, +table.dataTable tfoot td.dt-head-nowrap { + white-space: nowrap; +} +table.dataTable tbody th.dt-body-left, +table.dataTable tbody td.dt-body-left { + text-align: left; +} +table.dataTable tbody th.dt-body-center, +table.dataTable tbody td.dt-body-center { + text-align: center; +} +table.dataTable tbody th.dt-body-right, +table.dataTable tbody td.dt-body-right { + text-align: right; +} +table.dataTable tbody th.dt-body-justify, +table.dataTable tbody td.dt-body-justify { + text-align: justify; +} +table.dataTable tbody th.dt-body-nowrap, +table.dataTable tbody td.dt-body-nowrap { + white-space: nowrap; +} + +/* + * Table styles + */ +table.dataTable { + width: 100%; + margin: 0 auto; + border-spacing: 0; + /* + * Header and footer styles + */ + /* + * Body styles + */ +} +table.dataTable thead th, +table.dataTable tfoot th { + font-weight: bold; +} +table.dataTable > thead > tr > th, +table.dataTable > thead > tr > td { + padding: 10px; + border-bottom: 1px solid rgba(0, 0, 0, 0.3); +} +table.dataTable > thead > tr > th:active, +table.dataTable > thead > tr > td:active { + outline: none; +} +table.dataTable > tfoot > tr > th, +table.dataTable > tfoot > tr > td { + border-top: 1px solid rgba(0, 0, 0, 0.3); + padding: 10px 10px 6px 10px; +} +table.dataTable > tbody > tr { + background-color: transparent; +} +table.dataTable > tbody > tr:first-child > * { + border-top: none; +} +table.dataTable > tbody > tr:last-child > * { + border-bottom: none; +} +table.dataTable > tbody > tr.selected > * { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.9); + color: rgb(255, 255, 255); + color: rgb(var(--dt-row-selected-text)); +} +table.dataTable > tbody > tr.selected a { + color: rgb(9, 10, 11); + color: rgb(var(--dt-row-selected-link)); +} +table.dataTable > tbody > tr > th, +table.dataTable > tbody > tr > td { + padding: 8px 10px; +} +table.dataTable.row-border > tbody > tr > *, table.dataTable.display > tbody > tr > * { + border-top: 1px solid rgba(0, 0, 0, 0.15); +} +table.dataTable.row-border > tbody > tr:first-child > *, table.dataTable.display > tbody > tr:first-child > * { + border-top: none; +} +table.dataTable.row-border > tbody > tr.selected + tr.selected > td, table.dataTable.display > tbody > tr.selected + tr.selected > td { + border-top-color: rgba(13, 110, 253, 0.65); + border-top-color: rgba(var(--dt-row-selected), 0.65); +} +table.dataTable.cell-border > tbody > tr > * { + border-top: 1px solid rgba(0, 0, 0, 0.15); + border-right: 1px solid rgba(0, 0, 0, 0.15); +} +table.dataTable.cell-border > tbody > tr > *:first-child { + border-left: 1px solid rgba(0, 0, 0, 0.15); +} +table.dataTable.cell-border > tbody > tr:first-child > * { + border-top: 1px solid rgba(0, 0, 0, 0.3); +} +table.dataTable.stripe > tbody > tr:nth-child(odd) > *, table.dataTable.display > tbody > tr:nth-child(odd) > * { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.023); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.023); +} +table.dataTable.stripe > tbody > tr:nth-child(odd).selected > *, table.dataTable.display > tbody > tr:nth-child(odd).selected > * { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.923); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.923); +} +table.dataTable.hover > tbody > tr:hover > *, table.dataTable.display > tbody > tr:hover > * { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.035); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.035); +} +table.dataTable.hover > tbody > tr.selected:hover > *, table.dataTable.display > tbody > tr.selected:hover > * { + box-shadow: inset 0 0 0 9999px #0d6efd !important; + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 1) !important; +} +table.dataTable.order-column > tbody tr > .sorting_1, +table.dataTable.order-column > tbody tr > .sorting_2, +table.dataTable.order-column > tbody tr > .sorting_3, table.dataTable.display > tbody tr > .sorting_1, +table.dataTable.display > tbody tr > .sorting_2, +table.dataTable.display > tbody tr > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.019); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.019); +} +table.dataTable.order-column > tbody tr.selected > .sorting_1, +table.dataTable.order-column > tbody tr.selected > .sorting_2, +table.dataTable.order-column > tbody tr.selected > .sorting_3, table.dataTable.display > tbody tr.selected > .sorting_1, +table.dataTable.display > tbody tr.selected > .sorting_2, +table.dataTable.display > tbody tr.selected > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.919); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919); +} +table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_1, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.054); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.054); +} +table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_2, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.047); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.047); +} +table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_3, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.039); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.039); +} +table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.954); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.954); +} +table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.947); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.947); +} +table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.939); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.939); +} +table.dataTable.display > tbody > tr.even > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.019); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.019); +} +table.dataTable.display > tbody > tr.even > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.011); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.011); +} +table.dataTable.display > tbody > tr.even > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.003); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.003); +} +table.dataTable.display > tbody > tr.even.selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.919); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919); +} +table.dataTable.display > tbody > tr.even.selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.911); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.911); +} +table.dataTable.display > tbody > tr.even.selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.903); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.903); +} +table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.082); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.082); +} +table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.074); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.074); +} +table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.062); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.062); +} +table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.982); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.982); +} +table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.974); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.974); +} +table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.962); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.962); +} +table.dataTable.compact thead th, +table.dataTable.compact thead td, +table.dataTable.compact tfoot th, +table.dataTable.compact tfoot td, +table.dataTable.compact tbody th, +table.dataTable.compact tbody td { + padding: 4px; +} + +div.dt-container div.dt-layout-row { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + margin: 0.75em 0; +} +div.dt-container div.dt-layout-row div.dt-layout-cell { + display: flex; + justify-content: space-between; + align-items: center; +} +div.dt-container div.dt-layout-row div.dt-layout-cell.dt-layout-start { + justify-content: flex-start; + margin-right: auto; +} +div.dt-container div.dt-layout-row div.dt-layout-cell.dt-layout-end { + justify-content: flex-end; + margin-left: auto; +} +div.dt-container div.dt-layout-row div.dt-layout-cell:empty { + display: none; +} + +@media screen and (max-width: 767px) { + div.dt-container div.dt-layout-row:not(.dt-layout-table) { + display: block; + } + div.dt-container div.dt-layout-row:not(.dt-layout-table) div.dt-layout-cell { + display: block; + text-align: center; + } + div.dt-container div.dt-layout-row:not(.dt-layout-table) div.dt-layout-cell > * { + margin: 0.5em 0; + } + div.dt-container div.dt-layout-row:not(.dt-layout-table) div.dt-layout-cell.dt-layout-start { + margin-right: 0; + } + div.dt-container div.dt-layout-row:not(.dt-layout-table) div.dt-layout-cell.dt-layout-end { + margin-left: 0; + } +} +div.dt-container div.dt-layout-start > *:not(:last-child) { + margin-right: 1em; +} +div.dt-container div.dt-layout-end > *:not(:first-child) { + margin-left: 1em; +} +div.dt-container div.dt-layout-full { + width: 100%; +} +div.dt-container div.dt-layout-full > *:only-child { + margin-left: auto; + margin-right: auto; +} +div.dt-container div.dt-layout-table > div { + display: block !important; +} + +@media screen and (max-width: 767px) { + div.dt-container div.dt-layout-start > *:not(:last-child) { + margin-right: 0; + } + div.dt-container div.dt-layout-end > *:not(:first-child) { + margin-left: 0; + } +} +/* + * Control feature layout + */ +div.dt-container { + position: relative; + clear: both; +} +div.dt-container .dt-search input { + border: 1px solid #aaa; + border-radius: 3px; + padding: 5px; + background-color: transparent; + color: inherit; + margin-left: 3px; +} +div.dt-container .dt-input { + border: 1px solid #aaa; + border-radius: 3px; + padding: 5px; + background-color: transparent; + color: inherit; +} +div.dt-container select.dt-input { + padding: 4px; +} +div.dt-container .dt-paging .dt-paging-button { + box-sizing: border-box; + display: inline-block; + min-width: 1.5em; + padding: 0.5em 1em; + margin-left: 2px; + text-align: center; + text-decoration: none !important; + cursor: pointer; + color: inherit !important; + border: 1px solid transparent; + border-radius: 2px; + background: transparent; +} +div.dt-container .dt-paging .dt-paging-button.current, div.dt-container .dt-paging .dt-paging-button.current:hover { + color: inherit !important; + border: 1px solid rgba(0, 0, 0, 0.3); + background-color: rgba(0, 0, 0, 0.05); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.05)), color-stop(100%, rgba(0, 0, 0, 0.05))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* IE10+ */ + background: -o-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* W3C */ +} +div.dt-container .dt-paging .dt-paging-button.disabled, div.dt-container .dt-paging .dt-paging-button.disabled:hover, div.dt-container .dt-paging .dt-paging-button.disabled:active { + cursor: default; + color: rgba(0, 0, 0, 0.5) !important; + border: 1px solid transparent; + background: transparent; + box-shadow: none; +} +div.dt-container .dt-paging .dt-paging-button:hover { + color: white !important; + border: 1px solid #111; + background-color: #111; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #585858 0%, #111 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #585858 0%, #111 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, #585858 0%, #111 100%); /* IE10+ */ + background: -o-linear-gradient(top, #585858 0%, #111 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, #585858 0%, #111 100%); /* W3C */ +} +div.dt-container .dt-paging .dt-paging-button:active { + outline: none; + background-color: #0c0c0c; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* IE10+ */ + background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); /* W3C */ + box-shadow: inset 0 0 3px #111; +} +div.dt-container .dt-paging .ellipsis { + padding: 0 1em; +} +div.dt-container .dt-length, +div.dt-container .dt-search, +div.dt-container .dt-info, +div.dt-container .dt-processing, +div.dt-container .dt-paging { + color: inherit; +} +div.dt-container .dataTables_scroll { + clear: both; +} +div.dt-container .dataTables_scroll div.dt-scroll-body { + -webkit-overflow-scrolling: touch; +} +div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > th, div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > td, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > th, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > td { + vertical-align: middle; +} +div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > th > div.dataTables_sizing, +div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > td > div.dataTables_sizing, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > th > div.dataTables_sizing, +div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > td > div.dataTables_sizing { + height: 0; + overflow: hidden; + margin: 0 !important; + padding: 0 !important; +} +div.dt-container.dt-empty-footer tbody > tr:last-child > * { + border-bottom: 1px solid rgba(0, 0, 0, 0.3); +} +div.dt-container.dt-empty-footer .dt-scroll-body { + border-bottom: 1px solid rgba(0, 0, 0, 0.3); +} +div.dt-container.dt-empty-footer .dt-scroll-body tbody > tr:last-child > * { + border-bottom: none; +} + +html.dark { + --dt-row-hover: 255, 255, 255; + --dt-row-stripe: 255, 255, 255; + --dt-column-ordering: 255, 255, 255; +} +html.dark table.dataTable > thead > tr > th, +html.dark table.dataTable > thead > tr > td { + border-bottom: 1px solid rgb(89, 91, 94); +} +html.dark table.dataTable > thead > tr > th:active, +html.dark table.dataTable > thead > tr > td:active { + outline: none; +} +html.dark table.dataTable > tfoot > tr > th, +html.dark table.dataTable > tfoot > tr > td { + border-top: 1px solid rgb(89, 91, 94); +} +html.dark table.dataTable.row-border > tbody > tr > *, html.dark table.dataTable.display > tbody > tr > * { + border-top: 1px solid rgb(64, 67, 70); +} +html.dark table.dataTable.row-border > tbody > tr:first-child > *, html.dark table.dataTable.display > tbody > tr:first-child > * { + border-top: none; +} +html.dark table.dataTable.row-border > tbody > tr.selected + tr.selected > td, html.dark table.dataTable.display > tbody > tr.selected + tr.selected > td { + border-top-color: rgba(13, 110, 253, 0.65); + border-top-color: rgba(var(--dt-row-selected), 0.65); +} +html.dark table.dataTable.cell-border > tbody > tr > th, +html.dark table.dataTable.cell-border > tbody > tr > td { + border-top: 1px solid rgb(64, 67, 70); + border-right: 1px solid rgb(64, 67, 70); +} +html.dark table.dataTable.cell-border > tbody > tr > th:first-child, +html.dark table.dataTable.cell-border > tbody > tr > td:first-child { + border-left: 1px solid rgb(64, 67, 70); +} +html.dark .dt-container.dt-empty-footer table.dataTable { + border-bottom: 1px solid rgb(89, 91, 94); +} +html.dark .dt-container .dt-search input, +html.dark .dt-container .dt-length select { + border: 1px solid rgba(255, 255, 255, 0.2); + background-color: var(--dt-html-background); +} +html.dark .dt-container .dt-paging .dt-paging-button.current, html.dark .dt-container .dt-paging .dt-paging-button.current:hover { + border: 1px solid rgb(89, 91, 94); + background: rgba(255, 255, 255, 0.15); +} +html.dark .dt-container .dt-paging .dt-paging-button.disabled, html.dark .dt-container .dt-paging .dt-paging-button.disabled:hover, html.dark .dt-container .dt-paging .dt-paging-button.disabled:active { + color: #666 !important; +} +html.dark .dt-container .dt-paging .dt-paging-button:hover { + border: 1px solid rgb(53, 53, 53); + background: rgb(53, 53, 53); +} +html.dark .dt-container .dt-paging .dt-paging-button:active { + background: #3a3a3a; +} + +/* + * Overrides for RTL support + */ +*[dir=rtl] table.dataTable thead th, +*[dir=rtl] table.dataTable thead td, +*[dir=rtl] table.dataTable tfoot th, +*[dir=rtl] table.dataTable tfoot td { + text-align: right; +} +*[dir=rtl] table.dataTable th.dt-type-numeric, *[dir=rtl] table.dataTable th.dt-type-date, +*[dir=rtl] table.dataTable td.dt-type-numeric, +*[dir=rtl] table.dataTable td.dt-type-date { + text-align: left; +} +*[dir=rtl] div.dt-container div.dt-layout-cell.dt-start { + text-align: right; +} +*[dir=rtl] div.dt-container div.dt-layout-cell.dt-end { + text-align: left; +} +*[dir=rtl] div.dt-container div.dt-search input { + margin: 0 3px 0 0; +} + + +@keyframes dtb-spinner { + 100% { + transform: rotate(360deg); + } +} +@-o-keyframes dtb-spinner { + 100% { + -o-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@-ms-keyframes dtb-spinner { + 100% { + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@-webkit-keyframes dtb-spinner { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@-moz-keyframes dtb-spinner { + 100% { + -moz-transform: rotate(360deg); + transform: rotate(360deg); + } +} +div.dataTables_wrapper { + position: relative; +} + +div.dt-buttons { + position: initial; +} +div.dt-buttons .dt-button { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +div.dt-button-info { + position: fixed; + top: 50%; + left: 50%; + width: 400px; + margin-top: -100px; + margin-left: -200px; + background-color: white; + border-radius: 0.75em; + box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8); + text-align: center; + z-index: 2003; + overflow: hidden; +} +div.dt-button-info h2 { + padding: 2rem 2rem 1rem 2rem; + margin: 0; + font-weight: normal; +} +div.dt-button-info > div { + padding: 1em 2em 2em 2em; +} + +div.dtb-popover-close { + position: absolute; + top: 6px; + right: 6px; + width: 22px; + height: 22px; + text-align: center; + border-radius: 3px; + cursor: pointer; + z-index: 2003; +} + +button.dtb-hide-drop { + display: none !important; +} + +div.dt-button-collection-title { + text-align: center; + padding: 0.3em 0.5em 0.5em; + margin-left: 0.5em; + margin-right: 0.5em; + font-size: 0.9em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +div.dt-button-collection-title:empty { + display: none; +} + +span.dt-button-spacer { + display: inline-block; + margin: 0.5em; + white-space: nowrap; +} +span.dt-button-spacer.bar { + border-left: 1px solid rgba(0, 0, 0, 0.3); + vertical-align: middle; + padding-left: 0.5em; +} +span.dt-button-spacer.bar:empty { + height: 1em; + width: 1px; + padding-left: 0; +} + +div.dt-button-collection .dt-button-active { + padding-right: 3em; +} +div.dt-button-collection .dt-button-active:after { + position: absolute; + top: 50%; + margin-top: -10px; + right: 1em; + display: inline-block; + content: "✓"; + color: inherit; +} +div.dt-button-collection .dt-button-active.dt-button-split { + padding-right: 0; +} +div.dt-button-collection .dt-button-active.dt-button-split:after { + display: none; +} +div.dt-button-collection .dt-button-active.dt-button-split > *:first-child { + padding-right: 3em; +} +div.dt-button-collection .dt-button-active.dt-button-split > *:first-child:after { + position: absolute; + top: 50%; + margin-top: -10px; + right: 1em; + display: inline-block; + content: "✓"; + color: inherit; +} +div.dt-button-collection .dt-button-active-a a { + padding-right: 3em; +} +div.dt-button-collection .dt-button-active-a a:after { + position: absolute; + right: 1em; + display: inline-block; + content: "✓"; + color: inherit; +} +div.dt-button-collection span.dt-button-spacer { + width: 100%; + font-size: 0.9em; + text-align: center; + margin: 0.5em 0; +} +div.dt-button-collection span.dt-button-spacer:empty { + height: 0; + width: 100%; +} +div.dt-button-collection span.dt-button-spacer.bar { + border-left: none; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding-left: 0; +} + +@media print { + table.dataTable tr > * { + box-shadow: none !important; + } +} +html.dark div.dt-button-info { + background-color: var(--dt-html-background); + border: 1px solid rgba(255, 255, 255, 0.15); +} + +div.dt-buttons > .dt-button, +div.dt-buttons > div.dt-button-split .dt-button { + position: relative; + display: inline-block; + box-sizing: border-box; + margin-left: 0.167em; + margin-right: 0.167em; + margin-bottom: 0.333em; + padding: 0.5em 1em; + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: 2px; + cursor: pointer; + font-size: 0.88em; + line-height: 1.6em; + color: inherit; + white-space: nowrap; + overflow: hidden; + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ + background: linear-gradient(to bottom, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(230, 230, 230, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + text-decoration: none; + outline: none; + text-overflow: ellipsis; +} +div.dt-buttons > .dt-button:first-child, +div.dt-buttons > div.dt-button-split .dt-button:first-child { + margin-left: 0; +} +div.dt-buttons > .dt-button.disabled, +div.dt-buttons > div.dt-button-split .dt-button.disabled { + cursor: default; + opacity: 0.4; +} +div.dt-buttons > .dt-button.dt-button-active:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled) { + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ + background: linear-gradient(to bottom, rgba(179, 179, 179, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(179, 179, 179, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); + box-shadow: inset 1px 1px 3px #999999; +} +div.dt-buttons > .dt-button.dt-button-active:not(.disabled):hover:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled):hover:not(.disabled) { + box-shadow: inset 1px 1px 3px #999999; + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ + background: linear-gradient(to bottom, rgba(128, 128, 128, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(128, 128, 128, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); +} +div.dt-buttons > .dt-button:hover, +div.dt-buttons > div.dt-button-split .dt-button:hover { + text-decoration: none; +} +div.dt-buttons > .dt-button:hover:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button:hover:not(.disabled) { + border: 1px solid #666; + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ + background: linear-gradient(to bottom, rgba(153, 153, 153, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(153, 153, 153, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); +} +div.dt-buttons > .dt-button:focus:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button:focus:not(.disabled) { + outline: 2px solid rgb(53, 132, 228); +} +div.dt-buttons > .dt-button embed, +div.dt-buttons > div.dt-button-split .dt-button embed { + outline: none; +} +div.dt-buttons > div.dt-button-split .dt-button:first-child { + border-right: 1px solid rgba(0, 0, 0, 0.15); + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +div.dt-buttons > div.dt-button-split .dt-button:first-child:hover { + border-right: 1px solid #666; +} +div.dt-buttons > div.dt-button-split .dt-button:last-child { + border-left: 1px solid transparent; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +div.dt-buttons > div.dt-button-split .dt-button:last-child:hover { + border-left: 1px solid #666; +} +div.dt-buttons span.dt-button-down-arrow { + position: relative; + top: -2px; + font-size: 10px; + padding-left: 10px; + line-height: 1em; + opacity: 0.6; +} +div.dt-buttons div.dt-button-split { + display: inline-block; +} +div.dt-buttons div.dt-button-split .dt-button:first-child { + margin-right: 0; +} +div.dt-buttons div.dt-button-split .dt-button:last-child { + margin-left: -1px; + padding-left: 0.75em; + padding-right: 0.75em; + z-index: 2; +} +div.dt-buttons div.dt-button-split .dt-button:last-child span { + padding-left: 0; +} + +div.dt-button-collection { + position: absolute; + top: 0; + left: 0; + width: 200px; + margin-top: 3px; + margin-bottom: 3px; + padding: 0.75em 0; + border: 1px solid rgba(0, 0, 0, 0.4); + background-color: white; + overflow: hidden; + z-index: 2002; + border-radius: 5px; + box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.3); + box-sizing: border-box; +} +div.dt-button-collection .dt-button { + position: relative; + left: 0; + right: 0; + width: 100%; + display: block; + float: none; + background: none; + margin: 0; + padding: 0.5em 1em; + border: none; + text-align: left; + cursor: pointer; + color: inherit; +} +div.dt-button-collection .dt-button.dt-button-active { + background: none; + box-shadow: none; +} +div.dt-button-collection .dt-button.disabled { + cursor: default; + opacity: 0.4; +} +div.dt-button-collection .dt-button:hover:not(.disabled) { + border: none; + background: rgba(153, 153, 153, 0.1); + box-shadow: none; +} +div.dt-button-collection div.dt-button-split { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + align-content: flex-start; + align-items: stretch; +} +div.dt-button-collection div.dt-button-split button.dt-button { + margin: 0; + display: inline-block; + width: 0; + flex-grow: 1; + flex-shrink: 0; + flex-basis: 50px; +} +div.dt-button-collection div.dt-button-split button.dt-button-split-drop { + min-width: 33px; + flex: 0; +} +div.dt-button-collection.fixed .dt-button { + border-radius: 0.25em; + background: rgba(255, 255, 255, 0.1); +} +div.dt-button-collection.fixed { + position: fixed; + display: block; + top: 50%; + left: 50%; + margin-left: -75px; + border-radius: 5px; + background-color: white; + padding: 0.5em; +} +div.dt-button-collection.fixed.two-column { + margin-left: -200px; +} +div.dt-button-collection.fixed.three-column { + margin-left: -225px; +} +div.dt-button-collection.fixed.four-column { + margin-left: -300px; +} +div.dt-button-collection.fixed.columns { + margin-left: -409px; +} +@media screen and (max-width: 1024px) { + div.dt-button-collection.fixed.columns { + margin-left: -308px; + } +} +@media screen and (max-width: 640px) { + div.dt-button-collection.fixed.columns { + margin-left: -203px; + } +} +@media screen and (max-width: 460px) { + div.dt-button-collection.fixed.columns { + margin-left: -100px; + } +} +div.dt-button-collection.fixed > :last-child { + max-height: 100vh; + overflow: auto; +} +div.dt-button-collection.two-column > :last-child, div.dt-button-collection.three-column > :last-child, div.dt-button-collection.four-column > :last-child { + display: block !important; + -webkit-column-gap: 8px; + -moz-column-gap: 8px; + -ms-column-gap: 8px; + -o-column-gap: 8px; + column-gap: 8px; +} +div.dt-button-collection.two-column > :last-child > *, div.dt-button-collection.three-column > :last-child > *, div.dt-button-collection.four-column > :last-child > * { + -webkit-column-break-inside: avoid; + break-inside: avoid; +} +div.dt-button-collection.two-column { + width: 400px; +} +div.dt-button-collection.two-column > :last-child { + padding-bottom: 1px; + column-count: 2; +} +div.dt-button-collection.three-column { + width: 450px; +} +div.dt-button-collection.three-column > :last-child { + padding-bottom: 1px; + column-count: 3; +} +div.dt-button-collection.four-column { + width: 600px; +} +div.dt-button-collection.four-column > :last-child { + padding-bottom: 1px; + column-count: 4; +} +div.dt-button-collection .dt-button { + border-radius: 0; +} +div.dt-button-collection.columns { + width: auto; +} +div.dt-button-collection.columns > :last-child { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + align-items: center; + gap: 6px; + width: 818px; + padding-bottom: 1px; +} +div.dt-button-collection.columns > :last-child .dt-button { + min-width: 200px; + flex: 0 1; + margin: 0; +} +div.dt-button-collection.columns.dtb-b3 > :last-child, div.dt-button-collection.columns.dtb-b2 > :last-child, div.dt-button-collection.columns.dtb-b1 > :last-child { + justify-content: space-between; +} +div.dt-button-collection.columns.dtb-b3 .dt-button { + flex: 1 1 32%; +} +div.dt-button-collection.columns.dtb-b2 .dt-button { + flex: 1 1 48%; +} +div.dt-button-collection.columns.dtb-b1 .dt-button { + flex: 1 1 100%; +} +@media screen and (max-width: 1024px) { + div.dt-button-collection.columns > :last-child { + width: 612px; + } +} +@media screen and (max-width: 640px) { + div.dt-button-collection.columns > :last-child { + width: 406px; + } + div.dt-button-collection.columns.dtb-b3 .dt-button { + flex: 0 1 32%; + } +} +@media screen and (max-width: 460px) { + div.dt-button-collection.columns > :last-child { + width: 200px; + } +} + +div.dt-button-background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.7); /* Fallback */ + background: radial-gradient(ellipse farthest-corner at center, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%); /* W3C Markup, IE10 Release Preview */ + z-index: 2001; +} + +.dt-button.processing { + color: rgba(0, 0, 0, 0.2); +} +.dt-button.processing:after { + position: absolute; + top: 50%; + left: 50%; + width: 16px; + height: 16px; + margin: -8px 0 0 -8px; + box-sizing: border-box; + display: block; + content: " "; + border: 2px solid rgb(40, 40, 40); + border-radius: 50%; + border-left-color: transparent; + border-right-color: transparent; + animation: dtb-spinner 1500ms infinite linear; + -o-animation: dtb-spinner 1500ms infinite linear; + -ms-animation: dtb-spinner 1500ms infinite linear; + -webkit-animation: dtb-spinner 1500ms infinite linear; + -moz-animation: dtb-spinner 1500ms infinite linear; +} + +@media screen and (max-width: 640px) { + div.dt-buttons { + float: none !important; + text-align: center; + } +} +html.dark div.dt-buttons > .dt-button, +html.dark div.dt-buttons > div.dt-button-split .dt-button { + border: 1px solid rgb(89, 91, 94); + background: rgba(255, 255, 255, 0.15); +} +html.dark div.dt-buttons > .dt-button.dt-button-active:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled) { + background: rgba(179, 179, 179, 0.15); + box-shadow: inset 1px 1px 2px black; +} +html.dark div.dt-buttons > .dt-button.dt-button-active:not(.disabled):hover:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled):hover:not(.disabled) { + background: rgba(128, 128, 128, 0.15); + box-shadow: inset 1px 1px 3px black; +} +html.dark div.dt-buttons > .dt-button:hover:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button:hover:not(.disabled) { + background: rgba(179, 179, 179, 0.15); +} +html.dark div.dt-buttons > .dt-button:focus:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button:focus:not(.disabled) { + outline: 2px solid rgb(110, 168, 254); +} +html.dark div.dt-buttons > div.dt-button-split .dt-button:first-child { + border-right: 1px solid rgba(255, 255, 255, 0.1); +} +html.dark div.dt-buttons > div.dt-button-split .dt-button:first-child:hover { + border-right: 1px solid rgb(89, 91, 94); +} +html.dark div.dt-buttons > div.dt-button-split .dt-button:last-child:hover { + border-left: 1px solid rgb(89, 91, 94); +} +html.dark div.dt-button-collection { + border: 1px solid rgba(255, 255, 255, 0.15); + background-color: rgb(33, 37, 41); + box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8); +} + + +body.dtcr-dragging { + overflow-x: hidden; +} + +table.dtcr-cloned.dataTable { + position: absolute !important; + background-color: rgba(255, 255, 255, 0.7); + z-index: 202; + border-radius: 4px; +} + +table.dataTable tbody tr td.dtcr-moving { + background-color: rgba(127, 127, 127, 0.15); +} +table.dataTable tbody tr td.dtcr-moving-first { + border-left: 1px solid #0259C4; +} +table.dataTable tbody tr td.dtcr-moving-last { + border-right: 1px solid #0259C4; +} + +html.dark table.dtcr-cloned.dataTable { + background-color: rgba(33, 33, 33, 0.9); +} + + +table.dataTable thead tr > .dtfc-fixed-start, +table.dataTable thead tr > .dtfc-fixed-end, +table.dataTable tfoot tr > .dtfc-fixed-start, +table.dataTable tfoot tr > .dtfc-fixed-end { + top: 0; + bottom: 0; + z-index: 3; + background-color: white; +} +table.dataTable tbody tr > .dtfc-fixed-start, +table.dataTable tbody tr > .dtfc-fixed-end { + z-index: 1; + background-color: white; +} +table.dataTable tr > .dtfc-fixed-left::after, +table.dataTable tr > .dtfc-fixed-right::after { + position: absolute; + top: 0; + bottom: 0; + width: 10px; + transition: box-shadow 0.3s; + content: ""; + pointer-events: none; +} +table.dataTable tr > .dtfc-fixed-left::after { + right: 0; + transform: translateX(100%); +} +table.dataTable tr > .dtfc-fixed-right::after { + left: 0; + transform: translateX(-80%); +} +table.dataTable.dtfc-scrolling-left tr > .dtfc-fixed-left::after { + box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.2); +} +table.dataTable.dtfc-scrolling-right tr > .dtfc-fixed-right::after { + box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.2); +} +table.dataTable.dtfc-scrolling-right tr > .dtfc-fixed-right + .dtfc-fixed-right::after { + box-shadow: none; +} + +div.dt-scroll, +div.dtfh-floatingparent { + position: relative; +} +div.dt-scroll div.dtfc-top-blocker, +div.dt-scroll div.dtfc-bottom-blocker, +div.dtfh-floatingparent div.dtfc-top-blocker, +div.dtfh-floatingparent div.dtfc-bottom-blocker { + position: absolute; + background-color: white; +} + +html.dark table.dataTable thead tr > .dtfc-fixed-start, +html.dark table.dataTable thead tr > .dtfc-fixed-end, +html.dark table.dataTable tfoot tr > .dtfc-fixed-start, +html.dark table.dataTable tfoot tr > .dtfc-fixed-end { + background-color: var(--dt-html-background); +} +html.dark table.dataTable tbody tr > .dtfc-fixed-start, +html.dark table.dataTable tbody tr > .dtfc-fixed-end { + background-color: var(--dt-html-background); +} +html.dark table.dataTable.dtfc-scrolling-left tbody > tr > .dtfc-fixed-left::after { + box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.3); +} +html.dark table.dataTable.dtfc-scrolling-right tbody > tr > .dtfc-fixed-right::after { + box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.3); +} +html.dark table.dataTable.dtfc-scrolling-right tbody > tr > .dtfc-fixed-right + .dtfc-fixed-right::after { + box-shadow: none; +} +html.dark div.dtfc-top-blocker, +html.dark div.dtfc-bottom-blocker { + background-color: var(--dt-html-background); +} + + +table.fixedHeader-floating, +table.fixedHeader-locked { + position: relative !important; + background-color: var(--dt-html-background); + background-color: var(--dt-html-background); +} + +@media print { + table.fixedHeader-floating { + display: none; + } +} + + +div.dt-button-collection { + overflow: visible !important; + z-index: 2002 !important; +} +div.dt-button-collection div.dtsb-searchBuilder { + padding-left: 1em !important; + padding-right: 1em !important; +} + +div.dt-button-collection.dtb-collection-closeable div.dtsb-titleRow { + padding-right: 40px; +} + +.dtsb-greyscale { + border: 1px solid #cecece !important; +} + +div.dtsb-logicContainer .dtsb-greyscale { + border: none !important; +} + +div.dtsb-searchBuilder { + justify-content: space-evenly; + cursor: default; + margin-bottom: 1em; + text-align: left; + width: 100%; +} +div.dtsb-searchBuilder button.dtsb-button, +div.dtsb-searchBuilder select { + font-size: 1em; +} +div.dtsb-searchBuilder div.dtsb-titleRow { + justify-content: space-evenly; + margin-bottom: 0.5em; +} +div.dtsb-searchBuilder div.dtsb-titleRow div.dtsb-title { + display: inline-block; + padding-top: 14px; +} +div.dtsb-searchBuilder div.dtsb-titleRow div.dtsb-title:empty { + display: inline; +} +div.dtsb-searchBuilder div.dtsb-titleRow button.dtsb-clearAll { + float: right; + margin-bottom: 0.8em; +} +div.dtsb-searchBuilder div.dtsb-vertical .dtsb-value, div.dtsb-searchBuilder div.dtsb-vertical .dtsb-data, div.dtsb-searchBuilder div.dtsb-vertical .dtsb-condition { + display: block; +} +div.dtsb-searchBuilder div.dtsb-group { + position: relative; + clear: both; + margin-bottom: 0.8em; +} +div.dtsb-searchBuilder div.dtsb-group button.dtsb-search { + float: right; +} +div.dtsb-searchBuilder div.dtsb-group button.dtsb-clearGroup { + margin: 2px; + text-align: center; + padding: 0; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-logicContainer { + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -o-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + position: absolute; + margin-top: 0.8em; + margin-right: 0.8em; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria { + margin-bottom: 0.8em; + display: flex; + justify-content: start; + flex-flow: row wrap; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-dropDown, +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria input.dtsb-input { + padding: 0.4em; + margin-right: 0.8em; + min-width: 5em; + max-width: 20em; + color: inherit; + font-size: 1em; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-dropDown option.dtsb-notItalic, +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria input.dtsb-input option.dtsb-notItalic { + font-style: normal; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-italic { + font-style: italic; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont { + flex: 1; + white-space: nowrap; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont span.dtsb-joiner { + margin-right: 0.8em; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont input.dtsb-value { + width: 33%; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont select, +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont input { + height: 100%; + box-sizing: border-box; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer { + margin-left: auto; + display: inline-block; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-delete, div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-right, div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-left { + margin-right: 0.8em; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-delete:last-child, div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-right:last-child, div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-left:last-child { + margin-right: 0; +} +@media screen and (max-width: 550px) { + div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria { + display: flex; + flex-flow: none; + flex-direction: column; + justify-content: start; + padding-right: calc(35px + 0.8em); + margin-bottom: 0px; + } + div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:not(:first-child), div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:not(:nth-child(2)), div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:not(:last-child) { + padding-top: 0.8em; + } + div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:first-child, div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:nth-child(2), div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:last-child { + padding-top: 0em; + } + div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-dropDown, + div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria input.dtsb-input { + max-width: none; + width: 100%; + margin-bottom: 0.8em; + margin-right: 0.8em; + } + div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont { + margin-right: 0.8em; + } + div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer { + position: absolute; + width: 35px; + display: flex; + flex-wrap: wrap-reverse; + right: 0; + } + div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button { + margin-right: 0px !important; + } +} + +div.dtsb-searchBuilder button, +div.dtsb-searchBuilder select.dtsb-dropDown, +div.dtsb-searchBuilder input { + background-color: #f9f9f9; +} +div.dtsb-searchBuilder button.dtsb-button { + position: relative; + display: inline-block; + box-sizing: border-box; + padding: 0.5em 1em; + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: 2px; + cursor: pointer; + font-size: 0.88em; + line-height: 1.6em; + color: inherit; + white-space: nowrap; + overflow: hidden; + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ + background: linear-gradient(to bottom, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + text-decoration: none; + outline: none; + text-overflow: ellipsis; +} +div.dtsb-searchBuilder button.dtsb-button:hover { + background-color: #cecece; + cursor: pointer; +} +div.dtsb-searchBuilder div.dtsb-logicContainer { + border: 1px solid rgba(0, 0, 0, 0.3); + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ + background: linear-gradient(to right, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); +} +div.dtsb-searchBuilder div.dtsb-logicContainer button { + border: 1px solid transparent; + background: transparent; +} +div.dtsb-searchBuilder button.dtsb-clearGroup { + min-width: 2em; + padding: 0; +} +div.dtsb-searchBuilder button.dtsb-iptbtn { + min-width: 100px; + text-align: left; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-logicContainer { + border: 1px solid; + border-color: #cecece; + border-radius: 3px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + align-content: flex-start; + align-items: flex-start; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-logicContainer button.dtsb-logic { + border: none; + border-radius: 0px; + flex-grow: 1; + flex-shrink: 0; + flex-basis: 3em; + margin: 0px; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-logicContainer button.dtsb-clearGroup { + border: none; + border-radius: 0px; + width: 2em; + margin: 0px; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-dropDown, +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria input.dtsb-input { + border: 1px solid; + border-radius: 3px; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-condition { + border-color: #48b13c; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-data { + border-color: #e70f00; +} +div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-value, div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria input.dtsb-value { + border-color: #0069ba; +} + +html.dark div.dtsb-searchBuilder button.dtsb-button, +html.dark div.dtsb-searchBuilder select.dtsb-dropDown, +html.dark div.dtsb-searchBuilder input.dtsb-input { + background-color: rgb(66, 69, 73) !important; + color-scheme: dark; +} +html.dark div.dtsb-searchBuilder button.dtsb-button { + border: 1px solid rgb(89, 91, 94); + background: rgba(255, 255, 255, 0.15); +} +html.dark div.dtsb-searchBuilder button.dtsb-button:hover { + background: rgba(179, 179, 179, 0.15); +} +html.dark div.dtsb-searchBuilder div.dtsb-logicContainer { + border: 1px solid rgb(89, 91, 94); +} +html.dark div.dtsb-searchBuilder .dtsb-greyscale { + border-color: rgba(255, 255, 255, 0.2) !important; +} + + diff --git a/datatables.js b/datatables.js new file mode 100644 index 0000000..aa3c7ad --- /dev/null +++ b/datatables.js @@ -0,0 +1,23405 @@ +/* + * This combined file was created by the DataTables downloader builder: + * https://datatables.net/download + * + * To rebuild or modify this file with the latest versions of the included + * software please visit: + * https://datatables.net/download/#dt/dt-2.1.7/b-3.1.2/b-colvis-3.1.2/b-html5-3.1.2/cr-2.0.4/fc-5.0.2/fh-4.0.1/sb-1.8.0 + * + * Included libraries: + * DataTables 2.1.7, Buttons 3.1.2, Column visibility 3.1.2, HTML5 export 3.1.2, ColReorder 2.0.4, FixedColumns 5.0.2, FixedHeader 4.0.1, SearchBuilder 1.8.0 + */ + +/*! DataTables 2.1.7 + * © SpryMedia Ltd - datatables.net/license + */ + +/** + * @summary DataTables + * @description Paginate, search and order HTML tables + * @version 2.1.7 + * @author SpryMedia Ltd + * @contact www.datatables.net + * @copyright SpryMedia Ltd. + * + * This source file is free software, available under the following license: + * MIT license - https://datatables.net/license + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: https://www.datatables.net + */ + +;(function (factory) { + "use strict" + + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery"], function ($) { + return factory($, window, document) + }) + } else if (typeof exports === "object") { + // CommonJS + // jQuery's factory checks for a global window - if it isn't present then it + // returns a factory function that expects the window object + var jq = require("jquery") + + if (typeof window === "undefined") { + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window + } + + if (!$) { + $ = jq(root) + } + + return factory($, root, root.document) + } + } else { + module.exports = factory(jq, window, window.document) + } + } else { + // Browser + window.DataTable = factory(jQuery, window, document) + } +})(function ($, window, document) { + "use strict" + + var DataTable = function (selector, options) { + // Check if called with a window or jQuery object for DOM less applications + // This is for backwards compatibility + if (DataTable.factory(selector, options)) { + return DataTable + } + + // When creating with `new`, create a new DataTable, returning the API instance + if (this instanceof DataTable) { + return $(selector).DataTable(options) + } else { + // Argument switching + options = selector + } + + var _that = this + var emptyInit = options === undefined + var len = this.length + + if (emptyInit) { + options = {} + } + + // Method to get DT API instance from jQuery object + this.api = function () { + return new _Api(this) + } + + this.each(function () { + // For each initialisation we want to give it a clean initialisation + // object that can be bashed around + var o = {} + var oInit = + len > 1 // optimisation for single table case + ? _fnExtend(o, options, true) + : options + + var i = 0, + iLen + var sId = this.getAttribute("id") + var defaults = DataTable.defaults + var $this = $(this) + + /* Sanity check */ + if (this.nodeName.toLowerCase() != "table") { + _fnLog(null, 0, "Non-table node initialisation (" + this.nodeName + ")", 2) + return + } + + $(this).trigger("options.dt", oInit) + + /* Backwards compatibility for the defaults */ + _fnCompatOpts(defaults) + _fnCompatCols(defaults.column) + + /* Convert the camel-case defaults to Hungarian */ + _fnCamelToHungarian(defaults, defaults, true) + _fnCamelToHungarian(defaults.column, defaults.column, true) + + /* Setting up the initialisation object */ + _fnCamelToHungarian(defaults, $.extend(oInit, $this.data()), true) + + /* Check to see if we are re-initialising a table */ + var allSettings = DataTable.settings + for (i = 0, iLen = allSettings.length; i < iLen; i++) { + var s = allSettings[i] + + /* Base check on table node */ + if (s.nTable == this || (s.nTHead && s.nTHead.parentNode == this) || (s.nTFoot && s.nTFoot.parentNode == this)) { + var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve + var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy + + if (emptyInit || bRetrieve) { + return s.oInstance + } else if (bDestroy) { + new DataTable.Api(s).destroy() + break + } else { + _fnLog(s, 0, "Cannot reinitialise DataTable", 3) + return + } + } + + /* If the element we are initialising has the same ID as a table which was previously + * initialised, but the table nodes don't match (from before) then we destroy the old + * instance by simply deleting it. This is under the assumption that the table has been + * destroyed by other methods. Anyone using non-id selectors will need to do this manually + */ + if (s.sTableId == this.id) { + allSettings.splice(i, 1) + break + } + } + + /* Ensure the table has an ID - required for accessibility */ + if (sId === null || sId === "") { + sId = "DataTables_Table_" + DataTable.ext._unique++ + this.id = sId + } + + /* Create the settings object for this table and set some of the default parameters */ + var oSettings = $.extend(true, {}, DataTable.models.oSettings, { + sDestroyWidth: $this[0].style.width, + sInstance: sId, + sTableId: sId, + colgroup: $("<colgroup>").prependTo(this), + fastData: function (row, column, type) { + return _fnGetCellData(oSettings, row, column, type) + } + }) + oSettings.nTable = this + oSettings.oInit = oInit + + allSettings.push(oSettings) + + // Make a single API instance available for internal handling + oSettings.api = new _Api(oSettings) + + // Need to add the instance after the instance after the settings object has been added + // to the settings array, so we can self reference the table instance if more than one + oSettings.oInstance = _that.length === 1 ? _that : $this.dataTable() + + // Backwards compatibility, before we apply all the defaults + _fnCompatOpts(oInit) + + // If the length menu is given, but the init display length is not, use the length menu + if (oInit.aLengthMenu && !oInit.iDisplayLength) { + oInit.iDisplayLength = Array.isArray(oInit.aLengthMenu[0]) ? oInit.aLengthMenu[0][0] : $.isPlainObject(oInit.aLengthMenu[0]) ? oInit.aLengthMenu[0].value : oInit.aLengthMenu[0] + } + + // Apply the defaults and init options to make a single init object will all + // options defined from defaults and instance options. + oInit = _fnExtend($.extend(true, {}, defaults), oInit) + + // Map the initialisation options onto the settings object + _fnMap(oSettings.oFeatures, oInit, ["bPaginate", "bLengthChange", "bFilter", "bSort", "bSortMulti", "bInfo", "bProcessing", "bAutoWidth", "bSortClasses", "bServerSide", "bDeferRender"]) + _fnMap(oSettings, oInit, [ + "ajax", + "fnFormatNumber", + "sServerMethod", + "aaSorting", + "aaSortingFixed", + "aLengthMenu", + "sPaginationType", + "iStateDuration", + "bSortCellsTop", + "iTabIndex", + "sDom", + "fnStateLoadCallback", + "fnStateSaveCallback", + "renderer", + "searchDelay", + "rowId", + "caption", + "layout", + "orderDescReverse", + "typeDetect", + ["iCookieDuration", "iStateDuration"], // backwards compat + ["oSearch", "oPreviousSearch"], + ["aoSearchCols", "aoPreSearchCols"], + ["iDisplayLength", "_iDisplayLength"] + ]) + _fnMap(oSettings.oScroll, oInit, [ + ["sScrollX", "sX"], + ["sScrollXInner", "sXInner"], + ["sScrollY", "sY"], + ["bScrollCollapse", "bCollapse"] + ]) + _fnMap(oSettings.oLanguage, oInit, "fnInfoCallback") + + /* Callback functions which are array driven */ + _fnCallbackReg(oSettings, "aoDrawCallback", oInit.fnDrawCallback) + _fnCallbackReg(oSettings, "aoStateSaveParams", oInit.fnStateSaveParams) + _fnCallbackReg(oSettings, "aoStateLoadParams", oInit.fnStateLoadParams) + _fnCallbackReg(oSettings, "aoStateLoaded", oInit.fnStateLoaded) + _fnCallbackReg(oSettings, "aoRowCallback", oInit.fnRowCallback) + _fnCallbackReg(oSettings, "aoRowCreatedCallback", oInit.fnCreatedRow) + _fnCallbackReg(oSettings, "aoHeaderCallback", oInit.fnHeaderCallback) + _fnCallbackReg(oSettings, "aoFooterCallback", oInit.fnFooterCallback) + _fnCallbackReg(oSettings, "aoInitComplete", oInit.fnInitComplete) + _fnCallbackReg(oSettings, "aoPreDrawCallback", oInit.fnPreDrawCallback) + + oSettings.rowIdFn = _fnGetObjectDataFn(oInit.rowId) + + /* Browser support detection */ + _fnBrowserDetect(oSettings) + + var oClasses = oSettings.oClasses + + $.extend(oClasses, DataTable.ext.classes, oInit.oClasses) + $this.addClass(oClasses.table) + + if (!oSettings.oFeatures.bPaginate) { + oInit.iDisplayStart = 0 + } + + if (oSettings.iInitDisplayStart === undefined) { + /* Display start point, taking into account the save saving */ + oSettings.iInitDisplayStart = oInit.iDisplayStart + oSettings._iDisplayStart = oInit.iDisplayStart + } + + var defer = oInit.iDeferLoading + if (defer !== null) { + oSettings.deferLoading = true + + var tmp = Array.isArray(defer) + oSettings._iRecordsDisplay = tmp ? defer[0] : defer + oSettings._iRecordsTotal = tmp ? defer[1] : defer + } + + /* + * Columns + * See if we should load columns automatically or use defined ones + */ + var columnsInit = [] + var thead = this.getElementsByTagName("thead") + var initHeaderLayout = _fnDetectHeader(oSettings, thead[0]) + + // If we don't have a columns array, then generate one with nulls + if (oInit.aoColumns) { + columnsInit = oInit.aoColumns + } else if (initHeaderLayout.length) { + for (i = 0, iLen = initHeaderLayout[0].length; i < iLen; i++) { + columnsInit.push(null) + } + } + + // Add the columns + for (i = 0, iLen = columnsInit.length; i < iLen; i++) { + _fnAddColumn(oSettings) + } + + // Apply the column definitions + _fnApplyColumnDefs(oSettings, oInit.aoColumnDefs, columnsInit, initHeaderLayout, function (iCol, oDef) { + _fnColumnOptions(oSettings, iCol, oDef) + }) + + /* HTML5 attribute detection - build an mData object automatically if the + * attributes are found + */ + var rowOne = $this.children("tbody").find("tr").eq(0) + + if (rowOne.length) { + var a = function (cell, name) { + return cell.getAttribute("data-" + name) !== null ? name : null + } + + $(rowOne[0]) + .children("th, td") + .each(function (i, cell) { + var col = oSettings.aoColumns[i] + + if (!col) { + _fnLog(oSettings, 0, "Incorrect column count", 18) + } + + if (col.mData === i) { + var sort = a(cell, "sort") || a(cell, "order") + var filter = a(cell, "filter") || a(cell, "search") + + if (sort !== null || filter !== null) { + col.mData = { + _: i + ".display", + sort: sort !== null ? i + ".@data-" + sort : undefined, + type: sort !== null ? i + ".@data-" + sort : undefined, + filter: filter !== null ? i + ".@data-" + filter : undefined + } + col._isArrayHost = true + + _fnColumnOptions(oSettings, i) + } + } + }) + } + + // Must be done after everything which can be overridden by the state saving! + _fnCallbackReg(oSettings, "aoDrawCallback", _fnSaveState) + + var features = oSettings.oFeatures + if (oInit.bStateSave) { + features.bStateSave = true + } + + // If aaSorting is not defined, then we use the first indicator in asSorting + // in case that has been altered, so the default sort reflects that option + if (oInit.aaSorting === undefined) { + var sorting = oSettings.aaSorting + for (i = 0, iLen = sorting.length; i < iLen; i++) { + sorting[i][1] = oSettings.aoColumns[i].asSorting[0] + } + } + + // Do a first pass on the sorting classes (allows any size changes to be taken into + // account, and also will apply sorting disabled classes if disabled + _fnSortingClasses(oSettings) + + _fnCallbackReg(oSettings, "aoDrawCallback", function () { + if (oSettings.bSorted || _fnDataSource(oSettings) === "ssp" || features.bDeferRender) { + _fnSortingClasses(oSettings) + } + }) + + /* + * Table HTML init + * Cache the header, body and footer as required, creating them if needed + */ + var caption = $this.children("caption") + + if (oSettings.caption) { + if (caption.length === 0) { + caption = $("<caption/>").appendTo($this) + } + + caption.html(oSettings.caption) + } + + // Store the caption side, so we can remove the element from the document + // when creating the element + if (caption.length) { + caption[0]._captionSide = caption.css("caption-side") + oSettings.captionNode = caption[0] + } + + if (thead.length === 0) { + thead = $("<thead/>").appendTo($this) + } + oSettings.nTHead = thead[0] + $("tr", thead).addClass(oClasses.thead.row) + + var tbody = $this.children("tbody") + if (tbody.length === 0) { + tbody = $("<tbody/>").insertAfter(thead) + } + oSettings.nTBody = tbody[0] + + var tfoot = $this.children("tfoot") + if (tfoot.length === 0) { + // If we are a scrolling table, and no footer has been given, then we need to create + // a tfoot element for the caption element to be appended to + tfoot = $("<tfoot/>").appendTo($this) + } + oSettings.nTFoot = tfoot[0] + $("tr", tfoot).addClass(oClasses.tfoot.row) + + // Copy the data index array + oSettings.aiDisplay = oSettings.aiDisplayMaster.slice() + + // Initialisation complete - table can be drawn + oSettings.bInitialised = true + + // Language definitions + var oLanguage = oSettings.oLanguage + $.extend(true, oLanguage, oInit.oLanguage) + + if (oLanguage.sUrl) { + // Get the language definitions from a file + $.ajax({ + dataType: "json", + url: oLanguage.sUrl, + success: function (json) { + _fnCamelToHungarian(defaults.oLanguage, json) + $.extend(true, oLanguage, json, oSettings.oInit.oLanguage) + + _fnCallbackFire(oSettings, null, "i18n", [oSettings], true) + _fnInitialise(oSettings) + }, + error: function () { + // Error occurred loading language file + _fnLog(oSettings, 0, "i18n file loading error", 21) + + // Continue on as best we can + _fnInitialise(oSettings) + } + }) + } else { + _fnCallbackFire(oSettings, null, "i18n", [oSettings], true) + _fnInitialise(oSettings) + } + }) + _that = null + return this + } + + /** + * DataTables extensions + * + * This namespace acts as a collection area for plug-ins that can be used to + * extend DataTables capabilities. Indeed many of the build in methods + * use this method to provide their own capabilities (sorting methods for + * example). + * + * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy + * reasons + * + * @namespace + */ + DataTable.ext = _ext = { + /** + * Buttons. For use with the Buttons extension for DataTables. This is + * defined here so other extensions can define buttons regardless of load + * order. It is _not_ used by DataTables core. + * + * @type object + * @default {} + */ + buttons: {}, + + /** + * Element class names + * + * @type object + * @default {} + */ + classes: {}, + + /** + * DataTables build type (expanded by the download builder) + * + * @type string + */ + builder: "dt/dt-2.1.7/b-3.1.2/b-colvis-3.1.2/b-html5-3.1.2/cr-2.0.4/fc-5.0.2/fh-4.0.1/sb-1.8.0", + + /** + * Error reporting. + * + * How should DataTables report an error. Can take the value 'alert', + * 'throw', 'none' or a function. + * + * @type string|function + * @default alert + */ + errMode: "alert", + + /** + * Legacy so v1 plug-ins don't throw js errors on load + */ + feature: [], + + /** + * Feature plug-ins. + * + * This is an object of callbacks which provide the features for DataTables + * to be initialised via the `layout` option. + */ + features: {}, + + /** + * Row searching. + * + * This method of searching is complimentary to the default type based + * searching, and a lot more comprehensive as it allows you complete control + * over the searching logic. Each element in this array is a function + * (parameters described below) that is called for every row in the table, + * and your logic decides if it should be included in the searching data set + * or not. + * + * Searching functions have the following input parameters: + * + * 1. `{object}` DataTables settings object: see + * {@link DataTable.models.oSettings} + * 2. `{array|object}` Data for the row to be processed (same as the + * original format that was passed in as the data source, or an array + * from a DOM data source + * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which + * can be useful to retrieve the `TR` element if you need DOM interaction. + * + * And the following return is expected: + * + * * {boolean} Include the row in the searched result set (true) or not + * (false) + * + * Note that as with the main search ability in DataTables, technically this + * is "filtering", since it is subtractive. However, for consistency in + * naming we call it searching here. + * + * @type array + * @default [] + * + * @example + * // The following example shows custom search being applied to the + * // fourth column (i.e. the data[3] index) based on two input values + * // from the end-user, matching the data in a certain range. + * $.fn.dataTable.ext.search.push( + * function( settings, data, dataIndex ) { + * var min = document.getElementById('min').value * 1; + * var max = document.getElementById('max').value * 1; + * var version = data[3] == "-" ? 0 : data[3]*1; + * + * if ( min == "" && max == "" ) { + * return true; + * } + * else if ( min == "" && version < max ) { + * return true; + * } + * else if ( min < version && "" == max ) { + * return true; + * } + * else if ( min < version && version < max ) { + * return true; + * } + * return false; + * } + * ); + */ + search: [], + + /** + * Selector extensions + * + * The `selector` option can be used to extend the options available for the + * selector modifier options (`selector-modifier` object data type) that + * each of the three built in selector types offer (row, column and cell + + * their plural counterparts). For example the Select extension uses this + * mechanism to provide an option to select only rows, columns and cells + * that have been marked as selected by the end user (`{selected: true}`), + * which can be used in conjunction with the existing built in selector + * options. + * + * Each property is an array to which functions can be pushed. The functions + * take three attributes: + * + * * Settings object for the host table + * * Options object (`selector-modifier` object type) + * * Array of selected item indexes + * + * The return is an array of the resulting item indexes after the custom + * selector has been applied. + * + * @type object + */ + selector: { + cell: [], + column: [], + row: [] + }, + + /** + * Legacy configuration options. Enable and disable legacy options that + * are available in DataTables. + * + * @type object + */ + legacy: { + /** + * Enable / disable DataTables 1.9 compatible server-side processing + * requests + * + * @type boolean + * @default null + */ + ajax: null + }, + + /** + * Pagination plug-in methods. + * + * Each entry in this object is a function and defines which buttons should + * be shown by the pagination rendering method that is used for the table: + * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the + * buttons are displayed in the document, while the functions here tell it + * what buttons to display. This is done by returning an array of button + * descriptions (what each button will do). + * + * Pagination types (the four built in options and any additional plug-in + * options defined here) can be used through the `paginationType` + * initialisation parameter. + * + * The functions defined take two parameters: + * + * 1. `{int} page` The current page index + * 2. `{int} pages` The number of pages in the table + * + * Each function is expected to return an array where each element of the + * array can be one of: + * + * * `first` - Jump to first page when activated + * * `last` - Jump to last page when activated + * * `previous` - Show previous page when activated + * * `next` - Show next page when activated + * * `{int}` - Show page of the index given + * * `{array}` - A nested array containing the above elements to add a + * containing 'DIV' element (might be useful for styling). + * + * Note that DataTables v1.9- used this object slightly differently whereby + * an object with two functions would be defined for each plug-in. That + * ability is still supported by DataTables 1.10+ to provide backwards + * compatibility, but this option of use is now decremented and no longer + * documented in DataTables 1.10+. + * + * @type object + * @default {} + * + * @example + * // Show previous, next and current page buttons only + * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { + * return [ 'previous', page, 'next' ]; + * }; + */ + pager: {}, + + renderer: { + pageButton: {}, + header: {} + }, + + /** + * Ordering plug-ins - custom data source + * + * The extension options for ordering of data available here is complimentary + * to the default type based ordering that DataTables typically uses. It + * allows much greater control over the the data that is being used to + * order a column, but is necessarily therefore more complex. + * + * This type of ordering is useful if you want to do ordering based on data + * live from the DOM (for example the contents of an 'input' element) rather + * than just the static string that DataTables knows of. + * + * The way these plug-ins work is that you create an array of the values you + * wish to be ordering for the column in question and then return that + * array. The data in the array much be in the index order of the rows in + * the table (not the currently ordering order!). Which order data gathering + * function is run here depends on the `dt-init columns.orderDataType` + * parameter that is used for the column (if any). + * + * The functions defined take two parameters: + * + * 1. `{object}` DataTables settings object: see + * {@link DataTable.models.oSettings} + * 2. `{int}` Target column index + * + * Each function is expected to return an array: + * + * * `{array}` Data for the column to be ordering upon + * + * @type array + * + * @example + * // Ordering using `input` node values + * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) + * { + * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { + * return $('input', td).val(); + * } ); + * } + */ + order: {}, + + /** + * Type based plug-ins. + * + * Each column in DataTables has a type assigned to it, either by automatic + * detection or by direct assignment using the `type` option for the column. + * The type of a column will effect how it is ordering and search (plug-ins + * can also make use of the column type if required). + * + * @namespace + */ + type: { + /** + * Automatic column class assignment + */ + className: {}, + + /** + * Type detection functions. + * + * The functions defined in this object are used to automatically detect + * a column's type, making initialisation of DataTables super easy, even + * when complex data is in the table. + * + * The functions defined take two parameters: + * + * 1. `{*}` Data from the column cell to be analysed + * 2. `{settings}` DataTables settings object. This can be used to + * perform context specific type detection - for example detection + * based on language settings such as using a comma for a decimal + * place. Generally speaking the options from the settings will not + * be required + * + * Each function is expected to return: + * + * * `{string|null}` Data type detected, or null if unknown (and thus + * pass it on to the other type detection functions. + * + * @type array + * + * @example + * // Currency type detection plug-in: + * $.fn.dataTable.ext.type.detect.push( + * function ( data, settings ) { + * // Check the numeric part + * if ( ! data.substring(1).match(/[0-9]/) ) { + * return null; + * } + * + * // Check prefixed by currency + * if ( data.charAt(0) == '$' || data.charAt(0) == '&pound;' ) { + * return 'currency'; + * } + * return null; + * } + * ); + */ + detect: [], + + /** + * Automatic renderer assignment + */ + render: {}, + + /** + * Type based search formatting. + * + * The type based searching functions can be used to pre-format the + * data to be search on. For example, it can be used to strip HTML + * tags or to de-format telephone numbers for numeric only searching. + * + * Note that is a search is not defined for a column of a given type, + * no search formatting will be performed. + * + * Pre-processing of searching data plug-ins - When you assign the sType + * for a column (or have it automatically detected for you by DataTables + * or a type detection plug-in), you will typically be using this for + * custom sorting, but it can also be used to provide custom searching + * by allowing you to pre-processing the data and returning the data in + * the format that should be searched upon. This is done by adding + * functions this object with a parameter name which matches the sType + * for that target column. This is the corollary of <i>afnSortData</i> + * for searching data. + * + * The functions defined take a single parameter: + * + * 1. `{*}` Data from the column cell to be prepared for searching + * + * Each function is expected to return: + * + * * `{string|null}` Formatted string that will be used for the searching. + * + * @type object + * @default {} + * + * @example + * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { + * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); + * } + */ + search: {}, + + /** + * Type based ordering. + * + * The column type tells DataTables what ordering to apply to the table + * when a column is sorted upon. The order for each type that is defined, + * is defined by the functions available in this object. + * + * Each ordering option can be described by three properties added to + * this object: + * + * * `{type}-pre` - Pre-formatting function + * * `{type}-asc` - Ascending order function + * * `{type}-desc` - Descending order function + * + * All three can be used together, only `{type}-pre` or only + * `{type}-asc` and `{type}-desc` together. It is generally recommended + * that only `{type}-pre` is used, as this provides the optimal + * implementation in terms of speed, although the others are provided + * for compatibility with existing Javascript sort functions. + * + * `{type}-pre`: Functions defined take a single parameter: + * + * 1. `{*}` Data from the column cell to be prepared for ordering + * + * And return: + * + * * `{*}` Data to be sorted upon + * + * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort + * functions, taking two parameters: + * + * 1. `{*}` Data to compare to the second parameter + * 2. `{*}` Data to compare to the first parameter + * + * And returning: + * + * * `{*}` Ordering match: <0 if first parameter should be sorted lower + * than the second parameter, ===0 if the two parameters are equal and + * >0 if the first parameter should be sorted height than the second + * parameter. + * + * @type object + * @default {} + * + * @example + * // Numeric ordering of formatted numbers with a pre-formatter + * $.extend( $.fn.dataTable.ext.type.order, { + * "string-pre": function(x) { + * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); + * return parseFloat( a ); + * } + * } ); + * + * @example + * // Case-sensitive string ordering, with no pre-formatting method + * $.extend( $.fn.dataTable.ext.order, { + * "string-case-asc": function(x,y) { + * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + * }, + * "string-case-desc": function(x,y) { + * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + * } + * } ); + */ + order: {} + }, + + /** + * Unique DataTables instance counter + * + * @type int + * @private + */ + _unique: 0, + + // + // Depreciated + // The following properties are retained for backwards compatibility only. + // The should not be used in new projects and will be removed in a future + // version + // + + /** + * Version check function. + * @type function + * @depreciated Since 1.10 + */ + fnVersionCheck: DataTable.fnVersionCheck, + + /** + * Index for what 'this' index API functions should use + * @type int + * @deprecated Since v1.10 + */ + iApiIndex: 0, + + /** + * Software version + * @type string + * @deprecated Since v1.10 + */ + sVersion: DataTable.version + } + + // + // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts + // + $.extend(_ext, { + afnFiltering: _ext.search, + aTypes: _ext.type.detect, + ofnSearch: _ext.type.search, + oSort: _ext.type.order, + afnSortData: _ext.order, + aoFeatures: _ext.feature, + oStdClasses: _ext.classes, + oPagination: _ext.pager + }) + + $.extend(DataTable.ext.classes, { + container: "dt-container", + empty: { + row: "dt-empty" + }, + info: { + container: "dt-info" + }, + layout: { + row: "dt-layout-row", + cell: "dt-layout-cell", + tableRow: "dt-layout-table", + tableCell: "", + start: "dt-layout-start", + end: "dt-layout-end", + full: "dt-layout-full" + }, + length: { + container: "dt-length", + select: "dt-input" + }, + order: { + canAsc: "dt-orderable-asc", + canDesc: "dt-orderable-desc", + isAsc: "dt-ordering-asc", + isDesc: "dt-ordering-desc", + none: "dt-orderable-none", + position: "sorting_" + }, + processing: { + container: "dt-processing" + }, + scrolling: { + body: "dt-scroll-body", + container: "dt-scroll", + footer: { + self: "dt-scroll-foot", + inner: "dt-scroll-footInner" + }, + header: { + self: "dt-scroll-head", + inner: "dt-scroll-headInner" + } + }, + search: { + container: "dt-search", + input: "dt-input" + }, + table: "dataTable", + tbody: { + cell: "", + row: "" + }, + thead: { + cell: "", + row: "" + }, + tfoot: { + cell: "", + row: "" + }, + paging: { + active: "current", + button: "dt-paging-button", + container: "dt-paging", + disabled: "disabled", + nav: "" + } + }) + + /* + * It is useful to have variables which are scoped locally so only the + * DataTables functions can access them and they don't leak into global space. + * At the same time these functions are often useful over multiple files in the + * core and API, so we list, or at least document, all variables which are used + * by DataTables as private variables here. This also ensures that there is no + * clashing of variable names and that they can easily referenced for reuse. + */ + + // Defined else where + // _selector_run + // _selector_opts + // _selector_row_indexes + + var _ext // DataTable.ext + var _Api // DataTable.Api + var _api_register // DataTable.Api.register + var _api_registerPlural // DataTable.Api.registerPlural + + var _re_dic = {} + var _re_new_lines = /[\r\n\u2028]/g + var _re_html = /<([^>]*>)/g + var _max_str_len = Math.pow(2, 28) + + // This is not strict ISO8601 - Date.parse() is quite lax, although + // implementations differ between browsers. + var _re_date = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/ + + // Escape regular expression special characters + var _re_escape_regex = new RegExp("(\\" + ["/", ".", "*", "+", "?", "|", "(", ")", "[", "]", "{", "}", "\\", "$", "^", "-"].join("|\\") + ")", "g") + + // https://en.wikipedia.org/wiki/Foreign_exchange_market + // - \u20BD - Russian ruble. + // - \u20a9 - South Korean Won + // - \u20BA - Turkish Lira + // - \u20B9 - Indian Rupee + // - R - Brazil (R$) and South Africa + // - fr - Swiss Franc + // - kr - Swedish krona, Norwegian krone and Danish krone + // - \u2009 is thin space and \u202F is narrow no-break space, both used in many + // - Ƀ - Bitcoin + // - Ξ - Ethereum + // standards as thousands separators. + var _re_formatted_numeric = /['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi + + var _empty = function (d) { + return !d || d === true || d === "-" ? true : false + } + + var _intVal = function (s) { + var integer = parseInt(s, 10) + return !isNaN(integer) && isFinite(s) ? integer : null + } + + // Convert from a formatted number with characters other than `.` as the + // decimal place, to a Javascript number + var _numToDecimal = function (num, decimalPoint) { + // Cache created regular expressions for speed as this function is called often + if (!_re_dic[decimalPoint]) { + _re_dic[decimalPoint] = new RegExp(_fnEscapeRegex(decimalPoint), "g") + } + return typeof num === "string" && decimalPoint !== "." ? num.replace(/\./g, "").replace(_re_dic[decimalPoint], ".") : num + } + + var _isNumber = function (d, decimalPoint, formatted, allowEmpty) { + var type = typeof d + var strType = type === "string" + + if (type === "number" || type === "bigint") { + return true + } + + // If empty return immediately so there must be a number if it is a + // formatted string (this stops the string "k", or "kr", etc being detected + // as a formatted number for currency + if (allowEmpty && _empty(d)) { + return true + } + + if (decimalPoint && strType) { + d = _numToDecimal(d, decimalPoint) + } + + if (formatted && strType) { + d = d.replace(_re_formatted_numeric, "") + } + + return !isNaN(parseFloat(d)) && isFinite(d) + } + + // A string without HTML in it can be considered to be HTML still + var _isHtml = function (d) { + return _empty(d) || typeof d === "string" + } + + // Is a string a number surrounded by HTML? + var _htmlNumeric = function (d, decimalPoint, formatted, allowEmpty) { + if (allowEmpty && _empty(d)) { + return true + } + + // input and select strings mean that this isn't just a number + if (typeof d === "string" && d.match(/<(input|select)/i)) { + return null + } + + var html = _isHtml(d) + return !html ? null : _isNumber(_stripHtml(d), decimalPoint, formatted, allowEmpty) ? true : null + } + + var _pluck = function (a, prop, prop2) { + var out = [] + var i = 0, + ien = a.length + + // Could have the test in the loop for slightly smaller code, but speed + // is essential here + if (prop2 !== undefined) { + for (; i < ien; i++) { + if (a[i] && a[i][prop]) { + out.push(a[i][prop][prop2]) + } + } + } else { + for (; i < ien; i++) { + if (a[i]) { + out.push(a[i][prop]) + } + } + } + + return out + } + + // Basically the same as _pluck, but rather than looping over `a` we use `order` + // as the indexes to pick from `a` + var _pluck_order = function (a, order, prop, prop2) { + var out = [] + var i = 0, + ien = order.length + + // Could have the test in the loop for slightly smaller code, but speed + // is essential here + if (prop2 !== undefined) { + for (; i < ien; i++) { + if (a[order[i]] && a[order[i]][prop]) { + out.push(a[order[i]][prop][prop2]) + } + } + } else { + for (; i < ien; i++) { + if (a[order[i]]) { + out.push(a[order[i]][prop]) + } + } + } + + return out + } + + var _range = function (len, start) { + var out = [] + var end + + if (start === undefined) { + start = 0 + end = len + } else { + end = start + start = len + } + + for (var i = start; i < end; i++) { + out.push(i) + } + + return out + } + + var _removeEmpty = function (a) { + var out = [] + + for (var i = 0, ien = a.length; i < ien; i++) { + if (a[i]) { + // careful - will remove all falsy values! + out.push(a[i]) + } + } + + return out + } + + // Replaceable function in api.util + var _stripHtml = function (input) { + if (!input || typeof input !== "string") { + return input + } + + // Irrelevant check to workaround CodeQL's false positive on the regex + if (input.length > _max_str_len) { + throw new Error("Exceeded max str len") + } + + var previous + + input = input.replace(_re_html, "") // Complete tags + + // Safety for incomplete script tag - use do / while to ensure that + // we get all instances + do { + previous = input + input = input.replace(/<script/i, "") + } while (input !== previous) + + return previous + } + + // Replaceable function in api.util + var _escapeHtml = function (d) { + if (Array.isArray(d)) { + d = d.join(",") + } + + return typeof d === "string" ? d.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;") : d + } + + // Remove diacritics from a string by decomposing it and then removing + // non-ascii characters + var _normalize = function (str, both) { + if (typeof str !== "string") { + return str + } + + // It is faster to just run `normalize` than it is to check if + // we need to with a regex! (Check as it isn't available in old + // Safari) + var res = str.normalize ? str.normalize("NFD") : str + + // Equally, here we check if a regex is needed or not + return res.length !== str.length ? (both === true ? str + " " : "") + res.replace(/[\u0300-\u036f]/g, "") : res + } + + /** + * Determine if all values in the array are unique. This means we can short + * cut the _unique method at the cost of a single loop. A sorted array is used + * to easily check the values. + * + * @param {array} src Source array + * @return {boolean} true if all unique, false otherwise + * @ignore + */ + var _areAllUnique = function (src) { + if (src.length < 2) { + return true + } + + var sorted = src.slice().sort() + var last = sorted[0] + + for (var i = 1, ien = sorted.length; i < ien; i++) { + if (sorted[i] === last) { + return false + } + + last = sorted[i] + } + + return true + } + + /** + * Find the unique elements in a source array. + * + * @param {array} src Source array + * @return {array} Array of unique items + * @ignore + */ + var _unique = function (src) { + if (Array.from && Set) { + return Array.from(new Set(src)) + } + + if (_areAllUnique(src)) { + return src.slice() + } + + // A faster unique method is to use object keys to identify used values, + // but this doesn't work with arrays or objects, which we must also + // consider. See jsperf.app/compare-array-unique-versions/4 for more + // information. + var out = [], + val, + i, + ien = src.length, + j, + k = 0 + + again: for (i = 0; i < ien; i++) { + val = src[i] + + for (j = 0; j < k; j++) { + if (out[j] === val) { + continue again + } + } + + out.push(val) + k++ + } + + return out + } + + // Surprisingly this is faster than [].concat.apply + // https://jsperf.com/flatten-an-array-loop-vs-reduce/2 + var _flatten = function (out, val) { + if (Array.isArray(val)) { + for (var i = 0; i < val.length; i++) { + _flatten(out, val[i]) + } + } else { + out.push(val) + } + + return out + } + + // Similar to jQuery's addClass, but use classList.add + function _addClass(el, name) { + if (name) { + name.split(" ").forEach(function (n) { + if (n) { + // `add` does deduplication, so no need to check `contains` + el.classList.add(n) + } + }) + } + } + + /** + * DataTables utility methods + * + * This namespace provides helper methods that DataTables uses internally to + * create a DataTable, but which are not exclusively used only for DataTables. + * These methods can be used by extension authors to save the duplication of + * code. + * + * @namespace + */ + DataTable.util = { + /** + * Return a string with diacritic characters decomposed + * @param {*} mixed Function or string to normalize + * @param {*} both Return original string and the normalized string + * @returns String or undefined + */ + diacritics: function (mixed, both) { + var type = typeof mixed + + if (type !== "function") { + return _normalize(mixed, both) + } + _normalize = mixed + }, + + /** + * Debounce a function + * + * @param {function} fn Function to be called + * @param {integer} freq Call frequency in mS + * @return {function} Wrapped function + */ + debounce: function (fn, timeout) { + var timer + + return function () { + var that = this + var args = arguments + + clearTimeout(timer) + + timer = setTimeout(function () { + fn.apply(that, args) + }, timeout || 250) + } + }, + + /** + * Throttle the calls to a function. Arguments and context are maintained + * for the throttled function. + * + * @param {function} fn Function to be called + * @param {integer} freq Call frequency in mS + * @return {function} Wrapped function + */ + throttle: function (fn, freq) { + var frequency = freq !== undefined ? freq : 200, + last, + timer + + return function () { + var that = this, + now = +new Date(), + args = arguments + + if (last && now < last + frequency) { + clearTimeout(timer) + + timer = setTimeout(function () { + last = undefined + fn.apply(that, args) + }, frequency) + } else { + last = now + fn.apply(that, args) + } + } + }, + + /** + * Escape a string such that it can be used in a regular expression + * + * @param {string} val string to escape + * @returns {string} escaped string + */ + escapeRegex: function (val) { + return val.replace(_re_escape_regex, "\\$1") + }, + + /** + * Create a function that will write to a nested object or array + * @param {*} source JSON notation string + * @returns Write function + */ + set: function (source) { + if ($.isPlainObject(source)) { + /* Unlike get, only the underscore (global) option is used for for + * setting data since we don't know the type here. This is why an object + * option is not documented for `mData` (which is read/write), but it is + * for `mRender` which is read only. + */ + return DataTable.util.set(source._) + } else if (source === null) { + // Nothing to do when the data source is null + return function () {} + } else if (typeof source === "function") { + return function (data, val, meta) { + source(data, "set", val, meta) + } + } else if (typeof source === "string" && (source.indexOf(".") !== -1 || source.indexOf("[") !== -1 || source.indexOf("(") !== -1)) { + // Like the get, we need to get data from a nested object + var setData = function (data, val, src) { + var a = _fnSplitObjNotation(src), + b + var aLast = a[a.length - 1] + var arrayNotation, funcNotation, o, innerSrc + + for (var i = 0, iLen = a.length - 1; i < iLen; i++) { + // Protect against prototype pollution + if (a[i] === "__proto__" || a[i] === "constructor") { + throw new Error("Cannot set prototype values") + } + + // Check if we are dealing with an array notation request + arrayNotation = a[i].match(__reArray) + funcNotation = a[i].match(__reFn) + + if (arrayNotation) { + a[i] = a[i].replace(__reArray, "") + data[a[i]] = [] + + // Get the remainder of the nested object to set so we can recurse + b = a.slice() + b.splice(0, i + 1) + innerSrc = b.join(".") + + // Traverse each entry in the array setting the properties requested + if (Array.isArray(val)) { + for (var j = 0, jLen = val.length; j < jLen; j++) { + o = {} + setData(o, val[j], innerSrc) + data[a[i]].push(o) + } + } else { + // We've been asked to save data to an array, but it + // isn't array data to be saved. Best that can be done + // is to just save the value. + data[a[i]] = val + } + + // The inner call to setData has already traversed through the remainder + // of the source and has set the data, thus we can exit here + return + } else if (funcNotation) { + // Function call + a[i] = a[i].replace(__reFn, "") + data = data[a[i]](val) + } + + // If the nested object doesn't currently exist - since we are + // trying to set the value - create it + if (data[a[i]] === null || data[a[i]] === undefined) { + data[a[i]] = {} + } + data = data[a[i]] + } + + // Last item in the input - i.e, the actual set + if (aLast.match(__reFn)) { + // Function call + data = data[aLast.replace(__reFn, "")](val) + } else { + // If array notation is used, we just want to strip it and use the property name + // and assign the value. If it isn't used, then we get the result we want anyway + data[aLast.replace(__reArray, "")] = val + } + } + + return function (data, val) { + // meta is also passed in, but not used + return setData(data, val, source) + } + } else { + // Array or flat object mapping + return function (data, val) { + // meta is also passed in, but not used + data[source] = val + } + } + }, + + /** + * Create a function that will read nested objects from arrays, based on JSON notation + * @param {*} source JSON notation string + * @returns Value read + */ + get: function (source) { + if ($.isPlainObject(source)) { + // Build an object of get functions, and wrap them in a single call + var o = {} + $.each(source, function (key, val) { + if (val) { + o[key] = DataTable.util.get(val) + } + }) + + return function (data, type, row, meta) { + var t = o[type] || o._ + return t !== undefined ? t(data, type, row, meta) : data + } + } else if (source === null) { + // Give an empty string for rendering / sorting etc + return function (data) { + // type, row and meta also passed, but not used + return data + } + } else if (typeof source === "function") { + return function (data, type, row, meta) { + return source(data, type, row, meta) + } + } else if (typeof source === "string" && (source.indexOf(".") !== -1 || source.indexOf("[") !== -1 || source.indexOf("(") !== -1)) { + /* If there is a . in the source string then the data source is in a + * nested object so we loop over the data for each level to get the next + * level down. On each loop we test for undefined, and if found immediately + * return. This allows entire objects to be missing and sDefaultContent to + * be used if defined, rather than throwing an error + */ + var fetchData = function (data, type, src) { + var arrayNotation, funcNotation, out, innerSrc + + if (src !== "") { + var a = _fnSplitObjNotation(src) + + for (var i = 0, iLen = a.length; i < iLen; i++) { + // Check if we are dealing with special notation + arrayNotation = a[i].match(__reArray) + funcNotation = a[i].match(__reFn) + + if (arrayNotation) { + // Array notation + a[i] = a[i].replace(__reArray, "") + + // Condition allows simply [] to be passed in + if (a[i] !== "") { + data = data[a[i]] + } + out = [] + + // Get the remainder of the nested object to get + a.splice(0, i + 1) + innerSrc = a.join(".") + + // Traverse each entry in the array getting the properties requested + if (Array.isArray(data)) { + for (var j = 0, jLen = data.length; j < jLen; j++) { + out.push(fetchData(data[j], type, innerSrc)) + } + } + + // If a string is given in between the array notation indicators, that + // is used to join the strings together, otherwise an array is returned + var join = arrayNotation[0].substring(1, arrayNotation[0].length - 1) + data = join === "" ? out : out.join(join) + + // The inner call to fetchData has already traversed through the remainder + // of the source requested, so we exit from the loop + break + } else if (funcNotation) { + // Function call + a[i] = a[i].replace(__reFn, "") + data = data[a[i]]() + continue + } + + if (data === null || data[a[i]] === null) { + return null + } else if (data === undefined || data[a[i]] === undefined) { + return undefined + } + + data = data[a[i]] + } + } + + return data + } + + return function (data, type) { + // row and meta also passed, but not used + return fetchData(data, type, source) + } + } else { + // Array or flat object mapping + return function (data) { + // row and meta also passed, but not used + return data[source] + } + } + }, + + stripHtml: function (mixed) { + var type = typeof mixed + + if (type === "function") { + _stripHtml = mixed + return + } else if (type === "string") { + return _stripHtml(mixed) + } + return mixed + }, + + escapeHtml: function (mixed) { + var type = typeof mixed + + if (type === "function") { + _escapeHtml = mixed + return + } else if (type === "string" || Array.isArray(mixed)) { + return _escapeHtml(mixed) + } + return mixed + }, + + unique: _unique + } + + /** + * Create a mapping object that allows camel case parameters to be looked up + * for their Hungarian counterparts. The mapping is stored in a private + * parameter called `_hungarianMap` which can be accessed on the source object. + * @param {object} o + * @memberof DataTable#oApi + */ + function _fnHungarianMap(o) { + var hungarian = "a aa ai ao as b fn i m o s ", + match, + newKey, + map = {} + + $.each(o, function (key) { + match = key.match(/^([^A-Z]+?)([A-Z])/) + + if (match && hungarian.indexOf(match[1] + " ") !== -1) { + newKey = key.replace(match[0], match[2].toLowerCase()) + map[newKey] = key + + if (match[1] === "o") { + _fnHungarianMap(o[key]) + } + } + }) + + o._hungarianMap = map + } + + /** + * Convert from camel case parameters to Hungarian, based on a Hungarian map + * created by _fnHungarianMap. + * @param {object} src The model object which holds all parameters that can be + * mapped. + * @param {object} user The object to convert from camel case to Hungarian. + * @param {boolean} force When set to `true`, properties which already have a + * Hungarian value in the `user` object will be overwritten. Otherwise they + * won't be. + * @memberof DataTable#oApi + */ + function _fnCamelToHungarian(src, user, force) { + if (!src._hungarianMap) { + _fnHungarianMap(src) + } + + var hungarianKey + + $.each(user, function (key) { + hungarianKey = src._hungarianMap[key] + + if (hungarianKey !== undefined && (force || user[hungarianKey] === undefined)) { + // For objects, we need to buzz down into the object to copy parameters + if (hungarianKey.charAt(0) === "o") { + // Copy the camelCase options over to the hungarian + if (!user[hungarianKey]) { + user[hungarianKey] = {} + } + $.extend(true, user[hungarianKey], user[key]) + + _fnCamelToHungarian(src[hungarianKey], user[hungarianKey], force) + } else { + user[hungarianKey] = user[key] + } + } + }) + } + + /** + * Map one parameter onto another + * @param {object} o Object to map + * @param {*} knew The new parameter name + * @param {*} old The old parameter name + */ + var _fnCompatMap = function (o, knew, old) { + if (o[knew] !== undefined) { + o[old] = o[knew] + } + } + + /** + * Provide backwards compatibility for the main DT options. Note that the new + * options are mapped onto the old parameters, so this is an external interface + * change only. + * @param {object} init Object to map + */ + function _fnCompatOpts(init) { + _fnCompatMap(init, "ordering", "bSort") + _fnCompatMap(init, "orderMulti", "bSortMulti") + _fnCompatMap(init, "orderClasses", "bSortClasses") + _fnCompatMap(init, "orderCellsTop", "bSortCellsTop") + _fnCompatMap(init, "order", "aaSorting") + _fnCompatMap(init, "orderFixed", "aaSortingFixed") + _fnCompatMap(init, "paging", "bPaginate") + _fnCompatMap(init, "pagingType", "sPaginationType") + _fnCompatMap(init, "pageLength", "iDisplayLength") + _fnCompatMap(init, "searching", "bFilter") + + // Boolean initialisation of x-scrolling + if (typeof init.sScrollX === "boolean") { + init.sScrollX = init.sScrollX ? "100%" : "" + } + if (typeof init.scrollX === "boolean") { + init.scrollX = init.scrollX ? "100%" : "" + } + + // Column search objects are in an array, so it needs to be converted + // element by element + var searchCols = init.aoSearchCols + + if (searchCols) { + for (var i = 0, ien = searchCols.length; i < ien; i++) { + if (searchCols[i]) { + _fnCamelToHungarian(DataTable.models.oSearch, searchCols[i]) + } + } + } + + // Enable search delay if server-side processing is enabled + if (init.serverSide && !init.searchDelay) { + init.searchDelay = 400 + } + } + + /** + * Provide backwards compatibility for column options. Note that the new options + * are mapped onto the old parameters, so this is an external interface change + * only. + * @param {object} init Object to map + */ + function _fnCompatCols(init) { + _fnCompatMap(init, "orderable", "bSortable") + _fnCompatMap(init, "orderData", "aDataSort") + _fnCompatMap(init, "orderSequence", "asSorting") + _fnCompatMap(init, "orderDataType", "sortDataType") + + // orderData can be given as an integer + var dataSort = init.aDataSort + if (typeof dataSort === "number" && !Array.isArray(dataSort)) { + init.aDataSort = [dataSort] + } + } + + /** + * Browser feature detection for capabilities, quirks + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnBrowserDetect(settings) { + // We don't need to do this every time DataTables is constructed, the values + // calculated are specific to the browser and OS configuration which we + // don't expect to change between initialisations + if (!DataTable.__browser) { + var browser = {} + DataTable.__browser = browser + + // Scrolling feature / quirks detection + var n = $("<div/>") + .css({ + position: "fixed", + top: 0, + left: -1 * window.pageXOffset, // allow for scrolling + height: 1, + width: 1, + overflow: "hidden" + }) + .append( + $("<div/>") + .css({ + position: "absolute", + top: 1, + left: 1, + width: 100, + overflow: "scroll" + }) + .append( + $("<div/>").css({ + width: "100%", + height: 10 + }) + ) + ) + .appendTo("body") + + var outer = n.children() + var inner = outer.children() + + // Get scrollbar width + browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth + + // In rtl text layout, some browsers (most, but not all) will place the + // scrollbar on the left, rather than the right. + browser.bScrollbarLeft = Math.round(inner.offset().left) !== 1 + + n.remove() + } + + $.extend(settings.oBrowser, DataTable.__browser) + settings.oScroll.iBarWidth = DataTable.__browser.barWidth + } + + /** + * Add a column to the list used for the table with default values + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnAddColumn(oSettings) { + // Add column to aoColumns array + var oDefaults = DataTable.defaults.column + var iCol = oSettings.aoColumns.length + var oCol = $.extend({}, DataTable.models.oColumn, oDefaults, { + aDataSort: oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], + mData: oDefaults.mData ? oDefaults.mData : iCol, + idx: iCol, + searchFixed: {}, + colEl: $("<col>").attr("data-dt-column", iCol) + }) + oSettings.aoColumns.push(oCol) + + // Add search object for column specific search. Note that the `searchCols[ iCol ]` + // passed into extend can be undefined. This allows the user to give a default + // with only some of the parameters defined, and also not give a default + var searchCols = oSettings.aoPreSearchCols + searchCols[iCol] = $.extend({}, DataTable.models.oSearch, searchCols[iCol]) + } + + /** + * Apply options for a column + * @param {object} oSettings dataTables settings object + * @param {int} iCol column index to consider + * @param {object} oOptions object with sType, bVisible and bSearchable etc + * @memberof DataTable#oApi + */ + function _fnColumnOptions(oSettings, iCol, oOptions) { + var oCol = oSettings.aoColumns[iCol] + + /* User specified column options */ + if (oOptions !== undefined && oOptions !== null) { + // Backwards compatibility + _fnCompatCols(oOptions) + + // Map camel case parameters to their Hungarian counterparts + _fnCamelToHungarian(DataTable.defaults.column, oOptions, true) + + /* Backwards compatibility for mDataProp */ + if (oOptions.mDataProp !== undefined && !oOptions.mData) { + oOptions.mData = oOptions.mDataProp + } + + if (oOptions.sType) { + oCol._sManualType = oOptions.sType + } + + // `class` is a reserved word in Javascript, so we need to provide + // the ability to use a valid name for the camel case input + if (oOptions.className && !oOptions.sClass) { + oOptions.sClass = oOptions.className + } + + var origClass = oCol.sClass + + $.extend(oCol, oOptions) + _fnMap(oCol, oOptions, "sWidth", "sWidthOrig") + + // Merge class from previously defined classes with this one, rather than just + // overwriting it in the extend above + if (origClass !== oCol.sClass) { + oCol.sClass = origClass + " " + oCol.sClass + } + + /* iDataSort to be applied (backwards compatibility), but aDataSort will take + * priority if defined + */ + if (oOptions.iDataSort !== undefined) { + oCol.aDataSort = [oOptions.iDataSort] + } + _fnMap(oCol, oOptions, "aDataSort") + } + + /* Cache the data get and set functions for speed */ + var mDataSrc = oCol.mData + var mData = _fnGetObjectDataFn(mDataSrc) + + // The `render` option can be given as an array to access the helper rendering methods. + // The first element is the rendering method to use, the rest are the parameters to pass + if (oCol.mRender && Array.isArray(oCol.mRender)) { + var copy = oCol.mRender.slice() + var name = copy.shift() + + oCol.mRender = DataTable.render[name].apply(window, copy) + } + + oCol._render = oCol.mRender ? _fnGetObjectDataFn(oCol.mRender) : null + + var attrTest = function (src) { + return typeof src === "string" && src.indexOf("@") !== -1 + } + oCol._bAttrSrc = $.isPlainObject(mDataSrc) && (attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter)) + oCol._setter = null + + oCol.fnGetData = function (rowData, type, meta) { + var innerData = mData(rowData, type, undefined, meta) + + return oCol._render && type ? oCol._render(innerData, type, rowData, meta) : innerData + } + oCol.fnSetData = function (rowData, val, meta) { + return _fnSetObjectDataFn(mDataSrc)(rowData, val, meta) + } + + // Indicate if DataTables should read DOM data as an object or array + // Used in _fnGetRowElements + if (typeof mDataSrc !== "number" && !oCol._isArrayHost) { + oSettings._rowReadObject = true + } + + /* Feature sorting overrides column specific when off */ + if (!oSettings.oFeatures.bSort) { + oCol.bSortable = false + } + } + + /** + * Adjust the table column widths for new data. Note: you would probably want to + * do a redraw after calling this function! + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnAdjustColumnSizing(settings) { + _fnCalculateColumnWidths(settings) + _fnColumnSizes(settings) + + var scroll = settings.oScroll + if (scroll.sY !== "" || scroll.sX !== "") { + _fnScrollDraw(settings) + } + + _fnCallbackFire(settings, null, "column-sizing", [settings]) + } + + /** + * Apply column sizes + * + * @param {*} settings DataTables settings object + */ + function _fnColumnSizes(settings) { + var cols = settings.aoColumns + + for (var i = 0; i < cols.length; i++) { + var width = _fnColumnsSumWidth(settings, [i], false, false) + + cols[i].colEl.css("width", width) + } + } + + /** + * Convert the index of a visible column to the index in the data array (take account + * of hidden columns) + * @param {object} oSettings dataTables settings object + * @param {int} iMatch Visible column index to lookup + * @returns {int} i the data index + * @memberof DataTable#oApi + */ + function _fnVisibleToColumnIndex(oSettings, iMatch) { + var aiVis = _fnGetColumns(oSettings, "bVisible") + + return typeof aiVis[iMatch] === "number" ? aiVis[iMatch] : null + } + + /** + * Convert the index of an index in the data array and convert it to the visible + * column index (take account of hidden columns) + * @param {int} iMatch Column index to lookup + * @param {object} oSettings dataTables settings object + * @returns {int} i the data index + * @memberof DataTable#oApi + */ + function _fnColumnIndexToVisible(oSettings, iMatch) { + var aiVis = _fnGetColumns(oSettings, "bVisible") + var iPos = aiVis.indexOf(iMatch) + + return iPos !== -1 ? iPos : null + } + + /** + * Get the number of visible columns + * @param {object} oSettings dataTables settings object + * @returns {int} i the number of visible columns + * @memberof DataTable#oApi + */ + function _fnVisbleColumns(settings) { + var layout = settings.aoHeader + var columns = settings.aoColumns + var vis = 0 + + if (layout.length) { + for (var i = 0, ien = layout[0].length; i < ien; i++) { + if (columns[i].bVisible && $(layout[0][i].cell).css("display") !== "none") { + vis++ + } + } + } + + return vis + } + + /** + * Get an array of column indexes that match a given property + * @param {object} oSettings dataTables settings object + * @param {string} sParam Parameter in aoColumns to look for - typically + * bVisible or bSearchable + * @returns {array} Array of indexes with matched properties + * @memberof DataTable#oApi + */ + function _fnGetColumns(oSettings, sParam) { + var a = [] + + oSettings.aoColumns.map(function (val, i) { + if (val[sParam]) { + a.push(i) + } + }) + + return a + } + + /** + * Allow the result from a type detection function to be `true` while + * translating that into a string. Old type detection functions will + * return the type name if it passes. An obect store would be better, + * but not backwards compatible. + * + * @param {*} typeDetect Object or function for type detection + * @param {*} res Result from the type detection function + * @returns Type name or false + */ + function _typeResult(typeDetect, res) { + return res === true ? typeDetect._name : res + } + + /** + * Calculate the 'type' of a column + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnColumnTypes(settings) { + var columns = settings.aoColumns + var data = settings.aoData + var types = DataTable.ext.type.detect + var i, ien, j, jen, k, ken + var col, detectedType, cache + + // For each column, spin over the data type detection functions, seeing if one matches + for (i = 0, ien = columns.length; i < ien; i++) { + col = columns[i] + cache = [] + + if (!col.sType && col._sManualType) { + col.sType = col._sManualType + } else if (!col.sType) { + // With SSP type detection can be unreliable and error prone, so we provide a way + // to turn it off. + if (!settings.typeDetect) { + return + } + + for (j = 0, jen = types.length; j < jen; j++) { + var typeDetect = types[j] + + // There can be either one, or three type detection functions + var oneOf = typeDetect.oneOf + var allOf = typeDetect.allOf || typeDetect + var init = typeDetect.init + var one = false + + detectedType = null + + // Fast detect based on column assignment + if (init) { + detectedType = _typeResult(typeDetect, init(settings, col, i)) + + if (detectedType) { + col.sType = detectedType + break + } + } + + for (k = 0, ken = data.length; k < ken; k++) { + if (!data[k]) { + continue + } + + // Use a cache array so we only need to get the type data + // from the formatter once (when using multiple detectors) + if (cache[k] === undefined) { + cache[k] = _fnGetCellData(settings, k, i, "type") + } + + // Only one data point in the column needs to match this function + if (oneOf && !one) { + one = _typeResult(typeDetect, oneOf(cache[k], settings)) + } + + // All data points need to match this function + detectedType = _typeResult(typeDetect, allOf(cache[k], settings)) + + // If null, then this type can't apply to this column, so + // rather than testing all cells, break out. There is an + // exception for the last type which is `html`. We need to + // scan all rows since it is possible to mix string and HTML + // types + if (!detectedType && j !== types.length - 3) { + break + } + + // Only a single match is needed for html type since it is + // bottom of the pile and very similar to string - but it + // must not be empty + if (detectedType === "html" && !_empty(cache[k])) { + break + } + } + + // Type is valid for all data points in the column - use this + // type + if ((oneOf && one && detectedType) || (!oneOf && detectedType)) { + col.sType = detectedType + break + } + } + + // Fall back - if no type was detected, always use string + if (!col.sType) { + col.sType = "string" + } + } + + // Set class names for header / footer for auto type classes + var autoClass = _ext.type.className[col.sType] + + if (autoClass) { + _columnAutoClass(settings.aoHeader, i, autoClass) + _columnAutoClass(settings.aoFooter, i, autoClass) + } + + var renderer = _ext.type.render[col.sType] + + // This can only happen once! There is no way to remove + // a renderer. After the first time the renderer has + // already been set so createTr will run the renderer itself. + if (renderer && !col._render) { + col._render = DataTable.util.get(renderer) + + _columnAutoRender(settings, i) + } + } + } + + /** + * Apply an auto detected renderer to data which doesn't yet have + * a renderer + */ + function _columnAutoRender(settings, colIdx) { + var data = settings.aoData + + for (var i = 0; i < data.length; i++) { + if (data[i].nTr) { + // We have to update the display here since there is no + // invalidation check for the data + var display = _fnGetCellData(settings, i, colIdx, "display") + + data[i].displayData[colIdx] = display + _fnWriteCell(data[i].anCells[colIdx], display) + + // No need to update sort / filter data since it has + // been invalidated and will be re-read with the + // renderer now applied + } + } + } + + /** + * Apply a class name to a column's header cells + */ + function _columnAutoClass(container, colIdx, className) { + container.forEach(function (row) { + if (row[colIdx] && row[colIdx].unique) { + _addClass(row[colIdx].cell, className) + } + }) + } + + /** + * Take the column definitions and static columns arrays and calculate how + * they relate to column indexes. The callback function will then apply the + * definition found for a column to a suitable configuration object. + * @param {object} oSettings dataTables settings object + * @param {array} aoColDefs The aoColumnDefs array that is to be applied + * @param {array} aoCols The aoColumns array that defines columns individually + * @param {array} headerLayout Layout for header as it was loaded + * @param {function} fn Callback function - takes two parameters, the calculated + * column index and the definition for that column. + * @memberof DataTable#oApi + */ + function _fnApplyColumnDefs(oSettings, aoColDefs, aoCols, headerLayout, fn) { + var i, iLen, j, jLen, k, kLen, def + var columns = oSettings.aoColumns + + if (aoCols) { + for (i = 0, iLen = aoCols.length; i < iLen; i++) { + if (aoCols[i] && aoCols[i].name) { + columns[i].sName = aoCols[i].name + } + } + } + + // Column definitions with aTargets + if (aoColDefs) { + /* Loop over the definitions array - loop in reverse so first instance has priority */ + for (i = aoColDefs.length - 1; i >= 0; i--) { + def = aoColDefs[i] + + /* Each definition can target multiple columns, as it is an array */ + var aTargets = def.target !== undefined ? def.target : def.targets !== undefined ? def.targets : def.aTargets + + if (!Array.isArray(aTargets)) { + aTargets = [aTargets] + } + + for (j = 0, jLen = aTargets.length; j < jLen; j++) { + var target = aTargets[j] + + if (typeof target === "number" && target >= 0) { + /* Add columns that we don't yet know about */ + while (columns.length <= target) { + _fnAddColumn(oSettings) + } + + /* Integer, basic index */ + fn(target, def) + } else if (typeof target === "number" && target < 0) { + /* Negative integer, right to left column counting */ + fn(columns.length + target, def) + } else if (typeof target === "string") { + for (k = 0, kLen = columns.length; k < kLen; k++) { + if (target === "_all") { + // Apply to all columns + fn(k, def) + } else if (target.indexOf(":name") !== -1) { + // Column selector + if (columns[k].sName === target.replace(":name", "")) { + fn(k, def) + } + } else { + // Cell selector + headerLayout.forEach(function (row) { + if (row[k]) { + var cell = $(row[k].cell) + + // Legacy support. Note that it means that we don't support + // an element name selector only, since they are treated as + // class names for 1.x compat. + if (target.match(/^[a-z][\w-]*$/i)) { + target = "." + target + } + + if (cell.is(target)) { + fn(k, def) + } + } + }) + } + } + } + } + } + } + + // Statically defined columns array + if (aoCols) { + for (i = 0, iLen = aoCols.length; i < iLen; i++) { + fn(i, aoCols[i]) + } + } + } + + /** + * Get the width for a given set of columns + * + * @param {*} settings DataTables settings object + * @param {*} targets Columns - comma separated string or array of numbers + * @param {*} original Use the original width (true) or calculated (false) + * @param {*} incVisible Include visible columns (true) or not (false) + * @returns Combined CSS value + */ + function _fnColumnsSumWidth(settings, targets, original, incVisible) { + if (!Array.isArray(targets)) { + targets = _fnColumnsFromHeader(targets) + } + + var sum = 0 + var unit + var columns = settings.aoColumns + + for (var i = 0, ien = targets.length; i < ien; i++) { + var column = columns[targets[i]] + var definedWidth = original ? column.sWidthOrig : column.sWidth + + if (!incVisible && column.bVisible === false) { + continue + } + + if (definedWidth === null || definedWidth === undefined) { + return null // can't determine a defined width - browser defined + } else if (typeof definedWidth === "number") { + unit = "px" + sum += definedWidth + } else { + var matched = definedWidth.match(/([\d\.]+)([^\d]*)/) + + if (matched) { + sum += matched[1] * 1 + unit = matched.length === 3 ? matched[2] : "px" + } + } + } + + return sum + unit + } + + function _fnColumnsFromHeader(cell) { + var attr = $(cell).closest("[data-dt-column]").attr("data-dt-column") + + if (!attr) { + return [] + } + + return attr.split(",").map(function (val) { + return val * 1 + }) + } + /** + * Add a data array to the table, creating DOM node etc. This is the parallel to + * _fnGatherData, but for adding rows from a Javascript source, rather than a + * DOM source. + * @param {object} settings dataTables settings object + * @param {array} data data array to be added + * @param {node} [tr] TR element to add to the table - optional. If not given, + * DataTables will create a row automatically + * @param {array} [tds] Array of TD|TH elements for the row - must be given + * if nTr is. + * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed + * @memberof DataTable#oApi + */ + function _fnAddData(settings, dataIn, tr, tds) { + /* Create the object for storing information about this new row */ + var rowIdx = settings.aoData.length + var rowModel = $.extend(true, {}, DataTable.models.oRow, { + src: tr ? "dom" : "data", + idx: rowIdx + }) + + rowModel._aData = dataIn + settings.aoData.push(rowModel) + + var columns = settings.aoColumns + + for (var i = 0, iLen = columns.length; i < iLen; i++) { + // Invalidate the column types as the new data needs to be revalidated + columns[i].sType = null + } + + /* Add to the display array */ + settings.aiDisplayMaster.push(rowIdx) + + var id = settings.rowIdFn(dataIn) + if (id !== undefined) { + settings.aIds[id] = rowModel + } + + /* Create the DOM information, or register it if already present */ + if (tr || !settings.oFeatures.bDeferRender) { + _fnCreateTr(settings, rowIdx, tr, tds) + } + + return rowIdx + } + + /** + * Add one or more TR elements to the table. Generally we'd expect to + * use this for reading data from a DOM sourced table, but it could be + * used for an TR element. Note that if a TR is given, it is used (i.e. + * it is not cloned). + * @param {object} settings dataTables settings object + * @param {array|node|jQuery} trs The TR element(s) to add to the table + * @returns {array} Array of indexes for the added rows + * @memberof DataTable#oApi + */ + function _fnAddTr(settings, trs) { + var row + + // Allow an individual node to be passed in + if (!(trs instanceof $)) { + trs = $(trs) + } + + return trs.map(function (i, el) { + row = _fnGetRowElements(settings, el) + return _fnAddData(settings, row.data, el, row.cells) + }) + } + + /** + * Get the data for a given cell from the internal cache, taking into account data mapping + * @param {object} settings dataTables settings object + * @param {int} rowIdx aoData row id + * @param {int} colIdx Column index + * @param {string} type data get type ('display', 'type' 'filter|search' 'sort|order') + * @returns {*} Cell data + * @memberof DataTable#oApi + */ + function _fnGetCellData(settings, rowIdx, colIdx, type) { + if (type === "search") { + type = "filter" + } else if (type === "order") { + type = "sort" + } + + var row = settings.aoData[rowIdx] + + if (!row) { + return undefined + } + + var draw = settings.iDraw + var col = settings.aoColumns[colIdx] + var rowData = row._aData + var defaultContent = col.sDefaultContent + var cellData = col.fnGetData(rowData, type, { + settings: settings, + row: rowIdx, + col: colIdx + }) + + // Allow for a node being returned for non-display types + if (type !== "display" && cellData && typeof cellData === "object" && cellData.nodeName) { + cellData = cellData.innerHTML + } + + if (cellData === undefined) { + if (settings.iDrawError != draw && defaultContent === null) { + _fnLog(settings, 0, "Requested unknown parameter " + (typeof col.mData == "function" ? "{function}" : "'" + col.mData + "'") + " for row " + rowIdx + ", column " + colIdx, 4) + settings.iDrawError = draw + } + return defaultContent + } + + // When the data source is null and a specific data type is requested (i.e. + // not the original data), we can use default column data + if ((cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined) { + cellData = defaultContent + } else if (typeof cellData === "function") { + // If the data source is a function, then we run it and use the return, + // executing in the scope of the data object (for instances) + return cellData.call(rowData) + } + + if (cellData === null && type === "display") { + return "" + } + + if (type === "filter") { + var fomatters = DataTable.ext.type.search + + if (fomatters[col.sType]) { + cellData = fomatters[col.sType](cellData) + } + } + + return cellData + } + + /** + * Set the value for a specific cell, into the internal data cache + * @param {object} settings dataTables settings object + * @param {int} rowIdx aoData row id + * @param {int} colIdx Column index + * @param {*} val Value to set + * @memberof DataTable#oApi + */ + function _fnSetCellData(settings, rowIdx, colIdx, val) { + var col = settings.aoColumns[colIdx] + var rowData = settings.aoData[rowIdx]._aData + + col.fnSetData(rowData, val, { + settings: settings, + row: rowIdx, + col: colIdx + }) + } + + /** + * Write a value to a cell + * @param {*} td Cell + * @param {*} val Value + */ + function _fnWriteCell(td, val) { + if (val && typeof val === "object" && val.nodeName) { + $(td).empty().append(val) + } else { + td.innerHTML = val + } + } + + // Private variable that is used to match action syntax in the data property object + var __reArray = /\[.*?\]$/ + var __reFn = /\(\)$/ + + /** + * Split string on periods, taking into account escaped periods + * @param {string} str String to split + * @return {array} Split string + */ + function _fnSplitObjNotation(str) { + var parts = str.match(/(\\.|[^.])+/g) || [""] + + return parts.map(function (s) { + return s.replace(/\\\./g, ".") + }) + } + + /** + * Return a function that can be used to get data from a source object, taking + * into account the ability to use nested objects as a source + * @param {string|int|function} mSource The data source for the object + * @returns {function} Data get function + * @memberof DataTable#oApi + */ + var _fnGetObjectDataFn = DataTable.util.get + + /** + * Return a function that can be used to set data from a source object, taking + * into account the ability to use nested objects as a source + * @param {string|int|function} mSource The data source for the object + * @returns {function} Data set function + * @memberof DataTable#oApi + */ + var _fnSetObjectDataFn = DataTable.util.set + + /** + * Return an array with the full table data + * @param {object} oSettings dataTables settings object + * @returns array {array} aData Master data array + * @memberof DataTable#oApi + */ + function _fnGetDataMaster(settings) { + return _pluck(settings.aoData, "_aData") + } + + /** + * Nuke the table + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnClearTable(settings) { + settings.aoData.length = 0 + settings.aiDisplayMaster.length = 0 + settings.aiDisplay.length = 0 + settings.aIds = {} + } + + /** + * Mark cached data as invalid such that a re-read of the data will occur when + * the cached data is next requested. Also update from the data source object. + * + * @param {object} settings DataTables settings object + * @param {int} rowIdx Row index to invalidate + * @param {string} [src] Source to invalidate from: undefined, 'auto', 'dom' + * or 'data' + * @param {int} [colIdx] Column index to invalidate. If undefined the whole + * row will be invalidated + * @memberof DataTable#oApi + * + * @todo For the modularisation of v1.11 this will need to become a callback, so + * the sort and filter methods can subscribe to it. That will required + * initialisation options for sorting, which is why it is not already baked in + */ + function _fnInvalidate(settings, rowIdx, src, colIdx) { + var row = settings.aoData[rowIdx] + var i, ien + + // Remove the cached data for the row + row._aSortData = null + row._aFilterData = null + row.displayData = null + + // Are we reading last data from DOM or the data object? + if (src === "dom" || ((!src || src === "auto") && row.src === "dom")) { + // Read the data from the DOM + row._aData = _fnGetRowElements(settings, row, colIdx, colIdx === undefined ? undefined : row._aData).data + } else { + // Reading from data object, update the DOM + var cells = row.anCells + var display = _fnGetRowDisplay(settings, rowIdx) + + if (cells) { + if (colIdx !== undefined) { + _fnWriteCell(cells[colIdx], display[colIdx]) + } else { + for (i = 0, ien = cells.length; i < ien; i++) { + _fnWriteCell(cells[i], display[i]) + } + } + } + } + + // Column specific invalidation + var cols = settings.aoColumns + if (colIdx !== undefined) { + // Type - the data might have changed + cols[colIdx].sType = null + + // Max length string. Its a fairly cheep recalculation, so not worth + // something more complicated + cols[colIdx].maxLenString = null + } else { + for (i = 0, ien = cols.length; i < ien; i++) { + cols[i].sType = null + cols[i].maxLenString = null + } + + // Update DataTables special `DT_*` attributes for the row + _fnRowAttributes(settings, row) + } + } + + /** + * Build a data source object from an HTML row, reading the contents of the + * cells that are in the row. + * + * @param {object} settings DataTables settings object + * @param {node|object} TR element from which to read data or existing row + * object from which to re-read the data from the cells + * @param {int} [colIdx] Optional column index + * @param {array|object} [d] Data source object. If `colIdx` is given then this + * parameter should also be given and will be used to write the data into. + * Only the column in question will be written + * @returns {object} Object with two parameters: `data` the data read, in + * document order, and `cells` and array of nodes (they can be useful to the + * caller, so rather than needing a second traversal to get them, just return + * them from here). + * @memberof DataTable#oApi + */ + function _fnGetRowElements(settings, row, colIdx, d) { + var tds = [], + td = row.firstChild, + name, + col, + i = 0, + contents, + columns = settings.aoColumns, + objectRead = settings._rowReadObject + + // Allow the data object to be passed in, or construct + d = d !== undefined ? d : objectRead ? {} : [] + + var attr = function (str, td) { + if (typeof str === "string") { + var idx = str.indexOf("@") + + if (idx !== -1) { + var attr = str.substring(idx + 1) + var setter = _fnSetObjectDataFn(str) + setter(d, td.getAttribute(attr)) + } + } + } + + // Read data from a cell and store into the data object + var cellProcess = function (cell) { + if (colIdx === undefined || colIdx === i) { + col = columns[i] + contents = cell.innerHTML.trim() + + if (col && col._bAttrSrc) { + var setter = _fnSetObjectDataFn(col.mData._) + setter(d, contents) + + attr(col.mData.sort, cell) + attr(col.mData.type, cell) + attr(col.mData.filter, cell) + } else { + // Depending on the `data` option for the columns the data can + // be read to either an object or an array. + if (objectRead) { + if (!col._setter) { + // Cache the setter function + col._setter = _fnSetObjectDataFn(col.mData) + } + col._setter(d, contents) + } else { + d[i] = contents + } + } + } + + i++ + } + + if (td) { + // `tr` element was passed in + while (td) { + name = td.nodeName.toUpperCase() + + if (name == "TD" || name == "TH") { + cellProcess(td) + tds.push(td) + } + + td = td.nextSibling + } + } else { + // Existing row object passed in + tds = row.anCells + + for (var j = 0, jen = tds.length; j < jen; j++) { + cellProcess(tds[j]) + } + } + + // Read the ID from the DOM if present + var rowNode = row.firstChild ? row : row.nTr + + if (rowNode) { + var id = rowNode.getAttribute("id") + + if (id) { + _fnSetObjectDataFn(settings.rowId)(d, id) + } + } + + return { + data: d, + cells: tds + } + } + + /** + * Render and cache a row's display data for the columns, if required + * @returns + */ + function _fnGetRowDisplay(settings, rowIdx) { + var rowModal = settings.aoData[rowIdx] + var columns = settings.aoColumns + + if (!rowModal.displayData) { + // Need to render and cache + rowModal.displayData = [] + + for (var colIdx = 0, len = columns.length; colIdx < len; colIdx++) { + rowModal.displayData.push(_fnGetCellData(settings, rowIdx, colIdx, "display")) + } + } + + return rowModal.displayData + } + + /** + * Create a new TR element (and it's TD children) for a row + * @param {object} oSettings dataTables settings object + * @param {int} iRow Row to consider + * @param {node} [nTrIn] TR element to add to the table - optional. If not given, + * DataTables will create a row automatically + * @param {array} [anTds] Array of TD|TH elements for the row - must be given + * if nTr is. + * @memberof DataTable#oApi + */ + function _fnCreateTr(oSettings, iRow, nTrIn, anTds) { + var row = oSettings.aoData[iRow], + rowData = row._aData, + cells = [], + nTr, + nTd, + oCol, + i, + iLen, + create, + trClass = oSettings.oClasses.tbody.row + + if (row.nTr === null) { + nTr = nTrIn || document.createElement("tr") + + row.nTr = nTr + row.anCells = cells + + _addClass(nTr, trClass) + + /* Use a private property on the node to allow reserve mapping from the node + * to the aoData array for fast look up + */ + nTr._DT_RowIndex = iRow + + /* Special parameters can be given by the data source to be used on the row */ + _fnRowAttributes(oSettings, row) + + /* Process each column */ + for (i = 0, iLen = oSettings.aoColumns.length; i < iLen; i++) { + oCol = oSettings.aoColumns[i] + create = nTrIn && anTds[i] ? false : true + + nTd = create ? document.createElement(oCol.sCellType) : anTds[i] + + if (!nTd) { + _fnLog(oSettings, 0, "Incorrect column count", 18) + } + + nTd._DT_CellIndex = { + row: iRow, + column: i + } + + cells.push(nTd) + + var display = _fnGetRowDisplay(oSettings, iRow) + + // Need to create the HTML if new, or if a rendering function is defined + if (create || ((oCol.mRender || oCol.mData !== i) && (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i + ".display"))) { + _fnWriteCell(nTd, display[i]) + } + + // column class + _addClass(nTd, oCol.sClass) + + // Visibility - add or remove as required + if (oCol.bVisible && create) { + nTr.appendChild(nTd) + } else if (!oCol.bVisible && !create) { + nTd.parentNode.removeChild(nTd) + } + + if (oCol.fnCreatedCell) { + oCol.fnCreatedCell.call(oSettings.oInstance, nTd, _fnGetCellData(oSettings, iRow, i), rowData, iRow, i) + } + } + + _fnCallbackFire(oSettings, "aoRowCreatedCallback", "row-created", [nTr, rowData, iRow, cells]) + } else { + _addClass(row.nTr, trClass) + } + } + + /** + * Add attributes to a row based on the special `DT_*` parameters in a data + * source object. + * @param {object} settings DataTables settings object + * @param {object} DataTables row object for the row to be modified + * @memberof DataTable#oApi + */ + function _fnRowAttributes(settings, row) { + var tr = row.nTr + var data = row._aData + + if (tr) { + var id = settings.rowIdFn(data) + + if (id) { + tr.id = id + } + + if (data.DT_RowClass) { + // Remove any classes added by DT_RowClass before + var a = data.DT_RowClass.split(" ") + row.__rowc = row.__rowc ? _unique(row.__rowc.concat(a)) : a + + $(tr).removeClass(row.__rowc.join(" ")).addClass(data.DT_RowClass) + } + + if (data.DT_RowAttr) { + $(tr).attr(data.DT_RowAttr) + } + + if (data.DT_RowData) { + $(tr).data(data.DT_RowData) + } + } + } + + /** + * Create the HTML header for the table + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnBuildHead(settings, side) { + var classes = settings.oClasses + var columns = settings.aoColumns + var i, ien, row + var target = side === "header" ? settings.nTHead : settings.nTFoot + var titleProp = side === "header" ? "sTitle" : side + + // Footer might be defined + if (!target) { + return + } + + // If no cells yet and we have content for them, then create + if (side === "header" || _pluck(settings.aoColumns, titleProp).join("")) { + row = $("tr", target) + + // Add a row if needed + if (!row.length) { + row = $("<tr/>").appendTo(target) + } + + // Add the number of cells needed to make up to the number of columns + if (row.length === 1) { + var cells = $("td, th", row) + + for (i = cells.length, ien = columns.length; i < ien; i++) { + $("<th/>") + .html(columns[i][titleProp] || "") + .appendTo(row) + } + } + } + + var detected = _fnDetectHeader(settings, target, true) + + if (side === "header") { + settings.aoHeader = detected + } else { + settings.aoFooter = detected + } + + // ARIA role for the rows + $(target).children("tr").attr("role", "row") + + // Every cell needs to be passed through the renderer + $(target) + .children("tr") + .children("th, td") + .each(function () { + _fnRenderer(settings, side)(settings, $(this), classes) + }) + } + + /** + * Build a layout structure for a header or footer + * + * @param {*} settings DataTables settings + * @param {*} source Source layout array + * @param {*} incColumns What columns should be included + * @returns Layout array + */ + function _fnHeaderLayout(settings, source, incColumns) { + var row, column, cell + var local = [] + var structure = [] + var columns = settings.aoColumns + var columnCount = columns.length + var rowspan, colspan + + if (!source) { + return + } + + // Default is to work on only visible columns + if (!incColumns) { + incColumns = _range(columnCount).filter(function (idx) { + return columns[idx].bVisible + }) + } + + // Make a copy of the master layout array, but with only the columns we want + for (row = 0; row < source.length; row++) { + // Remove any columns we haven't selected + local[row] = source[row].slice().filter(function (cell, i) { + return incColumns.includes(i) + }) + + // Prep the structure array - it needs an element for each row + structure.push([]) + } + + for (row = 0; row < local.length; row++) { + for (column = 0; column < local[row].length; column++) { + rowspan = 1 + colspan = 1 + + // Check to see if there is already a cell (row/colspan) covering our target + // insert point. If there is, then there is nothing to do. + if (structure[row][column] === undefined) { + cell = local[row][column].cell + + // Expand for rowspan + while (local[row + rowspan] !== undefined && local[row][column].cell == local[row + rowspan][column].cell) { + structure[row + rowspan][column] = null + rowspan++ + } + + // And for colspan + while (local[row][column + colspan] !== undefined && local[row][column].cell == local[row][column + colspan].cell) { + // Which also needs to go over rows + for (var k = 0; k < rowspan; k++) { + structure[row + k][column + colspan] = null + } + + colspan++ + } + + var titleSpan = $("span.dt-column-title", cell) + + structure[row][column] = { + cell: cell, + colspan: colspan, + rowspan: rowspan, + title: titleSpan.length ? titleSpan.html() : $(cell).html() + } + } + } + } + + return structure + } + + /** + * Draw the header (or footer) element based on the column visibility states. + * + * @param object oSettings dataTables settings object + * @param array aoSource Layout array from _fnDetectHeader + * @memberof DataTable#oApi + */ + function _fnDrawHead(settings, source) { + var layout = _fnHeaderLayout(settings, source) + var tr, n + + for (var row = 0; row < source.length; row++) { + tr = source[row].row + + // All cells are going to be replaced, so empty out the row + // Can't use $().empty() as that kills event handlers + if (tr) { + while ((n = tr.firstChild)) { + tr.removeChild(n) + } + } + + for (var column = 0; column < layout[row].length; column++) { + var point = layout[row][column] + + if (point) { + $(point.cell).appendTo(tr).attr("rowspan", point.rowspan).attr("colspan", point.colspan) + } + } + } + } + + /** + * Insert the required TR nodes into the table for display + * @param {object} oSettings dataTables settings object + * @param ajaxComplete true after ajax call to complete rendering + * @memberof DataTable#oApi + */ + function _fnDraw(oSettings, ajaxComplete) { + // Allow for state saving and a custom start position + _fnStart(oSettings) + + /* Provide a pre-callback function which can be used to cancel the draw is false is returned */ + var aPreDraw = _fnCallbackFire(oSettings, "aoPreDrawCallback", "preDraw", [oSettings]) + if (aPreDraw.indexOf(false) !== -1) { + _fnProcessingDisplay(oSettings, false) + return + } + + var anRows = [] + var iRowCount = 0 + var bServerSide = _fnDataSource(oSettings) == "ssp" + var aiDisplay = oSettings.aiDisplay + var iDisplayStart = oSettings._iDisplayStart + var iDisplayEnd = oSettings.fnDisplayEnd() + var columns = oSettings.aoColumns + var body = $(oSettings.nTBody) + + oSettings.bDrawing = true + + /* Server-side processing draw intercept */ + if (oSettings.deferLoading) { + oSettings.deferLoading = false + oSettings.iDraw++ + _fnProcessingDisplay(oSettings, false) + } else if (!bServerSide) { + oSettings.iDraw++ + } else if (!oSettings.bDestroying && !ajaxComplete) { + // Show loading message for server-side processing + if (oSettings.iDraw === 0) { + body.empty().append(_emptyRow(oSettings)) + } + + _fnAjaxUpdate(oSettings) + return + } + + if (aiDisplay.length !== 0) { + var iStart = bServerSide ? 0 : iDisplayStart + var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd + + for (var j = iStart; j < iEnd; j++) { + var iDataIndex = aiDisplay[j] + var aoData = oSettings.aoData[iDataIndex] + if (aoData.nTr === null) { + _fnCreateTr(oSettings, iDataIndex) + } + + var nRow = aoData.nTr + + // Add various classes as needed + for (var i = 0; i < columns.length; i++) { + var col = columns[i] + var td = aoData.anCells[i] + + _addClass(td, _ext.type.className[col.sType]) // auto class + _addClass(td, oSettings.oClasses.tbody.cell) // all cells + } + + // Row callback functions - might want to manipulate the row + // iRowCount and j are not currently documented. Are they at all + // useful? + _fnCallbackFire(oSettings, "aoRowCallback", null, [nRow, aoData._aData, iRowCount, j, iDataIndex]) + + anRows.push(nRow) + iRowCount++ + } + } else { + anRows[0] = _emptyRow(oSettings) + } + + /* Header and footer callbacks */ + _fnCallbackFire(oSettings, "aoHeaderCallback", "header", [$(oSettings.nTHead).children("tr")[0], _fnGetDataMaster(oSettings), iDisplayStart, iDisplayEnd, aiDisplay]) + + _fnCallbackFire(oSettings, "aoFooterCallback", "footer", [$(oSettings.nTFoot).children("tr")[0], _fnGetDataMaster(oSettings), iDisplayStart, iDisplayEnd, aiDisplay]) + + // replaceChildren is faster, but only became widespread in 2020, + // so a fall back in jQuery is provided for older browsers. + if (body[0].replaceChildren) { + body[0].replaceChildren.apply(body[0], anRows) + } else { + body.children().detach() + body.append($(anRows)) + } + + // Empty table needs a specific class + $(oSettings.nTableWrapper).toggleClass("dt-empty-footer", $("tr", oSettings.nTFoot).length === 0) + + /* Call all required callback functions for the end of a draw */ + _fnCallbackFire(oSettings, "aoDrawCallback", "draw", [oSettings], true) + + /* Draw is complete, sorting and filtering must be as well */ + oSettings.bSorted = false + oSettings.bFiltered = false + oSettings.bDrawing = false + } + + /** + * Redraw the table - taking account of the various features which are enabled + * @param {object} oSettings dataTables settings object + * @param {boolean} [holdPosition] Keep the current paging position. By default + * the paging is reset to the first page + * @memberof DataTable#oApi + */ + function _fnReDraw(settings, holdPosition, recompute) { + var features = settings.oFeatures, + sort = features.bSort, + filter = features.bFilter + + if (recompute === undefined || recompute === true) { + // Resolve any column types that are unknown due to addition or invalidation + _fnColumnTypes(settings) + + if (sort) { + _fnSort(settings) + } + + if (filter) { + _fnFilterComplete(settings, settings.oPreviousSearch) + } else { + // No filtering, so we want to just use the display master + settings.aiDisplay = settings.aiDisplayMaster.slice() + } + } + + if (holdPosition !== true) { + settings._iDisplayStart = 0 + } + + // Let any modules know about the draw hold position state (used by + // scrolling internally) + settings._drawHold = holdPosition + + _fnDraw(settings) + + settings._drawHold = false + } + + /* + * Table is empty - create a row with an empty message in it + */ + function _emptyRow(settings) { + var oLang = settings.oLanguage + var zero = oLang.sZeroRecords + var dataSrc = _fnDataSource(settings) + + if ((settings.iDraw < 1 && dataSrc === "ssp") || (settings.iDraw <= 1 && dataSrc === "ajax")) { + zero = oLang.sLoadingRecords + } else if (oLang.sEmptyTable && settings.fnRecordsTotal() === 0) { + zero = oLang.sEmptyTable + } + + return $("<tr/>").append( + $("<td />", { + colSpan: _fnVisbleColumns(settings), + class: settings.oClasses.empty.row + }).html(zero) + )[0] + } + + /** + * Expand the layout items into an object for the rendering function + */ + function _layoutItems(row, align, items) { + if (Array.isArray(items)) { + for (var i = 0; i < items.length; i++) { + _layoutItems(row, align, items[i]) + } + + return + } + + var rowCell = row[align] + + // If it is an object, then there can be multiple features contained in it + if ($.isPlainObject(items)) { + // A feature plugin cannot be named "features" due to this check + if (items.features) { + if (items.rowId) { + row.id = items.rowId + } + if (items.rowClass) { + row.className = items.rowClass + } + + rowCell.id = items.id + rowCell.className = items.className + + _layoutItems(row, align, items.features) + } else { + Object.keys(items).map(function (key) { + rowCell.contents.push({ + feature: key, + opts: items[key] + }) + }) + } + } else { + rowCell.contents.push(items) + } + } + + /** + * Find, or create a layout row + */ + function _layoutGetRow(rows, rowNum, align) { + var row + + // Find existing rows + for (var i = 0; i < rows.length; i++) { + row = rows[i] + + if (row.rowNum === rowNum) { + // full is on its own, but start and end share a row + if ((align === "full" && row.full) || ((align === "start" || align === "end") && (row.start || row.end))) { + if (!row[align]) { + row[align] = { + contents: [] + } + } + + return row + } + } + } + + // If we get this far, then there was no match, create a new row + row = { + rowNum: rowNum + } + + row[align] = { + contents: [] + } + + rows.push(row) + + return row + } + + /** + * Convert a `layout` object given by a user to the object structure needed + * for the renderer. This is done twice, once for above and once for below + * the table. Ordering must also be considered. + * + * @param {*} settings DataTables settings object + * @param {*} layout Layout object to convert + * @param {string} side `top` or `bottom` + * @returns Converted array structure - one item for each row. + */ + function _layoutArray(settings, layout, side) { + var rows = [] + + // Split out into an array + $.each(layout, function (pos, items) { + if (items === null) { + return + } + + var parts = pos.match(/^([a-z]+)([0-9]*)([A-Za-z]*)$/) + var rowNum = parts[2] ? parts[2] * 1 : 0 + var align = parts[3] ? parts[3].toLowerCase() : "full" + + // Filter out the side we aren't interested in + if (parts[1] !== side) { + return + } + + // Get or create the row we should attach to + var row = _layoutGetRow(rows, rowNum, align) + + _layoutItems(row, align, items) + }) + + // Order by item identifier + rows.sort(function (a, b) { + var order1 = a.rowNum + var order2 = b.rowNum + + // If both in the same row, then the row with `full` comes first + if (order1 === order2) { + var ret = a.full && !b.full ? -1 : 1 + + return side === "bottom" ? ret * -1 : ret + } + + return order2 - order1 + }) + + // Invert for below the table + if (side === "bottom") { + rows.reverse() + } + + for (var row = 0; row < rows.length; row++) { + delete rows[row].rowNum + + _layoutResolve(settings, rows[row]) + } + + return rows + } + + /** + * Convert the contents of a row's layout object to nodes that can be inserted + * into the document by a renderer. Execute functions, look up plug-ins, etc. + * + * @param {*} settings DataTables settings object + * @param {*} row Layout object for this row + */ + function _layoutResolve(settings, row) { + var getFeature = function (feature, opts) { + if (!_ext.features[feature]) { + _fnLog(settings, 0, "Unknown feature: " + feature) + } + + return _ext.features[feature].apply(this, [settings, opts]) + } + + var resolve = function (item) { + if (!row[item]) { + return + } + + var line = row[item].contents + + for (var i = 0, ien = line.length; i < ien; i++) { + if (!line[i]) { + continue + } else if (typeof line[i] === "string") { + line[i] = getFeature(line[i], null) + } else if ($.isPlainObject(line[i])) { + // If it's an object, it just has feature and opts properties from + // the transform in _layoutArray + line[i] = getFeature(line[i].feature, line[i].opts) + } else if (typeof line[i].node === "function") { + line[i] = line[i].node(settings) + } else if (typeof line[i] === "function") { + var inst = line[i](settings) + + line[i] = typeof inst.node === "function" ? inst.node() : inst + } + } + } + + resolve("start") + resolve("end") + resolve("full") + } + + /** + * Add the options to the page HTML for the table + * @param {object} settings DataTables settings object + * @memberof DataTable#oApi + */ + function _fnAddOptionsHtml(settings) { + var classes = settings.oClasses + var table = $(settings.nTable) + + // Wrapper div around everything DataTables controls + var insert = $("<div/>") + .attr({ + id: settings.sTableId + "_wrapper", + class: classes.container + }) + .insertBefore(table) + + settings.nTableWrapper = insert[0] + + if (settings.sDom) { + // Legacy + _fnLayoutDom(settings, settings.sDom, insert) + } else { + var top = _layoutArray(settings, settings.layout, "top") + var bottom = _layoutArray(settings, settings.layout, "bottom") + var renderer = _fnRenderer(settings, "layout") + + // Everything above - the renderer will actually insert the contents into the document + top.forEach(function (item) { + renderer(settings, insert, item) + }) + + // The table - always the center of attention + renderer(settings, insert, { + full: { + table: true, + contents: [_fnFeatureHtmlTable(settings)] + } + }) + + // Everything below + bottom.forEach(function (item) { + renderer(settings, insert, item) + }) + } + + // Processing floats on top, so it isn't an inserted feature + _processingHtml(settings) + } + + /** + * Draw the table with the legacy DOM property + * @param {*} settings DT settings object + * @param {*} dom DOM string + * @param {*} insert Insert point + */ + function _fnLayoutDom(settings, dom, insert) { + var parts = dom.match(/(".*?")|('.*?')|./g) + var featureNode, option, newNode, next, attr + + for (var i = 0; i < parts.length; i++) { + featureNode = null + option = parts[i] + + if (option == "<") { + // New container div + newNode = $("<div/>") + + // Check to see if we should append an id and/or a class name to the container + next = parts[i + 1] + + if (next[0] == "'" || next[0] == '"') { + attr = next.replace(/['"]/g, "") + + var id = "", + className + + /* The attribute can be in the format of "#id.class", "#id" or "class" This logic + * breaks the string into parts and applies them as needed + */ + if (attr.indexOf(".") != -1) { + var split = attr.split(".") + + id = split[0] + className = split[1] + } else if (attr[0] == "#") { + id = attr + } else { + className = attr + } + + newNode.attr("id", id.substring(1)).addClass(className) + + i++ // Move along the position array + } + + insert.append(newNode) + insert = newNode + } else if (option == ">") { + // End container div + insert = insert.parent() + } else if (option == "t") { + // Table + featureNode = _fnFeatureHtmlTable(settings) + } else { + DataTable.ext.feature.forEach(function (feature) { + if (option == feature.cFeature) { + featureNode = feature.fnInit(settings) + } + }) + } + + // Add to the display + if (featureNode) { + insert.append(featureNode) + } + } + } + + /** + * Use the DOM source to create up an array of header cells. The idea here is to + * create a layout grid (array) of rows x columns, which contains a reference + * to the cell that that point in the grid (regardless of col/rowspan), such that + * any column / row could be removed and the new grid constructed + * @param {node} thead The header/footer element for the table + * @returns {array} Calculated layout array + * @memberof DataTable#oApi + */ + function _fnDetectHeader(settings, thead, write) { + var columns = settings.aoColumns + var rows = $(thead).children("tr") + var row, cell + var i, k, l, iLen, shifted, column, colspan, rowspan + var isHeader = thead && thead.nodeName.toLowerCase() === "thead" + var layout = [] + var unique + var shift = function (a, i, j) { + var k = a[i] + while (k[j]) { + j++ + } + return j + } + + // We know how many rows there are in the layout - so prep it + for (i = 0, iLen = rows.length; i < iLen; i++) { + layout.push([]) + } + + for (i = 0, iLen = rows.length; i < iLen; i++) { + row = rows[i] + column = 0 + + // For every cell in the row.. + cell = row.firstChild + while (cell) { + if (cell.nodeName.toUpperCase() == "TD" || cell.nodeName.toUpperCase() == "TH") { + var cols = [] + + // Get the col and rowspan attributes from the DOM and sanitise them + colspan = cell.getAttribute("colspan") * 1 + rowspan = cell.getAttribute("rowspan") * 1 + colspan = !colspan || colspan === 0 || colspan === 1 ? 1 : colspan + rowspan = !rowspan || rowspan === 0 || rowspan === 1 ? 1 : rowspan + + // There might be colspan cells already in this row, so shift our target + // accordingly + shifted = shift(layout, i, column) + + // Cache calculation for unique columns + unique = colspan === 1 ? true : false + + // Perform header setup + if (write) { + if (unique) { + // Allow column options to be set from HTML attributes + _fnColumnOptions(settings, shifted, $(cell).data()) + + // Get the width for the column. This can be defined from the + // width attribute, style attribute or `columns.width` option + var columnDef = columns[shifted] + var width = cell.getAttribute("width") || null + var t = cell.style.width.match(/width:\s*(\d+[pxem%]+)/) + if (t) { + width = t[1] + } + + columnDef.sWidthOrig = columnDef.sWidth || width + + if (isHeader) { + // Column title handling - can be user set, or read from the DOM + // This happens before the render, so the original is still in place + if (columnDef.sTitle !== null && !columnDef.autoTitle) { + cell.innerHTML = columnDef.sTitle + } + + if (!columnDef.sTitle && unique) { + columnDef.sTitle = _stripHtml(cell.innerHTML) + columnDef.autoTitle = true + } + } else { + // Footer specific operations + if (columnDef.footer) { + cell.innerHTML = columnDef.footer + } + } + + // Fall back to the aria-label attribute on the table header if no ariaTitle is + // provided. + if (!columnDef.ariaTitle) { + columnDef.ariaTitle = $(cell).attr("aria-label") || columnDef.sTitle + } + + // Column specific class names + if (columnDef.className) { + $(cell).addClass(columnDef.className) + } + } + + // Wrap the column title so we can write to it in future + if ($("span.dt-column-title", cell).length === 0) { + $("<span>").addClass("dt-column-title").append(cell.childNodes).appendTo(cell) + } + + if (isHeader && $("span.dt-column-order", cell).length === 0) { + $("<span>").addClass("dt-column-order").appendTo(cell) + } + } + + // If there is col / rowspan, copy the information into the layout grid + for (l = 0; l < colspan; l++) { + for (k = 0; k < rowspan; k++) { + layout[i + k][shifted + l] = { + cell: cell, + unique: unique + } + + layout[i + k].row = row + } + + cols.push(shifted + l) + } + + // Assign an attribute so spanning cells can still be identified + // as belonging to a column + cell.setAttribute("data-dt-column", _unique(cols).join(",")) + } + + cell = cell.nextSibling + } + } + + return layout + } + + /** + * Set the start position for draw + * @param {object} oSettings dataTables settings object + */ + function _fnStart(oSettings) { + var bServerSide = _fnDataSource(oSettings) == "ssp" + var iInitDisplayStart = oSettings.iInitDisplayStart + + // Check and see if we have an initial draw position from state saving + if (iInitDisplayStart !== undefined && iInitDisplayStart !== -1) { + oSettings._iDisplayStart = bServerSide ? iInitDisplayStart : iInitDisplayStart >= oSettings.fnRecordsDisplay() ? 0 : iInitDisplayStart + + oSettings.iInitDisplayStart = -1 + } + } + + /** + * Create an Ajax call based on the table's settings, taking into account that + * parameters can have multiple forms, and backwards compatibility. + * + * @param {object} oSettings dataTables settings object + * @param {array} data Data to send to the server, required by + * DataTables - may be augmented by developer callbacks + * @param {function} fn Callback function to run when data is obtained + */ + function _fnBuildAjax(oSettings, data, fn) { + var ajaxData + var ajax = oSettings.ajax + var instance = oSettings.oInstance + var callback = function (json) { + var status = oSettings.jqXHR ? oSettings.jqXHR.status : null + + if (json === null || (typeof status === "number" && status == 204)) { + json = {} + _fnAjaxDataSrc(oSettings, json, []) + } + + var error = json.error || json.sError + if (error) { + _fnLog(oSettings, 0, error) + } + + // Microsoft often wrap JSON as a string in another JSON object + // Let's handle that automatically + if (json.d && typeof json.d === "string") { + try { + json = JSON.parse(json.d) + } catch (e) { + // noop + } + } + + oSettings.json = json + + _fnCallbackFire(oSettings, null, "xhr", [oSettings, json, oSettings.jqXHR], true) + fn(json) + } + + if ($.isPlainObject(ajax) && ajax.data) { + ajaxData = ajax.data + + var newData = + typeof ajaxData === "function" + ? ajaxData(data, oSettings) // fn can manipulate data or return + : ajaxData // an object object or array to merge + + // If the function returned something, use that alone + data = typeof ajaxData === "function" && newData ? newData : $.extend(true, data, newData) + + // Remove the data property as we've resolved it already and don't want + // jQuery to do it again (it is restored at the end of the function) + delete ajax.data + } + + var baseAjax = { + url: typeof ajax === "string" ? ajax : "", + data: data, + success: callback, + dataType: "json", + cache: false, + type: oSettings.sServerMethod, + error: function (xhr, error) { + var ret = _fnCallbackFire(oSettings, null, "xhr", [oSettings, null, oSettings.jqXHR], true) + + if (ret.indexOf(true) === -1) { + if (error == "parsererror") { + _fnLog(oSettings, 0, "Invalid JSON response", 1) + } else if (xhr.readyState === 4) { + _fnLog(oSettings, 0, "Ajax error", 7) + } + } + + _fnProcessingDisplay(oSettings, false) + } + } + + // If `ajax` option is an object, extend and override our default base + if ($.isPlainObject(ajax)) { + $.extend(baseAjax, ajax) + } + + // Store the data submitted for the API + oSettings.oAjaxData = data + + // Allow plug-ins and external processes to modify the data + _fnCallbackFire(oSettings, null, "preXhr", [oSettings, data, baseAjax], true) + + if (typeof ajax === "function") { + // Is a function - let the caller define what needs to be done + oSettings.jqXHR = ajax.call(instance, data, callback, oSettings) + } else if (ajax.url === "") { + // No url, so don't load any data. Just apply an empty data array + // to the object for the callback. + var empty = {} + + DataTable.util.set(ajax.dataSrc)(empty, []) + callback(empty) + } else { + // Object to extend the base settings + oSettings.jqXHR = $.ajax(baseAjax) + } + + // Restore for next time around + if (ajaxData) { + ajax.data = ajaxData + } + } + + /** + * Update the table using an Ajax call + * @param {object} settings dataTables settings object + * @returns {boolean} Block the table drawing or not + * @memberof DataTable#oApi + */ + function _fnAjaxUpdate(settings) { + settings.iDraw++ + _fnProcessingDisplay(settings, true) + + _fnBuildAjax(settings, _fnAjaxParameters(settings), function (json) { + _fnAjaxUpdateDraw(settings, json) + }) + } + + /** + * Build up the parameters in an object needed for a server-side processing + * request. + * @param {object} oSettings dataTables settings object + * @returns {bool} block the table drawing or not + * @memberof DataTable#oApi + */ + function _fnAjaxParameters(settings) { + var columns = settings.aoColumns, + features = settings.oFeatures, + preSearch = settings.oPreviousSearch, + preColSearch = settings.aoPreSearchCols, + colData = function (idx, prop) { + return typeof columns[idx][prop] === "function" ? "function" : columns[idx][prop] + } + + return { + draw: settings.iDraw, + columns: columns.map(function (column, i) { + return { + data: colData(i, "mData"), + name: column.sName, + searchable: column.bSearchable, + orderable: column.bSortable, + search: { + value: preColSearch[i].search, + regex: preColSearch[i].regex, + fixed: Object.keys(column.searchFixed).map(function (name) { + return { + name: name, + term: column.searchFixed[name].toString() + } + }) + } + } + }), + order: _fnSortFlatten(settings).map(function (val) { + return { + column: val.col, + dir: val.dir, + name: colData(val.col, "sName") + } + }), + start: settings._iDisplayStart, + length: features.bPaginate ? settings._iDisplayLength : -1, + search: { + value: preSearch.search, + regex: preSearch.regex, + fixed: Object.keys(settings.searchFixed).map(function (name) { + return { + name: name, + term: settings.searchFixed[name].toString() + } + }) + } + } + } + + /** + * Data the data from the server (nuking the old) and redraw the table + * @param {object} oSettings dataTables settings object + * @param {object} json json data return from the server. + * @param {string} json.sEcho Tracking flag for DataTables to match requests + * @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering + * @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering + * @param {array} json.aaData The data to display on this page + * @param {string} [json.sColumns] Column ordering (sName, comma separated) + * @memberof DataTable#oApi + */ + function _fnAjaxUpdateDraw(settings, json) { + var data = _fnAjaxDataSrc(settings, json) + var draw = _fnAjaxDataSrcParam(settings, "draw", json) + var recordsTotal = _fnAjaxDataSrcParam(settings, "recordsTotal", json) + var recordsFiltered = _fnAjaxDataSrcParam(settings, "recordsFiltered", json) + + if (draw !== undefined) { + // Protect against out of sequence returns + if (draw * 1 < settings.iDraw) { + return + } + settings.iDraw = draw * 1 + } + + // No data in returned object, so rather than an array, we show an empty table + if (!data) { + data = [] + } + + _fnClearTable(settings) + settings._iRecordsTotal = parseInt(recordsTotal, 10) + settings._iRecordsDisplay = parseInt(recordsFiltered, 10) + + for (var i = 0, ien = data.length; i < ien; i++) { + _fnAddData(settings, data[i]) + } + settings.aiDisplay = settings.aiDisplayMaster.slice() + + _fnColumnTypes(settings) + _fnDraw(settings, true) + _fnInitComplete(settings) + _fnProcessingDisplay(settings, false) + } + + /** + * Get the data from the JSON data source to use for drawing a table. Using + * `_fnGetObjectDataFn` allows the data to be sourced from a property of the + * source object, or from a processing function. + * @param {object} settings dataTables settings object + * @param {object} json Data source object / array from the server + * @return {array} Array of data to use + */ + function _fnAjaxDataSrc(settings, json, write) { + var dataProp = "data" + + if ($.isPlainObject(settings.ajax) && settings.ajax.dataSrc !== undefined) { + // Could in inside a `dataSrc` object, or not! + var dataSrc = settings.ajax.dataSrc + + // string, function and object are valid types + if (typeof dataSrc === "string" || typeof dataSrc === "function") { + dataProp = dataSrc + } else if (dataSrc.data !== undefined) { + dataProp = dataSrc.data + } + } + + if (!write) { + if (dataProp === "data") { + // If the default, then we still want to support the old style, and safely ignore + // it if possible + return json.aaData || json[dataProp] + } + + return dataProp !== "" ? _fnGetObjectDataFn(dataProp)(json) : json + } + + // set + _fnSetObjectDataFn(dataProp)(json, write) + } + + /** + * Very similar to _fnAjaxDataSrc, but for the other SSP properties + * @param {*} settings DataTables settings object + * @param {*} param Target parameter + * @param {*} json JSON data + * @returns Resolved value + */ + function _fnAjaxDataSrcParam(settings, param, json) { + var dataSrc = $.isPlainObject(settings.ajax) ? settings.ajax.dataSrc : null + + if (dataSrc && dataSrc[param]) { + // Get from custom location + return _fnGetObjectDataFn(dataSrc[param])(json) + } + + // else - Default behaviour + var old = "" + + // Legacy support + if (param === "draw") { + old = "sEcho" + } else if (param === "recordsTotal") { + old = "iTotalRecords" + } else if (param === "recordsFiltered") { + old = "iTotalDisplayRecords" + } + + return json[old] !== undefined ? json[old] : json[param] + } + + /** + * Filter the table using both the global filter and column based filtering + * @param {object} settings dataTables settings object + * @param {object} input search information + * @memberof DataTable#oApi + */ + function _fnFilterComplete(settings, input) { + var columnsSearch = settings.aoPreSearchCols + + // In server-side processing all filtering is done by the server, so no point hanging around here + if (_fnDataSource(settings) != "ssp") { + // Check if any of the rows were invalidated + _fnFilterData(settings) + + // Start from the full data set + settings.aiDisplay = settings.aiDisplayMaster.slice() + + // Global filter first + _fnFilter(settings.aiDisplay, settings, input.search, input) + + $.each(settings.searchFixed, function (name, term) { + _fnFilter(settings.aiDisplay, settings, term, {}) + }) + + // Then individual column filters + for (var i = 0; i < columnsSearch.length; i++) { + var col = columnsSearch[i] + + _fnFilter(settings.aiDisplay, settings, col.search, col, i) + + $.each(settings.aoColumns[i].searchFixed, function (name, term) { + _fnFilter(settings.aiDisplay, settings, term, {}, i) + }) + } + + // And finally global filtering + _fnFilterCustom(settings) + } + + // Tell the draw function we have been filtering + settings.bFiltered = true + + _fnCallbackFire(settings, null, "search", [settings]) + } + + /** + * Apply custom filtering functions + * + * This is legacy now that we have named functions, but it is widely used + * from 1.x, so it is not yet deprecated. + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnFilterCustom(settings) { + var filters = DataTable.ext.search + var displayRows = settings.aiDisplay + var row, rowIdx + + for (var i = 0, ien = filters.length; i < ien; i++) { + var rows = [] + + // Loop over each row and see if it should be included + for (var j = 0, jen = displayRows.length; j < jen; j++) { + rowIdx = displayRows[j] + row = settings.aoData[rowIdx] + + if (filters[i](settings, row._aFilterData, rowIdx, row._aData, j)) { + rows.push(rowIdx) + } + } + + // So the array reference doesn't break set the results into the + // existing array + displayRows.length = 0 + displayRows.push.apply(displayRows, rows) + } + } + + /** + * Filter the data table based on user input and draw the table + */ + function _fnFilter(searchRows, settings, input, options, column) { + if (input === "") { + return + } + + var i = 0 + var matched = [] + + // Search term can be a function, regex or string - if a string we apply our + // smart filtering regex (assuming the options require that) + var searchFunc = typeof input === "function" ? input : null + var rpSearch = input instanceof RegExp ? input : searchFunc ? null : _fnFilterCreateSearch(input, options) + + // Then for each row, does the test pass. If not, lop the row from the array + for (i = 0; i < searchRows.length; i++) { + var row = settings.aoData[searchRows[i]] + var data = column === undefined ? row._sFilterRow : row._aFilterData[column] + + if ((searchFunc && searchFunc(data, row._aData, searchRows[i], column)) || (rpSearch && rpSearch.test(data))) { + matched.push(searchRows[i]) + } + } + + // Mutate the searchRows array + searchRows.length = matched.length + + for (i = 0; i < matched.length; i++) { + searchRows[i] = matched[i] + } + } + + /** + * Build a regular expression object suitable for searching a table + * @param {string} sSearch string to search for + * @param {bool} bRegex treat as a regular expression or not + * @param {bool} bSmart perform smart filtering or not + * @param {bool} bCaseInsensitive Do case insensitive matching or not + * @returns {RegExp} constructed object + * @memberof DataTable#oApi + */ + function _fnFilterCreateSearch(search, inOpts) { + var not = [] + var options = $.extend( + {}, + { + boundary: false, + caseInsensitive: true, + exact: false, + regex: false, + smart: true + }, + inOpts + ) + + if (typeof search !== "string") { + search = search.toString() + } + + // Remove diacritics if normalize is set up to do so + search = _normalize(search) + + if (options.exact) { + return new RegExp("^" + _fnEscapeRegex(search) + "$", options.caseInsensitive ? "i" : "") + } + + search = options.regex ? search : _fnEscapeRegex(search) + + if (options.smart) { + /* For smart filtering we want to allow the search to work regardless of + * word order. We also want double quoted text to be preserved, so word + * order is important - a la google. And a negative look around for + * finding rows which don't contain a given string. + * + * So this is the sort of thing we want to generate: + * + * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$ + */ + var parts = search.match(/!?["\u201C][^"\u201D]+["\u201D]|[^ ]+/g) || [""] + var a = parts.map(function (word) { + var negative = false + var m + + // Determine if it is a "does not include" + if (word.charAt(0) === "!") { + negative = true + word = word.substring(1) + } + + // Strip the quotes from around matched phrases + if (word.charAt(0) === '"') { + m = word.match(/^"(.*)"$/) + word = m ? m[1] : word + } else if (word.charAt(0) === "\u201C") { + // Smart quote match (iPhone users) + m = word.match(/^\u201C(.*)\u201D$/) + word = m ? m[1] : word + } + + // For our "not" case, we need to modify the string that is + // allowed to match at the end of the expression. + if (negative) { + if (word.length > 1) { + not.push("(?!" + word + ")") + } + + word = "" + } + + return word.replace(/"/g, "") + }) + + var match = not.length ? not.join("") : "" + + var boundary = options.boundary ? "\\b" : "" + + search = "^(?=.*?" + boundary + a.join(")(?=.*?" + boundary) + ")(" + match + ".)*$" + } + + return new RegExp(search, options.caseInsensitive ? "i" : "") + } + + /** + * Escape a string such that it can be used in a regular expression + * @param {string} sVal string to escape + * @returns {string} escaped string + * @memberof DataTable#oApi + */ + var _fnEscapeRegex = DataTable.util.escapeRegex + + var __filter_div = $("<div>")[0] + var __filter_div_textContent = __filter_div.textContent !== undefined + + // Update the filtering data for each row if needed (by invalidation or first run) + function _fnFilterData(settings) { + var columns = settings.aoColumns + var data = settings.aoData + var column + var j, jen, filterData, cellData, row + var wasInvalidated = false + + for (var rowIdx = 0; rowIdx < data.length; rowIdx++) { + if (!data[rowIdx]) { + continue + } + + row = data[rowIdx] + + if (!row._aFilterData) { + filterData = [] + + for (j = 0, jen = columns.length; j < jen; j++) { + column = columns[j] + + if (column.bSearchable) { + cellData = _fnGetCellData(settings, rowIdx, j, "filter") + + // Search in DataTables is string based + if (cellData === null) { + cellData = "" + } + + if (typeof cellData !== "string" && cellData.toString) { + cellData = cellData.toString() + } + } else { + cellData = "" + } + + // If it looks like there is an HTML entity in the string, + // attempt to decode it so sorting works as expected. Note that + // we could use a single line of jQuery to do this, but the DOM + // method used here is much faster https://jsperf.com/html-decode + if (cellData.indexOf && cellData.indexOf("&") !== -1) { + __filter_div.innerHTML = cellData + cellData = __filter_div_textContent ? __filter_div.textContent : __filter_div.innerText + } + + if (cellData.replace) { + cellData = cellData.replace(/[\r\n\u2028]/g, "") + } + + filterData.push(cellData) + } + + row._aFilterData = filterData + row._sFilterRow = filterData.join(" ") + wasInvalidated = true + } + } + + return wasInvalidated + } + + /** + * Draw the table for the first time, adding all required features + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnInitialise(settings) { + var i + var init = settings.oInit + var deferLoading = settings.deferLoading + var dataSrc = _fnDataSource(settings) + + // Ensure that the table data is fully initialised + if (!settings.bInitialised) { + setTimeout(function () { + _fnInitialise(settings) + }, 200) + return + } + + // Build the header / footer for the table + _fnBuildHead(settings, "header") + _fnBuildHead(settings, "footer") + + // Load the table's state (if needed) and then render around it and draw + _fnLoadState(settings, init, function () { + // Then draw the header / footer + _fnDrawHead(settings, settings.aoHeader) + _fnDrawHead(settings, settings.aoFooter) + + // Cache the paging start point, as the first redraw will reset it + var iAjaxStart = settings.iInitDisplayStart + + // Local data load + // Check if there is data passing into the constructor + if (init.aaData) { + for (i = 0; i < init.aaData.length; i++) { + _fnAddData(settings, init.aaData[i]) + } + } else if (deferLoading || dataSrc == "dom") { + // Grab the data from the page + _fnAddTr(settings, $(settings.nTBody).children("tr")) + } + + // Filter not yet applied - copy the display master + settings.aiDisplay = settings.aiDisplayMaster.slice() + + // Enable features + _fnAddOptionsHtml(settings) + _fnSortInit(settings) + + _colGroup(settings) + + /* Okay to show that something is going on now */ + _fnProcessingDisplay(settings, true) + + _fnCallbackFire(settings, null, "preInit", [settings], true) + + // If there is default sorting required - let's do it. The sort function + // will do the drawing for us. Otherwise we draw the table regardless of the + // Ajax source - this allows the table to look initialised for Ajax sourcing + // data (show 'loading' message possibly) + _fnReDraw(settings) + + // Server-side processing init complete is done by _fnAjaxUpdateDraw + if (dataSrc != "ssp" || deferLoading) { + // if there is an ajax source load the data + if (dataSrc == "ajax") { + _fnBuildAjax( + settings, + {}, + function (json) { + var aData = _fnAjaxDataSrc(settings, json) + + // Got the data - add it to the table + for (i = 0; i < aData.length; i++) { + _fnAddData(settings, aData[i]) + } + + // Reset the init display for cookie saving. We've already done + // a filter, and therefore cleared it before. So we need to make + // it appear 'fresh' + settings.iInitDisplayStart = iAjaxStart + + _fnReDraw(settings) + _fnProcessingDisplay(settings, false) + _fnInitComplete(settings) + }, + settings + ) + } else { + _fnInitComplete(settings) + _fnProcessingDisplay(settings, false) + } + } + }) + } + + /** + * Draw the table for the first time, adding all required features + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnInitComplete(settings) { + if (settings._bInitComplete) { + return + } + + var args = [settings, settings.json] + + settings._bInitComplete = true + + // Table is fully set up and we have data, so calculate the + // column widths + _fnAdjustColumnSizing(settings) + + _fnCallbackFire(settings, null, "plugin-init", args, true) + _fnCallbackFire(settings, "aoInitComplete", "init", args, true) + } + + function _fnLengthChange(settings, val) { + var len = parseInt(val, 10) + settings._iDisplayLength = len + + _fnLengthOverflow(settings) + + // Fire length change event + _fnCallbackFire(settings, null, "length", [settings, len]) + } + + /** + * Alter the display settings to change the page + * @param {object} settings DataTables settings object + * @param {string|int} action Paging action to take: "first", "previous", + * "next" or "last" or page number to jump to (integer) + * @param [bool] redraw Automatically draw the update or not + * @returns {bool} true page has changed, false - no change + * @memberof DataTable#oApi + */ + function _fnPageChange(settings, action, redraw) { + var start = settings._iDisplayStart, + len = settings._iDisplayLength, + records = settings.fnRecordsDisplay() + + if (records === 0 || len === -1) { + start = 0 + } else if (typeof action === "number") { + start = action * len + + if (start > records) { + start = 0 + } + } else if (action == "first") { + start = 0 + } else if (action == "previous") { + start = len >= 0 ? start - len : 0 + + if (start < 0) { + start = 0 + } + } else if (action == "next") { + if (start + len < records) { + start += len + } + } else if (action == "last") { + start = Math.floor((records - 1) / len) * len + } else if (action === "ellipsis") { + return + } else { + _fnLog(settings, 0, "Unknown paging action: " + action, 5) + } + + var changed = settings._iDisplayStart !== start + settings._iDisplayStart = start + + _fnCallbackFire(settings, null, changed ? "page" : "page-nc", [settings]) + + if (changed && redraw) { + _fnDraw(settings) + } + + return changed + } + + /** + * Generate the node required for the processing node + * @param {object} settings DataTables settings object + */ + function _processingHtml(settings) { + var table = settings.nTable + var scrolling = settings.oScroll.sX !== "" || settings.oScroll.sY !== "" + + if (settings.oFeatures.bProcessing) { + var n = $("<div/>", { + id: settings.sTableId + "_processing", + class: settings.oClasses.processing.container, + role: "status" + }) + .html(settings.oLanguage.sProcessing) + .append("<div><div></div><div></div><div></div><div></div></div>") + + // Different positioning depending on if scrolling is enabled or not + if (scrolling) { + n.prependTo($("div.dt-scroll", settings.nTableWrapper)) + } else { + n.insertBefore(table) + } + + $(table).on("processing.dt.DT", function (e, s, show) { + n.css("display", show ? "block" : "none") + }) + } + } + + /** + * Display or hide the processing indicator + * @param {object} settings DataTables settings object + * @param {bool} show Show the processing indicator (true) or not (false) + */ + function _fnProcessingDisplay(settings, show) { + // Ignore cases when we are still redrawing + if (settings.bDrawing && show === false) { + return + } + + _fnCallbackFire(settings, null, "processing", [settings, show]) + } + + /** + * Show the processing element if an action takes longer than a given time + * + * @param {*} settings DataTables settings object + * @param {*} enable Do (true) or not (false) async processing (local feature enablement) + * @param {*} run Function to run + */ + function _fnProcessingRun(settings, enable, run) { + if (!enable) { + // Immediate execution, synchronous + run() + } else { + _fnProcessingDisplay(settings, true) + + // Allow the processing display to show if needed + setTimeout(function () { + run() + + _fnProcessingDisplay(settings, false) + }, 0) + } + } + /** + * Add any control elements for the table - specifically scrolling + * @param {object} settings dataTables settings object + * @returns {node} Node to add to the DOM + * @memberof DataTable#oApi + */ + function _fnFeatureHtmlTable(settings) { + var table = $(settings.nTable) + + // Scrolling from here on in + var scroll = settings.oScroll + + if (scroll.sX === "" && scroll.sY === "") { + return settings.nTable + } + + var scrollX = scroll.sX + var scrollY = scroll.sY + var classes = settings.oClasses.scrolling + var caption = settings.captionNode + var captionSide = caption ? caption._captionSide : null + var headerClone = $(table[0].cloneNode(false)) + var footerClone = $(table[0].cloneNode(false)) + var footer = table.children("tfoot") + var _div = "<div/>" + var size = function (s) { + return !s ? null : _fnStringToCss(s) + } + + if (!footer.length) { + footer = null + } + + /* + * The HTML structure that we want to generate in this function is: + * div - scroller + * div - scroll head + * div - scroll head inner + * table - scroll head table + * thead - thead + * div - scroll body + * table - table (master table) + * thead - thead clone for sizing + * tbody - tbody + * div - scroll foot + * div - scroll foot inner + * table - scroll foot table + * tfoot - tfoot + */ + var scroller = $(_div, { class: classes.container }) + .append( + $(_div, { class: classes.header.self }) + .css({ + overflow: "hidden", + position: "relative", + border: 0, + width: scrollX ? size(scrollX) : "100%" + }) + .append( + $(_div, { class: classes.header.inner }) + .css({ + "box-sizing": "content-box", + width: scroll.sXInner || "100%" + }) + .append( + headerClone + .removeAttr("id") + .css("margin-left", 0) + .append(captionSide === "top" ? caption : null) + .append(table.children("thead")) + ) + ) + ) + .append( + $(_div, { class: classes.body }) + .css({ + position: "relative", + overflow: "auto", + width: size(scrollX) + }) + .append(table) + ) + + if (footer) { + scroller.append( + $(_div, { class: classes.footer.self }) + .css({ + overflow: "hidden", + border: 0, + width: scrollX ? size(scrollX) : "100%" + }) + .append( + $(_div, { class: classes.footer.inner }).append( + footerClone + .removeAttr("id") + .css("margin-left", 0) + .append(captionSide === "bottom" ? caption : null) + .append(table.children("tfoot")) + ) + ) + ) + } + + var children = scroller.children() + var scrollHead = children[0] + var scrollBody = children[1] + var scrollFoot = footer ? children[2] : null + + // When the body is scrolled, then we also want to scroll the headers + $(scrollBody).on("scroll.DT", function () { + var scrollLeft = this.scrollLeft + + scrollHead.scrollLeft = scrollLeft + + if (footer) { + scrollFoot.scrollLeft = scrollLeft + } + }) + + // When focus is put on the header cells, we might need to scroll the body + $("th, td", scrollHead).on("focus", function () { + var scrollLeft = scrollHead.scrollLeft + + scrollBody.scrollLeft = scrollLeft + + if (footer) { + scrollBody.scrollLeft = scrollLeft + } + }) + + $(scrollBody).css("max-height", scrollY) + if (!scroll.bCollapse) { + $(scrollBody).css("height", scrollY) + } + + settings.nScrollHead = scrollHead + settings.nScrollBody = scrollBody + settings.nScrollFoot = scrollFoot + + // On redraw - align columns + settings.aoDrawCallback.push(_fnScrollDraw) + + return scroller[0] + } + + /** + * Update the header, footer and body tables for resizing - i.e. column + * alignment. + * + * Welcome to the most horrible function DataTables. The process that this + * function follows is basically: + * 1. Re-create the table inside the scrolling div + * 2. Correct colgroup > col values if needed + * 3. Copy colgroup > col over to header and footer + * 4. Clean up + * + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnScrollDraw(settings) { + // Given that this is such a monster function, a lot of variables are use + // to try and keep the minimised size as small as possible + var scroll = settings.oScroll, + barWidth = scroll.iBarWidth, + divHeader = $(settings.nScrollHead), + divHeaderInner = divHeader.children("div"), + divHeaderTable = divHeaderInner.children("table"), + divBodyEl = settings.nScrollBody, + divBody = $(divBodyEl), + divFooter = $(settings.nScrollFoot), + divFooterInner = divFooter.children("div"), + divFooterTable = divFooterInner.children("table"), + header = $(settings.nTHead), + table = $(settings.nTable), + footer = settings.nTFoot && $("th, td", settings.nTFoot).length ? $(settings.nTFoot) : null, + browser = settings.oBrowser, + headerCopy, + footerCopy + + // If the scrollbar visibility has changed from the last draw, we need to + // adjust the column sizes as the table width will have changed to account + // for the scrollbar + var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight + + if (settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined) { + settings.scrollBarVis = scrollBarVis + _fnAdjustColumnSizing(settings) + return // adjust column sizing will call this function again + } else { + settings.scrollBarVis = scrollBarVis + } + + // 1. Re-create the table inside the scrolling div + // Remove the old minimised thead and tfoot elements in the inner table + table.children("thead, tfoot").remove() + + // Clone the current header and footer elements and then place it into the inner table + headerCopy = header.clone().prependTo(table) + headerCopy.find("th, td").removeAttr("tabindex") + headerCopy.find("[id]").removeAttr("id") + + if (footer) { + footerCopy = footer.clone().prependTo(table) + footerCopy.find("[id]").removeAttr("id") + } + + // 2. Correct colgroup > col values if needed + // It is possible that the cell sizes are smaller than the content, so we need to + // correct colgroup>col for such cases. This can happen if the auto width detection + // uses a cell which has a longer string, but isn't the widest! For example + // "Chief Executive Officer (CEO)" is the longest string in the demo, but + // "Systems Administrator" is actually the widest string since it doesn't collapse. + // Note the use of translating into a column index to get the `col` element. This + // is because of Responsive which might remove `col` elements, knocking the alignment + // of the indexes out. + if (settings.aiDisplay.length) { + // Get the column sizes from the first row in the table. This should really be a + // [].find, but it wasn't supported in Chrome until Sept 2015, and DT has 10 year + // browser support + var firstTr = null + + for (i = 0; i < settings.aiDisplay.length; i++) { + var idx = settings.aiDisplay[i] + var tr = settings.aoData[idx].nTr + + if (tr) { + firstTr = tr + break + } + } + + if (firstTr) { + var colSizes = $(firstTr) + .children("th, td") + .map(function (vis) { + return { + idx: _fnVisibleToColumnIndex(settings, vis), + width: $(this).outerWidth() + } + }) + + // Check against what the colgroup > col is set to and correct if needed + for (var i = 0; i < colSizes.length; i++) { + var colEl = settings.aoColumns[colSizes[i].idx].colEl[0] + var colWidth = colEl.style.width.replace("px", "") + + if (colWidth !== colSizes[i].width) { + colEl.style.width = colSizes[i].width + "px" + } + } + } + } + + // 3. Copy the colgroup over to the header and footer + divHeaderTable.find("colgroup").remove() + + divHeaderTable.append(settings.colgroup.clone()) + + if (footer) { + divFooterTable.find("colgroup").remove() + + divFooterTable.append(settings.colgroup.clone()) + } + + // "Hide" the header and footer that we used for the sizing. We need to keep + // the content of the cell so that the width applied to the header and body + // both match, but we want to hide it completely. + $("th, td", headerCopy).each(function () { + $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">') + }) + + if (footer) { + $("th, td", footerCopy).each(function () { + $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">') + }) + } + + // 4. Clean up + // Figure out if there are scrollbar present - if so then we need a the header and footer to + // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar) + var isScrolling = Math.floor(table.height()) > divBodyEl.clientHeight || divBody.css("overflow-y") == "scroll" + var paddingSide = "padding" + (browser.bScrollbarLeft ? "Left" : "Right") + + // Set the width's of the header and footer tables + var outerWidth = table.outerWidth() + + divHeaderTable.css("width", _fnStringToCss(outerWidth)) + divHeaderInner.css("width", _fnStringToCss(outerWidth)).css(paddingSide, isScrolling ? barWidth + "px" : "0px") + + if (footer) { + divFooterTable.css("width", _fnStringToCss(outerWidth)) + divFooterInner.css("width", _fnStringToCss(outerWidth)).css(paddingSide, isScrolling ? barWidth + "px" : "0px") + } + + // Correct DOM ordering for colgroup - comes before the thead + table.children("colgroup").prependTo(table) + + // Adjust the position of the header in case we loose the y-scrollbar + divBody.trigger("scroll") + + // If sorting or filtering has occurred, jump the scrolling back to the top + // only if we aren't holding the position + if ((settings.bSorted || settings.bFiltered) && !settings._drawHold) { + divBodyEl.scrollTop = 0 + } + } + + /** + * Calculate the width of columns for the table + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnCalculateColumnWidths(settings) { + // Not interested in doing column width calculation if auto-width is disabled + if (!settings.oFeatures.bAutoWidth) { + return + } + + var table = settings.nTable, + columns = settings.aoColumns, + scroll = settings.oScroll, + scrollY = scroll.sY, + scrollX = scroll.sX, + scrollXInner = scroll.sXInner, + visibleColumns = _fnGetColumns(settings, "bVisible"), + tableWidthAttr = table.getAttribute("width"), // from DOM element + tableContainer = table.parentNode, + i, + column, + columnIdx + + var styleWidth = table.style.width + + // If there is no width applied as a CSS style or as an attribute, we assume that + // the width is intended to be 100%, which is usually is in CSS, but it is very + // difficult to correctly parse the rules to get the final result. + if (!styleWidth && !tableWidthAttr) { + table.style.width = "100%" + styleWidth = "100%" + } + + if (styleWidth && styleWidth.indexOf("%") !== -1) { + tableWidthAttr = styleWidth + } + + // Let plug-ins know that we are doing a recalc, in case they have changed any of the + // visible columns their own way (e.g. Responsive uses display:none). + _fnCallbackFire(settings, null, "column-calc", { visible: visibleColumns }, false) + + // Construct a single row, worst case, table with the widest + // node in the data, assign any user defined widths, then insert it into + // the DOM and allow the browser to do all the hard work of calculating + // table widths + var tmpTable = $(table.cloneNode()).css("visibility", "hidden").removeAttr("id") + + // Clean up the table body + tmpTable.append("<tbody>") + var tr = $("<tr/>").appendTo(tmpTable.find("tbody")) + + // Clone the table header and footer - we can't use the header / footer + // from the cloned table, since if scrolling is active, the table's + // real header and footer are contained in different table tags + tmpTable.append($(settings.nTHead).clone()).append($(settings.nTFoot).clone()) + + // Remove any assigned widths from the footer (from scrolling) + tmpTable.find("tfoot th, tfoot td").css("width", "") + + // Apply custom sizing to the cloned header + tmpTable.find("thead th, thead td").each(function () { + // Get the `width` from the header layout + var width = _fnColumnsSumWidth(settings, this, true, false) + + if (width) { + this.style.width = width + + // For scrollX we need to force the column width otherwise the + // browser will collapse it. If this width is smaller than the + // width the column requires, then it will have no effect + if (scrollX) { + $(this).append( + $("<div/>").css({ + width: width, + margin: 0, + padding: 0, + border: 0, + height: 1 + }) + ) + } + } else { + this.style.width = "" + } + }) + + // Find the widest piece of data for each column and put it into the table + for (i = 0; i < visibleColumns.length; i++) { + columnIdx = visibleColumns[i] + column = columns[columnIdx] + + var longest = _fnGetMaxLenString(settings, columnIdx) + var autoClass = _ext.type.className[column.sType] + var text = longest + column.sContentPadding + var insert = longest.indexOf("<") === -1 ? document.createTextNode(text) : text + + $("<td/>").addClass(autoClass).addClass(column.sClass).append(insert).appendTo(tr) + } + + // Tidy the temporary table - remove name attributes so there aren't + // duplicated in the dom (radio elements for example) + $("[name]", tmpTable).removeAttr("name") + + // Table has been built, attach to the document so we can work with it. + // A holding element is used, positioned at the top of the container + // with minimal height, so it has no effect on if the container scrolls + // or not. Otherwise it might trigger scrolling when it actually isn't + // needed + var holder = $("<div/>") + .css( + scrollX || scrollY + ? { + position: "absolute", + top: 0, + left: 0, + height: 1, + right: 0, + overflow: "hidden" + } + : {} + ) + .append(tmpTable) + .appendTo(tableContainer) + + // When scrolling (X or Y) we want to set the width of the table as + // appropriate. However, when not scrolling leave the table width as it + // is. This results in slightly different, but I think correct behaviour + if (scrollX && scrollXInner) { + tmpTable.width(scrollXInner) + } else if (scrollX) { + tmpTable.css("width", "auto") + tmpTable.removeAttr("width") + + // If there is no width attribute or style, then allow the table to + // collapse + if (tmpTable.width() < tableContainer.clientWidth && tableWidthAttr) { + tmpTable.width(tableContainer.clientWidth) + } + } else if (scrollY) { + tmpTable.width(tableContainer.clientWidth) + } else if (tableWidthAttr) { + tmpTable.width(tableWidthAttr) + } + + // Get the width of each column in the constructed table + var total = 0 + var bodyCells = tmpTable.find("tbody tr").eq(0).children() + + for (i = 0; i < visibleColumns.length; i++) { + // Use getBounding for sub-pixel accuracy, which we then want to round up! + var bounding = bodyCells[i].getBoundingClientRect().width + + // Total is tracked to remove any sub-pixel errors as the outerWidth + // of the table might not equal the total given here + total += bounding + + // Width for each column to use + columns[visibleColumns[i]].sWidth = _fnStringToCss(bounding) + } + + table.style.width = _fnStringToCss(total) + + // Finished with the table - ditch it + holder.remove() + + // If there is a width attr, we want to attach an event listener which + // allows the table sizing to automatically adjust when the window is + // resized. Use the width attr rather than CSS, since we can't know if the + // CSS is a relative value or absolute - DOM read is always px. + if (tableWidthAttr) { + table.style.width = _fnStringToCss(tableWidthAttr) + } + + if ((tableWidthAttr || scrollX) && !settings._reszEvt) { + var bindResize = function () { + $(window).on( + "resize.DT-" + settings.sInstance, + DataTable.util.throttle(function () { + if (!settings.bDestroying) { + _fnAdjustColumnSizing(settings) + } + }) + ) + } + + bindResize() + + settings._reszEvt = true + } + } + + /** + * Get the maximum strlen for each data column + * @param {object} settings dataTables settings object + * @param {int} colIdx column of interest + * @returns {string} string of the max length + * @memberof DataTable#oApi + */ + function _fnGetMaxLenString(settings, colIdx) { + var column = settings.aoColumns[colIdx] + + if (!column.maxLenString) { + var s, + max = "", + maxLen = -1 + + for (var i = 0, ien = settings.aiDisplayMaster.length; i < ien; i++) { + var rowIdx = settings.aiDisplayMaster[i] + var data = _fnGetRowDisplay(settings, rowIdx)[colIdx] + + var cellString = data && typeof data === "object" && data.nodeType ? data.innerHTML : data + "" + + // Remove id / name attributes from elements so they + // don't interfere with existing elements + cellString = cellString.replace(/id=".*?"/g, "").replace(/name=".*?"/g, "") + + s = _stripHtml(cellString).replace(/&nbsp;/g, " ") + + if (s.length > maxLen) { + // We want the HTML in the string, but the length that + // is important is the stripped string + max = cellString + maxLen = s.length + } + } + + column.maxLenString = max + } + + return column.maxLenString + } + + /** + * Append a CSS unit (only if required) to a string + * @param {string} value to css-ify + * @returns {string} value with css unit + * @memberof DataTable#oApi + */ + function _fnStringToCss(s) { + if (s === null) { + return "0px" + } + + if (typeof s == "number") { + return s < 0 ? "0px" : s + "px" + } + + // Check it has a unit character already + return s.match(/\d$/) ? s + "px" : s + } + + /** + * Re-insert the `col` elements for current visibility + * + * @param {*} settings DT settings + */ + function _colGroup(settings) { + var cols = settings.aoColumns + + settings.colgroup.empty() + + for (i = 0; i < cols.length; i++) { + if (cols[i].bVisible) { + settings.colgroup.append(cols[i].colEl) + } + } + } + + function _fnSortInit(settings) { + var target = settings.nTHead + var headerRows = target.querySelectorAll("tr") + var legacyTop = settings.bSortCellsTop + var notSelector = ':not([data-dt-order="disable"]):not([data-dt-order="icon-only"])' + + // Legacy support for `orderCellsTop` + if (legacyTop === true) { + target = headerRows[0] + } else if (legacyTop === false) { + target = headerRows[headerRows.length - 1] + } + + _fnSortAttachListener(settings, target, target === settings.nTHead ? "tr" + notSelector + " th" + notSelector + ", tr" + notSelector + " td" + notSelector : "th" + notSelector + ", td" + notSelector) + + // Need to resolve the user input array into our internal structure + var order = [] + _fnSortResolve(settings, order, settings.aaSorting) + + settings.aaSorting = order + } + + function _fnSortAttachListener(settings, node, selector, column, callback) { + _fnBindAction(node, selector, function (e) { + var run = false + var columns = column === undefined ? _fnColumnsFromHeader(e.target) : [column] + + if (columns.length) { + for (var i = 0, ien = columns.length; i < ien; i++) { + var ret = _fnSortAdd(settings, columns[i], i, e.shiftKey) + + if (ret !== false) { + run = true + } + + // If the first entry is no sort, then subsequent + // sort columns are ignored + if (settings.aaSorting.length === 1 && settings.aaSorting[0][1] === "") { + break + } + } + + if (run) { + _fnProcessingRun(settings, true, function () { + _fnSort(settings) + _fnSortDisplay(settings, settings.aiDisplay) + + _fnReDraw(settings, false, false) + + if (callback) { + callback() + } + }) + } + } + }) + } + + /** + * Sort the display array to match the master's order + * @param {*} settings + */ + function _fnSortDisplay(settings, display) { + if (display.length < 2) { + return + } + + var master = settings.aiDisplayMaster + var masterMap = {} + var map = {} + var i + + // Rather than needing an `indexOf` on master array, we can create a map + for (i = 0; i < master.length; i++) { + masterMap[master[i]] = i + } + + // And then cache what would be the indexOf fom the display + for (i = 0; i < display.length; i++) { + map[display[i]] = masterMap[display[i]] + } + + display.sort(function (a, b) { + // Short version of this function is simply `master.indexOf(a) - master.indexOf(b);` + return map[a] - map[b] + }) + } + + function _fnSortResolve(settings, nestedSort, sort) { + var push = function (a) { + if ($.isPlainObject(a)) { + if (a.idx !== undefined) { + // Index based ordering + nestedSort.push([a.idx, a.dir]) + } else if (a.name) { + // Name based ordering + var cols = _pluck(settings.aoColumns, "sName") + var idx = cols.indexOf(a.name) + + if (idx !== -1) { + nestedSort.push([idx, a.dir]) + } + } + } else { + // Plain column index and direction pair + nestedSort.push(a) + } + } + + if ($.isPlainObject(sort)) { + // Object + push(sort) + } else if (sort.length && typeof sort[0] === "number") { + // 1D array + push(sort) + } else if (sort.length) { + // 2D array + for (var z = 0; z < sort.length; z++) { + push(sort[z]) // Object or array + } + } + } + + function _fnSortFlatten(settings) { + var i, + k, + kLen, + aSort = [], + extSort = DataTable.ext.type.order, + aoColumns = settings.aoColumns, + aDataSort, + iCol, + sType, + srcCol, + fixed = settings.aaSortingFixed, + fixedObj = $.isPlainObject(fixed), + nestedSort = [] + + if (!settings.oFeatures.bSort) { + return aSort + } + + // Build the sort array, with pre-fix and post-fix options if they have been + // specified + if (Array.isArray(fixed)) { + _fnSortResolve(settings, nestedSort, fixed) + } + + if (fixedObj && fixed.pre) { + _fnSortResolve(settings, nestedSort, fixed.pre) + } + + _fnSortResolve(settings, nestedSort, settings.aaSorting) + + if (fixedObj && fixed.post) { + _fnSortResolve(settings, nestedSort, fixed.post) + } + + for (i = 0; i < nestedSort.length; i++) { + srcCol = nestedSort[i][0] + + if (aoColumns[srcCol]) { + aDataSort = aoColumns[srcCol].aDataSort + + for (k = 0, kLen = aDataSort.length; k < kLen; k++) { + iCol = aDataSort[k] + sType = aoColumns[iCol].sType || "string" + + if (nestedSort[i]._idx === undefined) { + nestedSort[i]._idx = aoColumns[iCol].asSorting.indexOf(nestedSort[i][1]) + } + + if (nestedSort[i][1]) { + aSort.push({ + src: srcCol, + col: iCol, + dir: nestedSort[i][1], + index: nestedSort[i]._idx, + type: sType, + formatter: extSort[sType + "-pre"], + sorter: extSort[sType + "-" + nestedSort[i][1]] + }) + } + } + } + } + + return aSort + } + + /** + * Change the order of the table + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnSort(oSettings, col, dir) { + var i, + ien, + iLen, + aiOrig = [], + extSort = DataTable.ext.type.order, + aoData = oSettings.aoData, + sortCol, + displayMaster = oSettings.aiDisplayMaster, + aSort + + // Allow a specific column to be sorted, which will _not_ alter the display + // master + if (col !== undefined) { + var srcCol = oSettings.aoColumns[col] + aSort = [ + { + src: col, + col: col, + dir: dir, + index: 0, + type: srcCol.sType, + formatter: extSort[srcCol.sType + "-pre"], + sorter: extSort[srcCol.sType + "-" + dir] + } + ] + displayMaster = displayMaster.slice() + } else { + aSort = _fnSortFlatten(oSettings) + } + + for (i = 0, ien = aSort.length; i < ien; i++) { + sortCol = aSort[i] + + // Load the data needed for the sort, for each cell + _fnSortData(oSettings, sortCol.col) + } + + /* No sorting required if server-side or no sorting array */ + if (_fnDataSource(oSettings) != "ssp" && aSort.length !== 0) { + // Reset the initial positions on each pass so we get a stable sort + for (i = 0, iLen = displayMaster.length; i < iLen; i++) { + aiOrig[i] = i + } + + // If the first sort is desc, then reverse the array to preserve original + // order, just in reverse + if (aSort.length && aSort[0].dir === "desc" && oSettings.orderDescReverse) { + aiOrig.reverse() + } + + /* Do the sort - here we want multi-column sorting based on a given data source (column) + * and sorting function (from oSort) in a certain direction. It's reasonably complex to + * follow on it's own, but this is what we want (example two column sorting): + * fnLocalSorting = function(a,b){ + * var test; + * test = oSort['string-asc']('data11', 'data12'); + * if (test !== 0) + * return test; + * test = oSort['numeric-desc']('data21', 'data22'); + * if (test !== 0) + * return test; + * return oSort['numeric-asc']( aiOrig[a], aiOrig[b] ); + * } + * Basically we have a test for each sorting column, if the data in that column is equal, + * test the next column. If all columns match, then we use a numeric sort on the row + * positions in the original data array to provide a stable sort. + */ + displayMaster.sort(function (a, b) { + var x, + y, + k, + test, + sort, + len = aSort.length, + dataA = aoData[a]._aSortData, + dataB = aoData[b]._aSortData + + for (k = 0; k < len; k++) { + sort = aSort[k] + + // Data, which may have already been through a `-pre` function + x = dataA[sort.col] + y = dataB[sort.col] + + if (sort.sorter) { + // If there is a custom sorter (`-asc` or `-desc`) for this + // data type, use it + test = sort.sorter(x, y) + + if (test !== 0) { + return test + } + } else { + // Otherwise, use generic sorting + test = x < y ? -1 : x > y ? 1 : 0 + + if (test !== 0) { + return sort.dir === "asc" ? test : -test + } + } + } + + x = aiOrig[a] + y = aiOrig[b] + + return x < y ? -1 : x > y ? 1 : 0 + }) + } else if (aSort.length === 0) { + // Apply index order + displayMaster.sort(function (x, y) { + return x < y ? -1 : x > y ? 1 : 0 + }) + } + + if (col === undefined) { + // Tell the draw function that we have sorted the data + oSettings.bSorted = true + oSettings.sortDetails = aSort + + _fnCallbackFire(oSettings, null, "order", [oSettings, aSort]) + } + + return displayMaster + } + + /** + * Function to run on user sort request + * @param {object} settings dataTables settings object + * @param {node} attachTo node to attach the handler to + * @param {int} colIdx column sorting index + * @param {int} addIndex Counter + * @param {boolean} [shift=false] Shift click add + * @param {function} [callback] callback function + * @memberof DataTable#oApi + */ + function _fnSortAdd(settings, colIdx, addIndex, shift) { + var col = settings.aoColumns[colIdx] + var sorting = settings.aaSorting + var asSorting = col.asSorting + var nextSortIdx + var next = function (a, overflow) { + var idx = a._idx + if (idx === undefined) { + idx = asSorting.indexOf(a[1]) + } + + return idx + 1 < asSorting.length ? idx + 1 : overflow ? null : 0 + } + + if (!col.bSortable) { + return false + } + + // Convert to 2D array if needed + if (typeof sorting[0] === "number") { + sorting = settings.aaSorting = [sorting] + } + + // If appending the sort then we are multi-column sorting + if ((shift || addIndex) && settings.oFeatures.bSortMulti) { + // Are we already doing some kind of sort on this column? + var sortIdx = _pluck(sorting, "0").indexOf(colIdx) + + if (sortIdx !== -1) { + // Yes, modify the sort + nextSortIdx = next(sorting[sortIdx], true) + + if (nextSortIdx === null && sorting.length === 1) { + nextSortIdx = 0 // can't remove sorting completely + } + + if (nextSortIdx === null) { + sorting.splice(sortIdx, 1) + } else { + sorting[sortIdx][1] = asSorting[nextSortIdx] + sorting[sortIdx]._idx = nextSortIdx + } + } else if (shift) { + // No sort on this column yet, being added by shift click + // add it as itself + sorting.push([colIdx, asSorting[0], 0]) + sorting[sorting.length - 1]._idx = 0 + } else { + // No sort on this column yet, being added from a colspan + // so add with same direction as first column + sorting.push([colIdx, sorting[0][1], 0]) + sorting[sorting.length - 1]._idx = 0 + } + } else if (sorting.length && sorting[0][0] == colIdx) { + // Single column - already sorting on this column, modify the sort + nextSortIdx = next(sorting[0]) + + sorting.length = 1 + sorting[0][1] = asSorting[nextSortIdx] + sorting[0]._idx = nextSortIdx + } else { + // Single column - sort only on this column + sorting.length = 0 + sorting.push([colIdx, asSorting[0]]) + sorting[0]._idx = 0 + } + } + + /** + * Set the sorting classes on table's body, Note: it is safe to call this function + * when bSort and bSortClasses are false + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnSortingClasses(settings) { + var oldSort = settings.aLastSort + var sortClass = settings.oClasses.order.position + var sort = _fnSortFlatten(settings) + var features = settings.oFeatures + var i, ien, colIdx + + if (features.bSort && features.bSortClasses) { + // Remove old sorting classes + for (i = 0, ien = oldSort.length; i < ien; i++) { + colIdx = oldSort[i].src + + // Remove column sorting + $(_pluck(settings.aoData, "anCells", colIdx)).removeClass(sortClass + (i < 2 ? i + 1 : 3)) + } + + // Add new column sorting + for (i = 0, ien = sort.length; i < ien; i++) { + colIdx = sort[i].src + + $(_pluck(settings.aoData, "anCells", colIdx)).addClass(sortClass + (i < 2 ? i + 1 : 3)) + } + } + + settings.aLastSort = sort + } + + // Get the data to sort a column, be it from cache, fresh (populating the + // cache), or from a sort formatter + function _fnSortData(settings, colIdx) { + // Custom sorting function - provided by the sort data type + var column = settings.aoColumns[colIdx] + var customSort = DataTable.ext.order[column.sSortDataType] + var customData + + if (customSort) { + customData = customSort.call(settings.oInstance, settings, colIdx, _fnColumnIndexToVisible(settings, colIdx)) + } + + // Use / populate cache + var row, cellData + var formatter = DataTable.ext.type.order[column.sType + "-pre"] + var data = settings.aoData + + for (var rowIdx = 0; rowIdx < data.length; rowIdx++) { + // Sparse array + if (!data[rowIdx]) { + continue + } + + row = data[rowIdx] + + if (!row._aSortData) { + row._aSortData = [] + } + + if (!row._aSortData[colIdx] || customSort) { + cellData = customSort + ? customData[rowIdx] // If there was a custom sort function, use data from there + : _fnGetCellData(settings, rowIdx, colIdx, "sort") + + row._aSortData[colIdx] = formatter ? formatter(cellData, settings) : cellData + } + } + } + + /** + * State information for a table + * + * @param {*} settings + * @returns State object + */ + function _fnSaveState(settings) { + if (settings._bLoadingState) { + return + } + + /* Store the interesting variables */ + var state = { + time: +new Date(), + start: settings._iDisplayStart, + length: settings._iDisplayLength, + order: $.extend(true, [], settings.aaSorting), + search: $.extend({}, settings.oPreviousSearch), + columns: settings.aoColumns.map(function (col, i) { + return { + visible: col.bVisible, + search: $.extend({}, settings.aoPreSearchCols[i]) + } + }) + } + + settings.oSavedState = state + _fnCallbackFire(settings, "aoStateSaveParams", "stateSaveParams", [settings, state]) + + if (settings.oFeatures.bStateSave && !settings.bDestroying) { + settings.fnStateSaveCallback.call(settings.oInstance, settings, state) + } + } + + /** + * Attempt to load a saved table state + * @param {object} oSettings dataTables settings object + * @param {object} oInit DataTables init object so we can override settings + * @param {function} callback Callback to execute when the state has been loaded + * @memberof DataTable#oApi + */ + function _fnLoadState(settings, init, callback) { + if (!settings.oFeatures.bStateSave) { + callback() + return + } + + var loaded = function (state) { + _fnImplementState(settings, state, callback) + } + + var state = settings.fnStateLoadCallback.call(settings.oInstance, settings, loaded) + + if (state !== undefined) { + _fnImplementState(settings, state, callback) + } + // otherwise, wait for the loaded callback to be executed + + return true + } + + function _fnImplementState(settings, s, callback) { + var i, ien + var columns = settings.aoColumns + settings._bLoadingState = true + + // When StateRestore was introduced the state could now be implemented at any time + // Not just initialisation. To do this an api instance is required in some places + var api = settings._bInitComplete ? new DataTable.Api(settings) : null + + if (!s || !s.time) { + settings._bLoadingState = false + callback() + return + } + + // Reject old data + var duration = settings.iStateDuration + if (duration > 0 && s.time < +new Date() - duration * 1000) { + settings._bLoadingState = false + callback() + return + } + + // Allow custom and plug-in manipulation functions to alter the saved data set and + // cancelling of loading by returning false + var abStateLoad = _fnCallbackFire(settings, "aoStateLoadParams", "stateLoadParams", [settings, s]) + if (abStateLoad.indexOf(false) !== -1) { + settings._bLoadingState = false + callback() + return + } + + // Number of columns have changed - all bets are off, no restore of settings + if (s.columns && columns.length !== s.columns.length) { + settings._bLoadingState = false + callback() + return + } + + // Store the saved state so it might be accessed at any time + settings.oLoadedState = $.extend(true, {}, s) + + // This is needed for ColReorder, which has to happen first to allow all + // the stored indexes to be usable. It is not publicly documented. + _fnCallbackFire(settings, null, "stateLoadInit", [settings, s], true) + + // Page Length + if (s.length !== undefined) { + // If already initialised just set the value directly so that the select element is also updated + if (api) { + api.page.len(s.length) + } else { + settings._iDisplayLength = s.length + } + } + + // Restore key features + if (s.start !== undefined) { + if (api === null) { + settings._iDisplayStart = s.start + settings.iInitDisplayStart = s.start + } else { + _fnPageChange(settings, s.start / settings._iDisplayLength) + } + } + + // Order + if (s.order !== undefined) { + settings.aaSorting = [] + $.each(s.order, function (i, col) { + settings.aaSorting.push(col[0] >= columns.length ? [0, col[1]] : col) + }) + } + + // Search + if (s.search !== undefined) { + $.extend(settings.oPreviousSearch, s.search) + } + + // Columns + if (s.columns) { + for (i = 0, ien = s.columns.length; i < ien; i++) { + var col = s.columns[i] + + // Visibility + if (col.visible !== undefined) { + // If the api is defined, the table has been initialised so we need to use it rather than internal settings + if (api) { + // Don't redraw the columns on every iteration of this loop, we will do this at the end instead + api.column(i).visible(col.visible, false) + } else { + columns[i].bVisible = col.visible + } + } + + // Search + if (col.search !== undefined) { + $.extend(settings.aoPreSearchCols[i], col.search) + } + } + + // If the api is defined then we need to adjust the columns once the visibility has been changed + if (api) { + api.columns.adjust() + } + } + + settings._bLoadingState = false + _fnCallbackFire(settings, "aoStateLoaded", "stateLoaded", [settings, s]) + callback() + } + + /** + * Log an error message + * @param {object} settings dataTables settings object + * @param {int} level log error messages, or display them to the user + * @param {string} msg error message + * @param {int} tn Technical note id to get more information about the error. + * @memberof DataTable#oApi + */ + function _fnLog(settings, level, msg, tn) { + msg = "DataTables warning: " + (settings ? "table id=" + settings.sTableId + " - " : "") + msg + + if (tn) { + msg += ". For more information about this error, please see " + "https://datatables.net/tn/" + tn + } + + if (!level) { + // Backwards compatibility pre 1.10 + var ext = DataTable.ext + var type = ext.sErrMode || ext.errMode + + if (settings) { + _fnCallbackFire(settings, null, "dt-error", [settings, tn, msg], true) + } + + if (type == "alert") { + alert(msg) + } else if (type == "throw") { + throw new Error(msg) + } else if (typeof type == "function") { + type(settings, tn, msg) + } + } else if (window.console && console.log) { + console.log(msg) + } + } + + /** + * See if a property is defined on one object, if so assign it to the other object + * @param {object} ret target object + * @param {object} src source object + * @param {string} name property + * @param {string} [mappedName] name to map too - optional, name used if not given + * @memberof DataTable#oApi + */ + function _fnMap(ret, src, name, mappedName) { + if (Array.isArray(name)) { + $.each(name, function (i, val) { + if (Array.isArray(val)) { + _fnMap(ret, src, val[0], val[1]) + } else { + _fnMap(ret, src, val) + } + }) + + return + } + + if (mappedName === undefined) { + mappedName = name + } + + if (src[name] !== undefined) { + ret[mappedName] = src[name] + } + } + + /** + * Extend objects - very similar to jQuery.extend, but deep copy objects, and + * shallow copy arrays. The reason we need to do this, is that we don't want to + * deep copy array init values (such as aaSorting) since the dev wouldn't be + * able to override them, but we do want to deep copy arrays. + * @param {object} out Object to extend + * @param {object} extender Object from which the properties will be applied to + * out + * @param {boolean} breakRefs If true, then arrays will be sliced to take an + * independent copy with the exception of the `data` or `aaData` parameters + * if they are present. This is so you can pass in a collection to + * DataTables and have that used as your data source without breaking the + * references + * @returns {object} out Reference, just for convenience - out === the return. + * @memberof DataTable#oApi + * @todo This doesn't take account of arrays inside the deep copied objects. + */ + function _fnExtend(out, extender, breakRefs) { + var val + + for (var prop in extender) { + if (Object.prototype.hasOwnProperty.call(extender, prop)) { + val = extender[prop] + + if ($.isPlainObject(val)) { + if (!$.isPlainObject(out[prop])) { + out[prop] = {} + } + $.extend(true, out[prop], val) + } else if (breakRefs && prop !== "data" && prop !== "aaData" && Array.isArray(val)) { + out[prop] = val.slice() + } else { + out[prop] = val + } + } + } + + return out + } + + /** + * Bind an event handers to allow a click or return key to activate the callback. + * This is good for accessibility since a return on the keyboard will have the + * same effect as a click, if the element has focus. + * @param {element} n Element to bind the action to + * @param {object|string} selector Selector (for delegated events) or data object + * to pass to the triggered function + * @param {function} fn Callback function for when the event is triggered + * @memberof DataTable#oApi + */ + function _fnBindAction(n, selector, fn) { + $(n) + .on("click.DT", selector, function (e) { + fn(e) + }) + .on("keypress.DT", selector, function (e) { + if (e.which === 13) { + e.preventDefault() + fn(e) + } + }) + .on("selectstart.DT", selector, function () { + // Don't want a double click resulting in text selection + return false + }) + } + + /** + * Register a callback function. Easily allows a callback function to be added to + * an array store of callback functions that can then all be called together. + * @param {object} settings dataTables settings object + * @param {string} store Name of the array storage for the callbacks in oSettings + * @param {function} fn Function to be called back + * @memberof DataTable#oApi + */ + function _fnCallbackReg(settings, store, fn) { + if (fn) { + settings[store].push(fn) + } + } + + /** + * Fire callback functions and trigger events. Note that the loop over the + * callback array store is done backwards! Further note that you do not want to + * fire off triggers in time sensitive applications (for example cell creation) + * as its slow. + * @param {object} settings dataTables settings object + * @param {string} callbackArr Name of the array storage for the callbacks in + * oSettings + * @param {string} eventName Name of the jQuery custom event to trigger. If + * null no trigger is fired + * @param {array} args Array of arguments to pass to the callback function / + * trigger + * @param {boolean} [bubbles] True if the event should bubble + * @memberof DataTable#oApi + */ + function _fnCallbackFire(settings, callbackArr, eventName, args, bubbles) { + var ret = [] + + if (callbackArr) { + ret = settings[callbackArr] + .slice() + .reverse() + .map(function (val) { + return val.apply(settings.oInstance, args) + }) + } + + if (eventName !== null) { + var e = $.Event(eventName + ".dt") + var table = $(settings.nTable) + + // Expose the DataTables API on the event object for easy access + e.dt = settings.api + + table[bubbles ? "trigger" : "triggerHandler"](e, args) + + // If not yet attached to the document, trigger the event + // on the body directly to sort of simulate the bubble + if (bubbles && table.parents("body").length === 0) { + $("body").trigger(e, args) + } + + ret.push(e.result) + } + + return ret + } + + function _fnLengthOverflow(settings) { + var start = settings._iDisplayStart, + end = settings.fnDisplayEnd(), + len = settings._iDisplayLength + + /* If we have space to show extra rows (backing up from the end point - then do so */ + if (start >= end) { + start = end - len + } + + // Keep the start record on the current page + start -= start % len + + if (len === -1 || start < 0) { + start = 0 + } + + settings._iDisplayStart = start + } + + function _fnRenderer(settings, type) { + var renderer = settings.renderer + var host = DataTable.ext.renderer[type] + + if ($.isPlainObject(renderer) && renderer[type]) { + // Specific renderer for this type. If available use it, otherwise use + // the default. + return host[renderer[type]] || host._ + } else if (typeof renderer === "string") { + // Common renderer - if there is one available for this type use it, + // otherwise use the default + return host[renderer] || host._ + } + + // Use the default + return host._ + } + + /** + * Detect the data source being used for the table. Used to simplify the code + * a little (ajax) and to make it compress a little smaller. + * + * @param {object} settings dataTables settings object + * @returns {string} Data source + * @memberof DataTable#oApi + */ + function _fnDataSource(settings) { + if (settings.oFeatures.bServerSide) { + return "ssp" + } else if (settings.ajax) { + return "ajax" + } + return "dom" + } + + /** + * Common replacement for language strings + * + * @param {*} settings DT settings object + * @param {*} str String with values to replace + * @param {*} entries Plural number for _ENTRIES_ - can be undefined + * @returns String + */ + function _fnMacros(settings, str, entries) { + // When infinite scrolling, we are always starting at 1. _iDisplayStart is + // used only internally + var formatter = settings.fnFormatNumber, + start = settings._iDisplayStart + 1, + len = settings._iDisplayLength, + vis = settings.fnRecordsDisplay(), + max = settings.fnRecordsTotal(), + all = len === -1 + + return str + .replace(/_START_/g, formatter.call(settings, start)) + .replace(/_END_/g, formatter.call(settings, settings.fnDisplayEnd())) + .replace(/_MAX_/g, formatter.call(settings, max)) + .replace(/_TOTAL_/g, formatter.call(settings, vis)) + .replace(/_PAGE_/g, formatter.call(settings, all ? 1 : Math.ceil(start / len))) + .replace(/_PAGES_/g, formatter.call(settings, all ? 1 : Math.ceil(vis / len))) + .replace(/_ENTRIES_/g, settings.api.i18n("entries", "", entries)) + .replace(/_ENTRIES-MAX_/g, settings.api.i18n("entries", "", max)) + .replace(/_ENTRIES-TOTAL_/g, settings.api.i18n("entries", "", vis)) + } + + /** + * Computed structure of the DataTables API, defined by the options passed to + * `DataTable.Api.register()` when building the API. + * + * The structure is built in order to speed creation and extension of the Api + * objects since the extensions are effectively pre-parsed. + * + * The array is an array of objects with the following structure, where this + * base array represents the Api prototype base: + * + * [ + * { + * name: 'data' -- string - Property name + * val: function () {}, -- function - Api method (or undefined if just an object + * methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result + * propExt: [ ... ] -- array - Array of Api object definitions to extend the property + * }, + * { + * name: 'row' + * val: {}, + * methodExt: [ ... ], + * propExt: [ + * { + * name: 'data' + * val: function () {}, + * methodExt: [ ... ], + * propExt: [ ... ] + * }, + * ... + * ] + * } + * ] + * + * @type {Array} + * @ignore + */ + var __apiStruct = [] + + /** + * `Array.prototype` reference. + * + * @type object + * @ignore + */ + var __arrayProto = Array.prototype + + /** + * Abstraction for `context` parameter of the `Api` constructor to allow it to + * take several different forms for ease of use. + * + * Each of the input parameter types will be converted to a DataTables settings + * object where possible. + * + * @param {string|node|jQuery|object} mixed DataTable identifier. Can be one + * of: + * + * * `string` - jQuery selector. Any DataTables' matching the given selector + * with be found and used. + * * `node` - `TABLE` node which has already been formed into a DataTable. + * * `jQuery` - A jQuery object of `TABLE` nodes. + * * `object` - DataTables settings object + * * `DataTables.Api` - API instance + * @return {array|null} Matching DataTables settings objects. `null` or + * `undefined` is returned if no matching DataTable is found. + * @ignore + */ + var _toSettings = function (mixed) { + var idx, jq + var settings = DataTable.settings + var tables = _pluck(settings, "nTable") + + if (!mixed) { + return [] + } else if (mixed.nTable && mixed.oFeatures) { + // DataTables settings object + return [mixed] + } else if (mixed.nodeName && mixed.nodeName.toLowerCase() === "table") { + // Table node + idx = tables.indexOf(mixed) + return idx !== -1 ? [settings[idx]] : null + } else if (mixed && typeof mixed.settings === "function") { + return mixed.settings().toArray() + } else if (typeof mixed === "string") { + // jQuery selector + jq = $(mixed).get() + } else if (mixed instanceof $) { + // jQuery object (also DataTables instance) + jq = mixed.get() + } + + if (jq) { + return settings.filter(function (v, idx) { + return jq.includes(tables[idx]) + }) + } + } + + /** + * DataTables API class - used to control and interface with one or more + * DataTables enhanced tables. + * + * The API class is heavily based on jQuery, presenting a chainable interface + * that you can use to interact with tables. Each instance of the API class has + * a "context" - i.e. the tables that it will operate on. This could be a single + * table, all tables on a page or a sub-set thereof. + * + * Additionally the API is designed to allow you to easily work with the data in + * the tables, retrieving and manipulating it as required. This is done by + * presenting the API class as an array like interface. The contents of the + * array depend upon the actions requested by each method (for example + * `rows().nodes()` will return an array of nodes, while `rows().data()` will + * return an array of objects or arrays depending upon your table's + * configuration). The API object has a number of array like methods (`push`, + * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`, + * `unique` etc) to assist your working with the data held in a table. + * + * Most methods (those which return an Api instance) are chainable, which means + * the return from a method call also has all of the methods available that the + * top level object had. For example, these two calls are equivalent: + * + * // Not chained + * api.row.add( {...} ); + * api.draw(); + * + * // Chained + * api.row.add( {...} ).draw(); + * + * @class DataTable.Api + * @param {array|object|string|jQuery} context DataTable identifier. This is + * used to define which DataTables enhanced tables this API will operate on. + * Can be one of: + * + * * `string` - jQuery selector. Any DataTables' matching the given selector + * with be found and used. + * * `node` - `TABLE` node which has already been formed into a DataTable. + * * `jQuery` - A jQuery object of `TABLE` nodes. + * * `object` - DataTables settings object + * @param {array} [data] Data to initialise the Api instance with. + * + * @example + * // Direct initialisation during DataTables construction + * var api = $('#example').DataTable(); + * + * @example + * // Initialisation using a DataTables jQuery object + * var api = $('#example').dataTable().api(); + * + * @example + * // Initialisation as a constructor + * var api = new DataTable.Api( 'table.dataTable' ); + */ + _Api = function (context, data) { + if (!(this instanceof _Api)) { + return new _Api(context, data) + } + + var i + var settings = [] + var ctxSettings = function (o) { + var a = _toSettings(o) + if (a) { + settings.push.apply(settings, a) + } + } + + if (Array.isArray(context)) { + for (i = 0; i < context.length; i++) { + ctxSettings(context[i]) + } + } else { + ctxSettings(context) + } + + // Remove duplicates + this.context = settings.length > 1 ? _unique(settings) : settings + + // Initial data + if (data) { + // Chrome can throw a max stack error if apply is called with + // too large an array, but apply is faster. + if (data.length < 10000) { + this.push.apply(this, data) + } else { + for (i = 0; i < data.length; i++) { + this.push(data[i]) + } + } + } + + // selector + this.selector = { + rows: null, + cols: null, + opts: null + } + + _Api.extend(this, this, __apiStruct) + } + + DataTable.Api = _Api + + // Don't destroy the existing prototype, just extend it. Required for jQuery 2's + // isPlainObject. + $.extend(_Api.prototype, { + any: function () { + return this.count() !== 0 + }, + + context: [], // array of table settings objects + + count: function () { + return this.flatten().length + }, + + each: function (fn) { + for (var i = 0, ien = this.length; i < ien; i++) { + fn.call(this, this[i], i, this) + } + + return this + }, + + eq: function (idx) { + var ctx = this.context + + return ctx.length > idx ? new _Api(ctx[idx], this[idx]) : null + }, + + filter: function (fn) { + var a = __arrayProto.filter.call(this, fn, this) + + return new _Api(this.context, a) + }, + + flatten: function () { + var a = [] + + return new _Api(this.context, a.concat.apply(a, this.toArray())) + }, + + get: function (idx) { + return this[idx] + }, + + join: __arrayProto.join, + + includes: function (find) { + return this.indexOf(find) === -1 ? false : true + }, + + indexOf: __arrayProto.indexOf, + + iterator: function (flatten, type, fn, alwaysNew) { + var a = [], + ret, + i, + ien, + j, + jen, + context = this.context, + rows, + items, + item, + selector = this.selector + + // Argument shifting + if (typeof flatten === "string") { + alwaysNew = fn + fn = type + type = flatten + flatten = false + } + + for (i = 0, ien = context.length; i < ien; i++) { + var apiInst = new _Api(context[i]) + + if (type === "table") { + ret = fn.call(apiInst, context[i], i) + + if (ret !== undefined) { + a.push(ret) + } + } else if (type === "columns" || type === "rows") { + // this has same length as context - one entry for each table + ret = fn.call(apiInst, context[i], this[i], i) + + if (ret !== undefined) { + a.push(ret) + } + } else if (type === "every" || type === "column" || type === "column-rows" || type === "row" || type === "cell") { + // columns and rows share the same structure. + // 'this' is an array of column indexes for each context + items = this[i] + + if (type === "column-rows") { + rows = _selector_row_indexes(context[i], selector.opts) + } + + for (j = 0, jen = items.length; j < jen; j++) { + item = items[j] + + if (type === "cell") { + ret = fn.call(apiInst, context[i], item.row, item.column, i, j) + } else { + ret = fn.call(apiInst, context[i], item, i, j, rows) + } + + if (ret !== undefined) { + a.push(ret) + } + } + } + } + + if (a.length || alwaysNew) { + var api = new _Api(context, flatten ? a.concat.apply([], a) : a) + var apiSelector = api.selector + apiSelector.rows = selector.rows + apiSelector.cols = selector.cols + apiSelector.opts = selector.opts + return api + } + return this + }, + + lastIndexOf: __arrayProto.lastIndexOf, + + length: 0, + + map: function (fn) { + var a = __arrayProto.map.call(this, fn, this) + + return new _Api(this.context, a) + }, + + pluck: function (prop) { + var fn = DataTable.util.get(prop) + + return this.map(function (el) { + return fn(el) + }) + }, + + pop: __arrayProto.pop, + + push: __arrayProto.push, + + reduce: __arrayProto.reduce, + + reduceRight: __arrayProto.reduceRight, + + reverse: __arrayProto.reverse, + + // Object with rows, columns and opts + selector: null, + + shift: __arrayProto.shift, + + slice: function () { + return new _Api(this.context, this) + }, + + sort: __arrayProto.sort, + + splice: __arrayProto.splice, + + toArray: function () { + return __arrayProto.slice.call(this) + }, + + to$: function () { + return $(this) + }, + + toJQuery: function () { + return $(this) + }, + + unique: function () { + return new _Api(this.context, _unique(this.toArray())) + }, + + unshift: __arrayProto.unshift + }) + + function _api_scope(scope, fn, struc) { + return function () { + var ret = fn.apply(scope || this, arguments) + + // Method extension + _Api.extend(ret, ret, struc.methodExt) + return ret + } + } + + function _api_find(src, name) { + for (var i = 0, ien = src.length; i < ien; i++) { + if (src[i].name === name) { + return src[i] + } + } + return null + } + + window.__apiStruct = __apiStruct + + _Api.extend = function (scope, obj, ext) { + // Only extend API instances and static properties of the API + if (!ext.length || !obj || (!(obj instanceof _Api) && !obj.__dt_wrapper)) { + return + } + + var i, ien, struct + + for (i = 0, ien = ext.length; i < ien; i++) { + struct = ext[i] + + if (struct.name === "__proto__") { + continue + } + + // Value + obj[struct.name] = struct.type === "function" ? _api_scope(scope, struct.val, struct) : struct.type === "object" ? {} : struct.val + + obj[struct.name].__dt_wrapper = true + + // Property extension + _Api.extend(scope, obj[struct.name], struct.propExt) + } + } + + // [ + // { + // name: 'data' -- string - Property name + // val: function () {}, -- function - Api method (or undefined if just an object + // methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result + // propExt: [ ... ] -- array - Array of Api object definitions to extend the property + // }, + // { + // name: 'row' + // val: {}, + // methodExt: [ ... ], + // propExt: [ + // { + // name: 'data' + // val: function () {}, + // methodExt: [ ... ], + // propExt: [ ... ] + // }, + // ... + // ] + // } + // ] + + _Api.register = _api_register = function (name, val) { + if (Array.isArray(name)) { + for (var j = 0, jen = name.length; j < jen; j++) { + _Api.register(name[j], val) + } + return + } + + var i, + ien, + heir = name.split("."), + struct = __apiStruct, + key, + method + + for (i = 0, ien = heir.length; i < ien; i++) { + method = heir[i].indexOf("()") !== -1 + key = method ? heir[i].replace("()", "") : heir[i] + + var src = _api_find(struct, key) + if (!src) { + src = { + name: key, + val: {}, + methodExt: [], + propExt: [], + type: "object" + } + struct.push(src) + } + + if (i === ien - 1) { + src.val = val + src.type = typeof val === "function" ? "function" : $.isPlainObject(val) ? "object" : "other" + } else { + struct = method ? src.methodExt : src.propExt + } + } + } + + _Api.registerPlural = _api_registerPlural = function (pluralName, singularName, val) { + _Api.register(pluralName, val) + + _Api.register(singularName, function () { + var ret = val.apply(this, arguments) + + if (ret === this) { + // Returned item is the API instance that was passed in, return it + return this + } else if (ret instanceof _Api) { + // New API instance returned, want the value from the first item + // in the returned array for the singular result. + return ret.length + ? Array.isArray(ret[0]) + ? new _Api(ret.context, ret[0]) // Array results are 'enhanced' + : ret[0] + : undefined + } + + // Non-API return - just fire it back + return ret + }) + } + + /** + * Selector for HTML tables. Apply the given selector to the give array of + * DataTables settings objects. + * + * @param {string|integer} [selector] jQuery selector string or integer + * @param {array} Array of DataTables settings objects to be filtered + * @return {array} + * @ignore + */ + var __table_selector = function (selector, a) { + if (Array.isArray(selector)) { + var result = [] + + selector.forEach(function (sel) { + var inner = __table_selector(sel, a) + + result.push.apply(result, inner) + }) + + return result.filter(function (item) { + return item + }) + } + + // Integer is used to pick out a table by index + if (typeof selector === "number") { + return [a[selector]] + } + + // Perform a jQuery selector on the table nodes + var nodes = a.map(function (el) { + return el.nTable + }) + + return $(nodes) + .filter(selector) + .map(function () { + // Need to translate back from the table node to the settings + var idx = nodes.indexOf(this) + return a[idx] + }) + .toArray() + } + + /** + * Context selector for the API's context (i.e. the tables the API instance + * refers to. + * + * @name DataTable.Api#tables + * @param {string|integer} [selector] Selector to pick which tables the iterator + * should operate on. If not given, all tables in the current context are + * used. This can be given as a jQuery selector (for example `':gt(0)'`) to + * select multiple tables or as an integer to select a single table. + * @returns {DataTable.Api} Returns a new API instance if a selector is given. + */ + _api_register("tables()", function (selector) { + // A new instance is created if there was a selector specified + return selector !== undefined && selector !== null ? new _Api(__table_selector(selector, this.context)) : this + }) + + _api_register("table()", function (selector) { + var tables = this.tables(selector) + var ctx = tables.context + + // Truncate to the first matched table + return ctx.length ? new _Api(ctx[0]) : tables + }) + + // Common methods, combined to reduce size + ;[ + ["nodes", "node", "nTable"], + ["body", "body", "nTBody"], + ["header", "header", "nTHead"], + ["footer", "footer", "nTFoot"] + ].forEach(function (item) { + _api_registerPlural("tables()." + item[0] + "()", "table()." + item[1] + "()", function () { + return this.iterator( + "table", + function (ctx) { + return ctx[item[2]] + }, + 1 + ) + }) + }) + + // Structure methods + ;[ + ["header", "aoHeader"], + ["footer", "aoFooter"] + ].forEach(function (item) { + _api_register("table()." + item[0] + ".structure()", function (selector) { + var indexes = this.columns(selector).indexes().flatten() + var ctx = this.context[0] + + return _fnHeaderLayout(ctx, ctx[item[1]], indexes) + }) + }) + + _api_registerPlural("tables().containers()", "table().container()", function () { + return this.iterator( + "table", + function (ctx) { + return ctx.nTableWrapper + }, + 1 + ) + }) + + _api_register("tables().every()", function (fn) { + var that = this + + return this.iterator("table", function (s, i) { + fn.call(that.table(i), i) + }) + }) + + _api_register("caption()", function (value, side) { + var context = this.context + + // Getter - return existing node's content + if (value === undefined) { + var caption = context[0].captionNode + + return caption && context.length ? caption.innerHTML : null + } + + return this.iterator( + "table", + function (ctx) { + var table = $(ctx.nTable) + var caption = $(ctx.captionNode) + var container = $(ctx.nTableWrapper) + + // Create the node if it doesn't exist yet + if (!caption.length) { + caption = $("<caption/>").html(value) + ctx.captionNode = caption[0] + + // If side isn't set, we need to insert into the document to let the + // CSS decide so we can read it back, otherwise there is no way to + // know if the CSS would put it top or bottom for scrolling + if (!side) { + table.prepend(caption) + + side = caption.css("caption-side") + } + } + + caption.html(value) + + if (side) { + caption.css("caption-side", side) + caption[0]._captionSide = side + } + + if (container.find("div.dataTables_scroll").length) { + var selector = side === "top" ? "Head" : "Foot" + + container.find("div.dataTables_scroll" + selector + " table").prepend(caption) + } else { + table.prepend(caption) + } + }, + 1 + ) + }) + + _api_register("caption.node()", function () { + var ctx = this.context + + return ctx.length ? ctx[0].captionNode : null + }) + + /** + * Redraw the tables in the current context. + */ + _api_register("draw()", function (paging) { + return this.iterator("table", function (settings) { + if (paging === "page") { + _fnDraw(settings) + } else { + if (typeof paging === "string") { + paging = paging === "full-hold" ? false : true + } + + _fnReDraw(settings, paging === false) + } + }) + }) + + /** + * Get the current page index. + * + * @return {integer} Current page index (zero based) + */ /** + * Set the current page. + * + * Note that if you attempt to show a page which does not exist, DataTables will + * not throw an error, but rather reset the paging. + * + * @param {integer|string} action The paging action to take. This can be one of: + * * `integer` - The page index to jump to + * * `string` - An action to take: + * * `first` - Jump to first page. + * * `next` - Jump to the next page + * * `previous` - Jump to previous page + * * `last` - Jump to the last page. + * @returns {DataTables.Api} this + */ _api_register("page()", function (action) { + if (action === undefined) { + return this.page.info().page // not an expensive call + } + + // else, have an action to take on all tables + return this.iterator("table", function (settings) { + _fnPageChange(settings, action) + }) + }) + + /** + * Paging information for the first table in the current context. + * + * If you require paging information for another table, use the `table()` method + * with a suitable selector. + * + * @return {object} Object with the following properties set: + * * `page` - Current page index (zero based - i.e. the first page is `0`) + * * `pages` - Total number of pages + * * `start` - Display index for the first record shown on the current page + * * `end` - Display index for the last record shown on the current page + * * `length` - Display length (number of records). Note that generally `start + * + length = end`, but this is not always true, for example if there are + * only 2 records to show on the final page, with a length of 10. + * * `recordsTotal` - Full data set length + * * `recordsDisplay` - Data set length once the current filtering criterion + * are applied. + */ + _api_register("page.info()", function () { + if (this.context.length === 0) { + return undefined + } + + var settings = this.context[0], + start = settings._iDisplayStart, + len = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1, + visRecords = settings.fnRecordsDisplay(), + all = len === -1 + + return { + page: all ? 0 : Math.floor(start / len), + pages: all ? 1 : Math.ceil(visRecords / len), + start: start, + end: settings.fnDisplayEnd(), + length: len, + recordsTotal: settings.fnRecordsTotal(), + recordsDisplay: visRecords, + serverSide: _fnDataSource(settings) === "ssp" + } + }) + + /** + * Get the current page length. + * + * @return {integer} Current page length. Note `-1` indicates that all records + * are to be shown. + */ /** + * Set the current page length. + * + * @param {integer} Page length to set. Use `-1` to show all records. + * @returns {DataTables.Api} this + */ _api_register("page.len()", function (len) { + // Note that we can't call this function 'length()' because `length` + // is a Javascript property of functions which defines how many arguments + // the function expects. + if (len === undefined) { + return this.context.length !== 0 ? this.context[0]._iDisplayLength : undefined + } + + // else, set the page length + return this.iterator("table", function (settings) { + _fnLengthChange(settings, len) + }) + }) + + var __reload = function (settings, holdPosition, callback) { + // Use the draw event to trigger a callback + if (callback) { + var api = new _Api(settings) + + api.one("draw", function () { + callback(api.ajax.json()) + }) + } + + if (_fnDataSource(settings) == "ssp") { + _fnReDraw(settings, holdPosition) + } else { + _fnProcessingDisplay(settings, true) + + // Cancel an existing request + var xhr = settings.jqXHR + if (xhr && xhr.readyState !== 4) { + xhr.abort() + } + + // Trigger xhr + _fnBuildAjax(settings, {}, function (json) { + _fnClearTable(settings) + + var data = _fnAjaxDataSrc(settings, json) + for (var i = 0, ien = data.length; i < ien; i++) { + _fnAddData(settings, data[i]) + } + + _fnReDraw(settings, holdPosition) + _fnInitComplete(settings) + _fnProcessingDisplay(settings, false) + }) + } + } + + /** + * Get the JSON response from the last Ajax request that DataTables made to the + * server. Note that this returns the JSON from the first table in the current + * context. + * + * @return {object} JSON received from the server. + */ + _api_register("ajax.json()", function () { + var ctx = this.context + + if (ctx.length > 0) { + return ctx[0].json + } + + // else return undefined; + }) + + /** + * Get the data submitted in the last Ajax request + */ + _api_register("ajax.params()", function () { + var ctx = this.context + + if (ctx.length > 0) { + return ctx[0].oAjaxData + } + + // else return undefined; + }) + + /** + * Reload tables from the Ajax data source. Note that this function will + * automatically re-draw the table when the remote data has been loaded. + * + * @param {boolean} [reset=true] Reset (default) or hold the current paging + * position. A full re-sort and re-filter is performed when this method is + * called, which is why the pagination reset is the default action. + * @returns {DataTables.Api} this + */ + _api_register("ajax.reload()", function (callback, resetPaging) { + return this.iterator("table", function (settings) { + __reload(settings, resetPaging === false, callback) + }) + }) + + /** + * Get the current Ajax URL. Note that this returns the URL from the first + * table in the current context. + * + * @return {string} Current Ajax source URL + */ /** + * Set the Ajax URL. Note that this will set the URL for all tables in the + * current context. + * + * @param {string} url URL to set. + * @returns {DataTables.Api} this + */ _api_register("ajax.url()", function (url) { + var ctx = this.context + + if (url === undefined) { + // get + if (ctx.length === 0) { + return undefined + } + ctx = ctx[0] + + return $.isPlainObject(ctx.ajax) ? ctx.ajax.url : ctx.ajax + } + + // set + return this.iterator("table", function (settings) { + if ($.isPlainObject(settings.ajax)) { + settings.ajax.url = url + } else { + settings.ajax = url + } + }) + }) + + /** + * Load data from the newly set Ajax URL. Note that this method is only + * available when `ajax.url()` is used to set a URL. Additionally, this method + * has the same effect as calling `ajax.reload()` but is provided for + * convenience when setting a new URL. Like `ajax.reload()` it will + * automatically redraw the table once the remote data has been loaded. + * + * @returns {DataTables.Api} this + */ + _api_register("ajax.url().load()", function (callback, resetPaging) { + // Same as a reload, but makes sense to present it for easy access after a + // url change + return this.iterator("table", function (ctx) { + __reload(ctx, resetPaging === false, callback) + }) + }) + + var _selector_run = function (type, selector, selectFn, settings, opts) { + var out = [], + res, + a, + i, + ien, + j, + jen, + selectorType = typeof selector + + // Can't just check for isArray here, as an API or jQuery instance might be + // given with their array like look + if (!selector || selectorType === "string" || selectorType === "function" || selector.length === undefined) { + selector = [selector] + } + + for (i = 0, ien = selector.length; i < ien; i++) { + // Only split on simple strings - complex expressions will be jQuery selectors + a = selector[i] && selector[i].split && !selector[i].match(/[[(:]/) ? selector[i].split(",") : [selector[i]] + + for (j = 0, jen = a.length; j < jen; j++) { + res = selectFn(typeof a[j] === "string" ? a[j].trim() : a[j]) + + // Remove empty items + res = res.filter(function (item) { + return item !== null && item !== undefined + }) + + if (res && res.length) { + out = out.concat(res) + } + } + } + + // selector extensions + var ext = _ext.selector[type] + if (ext.length) { + for (i = 0, ien = ext.length; i < ien; i++) { + out = ext[i](settings, opts, out) + } + } + + return _unique(out) + } + + var _selector_opts = function (opts) { + if (!opts) { + opts = {} + } + + // Backwards compatibility for 1.9- which used the terminology filter rather + // than search + if (opts.filter && opts.search === undefined) { + opts.search = opts.filter + } + + return $.extend( + { + search: "none", + order: "current", + page: "all" + }, + opts + ) + } + + // Reduce the API instance to the first item found + var _selector_first = function (old) { + var inst = new _Api(old.context[0]) + + // Use a push rather than passing to the constructor, since it will + // merge arrays down automatically, which isn't what is wanted here + if (old.length) { + inst.push(old[0]) + } + + inst.selector = old.selector + + // Limit to a single row / column / cell + if (inst.length && inst[0].length > 1) { + inst[0].splice(1) + } + + return inst + } + + var _selector_row_indexes = function (settings, opts) { + var i, + ien, + tmp, + a = [], + displayFiltered = settings.aiDisplay, + displayMaster = settings.aiDisplayMaster + + var search = opts.search, // none, applied, removed + order = opts.order, // applied, current, index (original - compatibility with 1.9) + page = opts.page // all, current + + if (_fnDataSource(settings) == "ssp") { + // In server-side processing mode, most options are irrelevant since + // rows not shown don't exist and the index order is the applied order + // Removed is a special case - for consistency just return an empty + // array + return search === "removed" ? [] : _range(0, displayMaster.length) + } + + if (page == "current") { + // Current page implies that order=current and filter=applied, since it is + // fairly senseless otherwise, regardless of what order and search actually + // are + for (i = settings._iDisplayStart, ien = settings.fnDisplayEnd(); i < ien; i++) { + a.push(displayFiltered[i]) + } + } else if (order == "current" || order == "applied") { + if (search == "none") { + a = displayMaster.slice() + } else if (search == "applied") { + a = displayFiltered.slice() + } else if (search == "removed") { + // O(n+m) solution by creating a hash map + var displayFilteredMap = {} + + for (i = 0, ien = displayFiltered.length; i < ien; i++) { + displayFilteredMap[displayFiltered[i]] = null + } + + displayMaster.forEach(function (item) { + if (!Object.prototype.hasOwnProperty.call(displayFilteredMap, item)) { + a.push(item) + } + }) + } + } else if (order == "index" || order == "original") { + for (i = 0, ien = settings.aoData.length; i < ien; i++) { + if (!settings.aoData[i]) { + continue + } + + if (search == "none") { + a.push(i) + } else { + // applied | removed + tmp = displayFiltered.indexOf(i) + + if ((tmp === -1 && search == "removed") || (tmp >= 0 && search == "applied")) { + a.push(i) + } + } + } + } else if (typeof order === "number") { + // Order the rows by the given column + var ordered = _fnSort(settings, order, "asc") + + if (search === "none") { + a = ordered + } else { + // applied | removed + for (i = 0; i < ordered.length; i++) { + tmp = displayFiltered.indexOf(ordered[i]) + + if ((tmp === -1 && search == "removed") || (tmp >= 0 && search == "applied")) { + a.push(ordered[i]) + } + } + } + } + + return a + } + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Rows + * + * {} - no selector - use all available rows + * {integer} - row aoData index + * {node} - TR node + * {string} - jQuery selector to apply to the TR elements + * {array} - jQuery array of nodes, or simply an array of TR nodes + * + */ + var __row_selector = function (settings, selector, opts) { + var rows + var run = function (sel) { + var selInt = _intVal(sel) + var aoData = settings.aoData + + // Short cut - selector is a number and no options provided (default is + // all records, so no need to check if the index is in there, since it + // must be - dev error if the index doesn't exist). + if (selInt !== null && !opts) { + return [selInt] + } + + if (!rows) { + rows = _selector_row_indexes(settings, opts) + } + + if (selInt !== null && rows.indexOf(selInt) !== -1) { + // Selector - integer + return [selInt] + } else if (sel === null || sel === undefined || sel === "") { + // Selector - none + return rows + } + + // Selector - function + if (typeof sel === "function") { + return rows.map(function (idx) { + var row = aoData[idx] + return sel(idx, row._aData, row.nTr) ? idx : null + }) + } + + // Selector - node + if (sel.nodeName) { + var rowIdx = sel._DT_RowIndex // Property added by DT for fast lookup + var cellIdx = sel._DT_CellIndex + + if (rowIdx !== undefined) { + // Make sure that the row is actually still present in the table + return aoData[rowIdx] && aoData[rowIdx].nTr === sel ? [rowIdx] : [] + } else if (cellIdx) { + return aoData[cellIdx.row] && aoData[cellIdx.row].nTr === sel.parentNode ? [cellIdx.row] : [] + } else { + var host = $(sel).closest("*[data-dt-row]") + return host.length ? [host.data("dt-row")] : [] + } + } + + // ID selector. Want to always be able to select rows by id, regardless + // of if the tr element has been created or not, so can't rely upon + // jQuery here - hence a custom implementation. This does not match + // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything, + // but to select it using a CSS selector engine (like Sizzle or + // querySelect) it would need to need to be escaped for some characters. + // DataTables simplifies this for row selectors since you can select + // only a row. A # indicates an id any anything that follows is the id - + // unescaped. + if (typeof sel === "string" && sel.charAt(0) === "#") { + // get row index from id + var rowObj = settings.aIds[sel.replace(/^#/, "")] + if (rowObj !== undefined) { + return [rowObj.idx] + } + + // need to fall through to jQuery in case there is DOM id that + // matches + } + + // Get nodes in the order from the `rows` array with null values removed + var nodes = _removeEmpty(_pluck_order(settings.aoData, rows, "nTr")) + + // Selector - jQuery selector string, array of nodes or jQuery object/ + // As jQuery's .filter() allows jQuery objects to be passed in filter, + // it also allows arrays, so this will cope with all three options + return $(nodes) + .filter(sel) + .map(function () { + return this._DT_RowIndex + }) + .toArray() + } + + var matched = _selector_run("row", selector, run, settings, opts) + + if (opts.order === "current" || opts.order === "applied") { + _fnSortDisplay(settings, matched) + } + + return matched + } + + _api_register("rows()", function (selector, opts) { + // argument shifting + if (selector === undefined) { + selector = "" + } else if ($.isPlainObject(selector)) { + opts = selector + selector = "" + } + + opts = _selector_opts(opts) + + var inst = this.iterator( + "table", + function (settings) { + return __row_selector(settings, selector, opts) + }, + 1 + ) + + // Want argument shifting here and in __row_selector? + inst.selector.rows = selector + inst.selector.opts = opts + + return inst + }) + + _api_register("rows().nodes()", function () { + return this.iterator( + "row", + function (settings, row) { + return settings.aoData[row].nTr || undefined + }, + 1 + ) + }) + + _api_register("rows().data()", function () { + return this.iterator( + true, + "rows", + function (settings, rows) { + return _pluck_order(settings.aoData, rows, "_aData") + }, + 1 + ) + }) + + _api_registerPlural("rows().cache()", "row().cache()", function (type) { + return this.iterator( + "row", + function (settings, row) { + var r = settings.aoData[row] + return type === "search" ? r._aFilterData : r._aSortData + }, + 1 + ) + }) + + _api_registerPlural("rows().invalidate()", "row().invalidate()", function (src) { + return this.iterator("row", function (settings, row) { + _fnInvalidate(settings, row, src) + }) + }) + + _api_registerPlural("rows().indexes()", "row().index()", function () { + return this.iterator( + "row", + function (settings, row) { + return row + }, + 1 + ) + }) + + _api_registerPlural("rows().ids()", "row().id()", function (hash) { + var a = [] + var context = this.context + + // `iterator` will drop undefined values, but in this case we want them + for (var i = 0, ien = context.length; i < ien; i++) { + for (var j = 0, jen = this[i].length; j < jen; j++) { + var id = context[i].rowIdFn(context[i].aoData[this[i][j]]._aData) + a.push((hash === true ? "#" : "") + id) + } + } + + return new _Api(context, a) + }) + + _api_registerPlural("rows().remove()", "row().remove()", function () { + this.iterator("row", function (settings, row) { + var data = settings.aoData + var rowData = data[row] + + // Delete from the display arrays + var idx = settings.aiDisplayMaster.indexOf(row) + if (idx !== -1) { + settings.aiDisplayMaster.splice(idx, 1) + } + + // For server-side processing tables - subtract the deleted row from the count + if (settings._iRecordsDisplay > 0) { + settings._iRecordsDisplay-- + } + + // Check for an 'overflow' they case for displaying the table + _fnLengthOverflow(settings) + + // Remove the row's ID reference if there is one + var id = settings.rowIdFn(rowData._aData) + if (id !== undefined) { + delete settings.aIds[id] + } + + data[row] = null + }) + + return this + }) + + _api_register("rows.add()", function (rows) { + var newRows = this.iterator( + "table", + function (settings) { + var row, i, ien + var out = [] + + for (i = 0, ien = rows.length; i < ien; i++) { + row = rows[i] + + if (row.nodeName && row.nodeName.toUpperCase() === "TR") { + out.push(_fnAddTr(settings, row)[0]) + } else { + out.push(_fnAddData(settings, row)) + } + } + + return out + }, + 1 + ) + + // Return an Api.rows() extended instance, so rows().nodes() etc can be used + var modRows = this.rows(-1) + modRows.pop() + modRows.push.apply(modRows, newRows) + + return modRows + }) + + /** + * + */ + _api_register("row()", function (selector, opts) { + return _selector_first(this.rows(selector, opts)) + }) + + _api_register("row().data()", function (data) { + var ctx = this.context + + if (data === undefined) { + // Get + return ctx.length && this.length && this[0].length ? ctx[0].aoData[this[0]]._aData : undefined + } + + // Set + var row = ctx[0].aoData[this[0]] + row._aData = data + + // If the DOM has an id, and the data source is an array + if (Array.isArray(data) && row.nTr && row.nTr.id) { + _fnSetObjectDataFn(ctx[0].rowId)(data, row.nTr.id) + } + + // Automatically invalidate + _fnInvalidate(ctx[0], this[0], "data") + + return this + }) + + _api_register("row().node()", function () { + var ctx = this.context + + if (ctx.length && this.length && this[0].length) { + var row = ctx[0].aoData[this[0]] + + if (row && row.nTr) { + return row.nTr + } + } + + return null + }) + + _api_register("row.add()", function (row) { + // Allow a jQuery object to be passed in - only a single row is added from + // it though - the first element in the set + if (row instanceof $ && row.length) { + row = row[0] + } + + var rows = this.iterator("table", function (settings) { + if (row.nodeName && row.nodeName.toUpperCase() === "TR") { + return _fnAddTr(settings, row)[0] + } + return _fnAddData(settings, row) + }) + + // Return an Api.rows() extended instance, with the newly added row selected + return this.row(rows[0]) + }) + + $(document).on("plugin-init.dt", function (e, context) { + var api = new _Api(context) + + api.on("stateSaveParams.DT", function (e, settings, d) { + // This could be more compact with the API, but it is a lot faster as a simple + // internal loop + var idFn = settings.rowIdFn + var rows = settings.aiDisplayMaster + var ids = [] + + for (var i = 0; i < rows.length; i++) { + var rowIdx = rows[i] + var data = settings.aoData[rowIdx] + + if (data._detailsShow) { + ids.push("#" + idFn(data._aData)) + } + } + + d.childRows = ids + }) + + // For future state loads (e.g. with StateRestore) + api.on("stateLoaded.DT", function (e, settings, state) { + __details_state_load(api, state) + }) + + // And the initial load state + __details_state_load(api, api.state.loaded()) + }) + + var __details_state_load = function (api, state) { + if (state && state.childRows) { + api + .rows( + state.childRows.map(function (id) { + // Escape any `:` characters from the row id. Accounts for + // already escaped characters. + return id.replace(/([^:\\]*(?:\\.[^:\\]*)*):/g, "$1\\:") + }) + ) + .every(function () { + _fnCallbackFire(api.settings()[0], null, "requestChild", [this]) + }) + } + } + + var __details_add = function (ctx, row, data, klass) { + // Convert to array of TR elements + var rows = [] + var addRow = function (r, k) { + // Recursion to allow for arrays of jQuery objects + if (Array.isArray(r) || r instanceof $) { + for (var i = 0, ien = r.length; i < ien; i++) { + addRow(r[i], k) + } + return + } + + // If we get a TR element, then just add it directly - up to the dev + // to add the correct number of columns etc + if (r.nodeName && r.nodeName.toLowerCase() === "tr") { + r.setAttribute("data-dt-row", row.idx) + rows.push(r) + } else { + // Otherwise create a row with a wrapper + var created = $("<tr><td></td></tr>").attr("data-dt-row", row.idx).addClass(k) + + $("td", created).addClass(k).html(r)[0].colSpan = _fnVisbleColumns(ctx) + + rows.push(created[0]) + } + } + + addRow(data, klass) + + if (row._details) { + row._details.detach() + } + + row._details = $(rows) + + // If the children were already shown, that state should be retained + if (row._detailsShow) { + row._details.insertAfter(row.nTr) + } + } + + // Make state saving of child row details async to allow them to be batch processed + var __details_state = DataTable.util.throttle(function (ctx) { + _fnSaveState(ctx[0]) + }, 500) + + var __details_remove = function (api, idx) { + var ctx = api.context + + if (ctx.length) { + var row = ctx[0].aoData[idx !== undefined ? idx : api[0]] + + if (row && row._details) { + row._details.remove() + + row._detailsShow = undefined + row._details = undefined + $(row.nTr).removeClass("dt-hasChild") + __details_state(ctx) + } + } + } + + var __details_display = function (api, show) { + var ctx = api.context + + if (ctx.length && api.length) { + var row = ctx[0].aoData[api[0]] + + if (row._details) { + row._detailsShow = show + + if (show) { + row._details.insertAfter(row.nTr) + $(row.nTr).addClass("dt-hasChild") + } else { + row._details.detach() + $(row.nTr).removeClass("dt-hasChild") + } + + _fnCallbackFire(ctx[0], null, "childRow", [show, api.row(api[0])]) + + __details_events(ctx[0]) + __details_state(ctx) + } + } + } + + var __details_events = function (settings) { + var api = new _Api(settings) + var namespace = ".dt.DT_details" + var drawEvent = "draw" + namespace + var colvisEvent = "column-sizing" + namespace + var destroyEvent = "destroy" + namespace + var data = settings.aoData + + api.off(drawEvent + " " + colvisEvent + " " + destroyEvent) + + if (_pluck(data, "_details").length > 0) { + // On each draw, insert the required elements into the document + api.on(drawEvent, function (e, ctx) { + if (settings !== ctx) { + return + } + + api + .rows({ page: "current" }) + .eq(0) + .each(function (idx) { + // Internal data grab + var row = data[idx] + + if (row._detailsShow) { + row._details.insertAfter(row.nTr) + } + }) + }) + + // Column visibility change - update the colspan + api.on(colvisEvent, function (e, ctx) { + if (settings !== ctx) { + return + } + + // Update the colspan for the details rows (note, only if it already has + // a colspan) + var row, + visible = _fnVisbleColumns(ctx) + + for (var i = 0, ien = data.length; i < ien; i++) { + row = data[i] + + if (row && row._details) { + row._details.each(function () { + var el = $(this).children("td") + + if (el.length == 1) { + el.attr("colspan", visible) + } + }) + } + } + }) + + // Table destroyed - nuke any child rows + api.on(destroyEvent, function (e, ctx) { + if (settings !== ctx) { + return + } + + for (var i = 0, ien = data.length; i < ien; i++) { + if (data[i] && data[i]._details) { + __details_remove(api, i) + } + } + }) + } + } + + // Strings for the method names to help minification + var _emp = "" + var _child_obj = _emp + "row().child" + var _child_mth = _child_obj + "()" + + // data can be: + // tr + // string + // jQuery or array of any of the above + _api_register(_child_mth, function (data, klass) { + var ctx = this.context + + if (data === undefined) { + // get + return ctx.length && this.length && ctx[0].aoData[this[0]] ? ctx[0].aoData[this[0]]._details : undefined + } else if (data === true) { + // show + this.child.show() + } else if (data === false) { + // remove + __details_remove(this) + } else if (ctx.length && this.length) { + // set + __details_add(ctx[0], ctx[0].aoData[this[0]], data, klass) + } + + return this + }) + + _api_register( + [ + _child_obj + ".show()", + _child_mth + ".show()" // only when `child()` was called with parameters (without + ], + function () { + // it returns an object and this method is not executed) + __details_display(this, true) + return this + } + ) + + _api_register( + [ + _child_obj + ".hide()", + _child_mth + ".hide()" // only when `child()` was called with parameters (without + ], + function () { + // it returns an object and this method is not executed) + __details_display(this, false) + return this + } + ) + + _api_register( + [ + _child_obj + ".remove()", + _child_mth + ".remove()" // only when `child()` was called with parameters (without + ], + function () { + // it returns an object and this method is not executed) + __details_remove(this) + return this + } + ) + + _api_register(_child_obj + ".isShown()", function () { + var ctx = this.context + + if (ctx.length && this.length && ctx[0].aoData[this[0]]) { + // _detailsShown as false or undefined will fall through to return false + return ctx[0].aoData[this[0]]._detailsShow || false + } + return false + }) + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Columns + * + * {integer} - column index (>=0 count from left, <0 count from right) + * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right) + * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right) + * "{string}:name" - column name + * "{string}" - jQuery selector on column header nodes + * + */ + + // can be an array of these items, comma separated list, or an array of comma + // separated lists + + var __re_column_selector = /^([^:]+)?:(name|title|visIdx|visible)$/ + + // r1 and r2 are redundant - but it means that the parameters match for the + // iterator callback in columns().data() + var __columnData = function (settings, column, r1, r2, rows, type) { + var a = [] + for (var row = 0, ien = rows.length; row < ien; row++) { + a.push(_fnGetCellData(settings, rows[row], column, type)) + } + return a + } + + var __column_header = function (settings, column, row) { + var header = settings.aoHeader + var target = + row !== undefined + ? row + : settings.bSortCellsTop // legacy support + ? 0 + : header.length - 1 + + return header[target][column].cell + } + + var __column_selector = function (settings, selector, opts) { + var columns = settings.aoColumns, + names = _pluck(columns, "sName"), + titles = _pluck(columns, "sTitle"), + cells = DataTable.util.get("[].[].cell")(settings.aoHeader), + nodes = _unique(_flatten([], cells)) + + var run = function (s) { + var selInt = _intVal(s) + + // Selector - all + if (s === "") { + return _range(columns.length) + } + + // Selector - index + if (selInt !== null) { + return [ + selInt >= 0 + ? selInt // Count from left + : columns.length + selInt // Count from right (+ because its a negative value) + ] + } + + // Selector = function + if (typeof s === "function") { + var rows = _selector_row_indexes(settings, opts) + + return columns.map(function (col, idx) { + return s(idx, __columnData(settings, idx, 0, 0, rows), __column_header(settings, idx)) ? idx : null + }) + } + + // jQuery or string selector + var match = typeof s === "string" ? s.match(__re_column_selector) : "" + + if (match) { + switch (match[2]) { + case "visIdx": + case "visible": + // Selector is a column index + if (match[1] && match[1].match(/^\d+$/)) { + var idx = parseInt(match[1], 10) + + // Visible index given, convert to column index + if (idx < 0) { + // Counting from the right + var visColumns = columns.map(function (col, i) { + return col.bVisible ? i : null + }) + return [visColumns[visColumns.length + idx]] + } + // Counting from the left + return [_fnVisibleToColumnIndex(settings, idx)] + } + + return columns.map(function (col, idx) { + // Not visible, can't match + if (!col.bVisible) { + return null + } + + // Selector + if (match[1]) { + return $(nodes[idx]).filter(match[1]).length > 0 ? idx : null + } + + // `:visible` on its own + return idx + }) + + case "name": + // match by name. `names` is column index complete and in order + return names.map(function (name, i) { + return name === match[1] ? i : null + }) + + case "title": + // match by column title + return titles.map(function (title, i) { + return title === match[1] ? i : null + }) + + default: + return [] + } + } + + // Cell in the table body + if (s.nodeName && s._DT_CellIndex) { + return [s._DT_CellIndex.column] + } + + // jQuery selector on the TH elements for the columns + var jqResult = $(nodes) + .filter(s) + .map(function () { + return _fnColumnsFromHeader(this) // `nodes` is column index complete and in order + }) + .toArray() + + if (jqResult.length || !s.nodeName) { + return jqResult + } + + // Otherwise a node which might have a `dt-column` data attribute, or be + // a child or such an element + var host = $(s).closest("*[data-dt-column]") + return host.length ? [host.data("dt-column")] : [] + } + + return _selector_run("column", selector, run, settings, opts) + } + + var __setColumnVis = function (settings, column, vis) { + var cols = settings.aoColumns, + col = cols[column], + data = settings.aoData, + cells, + i, + ien, + tr + + // Get + if (vis === undefined) { + return col.bVisible + } + + // Set + // No change + if (col.bVisible === vis) { + return false + } + + if (vis) { + // Insert column + // Need to decide if we should use appendChild or insertBefore + var insertBefore = _pluck(cols, "bVisible").indexOf(true, column + 1) + + for (i = 0, ien = data.length; i < ien; i++) { + if (data[i]) { + tr = data[i].nTr + cells = data[i].anCells + + if (tr) { + // insertBefore can act like appendChild if 2nd arg is null + tr.insertBefore(cells[column], cells[insertBefore] || null) + } + } + } + } else { + // Remove column + $(_pluck(settings.aoData, "anCells", column)).detach() + } + + // Common actions + col.bVisible = vis + + _colGroup(settings) + + return true + } + + _api_register("columns()", function (selector, opts) { + // argument shifting + if (selector === undefined) { + selector = "" + } else if ($.isPlainObject(selector)) { + opts = selector + selector = "" + } + + opts = _selector_opts(opts) + + var inst = this.iterator( + "table", + function (settings) { + return __column_selector(settings, selector, opts) + }, + 1 + ) + + // Want argument shifting here and in _row_selector? + inst.selector.cols = selector + inst.selector.opts = opts + + return inst + }) + + _api_registerPlural("columns().header()", "column().header()", function (row) { + return this.iterator( + "column", + function (settings, column) { + return __column_header(settings, column, row) + }, + 1 + ) + }) + + _api_registerPlural("columns().footer()", "column().footer()", function (row) { + return this.iterator( + "column", + function (settings, column) { + var footer = settings.aoFooter + + if (!footer.length) { + return null + } + + return settings.aoFooter[row !== undefined ? row : 0][column].cell + }, + 1 + ) + }) + + _api_registerPlural("columns().data()", "column().data()", function () { + return this.iterator("column-rows", __columnData, 1) + }) + + _api_registerPlural("columns().render()", "column().render()", function (type) { + return this.iterator( + "column-rows", + function (settings, column, i, j, rows) { + return __columnData(settings, column, i, j, rows, type) + }, + 1 + ) + }) + + _api_registerPlural("columns().dataSrc()", "column().dataSrc()", function () { + return this.iterator( + "column", + function (settings, column) { + return settings.aoColumns[column].mData + }, + 1 + ) + }) + + _api_registerPlural("columns().cache()", "column().cache()", function (type) { + return this.iterator( + "column-rows", + function (settings, column, i, j, rows) { + return _pluck_order(settings.aoData, rows, type === "search" ? "_aFilterData" : "_aSortData", column) + }, + 1 + ) + }) + + _api_registerPlural("columns().init()", "column().init()", function () { + return this.iterator( + "column", + function (settings, column) { + return settings.aoColumns[column] + }, + 1 + ) + }) + + _api_registerPlural("columns().nodes()", "column().nodes()", function () { + return this.iterator( + "column-rows", + function (settings, column, i, j, rows) { + return _pluck_order(settings.aoData, rows, "anCells", column) + }, + 1 + ) + }) + + _api_registerPlural("columns().titles()", "column().title()", function (title, row) { + return this.iterator( + "column", + function (settings, column) { + // Argument shifting + if (typeof title === "number") { + row = title + title = undefined + } + + var span = $("span.dt-column-title", this.column(column).header(row)) + + if (title !== undefined) { + span.html(title) + return this + } + + return span.html() + }, + 1 + ) + }) + + _api_registerPlural("columns().types()", "column().type()", function () { + return this.iterator( + "column", + function (settings, column) { + var type = settings.aoColumns[column].sType + + // If the type was invalidated, then resolve it. This actually does + // all columns at the moment. Would only happen once if getting all + // column's data types. + if (!type) { + _fnColumnTypes(settings) + } + + return type + }, + 1 + ) + }) + + _api_registerPlural("columns().visible()", "column().visible()", function (vis, calc) { + var that = this + var changed = [] + var ret = this.iterator("column", function (settings, column) { + if (vis === undefined) { + return settings.aoColumns[column].bVisible + } // else + + if (__setColumnVis(settings, column, vis)) { + changed.push(column) + } + }) + + // Group the column visibility changes + if (vis !== undefined) { + this.iterator("table", function (settings) { + // Redraw the header after changes + _fnDrawHead(settings, settings.aoHeader) + _fnDrawHead(settings, settings.aoFooter) + + // Update colspan for no records display. Child rows and extensions will use their own + // listeners to do this - only need to update the empty table item here + if (!settings.aiDisplay.length) { + $(settings.nTBody).find("td[colspan]").attr("colspan", _fnVisbleColumns(settings)) + } + + _fnSaveState(settings) + + // Second loop once the first is done for events + that.iterator("column", function (settings, column) { + if (changed.includes(column)) { + _fnCallbackFire(settings, null, "column-visibility", [settings, column, vis, calc]) + } + }) + + if (changed.length && (calc === undefined || calc)) { + that.columns.adjust() + } + }) + } + + return ret + }) + + _api_registerPlural("columns().widths()", "column().width()", function () { + // Injects a fake row into the table for just a moment so the widths can + // be read, regardless of colspan in the header and rows being present in + // the body + var columns = this.columns(":visible").count() + var row = $("<tr>").html("<td>" + Array(columns).join("</td><td>") + "</td>") + + $(this.table().body()).append(row) + + var widths = row.children().map(function () { + return $(this).outerWidth() + }) + + row.remove() + + return this.iterator( + "column", + function (settings, column) { + var visIdx = _fnColumnIndexToVisible(settings, column) + + return visIdx !== null ? widths[visIdx] : 0 + }, + 1 + ) + }) + + _api_registerPlural("columns().indexes()", "column().index()", function (type) { + return this.iterator( + "column", + function (settings, column) { + return type === "visible" ? _fnColumnIndexToVisible(settings, column) : column + }, + 1 + ) + }) + + _api_register("columns.adjust()", function () { + return this.iterator( + "table", + function (settings) { + _fnAdjustColumnSizing(settings) + }, + 1 + ) + }) + + _api_register("column.index()", function (type, idx) { + if (this.context.length !== 0) { + var ctx = this.context[0] + + if (type === "fromVisible" || type === "toData") { + return _fnVisibleToColumnIndex(ctx, idx) + } else if (type === "fromData" || type === "toVisible") { + return _fnColumnIndexToVisible(ctx, idx) + } + } + }) + + _api_register("column()", function (selector, opts) { + return _selector_first(this.columns(selector, opts)) + }) + + var __cell_selector = function (settings, selector, opts) { + var data = settings.aoData + var rows = _selector_row_indexes(settings, opts) + var cells = _removeEmpty(_pluck_order(data, rows, "anCells")) + var allCells = $(_flatten([], cells)) + var row + var columns = settings.aoColumns.length + var a, i, ien, j, o, host + + var run = function (s) { + var fnSelector = typeof s === "function" + + if (s === null || s === undefined || fnSelector) { + // All cells and function selectors + a = [] + + for (i = 0, ien = rows.length; i < ien; i++) { + row = rows[i] + + for (j = 0; j < columns; j++) { + o = { + row: row, + column: j + } + + if (fnSelector) { + // Selector - function + host = data[row] + + if (s(o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null)) { + a.push(o) + } + } else { + // Selector - all + a.push(o) + } + } + } + + return a + } + + // Selector - index + if ($.isPlainObject(s)) { + // Valid cell index and its in the array of selectable rows + return s.column !== undefined && s.row !== undefined && rows.indexOf(s.row) !== -1 ? [s] : [] + } + + // Selector - jQuery filtered cells + var jqResult = allCells + .filter(s) + .map(function (i, el) { + return { + // use a new object, in case someone changes the values + row: el._DT_CellIndex.row, + column: el._DT_CellIndex.column + } + }) + .toArray() + + if (jqResult.length || !s.nodeName) { + return jqResult + } + + // Otherwise the selector is a node, and there is one last option - the + // element might be a child of an element which has dt-row and dt-column + // data attributes + host = $(s).closest("*[data-dt-row]") + return host.length + ? [ + { + row: host.data("dt-row"), + column: host.data("dt-column") + } + ] + : [] + } + + return _selector_run("cell", selector, run, settings, opts) + } + + _api_register("cells()", function (rowSelector, columnSelector, opts) { + // Argument shifting + if ($.isPlainObject(rowSelector)) { + // Indexes + if (rowSelector.row === undefined) { + // Selector options in first parameter + opts = rowSelector + rowSelector = null + } else { + // Cell index objects in first parameter + opts = columnSelector + columnSelector = null + } + } + if ($.isPlainObject(columnSelector)) { + opts = columnSelector + columnSelector = null + } + + // Cell selector + if (columnSelector === null || columnSelector === undefined) { + return this.iterator("table", function (settings) { + return __cell_selector(settings, rowSelector, _selector_opts(opts)) + }) + } + + // The default built in options need to apply to row and columns + var internalOpts = opts + ? { + page: opts.page, + order: opts.order, + search: opts.search + } + : {} + + // Row + column selector + var columns = this.columns(columnSelector, internalOpts) + var rows = this.rows(rowSelector, internalOpts) + var i, ien, j, jen + + var cellsNoOpts = this.iterator( + "table", + function (settings, idx) { + var a = [] + + for (i = 0, ien = rows[idx].length; i < ien; i++) { + for (j = 0, jen = columns[idx].length; j < jen; j++) { + a.push({ + row: rows[idx][i], + column: columns[idx][j] + }) + } + } + + return a + }, + 1 + ) + + // There is currently only one extension which uses a cell selector extension + // It is a _major_ performance drag to run this if it isn't needed, so this is + // an extension specific check at the moment + var cells = opts && opts.selected ? this.cells(cellsNoOpts, opts) : cellsNoOpts + + $.extend(cells.selector, { + cols: columnSelector, + rows: rowSelector, + opts: opts + }) + + return cells + }) + + _api_registerPlural("cells().nodes()", "cell().node()", function () { + return this.iterator( + "cell", + function (settings, row, column) { + var data = settings.aoData[row] + + return data && data.anCells ? data.anCells[column] : undefined + }, + 1 + ) + }) + + _api_register("cells().data()", function () { + return this.iterator( + "cell", + function (settings, row, column) { + return _fnGetCellData(settings, row, column) + }, + 1 + ) + }) + + _api_registerPlural("cells().cache()", "cell().cache()", function (type) { + type = type === "search" ? "_aFilterData" : "_aSortData" + + return this.iterator( + "cell", + function (settings, row, column) { + return settings.aoData[row][type][column] + }, + 1 + ) + }) + + _api_registerPlural("cells().render()", "cell().render()", function (type) { + return this.iterator( + "cell", + function (settings, row, column) { + return _fnGetCellData(settings, row, column, type) + }, + 1 + ) + }) + + _api_registerPlural("cells().indexes()", "cell().index()", function () { + return this.iterator( + "cell", + function (settings, row, column) { + return { + row: row, + column: column, + columnVisible: _fnColumnIndexToVisible(settings, column) + } + }, + 1 + ) + }) + + _api_registerPlural("cells().invalidate()", "cell().invalidate()", function (src) { + return this.iterator("cell", function (settings, row, column) { + _fnInvalidate(settings, row, src, column) + }) + }) + + _api_register("cell()", function (rowSelector, columnSelector, opts) { + return _selector_first(this.cells(rowSelector, columnSelector, opts)) + }) + + _api_register("cell().data()", function (data) { + var ctx = this.context + var cell = this[0] + + if (data === undefined) { + // Get + return ctx.length && cell.length ? _fnGetCellData(ctx[0], cell[0].row, cell[0].column) : undefined + } + + // Set + _fnSetCellData(ctx[0], cell[0].row, cell[0].column, data) + _fnInvalidate(ctx[0], cell[0].row, "data", cell[0].column) + + return this + }) + + /** + * Get current ordering (sorting) that has been applied to the table. + * + * @returns {array} 2D array containing the sorting information for the first + * table in the current context. Each element in the parent array represents + * a column being sorted upon (i.e. multi-sorting with two columns would have + * 2 inner arrays). The inner arrays may have 2 or 3 elements. The first is + * the column index that the sorting condition applies to, the second is the + * direction of the sort (`desc` or `asc`) and, optionally, the third is the + * index of the sorting order from the `column.sorting` initialisation array. + */ /** + * Set the ordering for the table. + * + * @param {integer} order Column index to sort upon. + * @param {string} direction Direction of the sort to be applied (`asc` or `desc`) + * @returns {DataTables.Api} this + */ /** + * Set the ordering for the table. + * + * @param {array} order 1D array of sorting information to be applied. + * @param {array} [...] Optional additional sorting conditions + * @returns {DataTables.Api} this + */ /** + * Set the ordering for the table. + * + * @param {array} order 2D array of sorting information to be applied. + * @returns {DataTables.Api} this + */ _api_register("order()", function (order, dir) { + var ctx = this.context + var args = Array.prototype.slice.call(arguments) + + if (order === undefined) { + // get + return ctx.length !== 0 ? ctx[0].aaSorting : undefined + } + + // set + if (typeof order === "number") { + // Simple column / direction passed in + order = [[order, dir]] + } else if (args.length > 1) { + // Arguments passed in (list of 1D arrays) + order = args + } + // otherwise a 2D array was passed in + + return this.iterator("table", function (settings) { + settings.aaSorting = Array.isArray(order) ? order.slice() : order + }) + }) + + /** + * Attach a sort listener to an element for a given column + * + * @param {node|jQuery|string} node Identifier for the element(s) to attach the + * listener to. This can take the form of a single DOM node, a jQuery + * collection of nodes or a jQuery selector which will identify the node(s). + * @param {integer} column the column that a click on this node will sort on + * @param {function} [callback] callback function when sort is run + * @returns {DataTables.Api} this + */ + _api_register("order.listener()", function (node, column, callback) { + return this.iterator("table", function (settings) { + _fnSortAttachListener(settings, node, {}, column, callback) + }) + }) + + _api_register("order.fixed()", function (set) { + if (!set) { + var ctx = this.context + var fixed = ctx.length ? ctx[0].aaSortingFixed : undefined + + return Array.isArray(fixed) ? { pre: fixed } : fixed + } + + return this.iterator("table", function (settings) { + settings.aaSortingFixed = $.extend(true, {}, set) + }) + }) + + // Order by the selected column(s) + _api_register(["columns().order()", "column().order()"], function (dir) { + var that = this + + if (!dir) { + return this.iterator( + "column", + function (settings, idx) { + var sort = _fnSortFlatten(settings) + + for (var i = 0, ien = sort.length; i < ien; i++) { + if (sort[i].col === idx) { + return sort[i].dir + } + } + + return null + }, + 1 + ) + } else { + return this.iterator("table", function (settings, i) { + settings.aaSorting = that[i].map(function (col) { + return [col, dir] + }) + }) + } + }) + + _api_registerPlural("columns().orderable()", "column().orderable()", function (directions) { + return this.iterator( + "column", + function (settings, idx) { + var col = settings.aoColumns[idx] + + return directions ? col.asSorting : col.bSortable + }, + 1 + ) + }) + + _api_register("processing()", function (show) { + return this.iterator("table", function (ctx) { + _fnProcessingDisplay(ctx, show) + }) + }) + + _api_register("search()", function (input, regex, smart, caseInsen) { + var ctx = this.context + + if (input === undefined) { + // get + return ctx.length !== 0 ? ctx[0].oPreviousSearch.search : undefined + } + + // set + return this.iterator("table", function (settings) { + if (!settings.oFeatures.bFilter) { + return + } + + if (typeof regex === "object") { + // New style options to pass to the search builder + _fnFilterComplete( + settings, + $.extend(settings.oPreviousSearch, regex, { + search: input + }) + ) + } else { + // Compat for the old options + _fnFilterComplete( + settings, + $.extend(settings.oPreviousSearch, { + search: input, + regex: regex === null ? false : regex, + smart: smart === null ? true : smart, + caseInsensitive: caseInsen === null ? true : caseInsen + }) + ) + } + }) + }) + + _api_register("search.fixed()", function (name, search) { + var ret = this.iterator(true, "table", function (settings) { + var fixed = settings.searchFixed + + if (!name) { + return Object.keys(fixed) + } else if (search === undefined) { + return fixed[name] + } else if (search === null) { + delete fixed[name] + } else { + fixed[name] = search + } + + return this + }) + + return name !== undefined && search === undefined ? ret[0] : ret + }) + + _api_registerPlural("columns().search()", "column().search()", function (input, regex, smart, caseInsen) { + return this.iterator("column", function (settings, column) { + var preSearch = settings.aoPreSearchCols + + if (input === undefined) { + // get + return preSearch[column].search + } + + // set + if (!settings.oFeatures.bFilter) { + return + } + + if (typeof regex === "object") { + // New style options to pass to the search builder + $.extend(preSearch[column], regex, { + search: input + }) + } else { + // Old style (with not all options available) + $.extend(preSearch[column], { + search: input, + regex: regex === null ? false : regex, + smart: smart === null ? true : smart, + caseInsensitive: caseInsen === null ? true : caseInsen + }) + } + + _fnFilterComplete(settings, settings.oPreviousSearch) + }) + }) + + _api_register(["columns().search.fixed()", "column().search.fixed()"], function (name, search) { + var ret = this.iterator(true, "column", function (settings, colIdx) { + var fixed = settings.aoColumns[colIdx].searchFixed + + if (!name) { + return Object.keys(fixed) + } else if (search === undefined) { + return fixed[name] + } else if (search === null) { + delete fixed[name] + } else { + fixed[name] = search + } + + return this + }) + + return name !== undefined && search === undefined ? ret[0] : ret + }) + /* + * State API methods + */ + + _api_register("state()", function (set, ignoreTime) { + // getter + if (!set) { + return this.context.length ? this.context[0].oSavedState : null + } + + var setMutate = $.extend(true, {}, set) + + // setter + return this.iterator("table", function (settings) { + if (ignoreTime !== false) { + setMutate.time = +new Date() + 100 + } + + _fnImplementState(settings, setMutate, function () {}) + }) + }) + + _api_register("state.clear()", function () { + return this.iterator("table", function (settings) { + // Save an empty object + settings.fnStateSaveCallback.call(settings.oInstance, settings, {}) + }) + }) + + _api_register("state.loaded()", function () { + return this.context.length ? this.context[0].oLoadedState : null + }) + + _api_register("state.save()", function () { + return this.iterator("table", function (settings) { + _fnSaveState(settings) + }) + }) + + /** + * Set the libraries that DataTables uses, or the global objects. + * Note that the arguments can be either way around (legacy support) + * and the second is optional. See docs. + */ + DataTable.use = function (arg1, arg2) { + // Reverse arguments for legacy support + var module = typeof arg1 === "string" ? arg2 : arg1 + var type = typeof arg2 === "string" ? arg2 : arg1 + + // Getter + if (module === undefined && typeof type === "string") { + switch (type) { + case "lib": + case "jq": + return $ + + case "win": + return window + + case "datetime": + return DataTable.DateTime + + case "luxon": + return __luxon + + case "moment": + return __moment + + default: + return null + } + } + + // Setter + if (type === "lib" || type === "jq" || (module && module.fn && module.fn.jquery)) { + $ = module + } else if (type == "win" || (module && module.document)) { + window = module + document = module.document + } else if (type === "datetime" || (module && module.type === "DateTime")) { + DataTable.DateTime = module + } else if (type === "luxon" || (module && module.FixedOffsetZone)) { + __luxon = module + } else if (type === "moment" || (module && module.isMoment)) { + __moment = module + } + } + + /** + * CommonJS factory function pass through. This will check if the arguments + * given are a window object or a jQuery object. If so they are set + * accordingly. + * @param {*} root Window + * @param {*} jq jQUery + * @returns {boolean} Indicator + */ + DataTable.factory = function (root, jq) { + var is = false + + // Test if the first parameter is a window object + if (root && root.document) { + window = root + document = root.document + } + + // Test if the second parameter is a jQuery object + if (jq && jq.fn && jq.fn.jquery) { + $ = jq + is = true + } + + return is + } + + /** + * Provide a common method for plug-ins to check the version of DataTables being + * used, in order to ensure compatibility. + * + * @param {string} version Version string to check for, in the format "X.Y.Z". + * Note that the formats "X" and "X.Y" are also acceptable. + * @param {string} [version2=current DataTables version] As above, but optional. + * If not given the current DataTables version will be used. + * @returns {boolean} true if this version of DataTables is greater or equal to + * the required version, or false if this version of DataTales is not + * suitable + * @static + * @dtopt API-Static + * + * @example + * alert( $.fn.dataTable.versionCheck( '1.9.0' ) ); + */ + DataTable.versionCheck = function (version, version2) { + var aThis = version2 ? version2.split(".") : DataTable.version.split(".") + var aThat = version.split(".") + var iThis, iThat + + for (var i = 0, iLen = aThat.length; i < iLen; i++) { + iThis = parseInt(aThis[i], 10) || 0 + iThat = parseInt(aThat[i], 10) || 0 + + // Parts are the same, keep comparing + if (iThis === iThat) { + continue + } + + // Parts are different, return immediately + return iThis > iThat + } + + return true + } + + /** + * Check if a `<table>` node is a DataTable table already or not. + * + * @param {node|jquery|string} table Table node, jQuery object or jQuery + * selector for the table to test. Note that if more than more than one + * table is passed on, only the first will be checked + * @returns {boolean} true the table given is a DataTable, or false otherwise + * @static + * @dtopt API-Static + * + * @example + * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) { + * $('#example').dataTable(); + * } + */ + DataTable.isDataTable = function (table) { + var t = $(table).get(0) + var is = false + + if (table instanceof DataTable.Api) { + return true + } + + $.each(DataTable.settings, function (i, o) { + var head = o.nScrollHead ? $("table", o.nScrollHead)[0] : null + var foot = o.nScrollFoot ? $("table", o.nScrollFoot)[0] : null + + if (o.nTable === t || head === t || foot === t) { + is = true + } + }) + + return is + } + + /** + * Get all DataTable tables that have been initialised - optionally you can + * select to get only currently visible tables. + * + * @param {boolean} [visible=false] Flag to indicate if you want all (default) + * or visible tables only. + * @returns {array} Array of `table` nodes (not DataTable instances) which are + * DataTables + * @static + * @dtopt API-Static + * + * @example + * $.each( $.fn.dataTable.tables(true), function () { + * $(table).DataTable().columns.adjust(); + * } ); + */ + DataTable.tables = function (visible) { + var api = false + + if ($.isPlainObject(visible)) { + api = visible.api + visible = visible.visible + } + + var a = DataTable.settings + .filter(function (o) { + return !visible || (visible && $(o.nTable).is(":visible")) ? true : false + }) + .map(function (o) { + return o.nTable + }) + + return api ? new _Api(a) : a + } + + /** + * Convert from camel case parameters to Hungarian notation. This is made public + * for the extensions to provide the same ability as DataTables core to accept + * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase + * parameters. + * + * @param {object} src The model object which holds all parameters that can be + * mapped. + * @param {object} user The object to convert from camel case to Hungarian. + * @param {boolean} force When set to `true`, properties which already have a + * Hungarian value in the `user` object will be overwritten. Otherwise they + * won't be. + */ + DataTable.camelToHungarian = _fnCamelToHungarian + + /** + * + */ + _api_register("$()", function (selector, opts) { + var rows = this.rows(opts).nodes(), // Get all rows + jqRows = $(rows) + + return $([].concat(jqRows.filter(selector).toArray(), jqRows.find(selector).toArray())) + }) + + // jQuery functions to operate on the tables + $.each(["on", "one", "off"], function (i, key) { + _api_register(key + "()", function (/* event, handler */) { + var args = Array.prototype.slice.call(arguments) + + // Add the `dt` namespace automatically if it isn't already present + args[0] = args[0] + .split(/\s/) + .map(function (e) { + return !e.match(/\.dt\b/) ? e + ".dt" : e + }) + .join(" ") + + var inst = $(this.tables().nodes()) + inst[key].apply(inst, args) + return this + }) + }) + + _api_register("clear()", function () { + return this.iterator("table", function (settings) { + _fnClearTable(settings) + }) + }) + + _api_register("error()", function (msg) { + return this.iterator("table", function (settings) { + _fnLog(settings, 0, msg) + }) + }) + + _api_register("settings()", function () { + return new _Api(this.context, this.context) + }) + + _api_register("init()", function () { + var ctx = this.context + return ctx.length ? ctx[0].oInit : null + }) + + _api_register("data()", function () { + return this.iterator("table", function (settings) { + return _pluck(settings.aoData, "_aData") + }).flatten() + }) + + _api_register("trigger()", function (name, args, bubbles) { + return this.iterator("table", function (settings) { + return _fnCallbackFire(settings, null, name, args, bubbles) + }).flatten() + }) + + _api_register("ready()", function (fn) { + var ctx = this.context + + // Get status of first table + if (!fn) { + return ctx.length ? ctx[0]._bInitComplete || false : null + } + + // Function to run either once the table becomes ready or + // immediately if it is already ready. + return this.tables().every(function () { + if (this.context[0]._bInitComplete) { + fn.call(this) + } else { + this.on("init.dt.DT", function () { + fn.call(this) + }) + } + }) + }) + + _api_register("destroy()", function (remove) { + remove = remove || false + + return this.iterator("table", function (settings) { + var classes = settings.oClasses + var table = settings.nTable + var tbody = settings.nTBody + var thead = settings.nTHead + var tfoot = settings.nTFoot + var jqTable = $(table) + var jqTbody = $(tbody) + var jqWrapper = $(settings.nTableWrapper) + var rows = settings.aoData.map(function (r) { + return r ? r.nTr : null + }) + var orderClasses = classes.order + + // Flag to note that the table is currently being destroyed - no action + // should be taken + settings.bDestroying = true + + // Fire off the destroy callbacks for plug-ins etc + _fnCallbackFire(settings, "aoDestroyCallback", "destroy", [settings], true) + + // If not being removed from the document, make all columns visible + if (!remove) { + new _Api(settings).columns().visible(true) + } + + // Blitz all `DT` namespaced events (these are internal events, the + // lowercase, `dt` events are user subscribed and they are responsible + // for removing them + jqWrapper.off(".DT").find(":not(tbody *)").off(".DT") + $(window).off(".DT-" + settings.sInstance) + + // When scrolling we had to break the table up - restore it + if (table != thead.parentNode) { + jqTable.children("thead").detach() + jqTable.append(thead) + } + + if (tfoot && table != tfoot.parentNode) { + jqTable.children("tfoot").detach() + jqTable.append(tfoot) + } + + settings.colgroup.remove() + + settings.aaSorting = [] + settings.aaSortingFixed = [] + _fnSortingClasses(settings) + + $("th, td", thead) + .removeClass(orderClasses.canAsc + " " + orderClasses.canDesc + " " + orderClasses.isAsc + " " + orderClasses.isDesc) + .css("width", "") + + // Add the TR elements back into the table in their original order + jqTbody.children().detach() + jqTbody.append(rows) + + var orig = settings.nTableWrapper.parentNode + var insertBefore = settings.nTableWrapper.nextSibling + + // Remove the DataTables generated nodes, events and classes + var removedMethod = remove ? "remove" : "detach" + jqTable[removedMethod]() + jqWrapper[removedMethod]() + + // If we need to reattach the table to the document + if (!remove && orig) { + // insertBefore acts like appendChild if !arg[1] + orig.insertBefore(table, insertBefore) + + // Restore the width of the original table - was read from the style property, + // so we can restore directly to that + jqTable.css("width", settings.sDestroyWidth).removeClass(classes.table) + } + + /* Remove the settings object from the settings array */ + var idx = DataTable.settings.indexOf(settings) + if (idx !== -1) { + DataTable.settings.splice(idx, 1) + } + }) + }) + + // Add the `every()` method for rows, columns and cells in a compact form + $.each(["column", "row", "cell"], function (i, type) { + _api_register(type + "s().every()", function (fn) { + var opts = this.selector.opts + var api = this + var inst + var counter = 0 + + return this.iterator("every", function (settings, selectedIdx, tableIdx) { + inst = api[type](selectedIdx, opts) + + if (type === "cell") { + fn.call(inst, inst[0][0].row, inst[0][0].column, tableIdx, counter) + } else { + fn.call(inst, selectedIdx, tableIdx, counter) + } + + counter++ + }) + }) + }) + + // i18n method for extensions to be able to use the language object from the + // DataTable + _api_register("i18n()", function (token, def, plural) { + var ctx = this.context[0] + var resolved = _fnGetObjectDataFn(token)(ctx.oLanguage) + + if (resolved === undefined) { + resolved = def + } + + if ($.isPlainObject(resolved)) { + resolved = plural !== undefined && resolved[plural] !== undefined ? resolved[plural] : resolved._ + } + + return typeof resolved === "string" + ? resolved.replace("%d", plural) // nb: plural might be undefined, + : resolved + }) + + /** + * Version string for plug-ins to check compatibility. Allowed format is + * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used + * only for non-release builds. See https://semver.org/ for more information. + * @member + * @type string + * @default Version number + */ + DataTable.version = "2.1.7" + + /** + * Private data store, containing all of the settings objects that are + * created for the tables on a given page. + * + * Note that the `DataTable.settings` object is aliased to + * `jQuery.fn.dataTableExt` through which it may be accessed and + * manipulated, or `jQuery.fn.dataTable.settings`. + * @member + * @type array + * @default [] + * @private + */ + DataTable.settings = [] + + /** + * Object models container, for the various models that DataTables has + * available to it. These models define the objects that are used to hold + * the active state and configuration of the table. + * @namespace + */ + DataTable.models = {} + + /** + * Template object for the way in which DataTables holds information about + * search information for the global filter and individual column filters. + * @namespace + */ + DataTable.models.oSearch = { + /** + * Flag to indicate if the filtering should be case insensitive or not + */ + caseInsensitive: true, + + /** + * Applied search term + */ + search: "", + + /** + * Flag to indicate if the search term should be interpreted as a + * regular expression (true) or not (false) and therefore and special + * regex characters escaped. + */ + regex: false, + + /** + * Flag to indicate if DataTables is to use its smart filtering or not. + */ + smart: true, + + /** + * Flag to indicate if DataTables should only trigger a search when + * the return key is pressed. + */ + return: false + } + + /** + * Template object for the way in which DataTables holds information about + * each individual row. This is the object format used for the settings + * aoData array. + * @namespace + */ + DataTable.models.oRow = { + /** + * TR element for the row + */ + nTr: null, + + /** + * Array of TD elements for each row. This is null until the row has been + * created. + */ + anCells: null, + + /** + * Data object from the original data source for the row. This is either + * an array if using the traditional form of DataTables, or an object if + * using mData options. The exact type will depend on the passed in + * data from the data source, or will be an array if using DOM a data + * source. + */ + _aData: [], + + /** + * Sorting data cache - this array is ostensibly the same length as the + * number of columns (although each index is generated only as it is + * needed), and holds the data that is used for sorting each column in the + * row. We do this cache generation at the start of the sort in order that + * the formatting of the sort data need be done only once for each cell + * per sort. This array should not be read from or written to by anything + * other than the master sorting methods. + */ + _aSortData: null, + + /** + * Per cell filtering data cache. As per the sort data cache, used to + * increase the performance of the filtering in DataTables + */ + _aFilterData: null, + + /** + * Filtering data cache. This is the same as the cell filtering cache, but + * in this case a string rather than an array. This is easily computed with + * a join on `_aFilterData`, but is provided as a cache so the join isn't + * needed on every search (memory traded for performance) + */ + _sFilterRow: null, + + /** + * Denote if the original data source was from the DOM, or the data source + * object. This is used for invalidating data, so DataTables can + * automatically read data from the original source, unless uninstructed + * otherwise. + */ + src: null, + + /** + * Index in the aoData array. This saves an indexOf lookup when we have the + * object, but want to know the index + */ + idx: -1, + + /** + * Cached display value + */ + displayData: null + } + + /** + * Template object for the column information object in DataTables. This object + * is held in the settings aoColumns array and contains all the information that + * DataTables needs about each individual column. + * + * Note that this object is related to {@link DataTable.defaults.column} + * but this one is the internal data store for DataTables's cache of columns. + * It should NOT be manipulated outside of DataTables. Any configuration should + * be done through the initialisation options. + * @namespace + */ + DataTable.models.oColumn = { + /** + * Column index. + */ + idx: null, + + /** + * A list of the columns that sorting should occur on when this column + * is sorted. That this property is an array allows multi-column sorting + * to be defined for a column (for example first name / last name columns + * would benefit from this). The values are integers pointing to the + * columns to be sorted on (typically it will be a single integer pointing + * at itself, but that doesn't need to be the case). + */ + aDataSort: null, + + /** + * Define the sorting directions that are applied to the column, in sequence + * as the column is repeatedly sorted upon - i.e. the first value is used + * as the sorting direction when the column if first sorted (clicked on). + * Sort it again (click again) and it will move on to the next index. + * Repeat until loop. + */ + asSorting: null, + + /** + * Flag to indicate if the column is searchable, and thus should be included + * in the filtering or not. + */ + bSearchable: null, + + /** + * Flag to indicate if the column is sortable or not. + */ + bSortable: null, + + /** + * Flag to indicate if the column is currently visible in the table or not + */ + bVisible: null, + + /** + * Store for manual type assignment using the `column.type` option. This + * is held in store so we can manipulate the column's `sType` property. + */ + _sManualType: null, + + /** + * Flag to indicate if HTML5 data attributes should be used as the data + * source for filtering or sorting. True is either are. + */ + _bAttrSrc: false, + + /** + * Developer definable function that is called whenever a cell is created (Ajax source, + * etc) or processed for input (DOM source). This can be used as a compliment to mRender + * allowing you to modify the DOM element (add background colour for example) when the + * element is available. + */ + fnCreatedCell: null, + + /** + * Function to get data from a cell in a column. You should <b>never</b> + * access data directly through _aData internally in DataTables - always use + * the method attached to this property. It allows mData to function as + * required. This function is automatically assigned by the column + * initialisation method + */ + fnGetData: null, + + /** + * Function to set data for a cell in the column. You should <b>never</b> + * set the data directly to _aData internally in DataTables - always use + * this method. It allows mData to function as required. This function + * is automatically assigned by the column initialisation method + */ + fnSetData: null, + + /** + * Property to read the value for the cells in the column from the data + * source array / object. If null, then the default content is used, if a + * function is given then the return from the function is used. + */ + mData: null, + + /** + * Partner property to mData which is used (only when defined) to get + * the data - i.e. it is basically the same as mData, but without the + * 'set' option, and also the data fed to it is the result from mData. + * This is the rendering method to match the data method of mData. + */ + mRender: null, + + /** + * The class to apply to all TD elements in the table's TBODY for the column + */ + sClass: null, + + /** + * When DataTables calculates the column widths to assign to each column, + * it finds the longest string in each column and then constructs a + * temporary table and reads the widths from that. The problem with this + * is that "mmm" is much wider then "iiii", but the latter is a longer + * string - thus the calculation can go wrong (doing it properly and putting + * it into an DOM object and measuring that is horribly(!) slow). Thus as + * a "work around" we provide this option. It will append its value to the + * text that is found to be the longest string for the column - i.e. padding. + */ + sContentPadding: null, + + /** + * Allows a default value to be given for a column's data, and will be used + * whenever a null data source is encountered (this can be because mData + * is set to null, or because the data source itself is null). + */ + sDefaultContent: null, + + /** + * Name for the column, allowing reference to the column by name as well as + * by index (needs a lookup to work by name). + */ + sName: null, + + /** + * Custom sorting data type - defines which of the available plug-ins in + * afnSortData the custom sorting will use - if any is defined. + */ + sSortDataType: "std", + + /** + * Class to be applied to the header element when sorting on this column + */ + sSortingClass: null, + + /** + * Title of the column - what is seen in the TH element (nTh). + */ + sTitle: null, + + /** + * Column sorting and filtering type + */ + sType: null, + + /** + * Width of the column + */ + sWidth: null, + + /** + * Width of the column when it was first "encountered" + */ + sWidthOrig: null, + + /** Cached string which is the longest in the column */ + maxLenString: null, + + /** + * Store for named searches + */ + searchFixed: null + } + + /* + * Developer note: The properties of the object below are given in Hungarian + * notation, that was used as the interface for DataTables prior to v1.10, however + * from v1.10 onwards the primary interface is camel case. In order to avoid + * breaking backwards compatibility utterly with this change, the Hungarian + * version is still, internally the primary interface, but is is not documented + * - hence the @name tags in each doc comment. This allows a Javascript function + * to create a map from Hungarian notation to camel case (going the other direction + * would require each property to be listed, which would add around 3K to the size + * of DataTables, while this method is about a 0.5K hit). + * + * Ultimately this does pave the way for Hungarian notation to be dropped + * completely, but that is a massive amount of work and will break current + * installs (therefore is on-hold until v2). + */ + + /** + * Initialisation options that can be given to DataTables at initialisation + * time. + * @namespace + */ + DataTable.defaults = { + /** + * An array of data to use for the table, passed in at initialisation which + * will be used in preference to any data which is already in the DOM. This is + * particularly useful for constructing tables purely in Javascript, for + * example with a custom Ajax call. + */ + aaData: null, + + /** + * If ordering is enabled, then DataTables will perform a first pass sort on + * initialisation. You can define which column(s) the sort is performed + * upon, and the sorting direction, with this variable. The `sorting` array + * should contain an array for each column to be sorted initially containing + * the column's index and a direction string ('asc' or 'desc'). + */ + aaSorting: [[0, "asc"]], + + /** + * This parameter is basically identical to the `sorting` parameter, but + * cannot be overridden by user interaction with the table. What this means + * is that you could have a column (visible or hidden) which the sorting + * will always be forced on first - any sorting after that (from the user) + * will then be performed as required. This can be useful for grouping rows + * together. + */ + aaSortingFixed: [], + + /** + * DataTables can be instructed to load data to display in the table from a + * Ajax source. This option defines how that Ajax call is made and where to. + * + * The `ajax` property has three different modes of operation, depending on + * how it is defined. These are: + * + * * `string` - Set the URL from where the data should be loaded from. + * * `object` - Define properties for `jQuery.ajax`. + * * `function` - Custom data get function + * + * `string` + * -------- + * + * As a string, the `ajax` property simply defines the URL from which + * DataTables will load data. + * + * `object` + * -------- + * + * As an object, the parameters in the object are passed to + * [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) allowing fine control + * of the Ajax request. DataTables has a number of default parameters which + * you can override using this option. Please refer to the jQuery + * documentation for a full description of the options available, although + * the following parameters provide additional options in DataTables or + * require special consideration: + * + * * `data` - As with jQuery, `data` can be provided as an object, but it + * can also be used as a function to manipulate the data DataTables sends + * to the server. The function takes a single parameter, an object of + * parameters with the values that DataTables has readied for sending. An + * object may be returned which will be merged into the DataTables + * defaults, or you can add the items to the object that was passed in and + * not return anything from the function. This supersedes `fnServerParams` + * from DataTables 1.9-. + * + * * `dataSrc` - By default DataTables will look for the property `data` (or + * `aaData` for compatibility with DataTables 1.9-) when obtaining data + * from an Ajax source or for server-side processing - this parameter + * allows that property to be changed. You can use Javascript dotted + * object notation to get a data source for multiple levels of nesting, or + * it my be used as a function. As a function it takes a single parameter, + * the JSON returned from the server, which can be manipulated as + * required, with the returned value being that used by DataTables as the + * data source for the table. + * + * * `success` - Should not be overridden it is used internally in + * DataTables. To manipulate / transform the data returned by the server + * use `ajax.dataSrc`, or use `ajax` as a function (see below). + * + * `function` + * ---------- + * + * As a function, making the Ajax call is left up to yourself allowing + * complete control of the Ajax request. Indeed, if desired, a method other + * than Ajax could be used to obtain the required data, such as Web storage + * or an AIR database. + * + * The function is given four parameters and no return is required. The + * parameters are: + * + * 1. _object_ - Data to send to the server + * 2. _function_ - Callback function that must be executed when the required + * data has been obtained. That data should be passed into the callback + * as the only parameter + * 3. _object_ - DataTables settings object for the table + */ + ajax: null, + + /** + * This parameter allows you to readily specify the entries in the length drop + * down menu that DataTables shows when pagination is enabled. It can be + * either a 1D array of options which will be used for both the displayed + * option and the value, or a 2D array which will use the array in the first + * position as the value, and the array in the second position as the + * displayed options (useful for language strings such as 'All'). + * + * Note that the `pageLength` property will be automatically set to the + * first value given in this array, unless `pageLength` is also provided. + */ + aLengthMenu: [10, 25, 50, 100], + + /** + * The `columns` option in the initialisation parameter allows you to define + * details about the way individual columns behave. For a full list of + * column options that can be set, please see + * {@link DataTable.defaults.column}. Note that if you use `columns` to + * define your columns, you must have an entry in the array for every single + * column that you have in your table (these can be null if you don't which + * to specify any options). + */ + aoColumns: null, + + /** + * Very similar to `columns`, `columnDefs` allows you to target a specific + * column, multiple columns, or all columns, using the `targets` property of + * each object in the array. This allows great flexibility when creating + * tables, as the `columnDefs` arrays can be of any length, targeting the + * columns you specifically want. `columnDefs` may use any of the column + * options available: {@link DataTable.defaults.column}, but it _must_ + * have `targets` defined in each object in the array. Values in the `targets` + * array may be: + * <ul> + * <li>a string - class name will be matched on the TH for the column</li> + * <li>0 or a positive integer - column index counting from the left</li> + * <li>a negative integer - column index counting from the right</li> + * <li>the string "_all" - all columns (i.e. assign a default)</li> + * </ul> + */ + aoColumnDefs: null, + + /** + * Basically the same as `search`, this parameter defines the individual column + * filtering state at initialisation time. The array must be of the same size + * as the number of columns, and each element be an object with the parameters + * `search` and `escapeRegex` (the latter is optional). 'null' is also + * accepted and the default will be used. + */ + aoSearchCols: [], + + /** + * Enable or disable automatic column width calculation. This can be disabled + * as an optimisation (it takes some time to calculate the widths) if the + * tables widths are passed in using `columns`. + */ + bAutoWidth: true, + + /** + * Deferred rendering can provide DataTables with a huge speed boost when you + * are using an Ajax or JS data source for the table. This option, when set to + * true, will cause DataTables to defer the creation of the table elements for + * each row until they are needed for a draw - saving a significant amount of + * time. + */ + bDeferRender: true, + + /** + * Replace a DataTable which matches the given selector and replace it with + * one which has the properties of the new initialisation object passed. If no + * table matches the selector, then the new DataTable will be constructed as + * per normal. + */ + bDestroy: false, + + /** + * Enable or disable filtering of data. Filtering in DataTables is "smart" in + * that it allows the end user to input multiple words (space separated) and + * will match a row containing those words, even if not in the order that was + * specified (this allow matching across multiple columns). Note that if you + * wish to use filtering in DataTables this must remain 'true' - to remove the + * default filtering input box and retain filtering abilities, please use + * {@link DataTable.defaults.dom}. + */ + bFilter: true, + + /** + * Used only for compatiblity with DT1 + * @deprecated + */ + bInfo: true, + + /** + * Used only for compatiblity with DT1 + * @deprecated + */ + bLengthChange: true, + + /** + * Enable or disable pagination. + */ + bPaginate: true, + + /** + * Enable or disable the display of a 'processing' indicator when the table is + * being processed (e.g. a sort). This is particularly useful for tables with + * large amounts of data where it can take a noticeable amount of time to sort + * the entries. + */ + bProcessing: false, + + /** + * Retrieve the DataTables object for the given selector. Note that if the + * table has already been initialised, this parameter will cause DataTables + * to simply return the object that has already been set up - it will not take + * account of any changes you might have made to the initialisation object + * passed to DataTables (setting this parameter to true is an acknowledgement + * that you understand this). `destroy` can be used to reinitialise a table if + * you need. + */ + bRetrieve: false, + + /** + * When vertical (y) scrolling is enabled, DataTables will force the height of + * the table's viewport to the given height at all times (useful for layout). + * However, this can look odd when filtering data down to a small data set, + * and the footer is left "floating" further down. This parameter (when + * enabled) will cause DataTables to collapse the table's viewport down when + * the result set will fit within the given Y height. + */ + bScrollCollapse: false, + + /** + * Configure DataTables to use server-side processing. Note that the + * `ajax` parameter must also be given in order to give DataTables a + * source to obtain the required data for each draw. + */ + bServerSide: false, + + /** + * Enable or disable sorting of columns. Sorting of individual columns can be + * disabled by the `sortable` option for each column. + */ + bSort: true, + + /** + * Enable or display DataTables' ability to sort multiple columns at the + * same time (activated by shift-click by the user). + */ + bSortMulti: true, + + /** + * Allows control over whether DataTables should use the top (true) unique + * cell that is found for a single column, or the bottom (false - default). + * This is useful when using complex headers. + */ + bSortCellsTop: null, + + /** + * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and + * `sorting\_3` to the columns which are currently being sorted on. This is + * presented as a feature switch as it can increase processing time (while + * classes are removed and added) so for large data sets you might want to + * turn this off. + */ + bSortClasses: true, + + /** + * Enable or disable state saving. When enabled HTML5 `localStorage` will be + * used to save table display information such as pagination information, + * display length, filtering and sorting. As such when the end user reloads + * the page the display display will match what thy had previously set up. + */ + bStateSave: false, + + /** + * This function is called when a TR element is created (and all TD child + * elements have been inserted), or registered if using a DOM source, allowing + * manipulation of the TR element (adding classes etc). + */ + fnCreatedRow: null, + + /** + * This function is called on every 'draw' event, and allows you to + * dynamically modify any aspect you want about the created DOM. + */ + fnDrawCallback: null, + + /** + * Identical to fnHeaderCallback() but for the table footer this function + * allows you to modify the table footer on every 'draw' event. + */ + fnFooterCallback: null, + + /** + * When rendering large numbers in the information element for the table + * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers + * to have a comma separator for the 'thousands' units (e.g. 1 million is + * rendered as "1,000,000") to help readability for the end user. This + * function will override the default method DataTables uses. + */ + fnFormatNumber: function (toFormat) { + return toFormat.toString().replace(/\B(?=(\d{3})+(?!\d))/g, this.oLanguage.sThousands) + }, + + /** + * This function is called on every 'draw' event, and allows you to + * dynamically modify the header row. This can be used to calculate and + * display useful information about the table. + */ + fnHeaderCallback: null, + + /** + * The information element can be used to convey information about the current + * state of the table. Although the internationalisation options presented by + * DataTables are quite capable of dealing with most customisations, there may + * be times where you wish to customise the string further. This callback + * allows you to do exactly that. + */ + fnInfoCallback: null, + + /** + * Called when the table has been initialised. Normally DataTables will + * initialise sequentially and there will be no need for this function, + * however, this does not hold true when using external language information + * since that is obtained using an async XHR call. + */ + fnInitComplete: null, + + /** + * Called at the very start of each table draw and can be used to cancel the + * draw by returning false, any other return (including undefined) results in + * the full draw occurring). + */ + fnPreDrawCallback: null, + + /** + * This function allows you to 'post process' each row after it have been + * generated for each table draw, but before it is rendered on screen. This + * function might be used for setting the row class name etc. + */ + fnRowCallback: null, + + /** + * Load the table state. With this function you can define from where, and how, the + * state of a table is loaded. By default DataTables will load from `localStorage` + * but you might wish to use a server-side database or cookies. + */ + fnStateLoadCallback: function (settings) { + try { + return JSON.parse((settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem("DataTables_" + settings.sInstance + "_" + location.pathname)) + } catch (e) { + return {} + } + }, + + /** + * Callback which allows modification of the saved state prior to loading that state. + * This callback is called when the table is loading state from the stored data, but + * prior to the settings object being modified by the saved state. Note that for + * plug-in authors, you should use the `stateLoadParams` event to load parameters for + * a plug-in. + */ + fnStateLoadParams: null, + + /** + * Callback that is called when the state has been loaded from the state saving method + * and the DataTables settings object has been modified as a result of the loaded state. + */ + fnStateLoaded: null, + + /** + * Save the table state. This function allows you to define where and how the state + * information for the table is stored By default DataTables will use `localStorage` + * but you might wish to use a server-side database or cookies. + */ + fnStateSaveCallback: function (settings, data) { + try { + ;(settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem("DataTables_" + settings.sInstance + "_" + location.pathname, JSON.stringify(data)) + } catch (e) { + // noop + } + }, + + /** + * Callback which allows modification of the state to be saved. Called when the table + * has changed state a new state save is required. This method allows modification of + * the state saving object prior to actually doing the save, including addition or + * other state properties or modification. Note that for plug-in authors, you should + * use the `stateSaveParams` event to save parameters for a plug-in. + */ + fnStateSaveParams: null, + + /** + * Duration for which the saved state information is considered valid. After this period + * has elapsed the state will be returned to the default. + * Value is given in seconds. + */ + iStateDuration: 7200, + + /** + * Number of rows to display on a single page when using pagination. If + * feature enabled (`lengthChange`) then the end user will be able to override + * this to a custom setting using a pop-up menu. + */ + iDisplayLength: 10, + + /** + * Define the starting point for data display when using DataTables with + * pagination. Note that this parameter is the number of records, rather than + * the page number, so if you have 10 records per page and want to start on + * the third page, it should be "20". + */ + iDisplayStart: 0, + + /** + * By default DataTables allows keyboard navigation of the table (sorting, paging, + * and filtering) by adding a `tabindex` attribute to the required elements. This + * allows you to tab through the controls and press the enter key to activate them. + * The tabindex is default 0, meaning that the tab follows the flow of the document. + * You can overrule this using this parameter if you wish. Use a value of -1 to + * disable built-in keyboard navigation. + */ + iTabIndex: 0, + + /** + * Classes that DataTables assigns to the various components and features + * that it adds to the HTML table. This allows classes to be configured + * during initialisation in addition to through the static + * {@link DataTable.ext.oStdClasses} object). + */ + oClasses: {}, + + /** + * All strings that DataTables uses in the user interface that it creates + * are defined in this object, allowing you to modified them individually or + * completely replace them all as required. + */ + oLanguage: { + /** + * Strings that are used for WAI-ARIA labels and controls only (these are not + * actually visible on the page, but will be read by screenreaders, and thus + * must be internationalised as well). + */ + oAria: { + /** + * ARIA label that is added to the table headers when the column may be sorted + */ + orderable: ": Activate to sort", + + /** + * ARIA label that is added to the table headers when the column is currently being sorted + */ + orderableReverse: ": Activate to invert sorting", + + /** + * ARIA label that is added to the table headers when the column is currently being + * sorted and next step is to remove sorting + */ + orderableRemove: ": Activate to remove sorting", + + paginate: { + first: "First", + last: "Last", + next: "Next", + previous: "Previous", + number: "" + } + }, + + /** + * Pagination string used by DataTables for the built-in pagination + * control types. + */ + oPaginate: { + /** + * Label and character for first page button («) + */ + sFirst: "\u00AB", + + /** + * Last page button (») + */ + sLast: "\u00BB", + + /** + * Next page button (›) + */ + sNext: "\u203A", + + /** + * Previous page button (‹) + */ + sPrevious: "\u2039" + }, + + /** + * Plural object for the data type the table is showing + */ + entries: { + _: "entries", + 1: "entry" + }, + + /** + * This string is shown in preference to `zeroRecords` when the table is + * empty of data (regardless of filtering). Note that this is an optional + * parameter - if it is not given, the value of `zeroRecords` will be used + * instead (either the default or given value). + */ + sEmptyTable: "No data available in table", + + /** + * This string gives information to the end user about the information + * that is current on display on the page. The following tokens can be + * used in the string and will be dynamically replaced as the table + * display updates. This tokens can be placed anywhere in the string, or + * removed as needed by the language requires: + * + * * `\_START\_` - Display index of the first record on the current page + * * `\_END\_` - Display index of the last record on the current page + * * `\_TOTAL\_` - Number of records in the table after filtering + * * `\_MAX\_` - Number of records in the table without filtering + * * `\_PAGE\_` - Current page number + * * `\_PAGES\_` - Total number of pages of data in the table + */ + sInfo: "Showing _START_ to _END_ of _TOTAL_ _ENTRIES-TOTAL_", + + /** + * Display information string for when the table is empty. Typically the + * format of this string should match `info`. + */ + sInfoEmpty: "Showing 0 to 0 of 0 _ENTRIES-TOTAL_", + + /** + * When a user filters the information in a table, this string is appended + * to the information (`info`) to give an idea of how strong the filtering + * is. The variable _MAX_ is dynamically updated. + */ + sInfoFiltered: "(filtered from _MAX_ total _ENTRIES-MAX_)", + + /** + * If can be useful to append extra information to the info string at times, + * and this variable does exactly that. This information will be appended to + * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are + * being used) at all times. + */ + sInfoPostFix: "", + + /** + * This decimal place operator is a little different from the other + * language options since DataTables doesn't output floating point + * numbers, so it won't ever use this for display of a number. Rather, + * what this parameter does is modify the sort methods of the table so + * that numbers which are in a format which has a character other than + * a period (`.`) as a decimal place will be sorted numerically. + * + * Note that numbers with different decimal places cannot be shown in + * the same table and still be sortable, the table must be consistent. + * However, multiple different tables on the page can use different + * decimal place characters. + */ + sDecimal: "", + + /** + * DataTables has a build in number formatter (`formatNumber`) which is + * used to format large numbers that are used in the table information. + * By default a comma is used, but this can be trivially changed to any + * character you wish with this parameter. + */ + sThousands: ",", + + /** + * Detail the action that will be taken when the drop down menu for the + * pagination length option is changed. The '_MENU_' variable is replaced + * with a default select list of 10, 25, 50 and 100, and can be replaced + * with a custom select box if required. + */ + sLengthMenu: "_MENU_ _ENTRIES_ per page", + + /** + * When using Ajax sourced data and during the first draw when DataTables is + * gathering the data, this message is shown in an empty row in the table to + * indicate to the end user the the data is being loaded. Note that this + * parameter is not used when loading data by server-side processing, just + * Ajax sourced data with client-side processing. + */ + sLoadingRecords: "Loading...", + + /** + * Text which is displayed when the table is processing a user action + * (usually a sort command or similar). + */ + sProcessing: "", + + /** + * Details the actions that will be taken when the user types into the + * filtering input text box. The variable "_INPUT_", if used in the string, + * is replaced with the HTML text box for the filtering input allowing + * control over where it appears in the string. If "_INPUT_" is not given + * then the input box is appended to the string automatically. + */ + sSearch: "Search:", + + /** + * Assign a `placeholder` attribute to the search `input` element + * @type string + * @default + * + * @dtopt Language + * @name DataTable.defaults.language.searchPlaceholder + */ + sSearchPlaceholder: "", + + /** + * All of the language information can be stored in a file on the + * server-side, which DataTables will look up if this parameter is passed. + * It must store the URL of the language file, which is in a JSON format, + * and the object has the same properties as the oLanguage object in the + * initialiser object (i.e. the above parameters). Please refer to one of + * the example language files to see how this works in action. + */ + sUrl: "", + + /** + * Text shown inside the table records when the is no information to be + * displayed after filtering. `emptyTable` is shown when there is simply no + * information in the table at all (regardless of filtering). + */ + sZeroRecords: "No matching records found" + }, + + /** The initial data order is reversed when `desc` ordering */ + orderDescReverse: true, + + /** + * This parameter allows you to have define the global filtering state at + * initialisation time. As an object the `search` parameter must be + * defined, but all other parameters are optional. When `regex` is true, + * the search string will be treated as a regular expression, when false + * (default) it will be treated as a straight string. When `smart` + * DataTables will use it's smart filtering methods (to word match at + * any point in the data), when false this will not be done. + */ + oSearch: $.extend({}, DataTable.models.oSearch), + + /** + * Table and control layout. This replaces the legacy `dom` option. + */ + layout: { + topStart: "pageLength", + topEnd: "search", + bottomStart: "info", + bottomEnd: "paging" + }, + + /** + * Legacy DOM layout option + */ + sDom: null, + + /** + * Search delay option. This will throttle full table searches that use the + * DataTables provided search input element (it does not effect calls to + * `dt-api search()`, providing a delay before the search is made. + */ + searchDelay: null, + + /** + * DataTables features six different built-in options for the buttons to + * display for pagination control: + * + * * `numbers` - Page number buttons only + * * `simple` - 'Previous' and 'Next' buttons only + * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers + * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons + * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers + * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers + */ + sPaginationType: "", + + /** + * Enable horizontal scrolling. When a table is too wide to fit into a + * certain layout, or you have a large number of columns in the table, you + * can enable x-scrolling to show the table in a viewport, which can be + * scrolled. This property can be `true` which will allow the table to + * scroll horizontally when needed, or any CSS unit, or a number (in which + * case it will be treated as a pixel measurement). Setting as simply `true` + * is recommended. + */ + sScrollX: "", + + /** + * This property can be used to force a DataTable to use more width than it + * might otherwise do when x-scrolling is enabled. For example if you have a + * table which requires to be well spaced, this parameter is useful for + * "over-sizing" the table, and thus forcing scrolling. This property can by + * any CSS unit, or a number (in which case it will be treated as a pixel + * measurement). + */ + sScrollXInner: "", + + /** + * Enable vertical scrolling. Vertical scrolling will constrain the DataTable + * to the given height, and enable scrolling for any data which overflows the + * current viewport. This can be used as an alternative to paging to display + * a lot of data in a small area (although paging and scrolling can both be + * enabled at the same time). This property can be any CSS unit, or a number + * (in which case it will be treated as a pixel measurement). + */ + sScrollY: "", + + /** + * __Deprecated__ The functionality provided by this parameter has now been + * superseded by that provided through `ajax`, which should be used instead. + * + * Set the HTTP method that is used to make the Ajax call for server-side + * processing or Ajax sourced data. + */ + sServerMethod: "GET", + + /** + * DataTables makes use of renderers when displaying HTML elements for + * a table. These renderers can be added or modified by plug-ins to + * generate suitable mark-up for a site. For example the Bootstrap + * integration plug-in for DataTables uses a paging button renderer to + * display pagination buttons in the mark-up required by Bootstrap. + * + * For further information about the renderers available see + * DataTable.ext.renderer + */ + renderer: null, + + /** + * Set the data property name that DataTables should use to get a row's id + * to set as the `id` property in the node. + */ + rowId: "DT_RowId", + + /** + * Caption value + */ + caption: null, + + /** + * For server-side processing - use the data from the DOM for the first draw + */ + iDeferLoading: null + } + + _fnHungarianMap(DataTable.defaults) + + /* + * Developer note - See note in model.defaults.js about the use of Hungarian + * notation and camel case. + */ + + /** + * Column options that can be given to DataTables at initialisation time. + * @namespace + */ + DataTable.defaults.column = { + /** + * Define which column(s) an order will occur on for this column. This + * allows a column's ordering to take multiple columns into account when + * doing a sort or use the data from a different column. For example first + * name / last name columns make sense to do a multi-column sort over the + * two columns. + */ + aDataSort: null, + iDataSort: -1, + + ariaTitle: "", + + /** + * You can control the default ordering direction, and even alter the + * behaviour of the sort handler (i.e. only allow ascending ordering etc) + * using this parameter. + */ + asSorting: ["asc", "desc", ""], + + /** + * Enable or disable filtering on the data in this column. + */ + bSearchable: true, + + /** + * Enable or disable ordering on this column. + */ + bSortable: true, + + /** + * Enable or disable the display of this column. + */ + bVisible: true, + + /** + * Developer definable function that is called whenever a cell is created (Ajax source, + * etc) or processed for input (DOM source). This can be used as a compliment to mRender + * allowing you to modify the DOM element (add background colour for example) when the + * element is available. + */ + fnCreatedCell: null, + + /** + * This property can be used to read data from any data source property, + * including deeply nested objects / properties. `data` can be given in a + * number of different ways which effect its behaviour: + * + * * `integer` - treated as an array index for the data source. This is the + * default that DataTables uses (incrementally increased for each column). + * * `string` - read an object property from the data source. There are + * three 'special' options that can be used in the string to alter how + * DataTables reads the data from the source object: + * * `.` - Dotted Javascript notation. Just as you use a `.` in + * Javascript to read from nested objects, so to can the options + * specified in `data`. For example: `browser.version` or + * `browser.name`. If your object parameter name contains a period, use + * `\\` to escape it - i.e. `first\\.name`. + * * `[]` - Array notation. DataTables can automatically combine data + * from and array source, joining the data with the characters provided + * between the two brackets. For example: `name[, ]` would provide a + * comma-space separated list from the source array. If no characters + * are provided between the brackets, the original array source is + * returned. + * * `()` - Function notation. Adding `()` to the end of a parameter will + * execute a function of the name given. For example: `browser()` for a + * simple function on the data source, `browser.version()` for a + * function in a nested property or even `browser().version` to get an + * object property if the function called returns an object. Note that + * function notation is recommended for use in `render` rather than + * `data` as it is much simpler to use as a renderer. + * * `null` - use the original data source for the row rather than plucking + * data directly from it. This action has effects on two other + * initialisation options: + * * `defaultContent` - When null is given as the `data` option and + * `defaultContent` is specified for the column, the value defined by + * `defaultContent` will be used for the cell. + * * `render` - When null is used for the `data` option and the `render` + * option is specified for the column, the whole data source for the + * row is used for the renderer. + * * `function` - the function given will be executed whenever DataTables + * needs to set or get the data for a cell in the column. The function + * takes three parameters: + * * Parameters: + * * `{array|object}` The data source for the row + * * `{string}` The type call data requested - this will be 'set' when + * setting data or 'filter', 'display', 'type', 'sort' or undefined + * when gathering data. Note that when `undefined` is given for the + * type DataTables expects to get the raw data for the object back< + * * `{*}` Data to set when the second parameter is 'set'. + * * Return: + * * The return value from the function is not required when 'set' is + * the type of call, but otherwise the return is what will be used + * for the data requested. + * + * Note that `data` is a getter and setter option. If you just require + * formatting of data for output, you will likely want to use `render` which + * is simply a getter and thus simpler to use. + * + * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The + * name change reflects the flexibility of this property and is consistent + * with the naming of mRender. If 'mDataProp' is given, then it will still + * be used by DataTables, as it automatically maps the old name to the new + * if required. + */ + mData: null, + + /** + * This property is the rendering partner to `data` and it is suggested that + * when you want to manipulate data for display (including filtering, + * sorting etc) without altering the underlying data for the table, use this + * property. `render` can be considered to be the the read only companion to + * `data` which is read / write (then as such more complex). Like `data` + * this option can be given in a number of different ways to effect its + * behaviour: + * + * * `integer` - treated as an array index for the data source. This is the + * default that DataTables uses (incrementally increased for each column). + * * `string` - read an object property from the data source. There are + * three 'special' options that can be used in the string to alter how + * DataTables reads the data from the source object: + * * `.` - Dotted Javascript notation. Just as you use a `.` in + * Javascript to read from nested objects, so to can the options + * specified in `data`. For example: `browser.version` or + * `browser.name`. If your object parameter name contains a period, use + * `\\` to escape it - i.e. `first\\.name`. + * * `[]` - Array notation. DataTables can automatically combine data + * from and array source, joining the data with the characters provided + * between the two brackets. For example: `name[, ]` would provide a + * comma-space separated list from the source array. If no characters + * are provided between the brackets, the original array source is + * returned. + * * `()` - Function notation. Adding `()` to the end of a parameter will + * execute a function of the name given. For example: `browser()` for a + * simple function on the data source, `browser.version()` for a + * function in a nested property or even `browser().version` to get an + * object property if the function called returns an object. + * * `object` - use different data for the different data types requested by + * DataTables ('filter', 'display', 'type' or 'sort'). The property names + * of the object is the data type the property refers to and the value can + * defined using an integer, string or function using the same rules as + * `render` normally does. Note that an `_` option _must_ be specified. + * This is the default value to use if you haven't specified a value for + * the data type requested by DataTables. + * * `function` - the function given will be executed whenever DataTables + * needs to set or get the data for a cell in the column. The function + * takes three parameters: + * * Parameters: + * * {array|object} The data source for the row (based on `data`) + * * {string} The type call data requested - this will be 'filter', + * 'display', 'type' or 'sort'. + * * {array|object} The full data source for the row (not based on + * `data`) + * * Return: + * * The return value from the function is what will be used for the + * data requested. + */ + mRender: null, + + /** + * Change the cell type created for the column - either TD cells or TH cells. This + * can be useful as TH cells have semantic meaning in the table body, allowing them + * to act as a header for a row (you may wish to add scope='row' to the TH elements). + */ + sCellType: "td", + + /** + * Class to give to each cell in this column. + */ + sClass: "", + + /** + * When DataTables calculates the column widths to assign to each column, + * it finds the longest string in each column and then constructs a + * temporary table and reads the widths from that. The problem with this + * is that "mmm" is much wider then "iiii", but the latter is a longer + * string - thus the calculation can go wrong (doing it properly and putting + * it into an DOM object and measuring that is horribly(!) slow). Thus as + * a "work around" we provide this option. It will append its value to the + * text that is found to be the longest string for the column - i.e. padding. + * Generally you shouldn't need this! + */ + sContentPadding: "", + + /** + * Allows a default value to be given for a column's data, and will be used + * whenever a null data source is encountered (this can be because `data` + * is set to null, or because the data source itself is null). + */ + sDefaultContent: null, + + /** + * This parameter is only used in DataTables' server-side processing. It can + * be exceptionally useful to know what columns are being displayed on the + * client side, and to map these to database fields. When defined, the names + * also allow DataTables to reorder information from the server if it comes + * back in an unexpected order (i.e. if you switch your columns around on the + * client-side, your server-side code does not also need updating). + */ + sName: "", + + /** + * Defines a data source type for the ordering which can be used to read + * real-time information from the table (updating the internally cached + * version) prior to ordering. This allows ordering to occur on user + * editable elements such as form inputs. + */ + sSortDataType: "std", + + /** + * The title of this column. + */ + sTitle: null, + + /** + * The type allows you to specify how the data for this column will be + * ordered. Four types (string, numeric, date and html (which will strip + * HTML tags before ordering)) are currently available. Note that only date + * formats understood by Javascript's Date() object will be accepted as type + * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string', + * 'numeric', 'date' or 'html' (by default). Further types can be adding + * through plug-ins. + */ + sType: null, + + /** + * Defining the width of the column, this parameter may take any CSS value + * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not + * been given a specific width through this interface ensuring that the table + * remains readable. + */ + sWidth: null + } + + _fnHungarianMap(DataTable.defaults.column) + + /** + * DataTables settings object - this holds all the information needed for a + * given table, including configuration, data and current application of the + * table options. DataTables does not have a single instance for each DataTable + * with the settings attached to that instance, but rather instances of the + * DataTable "class" are created on-the-fly as needed (typically by a + * $().dataTable() call) and the settings object is then applied to that + * instance. + * + * Note that this object is related to {@link DataTable.defaults} but this + * one is the internal data store for DataTables's cache of columns. It should + * NOT be manipulated outside of DataTables. Any configuration should be done + * through the initialisation options. + */ + DataTable.models.oSettings = { + /** + * Primary features of DataTables and their enablement state. + */ + oFeatures: { + /** + * Flag to say if DataTables should automatically try to calculate the + * optimum table and columns widths (true) or not (false). + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bAutoWidth: null, + + /** + * Delay the creation of TR and TD elements until they are actually + * needed by a driven page draw. This can give a significant speed + * increase for Ajax source and Javascript source data, but makes no + * difference at all for DOM and server-side processing tables. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bDeferRender: null, + + /** + * Enable filtering on the table or not. Note that if this is disabled + * then there is no filtering at all on the table, including fnFilter. + * To just remove the filtering input use sDom and remove the 'f' option. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bFilter: null, + + /** + * Used only for compatiblity with DT1 + * @deprecated + */ + bInfo: true, + + /** + * Used only for compatiblity with DT1 + * @deprecated + */ + bLengthChange: true, + + /** + * Pagination enabled or not. Note that if this is disabled then length + * changing must also be disabled. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bPaginate: null, + + /** + * Processing indicator enable flag whenever DataTables is enacting a + * user request - typically an Ajax request for server-side processing. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bProcessing: null, + + /** + * Server-side processing enabled flag - when enabled DataTables will + * get all data from the server for every draw - there is no filtering, + * sorting or paging done on the client-side. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bServerSide: null, + + /** + * Sorting enablement flag. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bSort: null, + + /** + * Multi-column sorting + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bSortMulti: null, + + /** + * Apply a class to the columns which are being sorted to provide a + * visual highlight or not. This can slow things down when enabled since + * there is a lot of DOM interaction. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bSortClasses: null, + + /** + * State saving enablement flag. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bStateSave: null + }, + + /** + * Scrolling settings for a table. + */ + oScroll: { + /** + * When the table is shorter in height than sScrollY, collapse the + * table container down to the height of the table (when true). + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bCollapse: null, + + /** + * Width of the scrollbar for the web-browser's platform. Calculated + * during table initialisation. + */ + iBarWidth: 0, + + /** + * Viewport width for horizontal scrolling. Horizontal scrolling is + * disabled if an empty string. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + sX: null, + + /** + * Width to expand the table to when using x-scrolling. Typically you + * should not need to use this. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @deprecated + */ + sXInner: null, + + /** + * Viewport height for vertical scrolling. Vertical scrolling is disabled + * if an empty string. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + sY: null + }, + + /** + * Language information for the table. + */ + oLanguage: { + /** + * Information callback function. See + * {@link DataTable.defaults.fnInfoCallback} + */ + fnInfoCallback: null + }, + + /** + * Browser support parameters + */ + oBrowser: { + /** + * Determine if the vertical scrollbar is on the right or left of the + * scrolling container - needed for rtl language layout, although not + * all browsers move the scrollbar (Safari). + */ + bScrollbarLeft: false, + + /** + * Browser scrollbar width + */ + barWidth: 0 + }, + + ajax: null, + + /** + * Array referencing the nodes which are used for the features. The + * parameters of this object match what is allowed by sDom - i.e. + * <ul> + * <li>'l' - Length changing</li> + * <li>'f' - Filtering input</li> + * <li>'t' - The table!</li> + * <li>'i' - Information</li> + * <li>'p' - Pagination</li> + * <li>'r' - pRocessing</li> + * </ul> + */ + aanFeatures: [], + + /** + * Store data information - see {@link DataTable.models.oRow} for detailed + * information. + */ + aoData: [], + + /** + * Array of indexes which are in the current display (after filtering etc) + */ + aiDisplay: [], + + /** + * Array of indexes for display - no filtering + */ + aiDisplayMaster: [], + + /** + * Map of row ids to data indexes + */ + aIds: {}, + + /** + * Store information about each column that is in use + */ + aoColumns: [], + + /** + * Store information about the table's header + */ + aoHeader: [], + + /** + * Store information about the table's footer + */ + aoFooter: [], + + /** + * Store the applied global search information in case we want to force a + * research or compare the old search to a new one. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + oPreviousSearch: {}, + + /** + * Store for named searches + */ + searchFixed: {}, + + /** + * Store the applied search for each column - see + * {@link DataTable.models.oSearch} for the format that is used for the + * filtering information for each column. + */ + aoPreSearchCols: [], + + /** + * Sorting that is applied to the table. Note that the inner arrays are + * used in the following manner: + * <ul> + * <li>Index 0 - column number</li> + * <li>Index 1 - current sorting direction</li> + * </ul> + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + aaSorting: null, + + /** + * Sorting that is always applied to the table (i.e. prefixed in front of + * aaSorting). + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + aaSortingFixed: [], + + /** + * If restoring a table - we should restore its width + */ + sDestroyWidth: 0, + + /** + * Callback functions array for every time a row is inserted (i.e. on a draw). + */ + aoRowCallback: [], + + /** + * Callback functions for the header on each draw. + */ + aoHeaderCallback: [], + + /** + * Callback function for the footer on each draw. + */ + aoFooterCallback: [], + + /** + * Array of callback functions for draw callback functions + */ + aoDrawCallback: [], + + /** + * Array of callback functions for row created function + */ + aoRowCreatedCallback: [], + + /** + * Callback functions for just before the table is redrawn. A return of + * false will be used to cancel the draw. + */ + aoPreDrawCallback: [], + + /** + * Callback functions for when the table has been initialised. + */ + aoInitComplete: [], + + /** + * Callbacks for modifying the settings to be stored for state saving, prior to + * saving state. + */ + aoStateSaveParams: [], + + /** + * Callbacks for modifying the settings that have been stored for state saving + * prior to using the stored values to restore the state. + */ + aoStateLoadParams: [], + + /** + * Callbacks for operating on the settings object once the saved state has been + * loaded + */ + aoStateLoaded: [], + + /** + * Cache the table ID for quick access + */ + sTableId: "", + + /** + * The TABLE node for the main table + */ + nTable: null, + + /** + * Permanent ref to the thead element + */ + nTHead: null, + + /** + * Permanent ref to the tfoot element - if it exists + */ + nTFoot: null, + + /** + * Permanent ref to the tbody element + */ + nTBody: null, + + /** + * Cache the wrapper node (contains all DataTables controlled elements) + */ + nTableWrapper: null, + + /** + * Indicate if all required information has been read in + */ + bInitialised: false, + + /** + * Information about open rows. Each object in the array has the parameters + * 'nTr' and 'nParent' + */ + aoOpenRows: [], + + /** + * Dictate the positioning of DataTables' control elements - see + * {@link DataTable.model.oInit.sDom}. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + sDom: null, + + /** + * Search delay (in mS) + */ + searchDelay: null, + + /** + * Which type of pagination should be used. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + sPaginationType: "two_button", + + /** + * Number of paging controls on the page. Only used for backwards compatibility + */ + pagingControls: 0, + + /** + * The state duration (for `stateSave`) in seconds. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + iStateDuration: 0, + + /** + * Array of callback functions for state saving. Each array element is an + * object with the following parameters: + * <ul> + * <li>function:fn - function to call. Takes two parameters, oSettings + * and the JSON string to save that has been thus far created. Returns + * a JSON string to be inserted into a json object + * (i.e. '"param": [ 0, 1, 2]')</li> + * <li>string:sName - name of callback</li> + * </ul> + */ + aoStateSave: [], + + /** + * Array of callback functions for state loading. Each array element is an + * object with the following parameters: + * <ul> + * <li>function:fn - function to call. Takes two parameters, oSettings + * and the object stored. May return false to cancel state loading</li> + * <li>string:sName - name of callback</li> + * </ul> + */ + aoStateLoad: [], + + /** + * State that was saved. Useful for back reference + */ + oSavedState: null, + + /** + * State that was loaded. Useful for back reference + */ + oLoadedState: null, + + /** + * Note if draw should be blocked while getting data + */ + bAjaxDataGet: true, + + /** + * The last jQuery XHR object that was used for server-side data gathering. + * This can be used for working with the XHR information in one of the + * callbacks + */ + jqXHR: null, + + /** + * JSON returned from the server in the last Ajax request + */ + json: undefined, + + /** + * Data submitted as part of the last Ajax request + */ + oAjaxData: undefined, + + /** + * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if + * required). + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + sServerMethod: null, + + /** + * Format numbers for display. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + fnFormatNumber: null, + + /** + * List of options that can be used for the user selectable length menu. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + aLengthMenu: null, + + /** + * Counter for the draws that the table does. Also used as a tracker for + * server-side processing + */ + iDraw: 0, + + /** + * Indicate if a redraw is being done - useful for Ajax + */ + bDrawing: false, + + /** + * Draw index (iDraw) of the last error when parsing the returned data + */ + iDrawError: -1, + + /** + * Paging display length + */ + _iDisplayLength: 10, + + /** + * Paging start point - aiDisplay index + */ + _iDisplayStart: 0, + + /** + * Server-side processing - number of records in the result set + * (i.e. before filtering), Use fnRecordsTotal rather than + * this property to get the value of the number of records, regardless of + * the server-side processing setting. + */ + _iRecordsTotal: 0, + + /** + * Server-side processing - number of records in the current display set + * (i.e. after filtering). Use fnRecordsDisplay rather than + * this property to get the value of the number of records, regardless of + * the server-side processing setting. + */ + _iRecordsDisplay: 0, + + /** + * The classes to use for the table + */ + oClasses: {}, + + /** + * Flag attached to the settings object so you can check in the draw + * callback if filtering has been done in the draw. Deprecated in favour of + * events. + * @deprecated + */ + bFiltered: false, + + /** + * Flag attached to the settings object so you can check in the draw + * callback if sorting has been done in the draw. Deprecated in favour of + * events. + * @deprecated + */ + bSorted: false, + + /** + * Indicate that if multiple rows are in the header and there is more than + * one unique cell per column, if the top one (true) or bottom one (false) + * should be used for sorting / title by DataTables. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + */ + bSortCellsTop: null, + + /** + * Initialisation object that is used for the table + */ + oInit: null, + + /** + * Destroy callback functions - for plug-ins to attach themselves to the + * destroy so they can clean up markup and events. + */ + aoDestroyCallback: [], + + /** + * Get the number of records in the current record set, before filtering + */ + fnRecordsTotal: function () { + return _fnDataSource(this) == "ssp" ? this._iRecordsTotal * 1 : this.aiDisplayMaster.length + }, + + /** + * Get the number of records in the current record set, after filtering + */ + fnRecordsDisplay: function () { + return _fnDataSource(this) == "ssp" ? this._iRecordsDisplay * 1 : this.aiDisplay.length + }, + + /** + * Get the display end point - aiDisplay index + */ + fnDisplayEnd: function () { + var len = this._iDisplayLength, + start = this._iDisplayStart, + calc = start + len, + records = this.aiDisplay.length, + features = this.oFeatures, + paginate = features.bPaginate + + if (features.bServerSide) { + return paginate === false || len === -1 ? start + records : Math.min(start + len, this._iRecordsDisplay) + } else { + return !paginate || calc > records || len === -1 ? records : calc + } + }, + + /** + * The DataTables object for this table + */ + oInstance: null, + + /** + * Unique identifier for each instance of the DataTables object. If there + * is an ID on the table node, then it takes that value, otherwise an + * incrementing internal counter is used. + */ + sInstance: null, + + /** + * tabindex attribute value that is added to DataTables control elements, allowing + * keyboard navigation of the table and its controls. + */ + iTabIndex: 0, + + /** + * DIV container for the footer scrolling table if scrolling + */ + nScrollHead: null, + + /** + * DIV container for the footer scrolling table if scrolling + */ + nScrollFoot: null, + + /** + * Last applied sort + */ + aLastSort: [], + + /** + * Stored plug-in instances + */ + oPlugins: {}, + + /** + * Function used to get a row's id from the row's data + */ + rowIdFn: null, + + /** + * Data location where to store a row's id + */ + rowId: null, + + caption: "", + + captionNode: null, + + colgroup: null, + + /** Delay loading of data */ + deferLoading: null, + + /** Allow auto type detection */ + typeDetect: true + } + + /** + * Extension object for DataTables that is used to provide all extension + * options. + * + * Note that the `DataTable.ext` object is available through + * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is + * also aliased to `jQuery.fn.dataTableExt` for historic reasons. + * @namespace + * @extends DataTable.models.ext + */ + + var extPagination = DataTable.ext.pager + + // Paging buttons configuration + $.extend(extPagination, { + simple: function () { + return ["previous", "next"] + }, + + full: function () { + return ["first", "previous", "next", "last"] + }, + + numbers: function () { + return ["numbers"] + }, + + simple_numbers: function () { + return ["previous", "numbers", "next"] + }, + + full_numbers: function () { + return ["first", "previous", "numbers", "next", "last"] + }, + + first_last: function () { + return ["first", "last"] + }, + + first_last_numbers: function () { + return ["first", "numbers", "last"] + }, + + // For testing and plug-ins to use + _numbers: _pagingNumbers, + + // Number of number buttons - legacy, use `numbers` option for paging feature + numbers_length: 7 + }) + + $.extend(true, DataTable.ext.renderer, { + pagingButton: { + _: function (settings, buttonType, content, active, disabled) { + var classes = settings.oClasses.paging + var btnClasses = [classes.button] + var btn + + if (active) { + btnClasses.push(classes.active) + } + + if (disabled) { + btnClasses.push(classes.disabled) + } + + if (buttonType === "ellipsis") { + btn = $('<span class="ellipsis"></span>').html(content)[0] + } else { + btn = $("<button>", { + class: btnClasses.join(" "), + role: "link", + type: "button" + }).html(content) + } + + return { + display: btn, + clicker: btn + } + } + }, + + pagingContainer: { + _: function (settings, buttons) { + // No wrapping element - just append directly to the host + return buttons + } + } + }) + + // Common function to remove new lines, strip HTML and diacritic control + var _filterString = function (stripHtml, normalize) { + return function (str) { + if (_empty(str) || typeof str !== "string") { + return str + } + + str = str.replace(_re_new_lines, " ") + + if (stripHtml) { + str = _stripHtml(str) + } + + if (normalize) { + str = _normalize(str, false) + } + + return str + } + } + + /* + * Public helper functions. These aren't used internally by DataTables, or + * called by any of the options passed into DataTables, but they can be used + * externally by developers working with DataTables. They are helper functions + * to make working with DataTables a little bit easier. + */ + + /** + * Common logic for moment, luxon or a date action. + * + * Happens after __mldObj, so don't need to call `resolveWindowsLibs` again + */ + function __mld(dtLib, momentFn, luxonFn, dateFn, arg1) { + if (__moment) { + return dtLib[momentFn](arg1) + } else if (__luxon) { + return dtLib[luxonFn](arg1) + } + + return dateFn ? dtLib[dateFn](arg1) : dtLib + } + + var __mlWarning = false + var __luxon // Can be assigned in DateTeble.use() + var __moment // Can be assigned in DateTeble.use() + + /** + * + */ + function resolveWindowLibs() { + if (window.luxon && !__luxon) { + __luxon = window.luxon + } + + if (window.moment && !__moment) { + __moment = window.moment + } + } + + function __mldObj(d, format, locale) { + var dt + + resolveWindowLibs() + + if (__moment) { + dt = __moment.utc(d, format, locale, true) + + if (!dt.isValid()) { + return null + } + } else if (__luxon) { + dt = format && typeof d === "string" ? __luxon.DateTime.fromFormat(d, format) : __luxon.DateTime.fromISO(d) + + if (!dt.isValid) { + return null + } + + dt.setLocale(locale) + } else if (!format) { + // No format given, must be ISO + dt = new Date(d) + } else { + if (!__mlWarning) { + alert("DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17") + } + + __mlWarning = true + } + + return dt + } + + // Wrapper for date, datetime and time which all operate the same way with the exception of + // the output string for auto locale support + function __mlHelper(localeString) { + return function (from, to, locale, def) { + // Luxon and Moment support + // Argument shifting + if (arguments.length === 0) { + locale = "en" + to = null // means toLocaleString + from = null // means iso8601 + } else if (arguments.length === 1) { + locale = "en" + to = from + from = null + } else if (arguments.length === 2) { + locale = to + to = from + from = null + } + + var typeName = "datetime" + (to ? "-" + to : "") + + // Add type detection and sorting specific to this date format - we need to be able to identify + // date type columns as such, rather than as numbers in extensions. Hence the need for this. + if (!DataTable.ext.type.order[typeName]) { + DataTable.type(typeName, { + detect: function (d) { + // The renderer will give the value to type detect as the type! + return d === typeName ? typeName : false + }, + order: { + pre: function (d) { + // The renderer gives us Moment, Luxon or Date obects for the sorting, all of which have a + // `valueOf` which gives milliseconds epoch + return d.valueOf() + } + }, + className: "dt-right" + }) + } + + return function (d, type) { + // Allow for a default value + if (d === null || d === undefined) { + if (def === "--now") { + // We treat everything as UTC further down, so no changes are + // made, as such need to get the local date / time as if it were + // UTC + var local = new Date() + d = new Date(Date.UTC(local.getFullYear(), local.getMonth(), local.getDate(), local.getHours(), local.getMinutes(), local.getSeconds())) + } else { + d = "" + } + } + + if (type === "type") { + // Typing uses the type name for fast matching + return typeName + } + + if (d === "") { + return type !== "sort" ? "" : __mldObj("0000-01-01 00:00:00", null, locale) + } + + // Shortcut. If `from` and `to` are the same, we are using the renderer to + // format for ordering, not display - its already in the display format. + if (to !== null && from === to && type !== "sort" && type !== "type" && !(d instanceof Date)) { + return d + } + + var dt = __mldObj(d, from, locale) + + if (dt === null) { + return d + } + + if (type === "sort") { + return dt + } + + var formatted = to === null ? __mld(dt, "toDate", "toJSDate", "")[localeString]() : __mld(dt, "format", "toFormat", "toISOString", to) + + // XSS protection + return type === "display" ? _escapeHtml(formatted) : formatted + } + } + } + + // Based on locale, determine standard number formatting + // Fallback for legacy browsers is US English + var __thousands = "," + var __decimal = "." + + if (window.Intl !== undefined) { + try { + var num = new Intl.NumberFormat().formatToParts(100000.1) + + for (var i = 0; i < num.length; i++) { + if (num[i].type === "group") { + __thousands = num[i].value + } else if (num[i].type === "decimal") { + __decimal = num[i].value + } + } + } catch (e) { + // noop + } + } + + // Formatted date time detection - use by declaring the formats you are going to use + DataTable.datetime = function (format, locale) { + var typeName = "datetime-" + format + + if (!locale) { + locale = "en" + } + + if (!DataTable.ext.type.order[typeName]) { + DataTable.type(typeName, { + detect: function (d) { + var dt = __mldObj(d, format, locale) + return d === "" || dt ? typeName : false + }, + order: { + pre: function (d) { + return __mldObj(d, format, locale) || 0 + } + }, + className: "dt-right" + }) + } + } + + /** + * Helpers for `columns.render`. + * + * The options defined here can be used with the `columns.render` initialisation + * option to provide a display renderer. The following functions are defined: + * + * * `moment` - Uses the MomentJS library to convert from a given format into another. + * This renderer has three overloads: + * * 1 parameter: + * * `string` - Format to convert to (assumes input is ISO8601 and locale is `en`) + * * 2 parameters: + * * `string` - Format to convert from + * * `string` - Format to convert to. Assumes `en` locale + * * 3 parameters: + * * `string` - Format to convert from + * * `string` - Format to convert to + * * `string` - Locale + * * `number` - Will format numeric data (defined by `columns.data`) for + * display, retaining the original unformatted data for sorting and filtering. + * It takes 5 parameters: + * * `string` - Thousands grouping separator + * * `string` - Decimal point indicator + * * `integer` - Number of decimal points to show + * * `string` (optional) - Prefix. + * * `string` (optional) - Postfix (/suffix). + * * `text` - Escape HTML to help prevent XSS attacks. It has no optional + * parameters. + * + * @example + * // Column definition using the number renderer + * { + * data: "salary", + * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' ) + * } + * + * @namespace + */ + DataTable.render = { + date: __mlHelper("toLocaleDateString"), + datetime: __mlHelper("toLocaleString"), + time: __mlHelper("toLocaleTimeString"), + number: function (thousands, decimal, precision, prefix, postfix) { + // Auto locale detection + if (thousands === null || thousands === undefined) { + thousands = __thousands + } + + if (decimal === null || decimal === undefined) { + decimal = __decimal + } + + return { + display: function (d) { + if (typeof d !== "number" && typeof d !== "string") { + return d + } + + if (d === "" || d === null) { + return d + } + + var negative = d < 0 ? "-" : "" + var flo = parseFloat(d) + var abs = Math.abs(flo) + + // Scientific notation for large and small numbers + if (abs >= 100000000000 || (abs < 0.0001 && abs !== 0)) { + var exp = flo.toExponential(precision).split(/e\+?/) + return exp[0] + " x 10<sup>" + exp[1] + "</sup>" + } + + // If NaN then there isn't much formatting that we can do - just + // return immediately, escaping any HTML (this was supposed to + // be a number after all) + if (isNaN(flo)) { + return _escapeHtml(d) + } + + flo = flo.toFixed(precision) + d = Math.abs(flo) + + var intPart = parseInt(d, 10) + var floatPart = precision ? decimal + (d - intPart).toFixed(precision).substring(2) : "" + + // If zero, then can't have a negative prefix + if (intPart === 0 && parseFloat(floatPart) === 0) { + negative = "" + } + + return negative + (prefix || "") + intPart.toString().replace(/\B(?=(\d{3})+(?!\d))/g, thousands) + floatPart + (postfix || "") + } + } + }, + + text: function () { + return { + display: _escapeHtml, + filter: _escapeHtml + } + } + } + + var _extTypes = DataTable.ext.type + + // Get / set type + DataTable.type = function (name, prop, val) { + if (!prop) { + return { + className: _extTypes.className[name], + detect: _extTypes.detect.find(function (fn) { + return fn._name === name + }), + order: { + pre: _extTypes.order[name + "-pre"], + asc: _extTypes.order[name + "-asc"], + desc: _extTypes.order[name + "-desc"] + }, + render: _extTypes.render[name], + search: _extTypes.search[name] + } + } + + var setProp = function (prop, propVal) { + _extTypes[prop][name] = propVal + } + var setDetect = function (detect) { + // `detect` can be a function or an object - we set a name + // property for either - that is used for the detection + Object.defineProperty(detect, "_name", { value: name }) + + var idx = _extTypes.detect.findIndex(function (item) { + return item._name === name + }) + + if (idx === -1) { + _extTypes.detect.unshift(detect) + } else { + _extTypes.detect.splice(idx, 1, detect) + } + } + var setOrder = function (obj) { + _extTypes.order[name + "-pre"] = obj.pre // can be undefined + _extTypes.order[name + "-asc"] = obj.asc // can be undefined + _extTypes.order[name + "-desc"] = obj.desc // can be undefined + } + + // prop is optional + if (val === undefined) { + val = prop + prop = null + } + + if (prop === "className") { + setProp("className", val) + } else if (prop === "detect") { + setDetect(val) + } else if (prop === "order") { + setOrder(val) + } else if (prop === "render") { + setProp("render", val) + } else if (prop === "search") { + setProp("search", val) + } else if (!prop) { + if (val.className) { + setProp("className", val.className) + } + + if (val.detect !== undefined) { + setDetect(val.detect) + } + + if (val.order) { + setOrder(val.order) + } + + if (val.render !== undefined) { + setProp("render", val.render) + } + + if (val.search !== undefined) { + setProp("search", val.search) + } + } + } + + // Get a list of types + DataTable.types = function () { + return _extTypes.detect.map(function (fn) { + return fn._name + }) + } + + var __diacriticSort = function (a, b) { + a = a !== null && a !== undefined ? a.toString().toLowerCase() : "" + b = b !== null && b !== undefined ? b.toString().toLowerCase() : "" + + // Checked for `navigator.languages` support in `oneOf` so this code can't execute in old + // Safari and thus can disable this check + // eslint-disable-next-line compat/compat + return a.localeCompare(b, navigator.languages[0] || navigator.language, { + numeric: true, + ignorePunctuation: true + }) + } + + // + // Built in data types + // + + DataTable.type("string", { + detect: function () { + return "string" + }, + order: { + pre: function (a) { + // This is a little complex, but faster than always calling toString, + // http://jsperf.com/tostring-v-check + return _empty(a) && typeof a !== "boolean" ? "" : typeof a === "string" ? a.toLowerCase() : !a.toString ? "" : a.toString() + } + }, + search: _filterString(false, true) + }) + + DataTable.type("string-utf8", { + detect: { + allOf: function (d) { + return true + }, + oneOf: function (d) { + // At least one data point must contain a non-ASCII character + // This line will also check if navigator.languages is supported or not. If not (Safari 10.0-) + // this data type won't be supported. + // eslint-disable-next-line compat/compat + return !_empty(d) && navigator.languages && typeof d === "string" && d.match(/[^\x00-\x7F]/) + } + }, + order: { + asc: __diacriticSort, + desc: function (a, b) { + return __diacriticSort(a, b) * -1 + } + }, + search: _filterString(false, true) + }) + + DataTable.type("html", { + detect: { + allOf: function (d) { + return _empty(d) || (typeof d === "string" && d.indexOf("<") !== -1) + }, + oneOf: function (d) { + // At least one data point must contain a `<` + return !_empty(d) && typeof d === "string" && d.indexOf("<") !== -1 + } + }, + order: { + pre: function (a) { + return _empty(a) ? "" : a.replace ? _stripHtml(a).trim().toLowerCase() : a + "" + } + }, + search: _filterString(true, true) + }) + + DataTable.type("date", { + className: "dt-type-date", + detect: { + allOf: function (d) { + // V8 tries _very_ hard to make a string passed into `Date.parse()` + // valid, so we need to use a regex to restrict date formats. Use a + // plug-in for anything other than ISO8601 style strings + if (d && !(d instanceof Date) && !_re_date.test(d)) { + return null + } + var parsed = Date.parse(d) + return (parsed !== null && !isNaN(parsed)) || _empty(d) + }, + oneOf: function (d) { + // At least one entry must be a date or a string with a date + return d instanceof Date || (typeof d === "string" && _re_date.test(d)) + } + }, + order: { + pre: function (d) { + var ts = Date.parse(d) + return isNaN(ts) ? -Infinity : ts + } + } + }) + + DataTable.type("html-num-fmt", { + className: "dt-type-numeric", + detect: { + allOf: function (d, settings) { + var decimal = settings.oLanguage.sDecimal + return _htmlNumeric(d, decimal, true, false) + }, + oneOf: function (d, settings) { + // At least one data point must contain a numeric value + var decimal = settings.oLanguage.sDecimal + return _htmlNumeric(d, decimal, true, false) + } + }, + order: { + pre: function (d, s) { + var dp = s.oLanguage.sDecimal + return __numericReplace(d, dp, _re_html, _re_formatted_numeric) + } + }, + search: _filterString(true, true) + }) + + DataTable.type("html-num", { + className: "dt-type-numeric", + detect: { + allOf: function (d, settings) { + var decimal = settings.oLanguage.sDecimal + return _htmlNumeric(d, decimal, false, true) + }, + oneOf: function (d, settings) { + // At least one data point must contain a numeric value + var decimal = settings.oLanguage.sDecimal + return _htmlNumeric(d, decimal, false, false) + } + }, + order: { + pre: function (d, s) { + var dp = s.oLanguage.sDecimal + return __numericReplace(d, dp, _re_html) + } + }, + search: _filterString(true, true) + }) + + DataTable.type("num-fmt", { + className: "dt-type-numeric", + detect: { + allOf: function (d, settings) { + var decimal = settings.oLanguage.sDecimal + return _isNumber(d, decimal, true, true) + }, + oneOf: function (d, settings) { + // At least one data point must contain a numeric value + var decimal = settings.oLanguage.sDecimal + return _isNumber(d, decimal, true, false) + } + }, + order: { + pre: function (d, s) { + var dp = s.oLanguage.sDecimal + return __numericReplace(d, dp, _re_formatted_numeric) + } + } + }) + + DataTable.type("num", { + className: "dt-type-numeric", + detect: { + allOf: function (d, settings) { + var decimal = settings.oLanguage.sDecimal + return _isNumber(d, decimal, false, true) + }, + oneOf: function (d, settings) { + // At least one data point must contain a numeric value + var decimal = settings.oLanguage.sDecimal + return _isNumber(d, decimal, false, false) + } + }, + order: { + pre: function (d, s) { + var dp = s.oLanguage.sDecimal + return __numericReplace(d, dp) + } + } + }) + + var __numericReplace = function (d, decimalPlace, re1, re2) { + if (d !== 0 && (!d || d === "-")) { + return -Infinity + } + + var type = typeof d + + if (type === "number" || type === "bigint") { + return d + } + + // If a decimal place other than `.` is used, it needs to be given to the + // function so we can detect it and replace with a `.` which is the only + // decimal place Javascript recognises - it is not locale aware. + if (decimalPlace) { + d = _numToDecimal(d, decimalPlace) + } + + if (d.replace) { + if (re1) { + d = d.replace(re1, "") + } + + if (re2) { + d = d.replace(re2, "") + } + } + + return d * 1 + } + + $.extend(true, DataTable.ext.renderer, { + footer: { + _: function (settings, cell, classes) { + cell.addClass(classes.tfoot.cell) + } + }, + + header: { + _: function (settings, cell, classes) { + cell.addClass(classes.thead.cell) + + if (!settings.oFeatures.bSort) { + cell.addClass(classes.order.none) + } + + var legacyTop = settings.bSortCellsTop + var headerRows = cell.closest("thead").find("tr") + var rowIdx = cell.parent().index() + + // Conditions to not apply the ordering icons + if ( + // Cells and rows which have the attribute to disable the icons + cell.attr("data-dt-order") === "disable" || + cell.parent().attr("data-dt-order") === "disable" || + // Legacy support for `orderCellsTop`. If it is set, then cells + // which are not in the top or bottom row of the header (depending + // on the value) do not get the sorting classes applied to them + (legacyTop === true && rowIdx !== 0) || + (legacyTop === false && rowIdx !== headerRows.length - 1) + ) { + return + } + + // No additional mark-up required + // Attach a sort listener to update on sort - note that using the + // `DT` namespace will allow the event to be removed automatically + // on destroy, while the `dt` namespaced event is the one we are + // listening for + $(settings.nTable).on("order.dt.DT column-visibility.dt.DT", function (e, ctx) { + if (settings !== ctx) { + // need to check this this is the host + return // table, not a nested one + } + + var sorting = ctx.sortDetails + + if (!sorting) { + return + } + + var i + var orderClasses = classes.order + var columns = ctx.api.columns(cell) + var col = settings.aoColumns[columns.flatten()[0]] + var orderable = columns.orderable().includes(true) + var ariaType = "" + var indexes = columns.indexes() + var sortDirs = columns.orderable(true).flatten() + var orderedColumns = _pluck(sorting, "col") + + cell + .removeClass(orderClasses.isAsc + " " + orderClasses.isDesc) + .toggleClass(orderClasses.none, !orderable) + .toggleClass(orderClasses.canAsc, orderable && sortDirs.includes("asc")) + .toggleClass(orderClasses.canDesc, orderable && sortDirs.includes("desc")) + + // Determine if all of the columns that this cell covers are included in the + // current ordering + var isOrdering = true + + for (i = 0; i < indexes.length; i++) { + if (!orderedColumns.includes(indexes[i])) { + isOrdering = false + } + } + + if (isOrdering) { + // Get the ordering direction for the columns under this cell + // Note that it is possible for a cell to be asc and desc sorting + // (column spanning cells) + var orderDirs = columns.order() + + cell.addClass(orderDirs.includes("asc") ? orderClasses.isAsc : "" + orderDirs.includes("desc") ? orderClasses.isDesc : "") + } + + // Find the first visible column that has ordering applied to it - it get's + // the aria information, as the ARIA spec says that only one column should + // be marked with aria-sort + var firstVis = -1 // column index + + for (i = 0; i < orderedColumns.length; i++) { + if (settings.aoColumns[orderedColumns[i]].bVisible) { + firstVis = orderedColumns[i] + break + } + } + + if (indexes[0] == firstVis) { + var firstSort = sorting[0] + var sortOrder = col.asSorting + + cell.attr("aria-sort", firstSort.dir === "asc" ? "ascending" : "descending") + + // Determine if the next click will remove sorting or change the sort + ariaType = !sortOrder[firstSort.index + 1] ? "Remove" : "Reverse" + } else { + cell.removeAttr("aria-sort") + } + + cell.attr("aria-label", orderable ? col.ariaTitle + ctx.api.i18n("oAria.orderable" + ariaType) : col.ariaTitle) + + // Make the headers tab-able for keyboard navigation + if (orderable) { + cell.find(".dt-column-title").attr("role", "button") + cell.attr("tabindex", 0) + } + }) + } + }, + + layout: { + _: function (settings, container, items) { + var classes = settings.oClasses.layout + var row = $("<div/>") + .attr("id", items.id || null) + .addClass(items.className || classes.row) + .appendTo(container) + + $.each(items, function (key, val) { + if (key === "id" || key === "className") { + return + } + + var klass = "" + + if (val.table) { + row.addClass(classes.tableRow) + klass += classes.tableCell + " " + } + + if (key === "start") { + klass += classes.start + } else if (key === "end") { + klass += classes.end + } else { + klass += classes.full + } + + $("<div/>") + .attr({ + id: val.id || null, + class: val.className ? val.className : classes.cell + " " + klass + }) + .append(val.contents) + .appendTo(row) + }) + } + } + }) + + DataTable.feature = {} + + // Third parameter is internal only! + DataTable.feature.register = function (name, cb, legacy) { + DataTable.ext.features[name] = cb + + if (legacy) { + _ext.feature.push({ + cFeature: legacy, + fnInit: cb + }) + } + } + + function _divProp(el, prop, val) { + if (val) { + el[prop] = val + } + } + + DataTable.feature.register("div", function (settings, opts) { + var n = $("<div>")[0] + + if (opts) { + _divProp(n, "className", opts.className) + _divProp(n, "id", opts.id) + _divProp(n, "innerHTML", opts.html) + _divProp(n, "textContent", opts.text) + } + + return n + }) + + DataTable.feature.register( + "info", + function (settings, opts) { + // For compatibility with the legacy `info` top level option + if (!settings.oFeatures.bInfo) { + return null + } + + var lang = settings.oLanguage, + tid = settings.sTableId, + n = $("<div/>", { + class: settings.oClasses.info.container + }) + + opts = $.extend( + { + callback: lang.fnInfoCallback, + empty: lang.sInfoEmpty, + postfix: lang.sInfoPostFix, + search: lang.sInfoFiltered, + text: lang.sInfo + }, + opts + ) + + // Update display on each draw + settings.aoDrawCallback.push(function (s) { + _fnUpdateInfo(s, opts, n) + }) + + // For the first info display in the table, we add a callback and aria information. + if (!settings._infoEl) { + n.attr({ + "aria-live": "polite", + id: tid + "_info", + role: "status" + }) + + // Table is described by our info div + $(settings.nTable).attr("aria-describedby", tid + "_info") + + settings._infoEl = n + } + + return n + }, + "i" + ) + + /** + * Update the information elements in the display + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnUpdateInfo(settings, opts, node) { + var start = settings._iDisplayStart + 1, + end = settings.fnDisplayEnd(), + max = settings.fnRecordsTotal(), + total = settings.fnRecordsDisplay(), + out = total ? opts.text : opts.empty + + if (total !== max) { + // Record set after filtering + out += " " + opts.search + } + + // Convert the macros + out += opts.postfix + out = _fnMacros(settings, out) + + if (opts.callback) { + out = opts.callback.call(settings.oInstance, settings, start, end, max, total, out) + } + + node.html(out) + + _fnCallbackFire(settings, null, "info", [settings, node[0], out]) + } + + var __searchCounter = 0 + + // opts + // - text + // - placeholder + DataTable.feature.register( + "search", + function (settings, opts) { + // Don't show the input if filtering isn't available on the table + if (!settings.oFeatures.bFilter) { + return null + } + + var classes = settings.oClasses.search + var tableId = settings.sTableId + var language = settings.oLanguage + var previousSearch = settings.oPreviousSearch + var input = '<input type="search" class="' + classes.input + '"/>' + + opts = $.extend( + { + placeholder: language.sSearchPlaceholder, + processing: false, + text: language.sSearch + }, + opts + ) + + // The _INPUT_ is optional - is appended if not present + if (opts.text.indexOf("_INPUT_") === -1) { + opts.text += "_INPUT_" + } + + opts.text = _fnMacros(settings, opts.text) + + // We can put the <input> outside of the label if it is at the start or end + // which helps improve accessability (not all screen readers like implicit + // for elements). + var end = opts.text.match(/_INPUT_$/) + var start = opts.text.match(/^_INPUT_/) + var removed = opts.text.replace(/_INPUT_/, "") + var str = "<label>" + opts.text + "</label>" + + if (start) { + str = "_INPUT_<label>" + removed + "</label>" + } else if (end) { + str = "<label>" + removed + "</label>_INPUT_" + } + + var filter = $("<div>") + .addClass(classes.container) + .append(str.replace(/_INPUT_/, input)) + + // add for and id to label and input + filter.find("label").attr("for", "dt-search-" + __searchCounter) + filter.find("input").attr("id", "dt-search-" + __searchCounter) + __searchCounter++ + + var searchFn = function (event) { + var val = this.value + + if (previousSearch.return && event.key !== "Enter") { + return + } + + /* Now do the filter */ + if (val != previousSearch.search) { + _fnProcessingRun(settings, opts.processing, function () { + previousSearch.search = val + + _fnFilterComplete(settings, previousSearch) + + // Need to redraw, without resorting + settings._iDisplayStart = 0 + _fnDraw(settings) + }) + } + } + + var searchDelay = settings.searchDelay !== null ? settings.searchDelay : 0 + + var jqFilter = $("input", filter) + .val(previousSearch.search) + .attr("placeholder", opts.placeholder) + .on("keyup.DT search.DT input.DT paste.DT cut.DT", searchDelay ? DataTable.util.debounce(searchFn, searchDelay) : searchFn) + .on("mouseup.DT", function (e) { + // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking + // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn` + // checks the value to see if it has changed. In other browsers it won't have. + setTimeout(function () { + searchFn.call(jqFilter[0], e) + }, 10) + }) + .on("keypress.DT", function (e) { + /* Prevent form submission */ + if (e.keyCode == 13) { + return false + } + }) + .attr("aria-controls", tableId) + + // Update the input elements whenever the table is filtered + $(settings.nTable).on("search.dt.DT", function (ev, s) { + if (settings === s && jqFilter[0] !== document.activeElement) { + jqFilter.val(typeof previousSearch.search !== "function" ? previousSearch.search : "") + } + }) + + return filter + }, + "f" + ) + + // opts + // - type - button configuration + // - buttons - number of buttons to show - must be odd + DataTable.feature.register( + "paging", + function (settings, opts) { + // Don't show the paging input if the table doesn't have paging enabled + if (!settings.oFeatures.bPaginate) { + return null + } + + opts = $.extend( + { + buttons: DataTable.ext.pager.numbers_length, + type: settings.sPaginationType, + boundaryNumbers: true, + firstLast: true, + previousNext: true, + numbers: true + }, + opts + ) + + var host = $("<div/>") + .addClass(settings.oClasses.paging.container + (opts.type ? " paging_" + opts.type : "")) + .append($("<nav>").attr("aria-label", "pagination").addClass(settings.oClasses.paging.nav)) + var draw = function () { + _pagingDraw(settings, host.children(), opts) + } + + settings.aoDrawCallback.push(draw) + + // Responsive redraw of paging control + $(settings.nTable).on("column-sizing.dt.DT", draw) + + return host + }, + "p" + ) + + /** + * Dynamically create the button type array based on the configuration options. + * This will only happen if the paging type is not defined. + */ + function _pagingDynamic(opts) { + var out = [] + + if (opts.numbers) { + out.push("numbers") + } + + if (opts.previousNext) { + out.unshift("previous") + out.push("next") + } + + if (opts.firstLast) { + out.unshift("first") + out.push("last") + } + + return out + } + + function _pagingDraw(settings, host, opts) { + if (!settings._bInitComplete) { + return + } + + var plugin = opts.type ? DataTable.ext.pager[opts.type] : _pagingDynamic, + aria = settings.oLanguage.oAria.paginate || {}, + start = settings._iDisplayStart, + len = settings._iDisplayLength, + visRecords = settings.fnRecordsDisplay(), + all = len === -1, + page = all ? 0 : Math.ceil(start / len), + pages = all ? 1 : Math.ceil(visRecords / len), + buttons = [], + buttonEls = [], + buttonsNested = plugin(opts).map(function (val) { + return val === "numbers" ? _pagingNumbers(page, pages, opts.buttons, opts.boundaryNumbers) : val + }) + + // .flat() would be better, but not supported in old Safari + buttons = buttons.concat.apply(buttons, buttonsNested) + + for (var i = 0; i < buttons.length; i++) { + var button = buttons[i] + + var btnInfo = _pagingButtonInfo(settings, button, page, pages) + var btn = _fnRenderer(settings, "pagingButton")(settings, button, btnInfo.display, btnInfo.active, btnInfo.disabled) + + var ariaLabel = typeof button === "string" ? aria[button] : aria.number ? aria.number + (button + 1) : null + + // Common attributes + $(btn.clicker).attr({ + "aria-controls": settings.sTableId, + "aria-disabled": btnInfo.disabled ? "true" : null, + "aria-current": btnInfo.active ? "page" : null, + "aria-label": ariaLabel, + "data-dt-idx": button, + tabIndex: btnInfo.disabled ? -1 : settings.iTabIndex ? settings.iTabIndex : null // `0` doesn't need a tabIndex since it is the default + }) + + if (typeof button !== "number") { + $(btn.clicker).addClass(button) + } + + _fnBindAction(btn.clicker, { action: button }, function (e) { + e.preventDefault() + + _fnPageChange(settings, e.data.action, true) + }) + + buttonEls.push(btn.display) + } + + var wrapped = _fnRenderer(settings, "pagingContainer")(settings, buttonEls) + + var activeEl = host.find(document.activeElement).data("dt-idx") + + host.empty().append(wrapped) + + if (activeEl !== undefined) { + host.find("[data-dt-idx=" + activeEl + "]").trigger("focus") + } + + // Responsive - check if the buttons are over two lines based on the + // height of the buttons and the container. + if ( + buttonEls.length && // any buttons + opts.buttons > 1 && // prevent infinite + $(host).height() >= $(buttonEls[0]).outerHeight() * 2 - 10 + ) { + _pagingDraw(settings, host, $.extend({}, opts, { buttons: opts.buttons - 2 })) + } + } + + /** + * Get properties for a button based on the current paging state of the table + * + * @param {*} settings DT settings object + * @param {*} button The button type in question + * @param {*} page Table's current page + * @param {*} pages Number of pages + * @returns Info object + */ + function _pagingButtonInfo(settings, button, page, pages) { + var lang = settings.oLanguage.oPaginate + var o = { + display: "", + active: false, + disabled: false + } + + switch (button) { + case "ellipsis": + o.display = "&#x2026;" + o.disabled = true + break + + case "first": + o.display = lang.sFirst + + if (page === 0) { + o.disabled = true + } + break + + case "previous": + o.display = lang.sPrevious + + if (page === 0) { + o.disabled = true + } + break + + case "next": + o.display = lang.sNext + + if (pages === 0 || page === pages - 1) { + o.disabled = true + } + break + + case "last": + o.display = lang.sLast + + if (pages === 0 || page === pages - 1) { + o.disabled = true + } + break + + default: + if (typeof button === "number") { + o.display = settings.fnFormatNumber(button + 1) + + if (page === button) { + o.active = true + } + } + break + } + + return o + } + + /** + * Compute what number buttons to show in the paging control + * + * @param {*} page Current page + * @param {*} pages Total number of pages + * @param {*} buttons Target number of number buttons + * @param {boolean} addFirstLast Indicate if page 1 and end should be included + * @returns Buttons to show + */ + function _pagingNumbers(page, pages, buttons, addFirstLast) { + var numbers = [], + half = Math.floor(buttons / 2), + before = addFirstLast ? 2 : 1, + after = addFirstLast ? 1 : 0 + + if (pages <= buttons) { + numbers = _range(0, pages) + } else if (buttons === 1) { + // Single button - current page only + numbers = [page] + } else if (buttons === 3) { + // Special logic for just three buttons + if (page <= 1) { + numbers = [0, 1, "ellipsis"] + } else if (page >= pages - 2) { + numbers = _range(pages - 2, pages) + numbers.unshift("ellipsis") + } else { + numbers = ["ellipsis", page, "ellipsis"] + } + } else if (page <= half) { + numbers = _range(0, buttons - before) + numbers.push("ellipsis") + + if (addFirstLast) { + numbers.push(pages - 1) + } + } else if (page >= pages - 1 - half) { + numbers = _range(pages - (buttons - before), pages) + numbers.unshift("ellipsis") + + if (addFirstLast) { + numbers.unshift(0) + } + } else { + numbers = _range(page - half + before, page + half - after) + numbers.push("ellipsis") + numbers.unshift("ellipsis") + + if (addFirstLast) { + numbers.push(pages - 1) + numbers.unshift(0) + } + } + + return numbers + } + + var __lengthCounter = 0 + + // opts + // - menu + // - text + DataTable.feature.register( + "pageLength", + function (settings, opts) { + var features = settings.oFeatures + + // For compatibility with the legacy `pageLength` top level option + if (!features.bPaginate || !features.bLengthChange) { + return null + } + + opts = $.extend( + { + menu: settings.aLengthMenu, + text: settings.oLanguage.sLengthMenu + }, + opts + ) + + var classes = settings.oClasses.length, + tableId = settings.sTableId, + menu = opts.menu, + lengths = [], + language = [], + i + + // Options can be given in a number of ways + if (Array.isArray(menu[0])) { + // Old 1.x style - 2D array + lengths = menu[0] + language = menu[1] + } else { + for (i = 0; i < menu.length; i++) { + // An object with different label and value + if ($.isPlainObject(menu[i])) { + lengths.push(menu[i].value) + language.push(menu[i].label) + } else { + // Or just a number to display and use + lengths.push(menu[i]) + language.push(menu[i]) + } + } + } + + // We can put the <select> outside of the label if it is at the start or + // end which helps improve accessability (not all screen readers like + // implicit for elements). + var end = opts.text.match(/_MENU_$/) + var start = opts.text.match(/^_MENU_/) + var removed = opts.text.replace(/_MENU_/, "") + var str = "<label>" + opts.text + "</label>" + + if (start) { + str = "_MENU_<label>" + removed + "</label>" + } else if (end) { + str = "<label>" + removed + "</label>_MENU_" + } + + // Wrapper element - use a span as a holder for where the select will go + var tmpId = "tmp-" + +new Date() + var div = $("<div/>") + .addClass(classes.container) + .append(str.replace("_MENU_", '<span id="' + tmpId + '"></span>')) + + // Save text node content for macro updating + var textNodes = [] + Array.from(div.find("label")[0].childNodes).forEach(function (el) { + if (el.nodeType === Node.TEXT_NODE) { + textNodes.push({ + el: el, + text: el.textContent + }) + } + }) + + // Update the label text in case it has an entries value + var updateEntries = function (len) { + textNodes.forEach(function (node) { + node.el.textContent = _fnMacros(settings, node.text, len) + }) + } + + // Next, the select itself, along with the options + var select = $("<select/>", { + name: tableId + "_length", + "aria-controls": tableId, + class: classes.select + }) + + for (i = 0; i < lengths.length; i++) { + select[0][i] = new Option(typeof language[i] === "number" ? settings.fnFormatNumber(language[i]) : language[i], lengths[i]) + } + + // add for and id to label and input + div.find("label").attr("for", "dt-length-" + __lengthCounter) + select.attr("id", "dt-length-" + __lengthCounter) + __lengthCounter++ + + // Swap in the select list + div.find("#" + tmpId).replaceWith(select) + + // Can't use `select` variable as user might provide their own and the + // reference is broken by the use of outerHTML + $("select", div) + .val(settings._iDisplayLength) + .on("change.DT", function () { + _fnLengthChange(settings, $(this).val()) + _fnDraw(settings) + }) + + // Update node value whenever anything changes the table's length + $(settings.nTable).on("length.dt.DT", function (e, s, len) { + if (settings === s) { + $("select", div).val(len) + + // Resolve plurals in the text for the new length + updateEntries(len) + } + }) + + updateEntries(settings._iDisplayLength) + + return div + }, + "l" + ) + + // jQuery access + $.fn.dataTable = DataTable + + // Provide access to the host jQuery object (circular reference) + DataTable.$ = $ + + // Legacy aliases + $.fn.dataTableSettings = DataTable.settings + $.fn.dataTableExt = DataTable.ext + + // With a capital `D` we return a DataTables API instance rather than a + // jQuery object + $.fn.DataTable = function (opts) { + return $(this).dataTable(opts).api() + } + + // All properties that are available to $.fn.dataTable should also be + // available on $.fn.DataTable + $.each(DataTable, function (prop, val) { + $.fn.DataTable[prop] = val + }) + + return DataTable +}) + +/*! DataTables styling integration + * © SpryMedia Ltd - datatables.net/license + */ +;(function (factory) { + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery", "datatables.net"], function ($) { + return factory($, window, document) + }) + } else if (typeof exports === "object") { + // CommonJS + var jq = require("jquery") + var cjsRequires = function (root, $) { + if (!$.fn.dataTable) { + require("datatables.net")(root, $) + } + } + + if (typeof window === "undefined") { + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window + } + + if (!$) { + $ = jq(root) + } + + cjsRequires(root, $) + return factory($, root, root.document) + } + } else { + cjsRequires(window, jq) + module.exports = factory(jq, window, window.document) + } + } else { + // Browser + factory(jQuery, window, document) + } +})(function ($, window, document) { + "use strict" + var DataTable = $.fn.dataTable + + return DataTable +}) + +/*! Buttons for DataTables 3.1.2 + * © SpryMedia Ltd - datatables.net/license + */ +;(function (factory) { + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery", "datatables.net"], function ($) { + return factory($, window, document) + }) + } else if (typeof exports === "object") { + // CommonJS + var jq = require("jquery") + var cjsRequires = function (root, $) { + if (!$.fn.dataTable) { + require("datatables.net")(root, $) + } + } + + if (typeof window === "undefined") { + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window + } + + if (!$) { + $ = jq(root) + } + + cjsRequires(root, $) + return factory($, root, root.document) + } + } else { + cjsRequires(window, jq) + module.exports = factory(jq, window, window.document) + } + } else { + // Browser + factory(jQuery, window, document) + } +})(function ($, window, document) { + "use strict" + var DataTable = $.fn.dataTable + + // Used for namespacing events added to the document by each instance, so they + // can be removed on destroy + var _instCounter = 0 + + // Button namespacing counter for namespacing events on individual buttons + var _buttonCounter = 0 + + var _dtButtons = DataTable.ext.buttons + + // Custom entity decoder for data export + var _entityDecoder = null + + // Allow for jQuery slim + function _fadeIn(el, duration, fn) { + if ($.fn.animate) { + el.stop().fadeIn(duration, fn) + } else { + el.css("display", "block") + + if (fn) { + fn.call(el) + } + } + } + + function _fadeOut(el, duration, fn) { + if ($.fn.animate) { + el.stop().fadeOut(duration, fn) + } else { + el.css("display", "none") + + if (fn) { + fn.call(el) + } + } + } + + /** + * [Buttons description] + * @param {[type]} + * @param {[type]} + */ + var Buttons = function (dt, config) { + if (!DataTable.versionCheck("2")) { + throw "Warning: Buttons requires DataTables 2 or newer" + } + + // If not created with a `new` keyword then we return a wrapper function that + // will take the settings object for a DT. This allows easy use of new instances + // with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons( ... )`. + if (!(this instanceof Buttons)) { + return function (settings) { + return new Buttons(settings, dt).container() + } + } + + // If there is no config set it to an empty object + if (typeof config === "undefined") { + config = {} + } + + // Allow a boolean true for defaults + if (config === true) { + config = {} + } + + // For easy configuration of buttons an array can be given + if (Array.isArray(config)) { + config = { buttons: config } + } + + this.c = $.extend(true, {}, Buttons.defaults, config) + + // Don't want a deep copy for the buttons + if (config.buttons) { + this.c.buttons = config.buttons + } + + this.s = { + dt: new DataTable.Api(dt), + buttons: [], + listenKeys: "", + namespace: "dtb" + _instCounter++ + } + + this.dom = { + container: $("<" + this.c.dom.container.tag + "/>").addClass(this.c.dom.container.className) + } + + this._constructor() + } + + $.extend(Buttons.prototype, { + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Public methods + */ + + /** + * Get the action of a button + * @param {int|string} Button index + * @return {function} + */ /** + * Set the action of a button + * @param {node} node Button element + * @param {function} action Function to set + * @return {Buttons} Self for chaining + */ + action: function (node, action) { + var button = this._nodeToButton(node) + + if (action === undefined) { + return button.conf.action + } + + button.conf.action = action + + return this + }, + + /** + * Add an active class to the button to make to look active or get current + * active state. + * @param {node} node Button element + * @param {boolean} [flag] Enable / disable flag + * @return {Buttons} Self for chaining or boolean for getter + */ + active: function (node, flag) { + var button = this._nodeToButton(node) + var klass = this.c.dom.button.active + var jqNode = $(button.node) + + if (button.inCollection && this.c.dom.collection.button && this.c.dom.collection.button.active !== undefined) { + klass = this.c.dom.collection.button.active + } + + if (flag === undefined) { + return jqNode.hasClass(klass) + } + + jqNode.toggleClass(klass, flag === undefined ? true : flag) + + return this + }, + + /** + * Add a new button + * @param {object} config Button configuration object, base string name or function + * @param {int|string} [idx] Button index for where to insert the button + * @param {boolean} [draw=true] Trigger a draw. Set a false when adding + * lots of buttons, until the last button. + * @return {Buttons} Self for chaining + */ + add: function (config, idx, draw) { + var buttons = this.s.buttons + + if (typeof idx === "string") { + var split = idx.split("-") + var base = this.s + + for (var i = 0, ien = split.length - 1; i < ien; i++) { + base = base.buttons[split[i] * 1] + } + + buttons = base.buttons + idx = split[split.length - 1] * 1 + } + + this._expandButton(buttons, config, config !== undefined ? config.split : undefined, (config === undefined || config.split === undefined || config.split.length === 0) && base !== undefined, false, idx) + + if (draw === undefined || draw === true) { + this._draw() + } + + return this + }, + + /** + * Clear buttons from a collection and then insert new buttons + */ + collectionRebuild: function (node, newButtons) { + var button = this._nodeToButton(node) + + if (newButtons !== undefined) { + var i + // Need to reverse the array + for (i = button.buttons.length - 1; i >= 0; i--) { + this.remove(button.buttons[i].node) + } + + // If the collection has prefix and / or postfix buttons we need to add them in + if (button.conf.prefixButtons) { + newButtons.unshift.apply(newButtons, button.conf.prefixButtons) + } + + if (button.conf.postfixButtons) { + newButtons.push.apply(newButtons, button.conf.postfixButtons) + } + + for (i = 0; i < newButtons.length; i++) { + var newBtn = newButtons[i] + + this._expandButton( + button.buttons, + newBtn, + newBtn !== undefined && newBtn.config !== undefined && newBtn.config.split !== undefined, + true, + newBtn.parentConf !== undefined && newBtn.parentConf.split !== undefined, + null, + newBtn.parentConf + ) + } + } + + this._draw(button.collection, button.buttons) + }, + + /** + * Get the container node for the buttons + * @return {jQuery} Buttons node + */ + container: function () { + return this.dom.container + }, + + /** + * Disable a button + * @param {node} node Button node + * @return {Buttons} Self for chaining + */ + disable: function (node) { + var button = this._nodeToButton(node) + + $(button.node).addClass(this.c.dom.button.disabled).prop("disabled", true) + + return this + }, + + /** + * Destroy the instance, cleaning up event handlers and removing DOM + * elements + * @return {Buttons} Self for chaining + */ + destroy: function () { + // Key event listener + $("body").off("keyup." + this.s.namespace) + + // Individual button destroy (so they can remove their own events if + // needed). Take a copy as the array is modified by `remove` + var buttons = this.s.buttons.slice() + var i, ien + + for (i = 0, ien = buttons.length; i < ien; i++) { + this.remove(buttons[i].node) + } + + // Container + this.dom.container.remove() + + // Remove from the settings object collection + var buttonInsts = this.s.dt.settings()[0] + + for (i = 0, ien = buttonInsts.length; i < ien; i++) { + if (buttonInsts.inst === this) { + buttonInsts.splice(i, 1) + break + } + } + + return this + }, + + /** + * Enable / disable a button + * @param {node} node Button node + * @param {boolean} [flag=true] Enable / disable flag + * @return {Buttons} Self for chaining + */ + enable: function (node, flag) { + if (flag === false) { + return this.disable(node) + } + + var button = this._nodeToButton(node) + $(button.node).removeClass(this.c.dom.button.disabled).prop("disabled", false) + + return this + }, + + /** + * Get a button's index + * + * This is internally recursive + * @param {element} node Button to get the index of + * @return {string} Button index + */ + index: function (node, nested, buttons) { + if (!nested) { + nested = "" + buttons = this.s.buttons + } + + for (var i = 0, ien = buttons.length; i < ien; i++) { + var inner = buttons[i].buttons + + if (buttons[i].node === node) { + return nested + i + } + + if (inner && inner.length) { + var match = this.index(node, i + "-", inner) + + if (match !== null) { + return match + } + } + } + + return null + }, + + /** + * Get the instance name for the button set selector + * @return {string} Instance name + */ + name: function () { + return this.c.name + }, + + /** + * Get a button's node of the buttons container if no button is given + * @param {node} [node] Button node + * @return {jQuery} Button element, or container + */ + node: function (node) { + if (!node) { + return this.dom.container + } + + var button = this._nodeToButton(node) + return $(button.node) + }, + + /** + * Set / get a processing class on the selected button + * @param {element} node Triggering button node + * @param {boolean} flag true to add, false to remove, undefined to get + * @return {boolean|Buttons} Getter value or this if a setter. + */ + processing: function (node, flag) { + var dt = this.s.dt + var button = this._nodeToButton(node) + + if (flag === undefined) { + return $(button.node).hasClass("processing") + } + + $(button.node).toggleClass("processing", flag) + + $(dt.table().node()).triggerHandler("buttons-processing.dt", [flag, dt.button(node), dt, $(node), button.conf]) + + return this + }, + + /** + * Remove a button. + * @param {node} node Button node + * @return {Buttons} Self for chaining + */ + remove: function (node) { + var button = this._nodeToButton(node) + var host = this._nodeToHost(node) + var dt = this.s.dt + + // Remove any child buttons first + if (button.buttons.length) { + for (var i = button.buttons.length - 1; i >= 0; i--) { + this.remove(button.buttons[i].node) + } + } + + button.conf.destroying = true + + // Allow the button to remove event handlers, etc + if (button.conf.destroy) { + button.conf.destroy.call(dt.button(node), dt, $(node), button.conf) + } + + this._removeKey(button.conf) + + $(button.node).remove() + + var idx = $.inArray(button, host) + host.splice(idx, 1) + + return this + }, + + /** + * Get the text for a button + * @param {int|string} node Button index + * @return {string} Button text + */ /** + * Set the text for a button + * @param {int|string|function} node Button index + * @param {string} label Text + * @return {Buttons} Self for chaining + */ + text: function (node, label) { + var button = this._nodeToButton(node) + var textNode = button.textNode + var dt = this.s.dt + var jqNode = $(button.node) + var text = function (opt) { + return typeof opt === "function" ? opt(dt, jqNode, button.conf) : opt + } + + if (label === undefined) { + return text(button.conf.text) + } + + button.conf.text = label + textNode.html(text(label)) + + return this + }, + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Constructor + */ + + /** + * Buttons constructor + * @private + */ + _constructor: function () { + var that = this + var dt = this.s.dt + var dtSettings = dt.settings()[0] + var buttons = this.c.buttons + + if (!dtSettings._buttons) { + dtSettings._buttons = [] + } + + dtSettings._buttons.push({ + inst: this, + name: this.c.name + }) + + for (var i = 0, ien = buttons.length; i < ien; i++) { + this.add(buttons[i]) + } + + dt.on("destroy", function (e, settings) { + if (settings === dtSettings) { + that.destroy() + } + }) + + // Global key event binding to listen for button keys + $("body").on("keyup." + this.s.namespace, function (e) { + if (!document.activeElement || document.activeElement === document.body) { + // SUse a string of characters for fast lookup of if we need to + // handle this + var character = String.fromCharCode(e.keyCode).toLowerCase() + + if (that.s.listenKeys.toLowerCase().indexOf(character) !== -1) { + that._keypress(character, e) + } + } + }) + }, + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Private methods + */ + + /** + * Add a new button to the key press listener + * @param {object} conf Resolved button configuration object + * @private + */ + _addKey: function (conf) { + if (conf.key) { + this.s.listenKeys += $.isPlainObject(conf.key) ? conf.key.key : conf.key + } + }, + + /** + * Insert the buttons into the container. Call without parameters! + * @param {node} [container] Recursive only - Insert point + * @param {array} [buttons] Recursive only - Buttons array + * @private + */ + _draw: function (container, buttons) { + if (!container) { + container = this.dom.container + buttons = this.s.buttons + } + + container.children().detach() + + for (var i = 0, ien = buttons.length; i < ien; i++) { + container.append(buttons[i].inserter) + container.append(" ") + + if (buttons[i].buttons && buttons[i].buttons.length) { + this._draw(buttons[i].collection, buttons[i].buttons) + } + } + }, + + /** + * Create buttons from an array of buttons + * @param {array} attachTo Buttons array to attach to + * @param {object} button Button definition + * @param {boolean} inCollection true if the button is in a collection + * @private + */ + _expandButton: function (attachTo, button, split, inCollection, inSplit, attachPoint, parentConf) { + var dt = this.s.dt + var isSplit = false + var domCollection = this.c.dom.collection + var buttons = !Array.isArray(button) ? [button] : button + + if (button === undefined) { + buttons = !Array.isArray(split) ? [split] : split + } + + for (var i = 0, ien = buttons.length; i < ien; i++) { + var conf = this._resolveExtends(buttons[i]) + + if (!conf) { + continue + } + + isSplit = conf.config && conf.config.split ? true : false + + // If the configuration is an array, then expand the buttons at this + // point + if (Array.isArray(conf)) { + this._expandButton(attachTo, conf, built !== undefined && built.conf !== undefined ? built.conf.split : undefined, inCollection, parentConf !== undefined && parentConf.split !== undefined, attachPoint, parentConf) + continue + } + + var built = this._buildButton(conf, inCollection, conf.split !== undefined || (conf.config !== undefined && conf.config.split !== undefined), inSplit) + if (!built) { + continue + } + + if (attachPoint !== undefined && attachPoint !== null) { + attachTo.splice(attachPoint, 0, built) + attachPoint++ + } else { + attachTo.push(built) + } + + // Create the dropdown for a collection + if (built.conf.buttons) { + built.collection = $("<" + domCollection.container.content.tag + "/>") + built.conf._collection = built.collection + + $(built.node).append(domCollection.action.dropHtml) + + this._expandButton(built.buttons, built.conf.buttons, built.conf.split, !isSplit, isSplit, attachPoint, built.conf) + } + + // And the split collection + if (built.conf.split) { + built.collection = $("<" + domCollection.container.tag + "/>") + built.conf._collection = built.collection + + for (var j = 0; j < built.conf.split.length; j++) { + var item = built.conf.split[j] + + if (typeof item === "object") { + item.parent = parentConf + + if (item.collectionLayout === undefined) { + item.collectionLayout = built.conf.collectionLayout + } + + if (item.dropup === undefined) { + item.dropup = built.conf.dropup + } + + if (item.fade === undefined) { + item.fade = built.conf.fade + } + } + } + + this._expandButton(built.buttons, built.conf.buttons, built.conf.split, !isSplit, isSplit, attachPoint, built.conf) + } + + built.conf.parent = parentConf + + // init call is made here, rather than buildButton as it needs to + // be selectable, and for that it needs to be in the buttons array + if (conf.init) { + conf.init.call(dt.button(built.node), dt, $(built.node), conf) + } + } + }, + + /** + * Create an individual button + * @param {object} config Resolved button configuration + * @param {boolean} inCollection `true` if a collection button + * @return {object} Completed button description object + * @private + */ + _buildButton: function (config, inCollection, isSplit, inSplit) { + var that = this + var configDom = this.c.dom + var textNode + var dt = this.s.dt + var text = function (opt) { + return typeof opt === "function" ? opt(dt, button, config) : opt + } + + // Create an object that describes the button which can be in `dom.button`, or + // `dom.collection.button` or `dom.split.button` or `dom.collection.split.button`! + // Each should extend from `dom.button`. + var dom = $.extend(true, {}, configDom.button) + + if (inCollection && isSplit && configDom.collection.split) { + $.extend(true, dom, configDom.collection.split.action) + } else if (inSplit || inCollection) { + $.extend(true, dom, configDom.collection.button) + } else if (isSplit) { + $.extend(true, dom, configDom.split.button) + } + + // Spacers don't do much other than insert an element into the DOM + if (config.spacer) { + var spacer = $("<" + dom.spacer.tag + "/>") + .addClass("dt-button-spacer " + config.style + " " + dom.spacer.className) + .html(text(config.text)) + + return { + conf: config, + node: spacer, + inserter: spacer, + buttons: [], + inCollection: inCollection, + isSplit: isSplit, + collection: null, + textNode: spacer + } + } + + // Make sure that the button is available based on whatever requirements + // it has. For example, PDF button require pdfmake + if (config.available && !config.available(dt, config) && !config.html) { + return false + } + + var button + + if (!config.html) { + var run = function (e, dt, button, config, done) { + config.action.call(dt.button(button), e, dt, button, config, done) + + $(dt.table().node()).triggerHandler("buttons-action.dt", [dt.button(button), dt, button, config]) + } + + var action = function (e, dt, button, config) { + if (config.async) { + that.processing(button[0], true) + + setTimeout(function () { + run(e, dt, button, config, function () { + that.processing(button[0], false) + }) + }, config.async) + } else { + run(e, dt, button, config, function () {}) + } + } + + var tag = config.tag || dom.tag + var clickBlurs = config.clickBlurs === undefined ? true : config.clickBlurs + + button = $("<" + tag + "/>") + .addClass(dom.className) + .attr("tabindex", this.s.dt.settings()[0].iTabIndex) + .attr("aria-controls", this.s.dt.table().node().id) + .on("click.dtb", function (e) { + e.preventDefault() + + if (!button.hasClass(dom.disabled) && config.action) { + action(e, dt, button, config) + } + + if (clickBlurs) { + button.trigger("blur") + } + }) + .on("keypress.dtb", function (e) { + if (e.keyCode === 13) { + e.preventDefault() + + if (!button.hasClass(dom.disabled) && config.action) { + action(e, dt, button, config) + } + } + }) + + // Make `a` tags act like a link + if (tag.toLowerCase() === "a") { + button.attr("href", "#") + } + + // Button tags should have `type=button` so they don't have any default behaviour + if (tag.toLowerCase() === "button") { + button.attr("type", "button") + } + + if (dom.liner.tag) { + var liner = $("<" + dom.liner.tag + "/>") + .html(text(config.text)) + .addClass(dom.liner.className) + + if (dom.liner.tag.toLowerCase() === "a") { + liner.attr("href", "#") + } + + button.append(liner) + textNode = liner + } else { + button.html(text(config.text)) + textNode = button + } + + if (config.enabled === false) { + button.addClass(dom.disabled) + } + + if (config.className) { + button.addClass(config.className) + } + + if (config.titleAttr) { + button.attr("title", text(config.titleAttr)) + } + + if (config.attr) { + button.attr(config.attr) + } + + if (!config.namespace) { + config.namespace = ".dt-button-" + _buttonCounter++ + } + + if (config.config !== undefined && config.config.split) { + config.split = config.config.split + } + } else { + button = $(config.html) + } + + var buttonContainer = this.c.dom.buttonContainer + var inserter + if (buttonContainer && buttonContainer.tag) { + inserter = $("<" + buttonContainer.tag + "/>") + .addClass(buttonContainer.className) + .append(button) + } else { + inserter = button + } + + this._addKey(config) + + // Style integration callback for DOM manipulation + // Note that this is _not_ documented. It is currently + // for style integration only + if (this.c.buttonCreated) { + inserter = this.c.buttonCreated(config, inserter) + } + + var splitDiv + + if (isSplit) { + var dropdownConf = inCollection ? $.extend(true, this.c.dom.split, this.c.dom.collection.split) : this.c.dom.split + var wrapperConf = dropdownConf.wrapper + + splitDiv = $("<" + wrapperConf.tag + "/>") + .addClass(wrapperConf.className) + .append(button) + + var dropButtonConfig = $.extend(config, { + align: dropdownConf.dropdown.align, + attr: { + "aria-haspopup": "dialog", + "aria-expanded": false + }, + className: dropdownConf.dropdown.className, + closeButton: false, + splitAlignClass: dropdownConf.dropdown.splitAlignClass, + text: dropdownConf.dropdown.text + }) + + this._addKey(dropButtonConfig) + + var splitAction = function (e, dt, button, config) { + _dtButtons.split.action.call(dt.button(splitDiv), e, dt, button, config) + + $(dt.table().node()).triggerHandler("buttons-action.dt", [dt.button(button), dt, button, config]) + button.attr("aria-expanded", true) + } + + var dropButton = $('<button class="' + dropdownConf.dropdown.className + ' dt-button"></button>') + .html(dropdownConf.dropdown.dropHtml) + .on("click.dtb", function (e) { + e.preventDefault() + e.stopPropagation() + + if (!dropButton.hasClass(dom.disabled)) { + splitAction(e, dt, dropButton, dropButtonConfig) + } + if (clickBlurs) { + dropButton.trigger("blur") + } + }) + .on("keypress.dtb", function (e) { + if (e.keyCode === 13) { + e.preventDefault() + + if (!dropButton.hasClass(dom.disabled)) { + splitAction(e, dt, dropButton, dropButtonConfig) + } + } + }) + + if (config.split.length === 0) { + dropButton.addClass("dtb-hide-drop") + } + + splitDiv.append(dropButton).attr(dropButtonConfig.attr) + } + + return { + conf: config, + node: isSplit ? splitDiv.get(0) : button.get(0), + inserter: isSplit ? splitDiv : inserter, + buttons: [], + inCollection: inCollection, + isSplit: isSplit, + inSplit: inSplit, + collection: null, + textNode: textNode + } + }, + + /** + * Get the button object from a node (recursive) + * @param {node} node Button node + * @param {array} [buttons] Button array, uses base if not defined + * @return {object} Button object + * @private + */ + _nodeToButton: function (node, buttons) { + if (!buttons) { + buttons = this.s.buttons + } + + for (var i = 0, ien = buttons.length; i < ien; i++) { + if (buttons[i].node === node) { + return buttons[i] + } + + if (buttons[i].buttons.length) { + var ret = this._nodeToButton(node, buttons[i].buttons) + + if (ret) { + return ret + } + } + } + }, + + /** + * Get container array for a button from a button node (recursive) + * @param {node} node Button node + * @param {array} [buttons] Button array, uses base if not defined + * @return {array} Button's host array + * @private + */ + _nodeToHost: function (node, buttons) { + if (!buttons) { + buttons = this.s.buttons + } + + for (var i = 0, ien = buttons.length; i < ien; i++) { + if (buttons[i].node === node) { + return buttons + } + + if (buttons[i].buttons.length) { + var ret = this._nodeToHost(node, buttons[i].buttons) + + if (ret) { + return ret + } + } + } + }, + + /** + * Handle a key press - determine if any button's key configured matches + * what was typed and trigger the action if so. + * @param {string} character The character pressed + * @param {object} e Key event that triggered this call + * @private + */ + _keypress: function (character, e) { + // Check if this button press already activated on another instance of Buttons + if (e._buttonsHandled) { + return + } + + var run = function (conf, node) { + if (!conf.key) { + return + } + + if (conf.key === character) { + e._buttonsHandled = true + $(node).click() + } else if ($.isPlainObject(conf.key)) { + if (conf.key.key !== character) { + return + } + + if (conf.key.shiftKey && !e.shiftKey) { + return + } + + if (conf.key.altKey && !e.altKey) { + return + } + + if (conf.key.ctrlKey && !e.ctrlKey) { + return + } + + if (conf.key.metaKey && !e.metaKey) { + return + } + + // Made it this far - it is good + e._buttonsHandled = true + $(node).click() + } + } + + var recurse = function (a) { + for (var i = 0, ien = a.length; i < ien; i++) { + run(a[i].conf, a[i].node) + + if (a[i].buttons.length) { + recurse(a[i].buttons) + } + } + } + + recurse(this.s.buttons) + }, + + /** + * Remove a key from the key listener for this instance (to be used when a + * button is removed) + * @param {object} conf Button configuration + * @private + */ + _removeKey: function (conf) { + if (conf.key) { + var character = $.isPlainObject(conf.key) ? conf.key.key : conf.key + + // Remove only one character, as multiple buttons could have the + // same listening key + var a = this.s.listenKeys.split("") + var idx = $.inArray(character, a) + a.splice(idx, 1) + this.s.listenKeys = a.join("") + } + }, + + /** + * Resolve a button configuration + * @param {string|function|object} conf Button config to resolve + * @return {object} Button configuration + * @private + */ + _resolveExtends: function (conf) { + var that = this + var dt = this.s.dt + var i, ien + var toConfObject = function (base) { + var loop = 0 + + // Loop until we have resolved to a button configuration, or an + // array of button configurations (which will be iterated + // separately) + while (!$.isPlainObject(base) && !Array.isArray(base)) { + if (base === undefined) { + return + } + + if (typeof base === "function") { + base = base.call(that, dt, conf) + + if (!base) { + return false + } + } else if (typeof base === "string") { + if (!_dtButtons[base]) { + return { html: base } + } + + base = _dtButtons[base] + } + + loop++ + if (loop > 30) { + // Protect against misconfiguration killing the browser + throw "Buttons: Too many iterations" + } + } + + return Array.isArray(base) ? base : $.extend({}, base) + } + + conf = toConfObject(conf) + + while (conf && conf.extend) { + // Use `toConfObject` in case the button definition being extended + // is itself a string or a function + if (!_dtButtons[conf.extend]) { + throw "Cannot extend unknown button type: " + conf.extend + } + + var objArray = toConfObject(_dtButtons[conf.extend]) + if (Array.isArray(objArray)) { + return objArray + } else if (!objArray) { + // This is a little brutal as it might be possible to have a + // valid button without the extend, but if there is no extend + // then the host button would be acting in an undefined state + return false + } + + // Stash the current class name + var originalClassName = objArray.className + + if (conf.config !== undefined && objArray.config !== undefined) { + conf.config = $.extend({}, objArray.config, conf.config) + } + + conf = $.extend({}, objArray, conf) + + // The extend will have overwritten the original class name if the + // `conf` object also assigned a class, but we want to concatenate + // them so they are list that is combined from all extended buttons + if (originalClassName && conf.className !== originalClassName) { + conf.className = originalClassName + " " + conf.className + } + + // Although we want the `conf` object to overwrite almost all of + // the properties of the object being extended, the `extend` + // property should come from the object being extended + conf.extend = objArray.extend + } + + // Buttons to be added to a collection -gives the ability to define + // if buttons should be added to the start or end of a collection + var postfixButtons = conf.postfixButtons + if (postfixButtons) { + if (!conf.buttons) { + conf.buttons = [] + } + + for (i = 0, ien = postfixButtons.length; i < ien; i++) { + conf.buttons.push(postfixButtons[i]) + } + } + + var prefixButtons = conf.prefixButtons + if (prefixButtons) { + if (!conf.buttons) { + conf.buttons = [] + } + + for (i = 0, ien = prefixButtons.length; i < ien; i++) { + conf.buttons.splice(i, 0, prefixButtons[i]) + } + } + + return conf + }, + + /** + * Display (and replace if there is an existing one) a popover attached to a button + * @param {string|node} content Content to show + * @param {DataTable.Api} hostButton DT API instance of the button + * @param {object} inOpts Options (see object below for all options) + */ + _popover: function (content, hostButton, inOpts) { + var dt = hostButton + var c = this.c + var closed = false + var options = $.extend( + { + align: "button-left", // button-right, dt-container, split-left, split-right + autoClose: false, + background: true, + backgroundClassName: "dt-button-background", + closeButton: true, + containerClassName: c.dom.collection.container.className, + contentClassName: c.dom.collection.container.content.className, + collectionLayout: "", + collectionTitle: "", + dropup: false, + fade: 400, + popoverTitle: "", + rightAlignClassName: "dt-button-right", + tag: c.dom.collection.container.tag + }, + inOpts + ) + + var containerSelector = options.tag + "." + options.containerClassName.replace(/ /g, ".") + var hostNode = hostButton.node() + + var close = function () { + closed = true + + _fadeOut($(containerSelector), options.fade, function () { + $(this).detach() + }) + + $(dt.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes()).attr("aria-expanded", "false") + + $("div.dt-button-background").off("click.dtb-collection") + Buttons.background(false, options.backgroundClassName, options.fade, hostNode) + + $(window).off("resize.resize.dtb-collection") + $("body").off(".dtb-collection") + dt.off("buttons-action.b-internal") + dt.off("destroy") + } + + if (content === false) { + close() + return + } + + var existingExpanded = $(dt.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes()) + if (existingExpanded.length) { + // Reuse the current position if the button that was triggered is inside an existing collection + if (hostNode.closest(containerSelector).length) { + hostNode = existingExpanded.eq(0) + } + + close() + } + + // Try to be smart about the layout + var cnt = $(".dt-button", content).length + var mod = "" + + if (cnt === 3) { + mod = "dtb-b3" + } else if (cnt === 2) { + mod = "dtb-b2" + } else if (cnt === 1) { + mod = "dtb-b1" + } + + var display = $("<" + options.tag + "/>") + .addClass(options.containerClassName) + .addClass(options.collectionLayout) + .addClass(options.splitAlignClass) + .addClass(mod) + .css("display", "none") + .attr({ + "aria-modal": true, + role: "dialog" + }) + + content = $(content).addClass(options.contentClassName).attr("role", "menu").appendTo(display) + + hostNode.attr("aria-expanded", "true") + + if (hostNode.parents("body")[0] !== document.body) { + hostNode = document.body.lastChild + } + + if (options.popoverTitle) { + display.prepend('<div class="dt-button-collection-title">' + options.popoverTitle + "</div>") + } else if (options.collectionTitle) { + display.prepend('<div class="dt-button-collection-title">' + options.collectionTitle + "</div>") + } + + if (options.closeButton) { + display.prepend('<div class="dtb-popover-close">&times;</div>').addClass("dtb-collection-closeable") + } + + _fadeIn(display.insertAfter(hostNode), options.fade) + + var tableContainer = $(hostButton.table().container()) + var position = display.css("position") + + if (options.span === "container" || options.align === "dt-container") { + hostNode = hostNode.parent() + display.css("width", tableContainer.width()) + } + + // Align the popover relative to the DataTables container + // Useful for wide popovers such as SearchPanes + if (position === "absolute") { + // Align relative to the host button + var offsetParent = $(hostNode[0].offsetParent) + var buttonPosition = hostNode.position() + var buttonOffset = hostNode.offset() + var tableSizes = offsetParent.offset() + var containerPosition = offsetParent.position() + var computed = window.getComputedStyle(offsetParent[0]) + + tableSizes.height = offsetParent.outerHeight() + tableSizes.width = offsetParent.width() + parseFloat(computed.paddingLeft) + tableSizes.right = tableSizes.left + tableSizes.width + tableSizes.bottom = tableSizes.top + tableSizes.height + + // Set the initial position so we can read height / width + var top = buttonPosition.top + hostNode.outerHeight() + var left = buttonPosition.left + + display.css({ + top: top, + left: left + }) + + // Get the popover position + computed = window.getComputedStyle(display[0]) + var popoverSizes = display.offset() + + popoverSizes.height = display.outerHeight() + popoverSizes.width = display.outerWidth() + popoverSizes.right = popoverSizes.left + popoverSizes.width + popoverSizes.bottom = popoverSizes.top + popoverSizes.height + popoverSizes.marginTop = parseFloat(computed.marginTop) + popoverSizes.marginBottom = parseFloat(computed.marginBottom) + + // First position per the class requirements - pop up and right align + if (options.dropup) { + top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom + } + + if (options.align === "button-right" || display.hasClass(options.rightAlignClassName)) { + left = buttonPosition.left - popoverSizes.width + hostNode.outerWidth() + } + + // Container alignment - make sure it doesn't overflow the table container + if (options.align === "dt-container" || options.align === "container") { + if (left < buttonPosition.left) { + left = -buttonPosition.left + } + } + + // Window adjustment + if (containerPosition.left + left + popoverSizes.width > $(window).width()) { + // Overflowing the document to the right + left = $(window).width() - popoverSizes.width - containerPosition.left + } + + if (buttonOffset.left + left < 0) { + // Off to the left of the document + left = -buttonOffset.left + } + + if (containerPosition.top + top + popoverSizes.height > $(window).height() + $(window).scrollTop()) { + // Pop up if otherwise we'd need the user to scroll down + top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom + } + + if (containerPosition.top + top < $(window).scrollTop()) { + // Correction for when the top is beyond the top of the page + top = buttonPosition.top + hostNode.outerHeight() + } + + // Calculations all done - now set it + display.css({ + top: top, + left: left + }) + } else { + // Fix position - centre on screen + var place = function () { + var half = $(window).height() / 2 + + var top = display.height() / 2 + if (top > half) { + top = half + } + + display.css("marginTop", top * -1) + } + + place() + + $(window).on("resize.dtb-collection", function () { + place() + }) + } + + if (options.background) { + Buttons.background(true, options.backgroundClassName, options.fade, options.backgroundHost || hostNode) + } + + // This is bonkers, but if we don't have a click listener on the + // background element, iOS Safari will ignore the body click + // listener below. An empty function here is all that is + // required to make it work... + $("div.dt-button-background").on("click.dtb-collection", function () {}) + + if (options.autoClose) { + setTimeout(function () { + dt.on("buttons-action.b-internal", function (e, btn, dt, node) { + if (node[0] === hostNode[0]) { + return + } + close() + }) + }, 0) + } + + $(display).trigger("buttons-popover.dt") + + dt.on("destroy", close) + + setTimeout(function () { + closed = false + $("body") + .on("click.dtb-collection", function (e) { + if (closed) { + return + } + + // andSelf is deprecated in jQ1.8, but we want 1.7 compat + var back = $.fn.addBack ? "addBack" : "andSelf" + var parent = $(e.target).parent()[0] + + if ((!$(e.target).parents()[back]().filter(content).length && !$(parent).hasClass("dt-buttons")) || $(e.target).hasClass("dt-button-background")) { + close() + } + }) + .on("keyup.dtb-collection", function (e) { + if (e.keyCode === 27) { + close() + } + }) + .on("keydown.dtb-collection", function (e) { + // Focus trap for tab key + var elements = $("a, button", content) + var active = document.activeElement + + if (e.keyCode !== 9) { + // tab + return + } + + if (elements.index(active) === -1) { + // If current focus is not inside the popover + elements.first().focus() + e.preventDefault() + } else if (e.shiftKey) { + // Reverse tabbing order when shift key is pressed + if (active === elements[0]) { + elements.last().focus() + e.preventDefault() + } + } else { + if (active === elements.last()[0]) { + elements.first().focus() + e.preventDefault() + } + } + }) + }, 0) + } + }) + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Statics + */ + + /** + * Show / hide a background layer behind a collection + * @param {boolean} Flag to indicate if the background should be shown or + * hidden + * @param {string} Class to assign to the background + * @static + */ + Buttons.background = function (show, className, fade, insertPoint) { + if (fade === undefined) { + fade = 400 + } + if (!insertPoint) { + insertPoint = document.body + } + + if (show) { + _fadeIn($("<div/>").addClass(className).css("display", "none").insertAfter(insertPoint), fade) + } else { + _fadeOut($("div." + className), fade, function () { + $(this).removeClass(className).remove() + }) + } + } + + /** + * Instance selector - select Buttons instances based on an instance selector + * value from the buttons assigned to a DataTable. This is only useful if + * multiple instances are attached to a DataTable. + * @param {string|int|array} Instance selector - see `instance-selector` + * documentation on the DataTables site + * @param {array} Button instance array that was attached to the DataTables + * settings object + * @return {array} Buttons instances + * @static + */ + Buttons.instanceSelector = function (group, buttons) { + if (group === undefined || group === null) { + return $.map(buttons, function (v) { + return v.inst + }) + } + + var ret = [] + var names = $.map(buttons, function (v) { + return v.name + }) + + // Flatten the group selector into an array of single options + var process = function (input) { + if (Array.isArray(input)) { + for (var i = 0, ien = input.length; i < ien; i++) { + process(input[i]) + } + return + } + + if (typeof input === "string") { + if (input.indexOf(",") !== -1) { + // String selector, list of names + process(input.split(",")) + } else { + // String selector individual name + var idx = $.inArray(input.trim(), names) + + if (idx !== -1) { + ret.push(buttons[idx].inst) + } + } + } else if (typeof input === "number") { + // Index selector + ret.push(buttons[input].inst) + } else if (typeof input === "object" && input.nodeName) { + // Element selector + for (var j = 0; j < buttons.length; j++) { + if (buttons[j].inst.dom.container[0] === input) { + ret.push(buttons[j].inst) + } + } + } else if (typeof input === "object") { + // Actual instance selector + ret.push(input) + } + } + + process(group) + + return ret + } + + /** + * Button selector - select one or more buttons from a selector input so some + * operation can be performed on them. + * @param {array} Button instances array that the selector should operate on + * @param {string|int|node|jQuery|array} Button selector - see + * `button-selector` documentation on the DataTables site + * @return {array} Array of objects containing `inst` and `idx` properties of + * the selected buttons so you know which instance each button belongs to. + * @static + */ + Buttons.buttonSelector = function (insts, selector) { + var ret = [] + var nodeBuilder = function (a, buttons, baseIdx) { + var button + var idx + + for (var i = 0, ien = buttons.length; i < ien; i++) { + button = buttons[i] + + if (button) { + idx = baseIdx !== undefined ? baseIdx + i : i + "" + + a.push({ + node: button.node, + name: button.conf.name, + idx: idx + }) + + if (button.buttons) { + nodeBuilder(a, button.buttons, idx + "-") + } + } + } + } + + var run = function (selector, inst) { + var i, ien + var buttons = [] + nodeBuilder(buttons, inst.s.buttons) + + var nodes = $.map(buttons, function (v) { + return v.node + }) + + if (Array.isArray(selector) || selector instanceof $) { + for (i = 0, ien = selector.length; i < ien; i++) { + run(selector[i], inst) + } + return + } + + if (selector === null || selector === undefined || selector === "*") { + // Select all + for (i = 0, ien = buttons.length; i < ien; i++) { + ret.push({ + inst: inst, + node: buttons[i].node + }) + } + } else if (typeof selector === "number") { + // Main button index selector + if (inst.s.buttons[selector]) { + ret.push({ + inst: inst, + node: inst.s.buttons[selector].node + }) + } + } else if (typeof selector === "string") { + if (selector.indexOf(",") !== -1) { + // Split + var a = selector.split(",") + + for (i = 0, ien = a.length; i < ien; i++) { + run(a[i].trim(), inst) + } + } else if (selector.match(/^\d+(\-\d+)*$/)) { + // Sub-button index selector + var indexes = $.map(buttons, function (v) { + return v.idx + }) + + ret.push({ + inst: inst, + node: buttons[$.inArray(selector, indexes)].node + }) + } else if (selector.indexOf(":name") !== -1) { + // Button name selector + var name = selector.replace(":name", "") + + for (i = 0, ien = buttons.length; i < ien; i++) { + if (buttons[i].name === name) { + ret.push({ + inst: inst, + node: buttons[i].node + }) + } + } + } else { + // jQuery selector on the nodes + $(nodes) + .filter(selector) + .each(function () { + ret.push({ + inst: inst, + node: this + }) + }) + } + } else if (typeof selector === "object" && selector.nodeName) { + // Node selector + var idx = $.inArray(selector, nodes) + + if (idx !== -1) { + ret.push({ + inst: inst, + node: nodes[idx] + }) + } + } + } + + for (var i = 0, ien = insts.length; i < ien; i++) { + var inst = insts[i] + + run(selector, inst) + } + + return ret + } + + /** + * Default function used for formatting output data. + * @param {*} str Data to strip + */ + Buttons.stripData = function (str, config) { + if (typeof str !== "string") { + return str + } + + // Always remove script tags + str = Buttons.stripHtmlScript(str) + + // Always remove comments + str = Buttons.stripHtmlComments(str) + + if (!config || config.stripHtml) { + str = DataTable.util.stripHtml(str) + } + + if (!config || config.trim) { + str = str.trim() + } + + if (!config || config.stripNewlines) { + str = str.replace(/\n/g, " ") + } + + if (!config || config.decodeEntities) { + if (_entityDecoder) { + str = _entityDecoder(str) + } else { + _exportTextarea.innerHTML = str + str = _exportTextarea.value + } + } + + return str + } + + /** + * Provide a custom entity decoding function - e.g. a regex one, which can be + * much faster than the built in DOM option, but also larger code size. + * @param {function} fn + */ + Buttons.entityDecoder = function (fn) { + _entityDecoder = fn + } + + /** + * Common function for stripping HTML comments + * + * @param {*} input + * @returns + */ + Buttons.stripHtmlComments = function (input) { + var previous + + do { + previous = input + input = input.replace(/(<!--.*?--!?>)|(<!--[\S\s]+?--!?>)|(<!--[\S\s]*?$)/g, "") + } while (input !== previous) + + return input + } + + /** + * Common function for stripping HTML script tags + * + * @param {*} input + * @returns + */ + Buttons.stripHtmlScript = function (input) { + var previous + + do { + previous = input + input = input.replace(/<script\b[^<]*(?:(?!<\/script[^>]*>)<[^<]*)*<\/script[^>]*>/gi, "") + } while (input !== previous) + + return input + } + + /** + * Buttons defaults. For full documentation, please refer to the docs/option + * directory or the DataTables site. + * @type {Object} + * @static + */ + Buttons.defaults = { + buttons: ["copy", "excel", "csv", "pdf", "print"], + name: "main", + tabIndex: 0, + dom: { + container: { + tag: "div", + className: "dt-buttons" + }, + collection: { + action: { + // action button + dropHtml: '<span class="dt-button-down-arrow">&#x25BC;</span>' + }, + container: { + // The element used for the dropdown + className: "dt-button-collection", + content: { + className: "", + tag: "div" + }, + tag: "div" + } + // optionally + // , button: IButton - buttons inside the collection container + // , split: ISplit - splits inside the collection container + }, + button: { + tag: "button", + className: "dt-button", + active: "dt-button-active", // class name + disabled: "disabled", // class name + spacer: { + className: "dt-button-spacer", + tag: "span" + }, + liner: { + tag: "span", + className: "" + } + }, + split: { + action: { + // action button + className: "dt-button-split-drop-button dt-button", + tag: "button" + }, + dropdown: { + // button to trigger the dropdown + align: "split-right", + className: "dt-button-split-drop", + dropHtml: '<span class="dt-button-down-arrow">&#x25BC;</span>', + splitAlignClass: "dt-button-split-left", + tag: "button" + }, + wrapper: { + // wrap around both + className: "dt-button-split", + tag: "div" + } + } + } + } + + /** + * Version information + * @type {string} + * @static + */ + Buttons.version = "3.1.2" + + $.extend(_dtButtons, { + collection: { + text: function (dt) { + return dt.i18n("buttons.collection", "Collection") + }, + className: "buttons-collection", + closeButton: false, + init: function (dt, button) { + button.attr("aria-expanded", false) + }, + action: function (e, dt, button, config) { + if (config._collection.parents("body").length) { + this.popover(false, config) + } else { + this.popover(config._collection, config) + } + + // When activated using a key - auto focus on the + // first item in the popover + if (e.type === "keypress") { + $("a, button", config._collection).eq(0).focus() + } + }, + attr: { + "aria-haspopup": "dialog" + } + // Also the popover options, defined in Buttons.popover + }, + split: { + text: function (dt) { + return dt.i18n("buttons.split", "Split") + }, + className: "buttons-split", + closeButton: false, + init: function (dt, button) { + return button.attr("aria-expanded", false) + }, + action: function (e, dt, button, config) { + this.popover(config._collection, config) + }, + attr: { + "aria-haspopup": "dialog" + } + // Also the popover options, defined in Buttons.popover + }, + copy: function () { + if (_dtButtons.copyHtml5) { + return "copyHtml5" + } + }, + csv: function (dt, conf) { + if (_dtButtons.csvHtml5 && _dtButtons.csvHtml5.available(dt, conf)) { + return "csvHtml5" + } + }, + excel: function (dt, conf) { + if (_dtButtons.excelHtml5 && _dtButtons.excelHtml5.available(dt, conf)) { + return "excelHtml5" + } + }, + pdf: function (dt, conf) { + if (_dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available(dt, conf)) { + return "pdfHtml5" + } + }, + pageLength: function (dt) { + var lengthMenu = dt.settings()[0].aLengthMenu + var vals = [] + var lang = [] + var text = function (dt) { + return dt.i18n( + "buttons.pageLength", + { + "-1": "Show all rows", + _: "Show %d rows" + }, + dt.page.len() + ) + } + + // Support for DataTables 1.x 2D array + if (Array.isArray(lengthMenu[0])) { + vals = lengthMenu[0] + lang = lengthMenu[1] + } else { + for (var i = 0; i < lengthMenu.length; i++) { + var option = lengthMenu[i] + + // Support for DataTables 2 object in the array + if ($.isPlainObject(option)) { + vals.push(option.value) + lang.push(option.label) + } else { + vals.push(option) + lang.push(option) + } + } + } + + return { + extend: "collection", + text: text, + className: "buttons-page-length", + autoClose: true, + buttons: $.map(vals, function (val, i) { + return { + text: lang[i], + className: "button-page-length", + action: function (e, dt) { + dt.page.len(val).draw() + }, + init: function (dt, node, conf) { + var that = this + var fn = function () { + that.active(dt.page.len() === val) + } + + dt.on("length.dt" + conf.namespace, fn) + fn() + }, + destroy: function (dt, node, conf) { + dt.off("length.dt" + conf.namespace) + } + } + }), + init: function (dt, node, conf) { + var that = this + dt.on("length.dt" + conf.namespace, function () { + that.text(conf.text) + }) + }, + destroy: function (dt, node, conf) { + dt.off("length.dt" + conf.namespace) + } + } + }, + spacer: { + style: "empty", + spacer: true, + text: function (dt) { + return dt.i18n("buttons.spacer", "") + } + } + }) + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables API + * + * For complete documentation, please refer to the docs/api directory or the + * DataTables site + */ + + // Buttons group and individual button selector + DataTable.Api.register("buttons()", function (group, selector) { + // Argument shifting + if (selector === undefined) { + selector = group + group = undefined + } + + this.selector.buttonGroup = group + + var res = this.iterator( + true, + "table", + function (ctx) { + if (ctx._buttons) { + return Buttons.buttonSelector(Buttons.instanceSelector(group, ctx._buttons), selector) + } + }, + true + ) + + res._groupSelector = group + return res + }) + + // Individual button selector + DataTable.Api.register("button()", function (group, selector) { + // just run buttons() and truncate + var buttons = this.buttons(group, selector) + + if (buttons.length > 1) { + buttons.splice(1, buttons.length) + } + + return buttons + }) + + // Active buttons + DataTable.Api.registerPlural("buttons().active()", "button().active()", function (flag) { + if (flag === undefined) { + return this.map(function (set) { + return set.inst.active(set.node) + }) + } + + return this.each(function (set) { + set.inst.active(set.node, flag) + }) + }) + + // Get / set button action + DataTable.Api.registerPlural("buttons().action()", "button().action()", function (action) { + if (action === undefined) { + return this.map(function (set) { + return set.inst.action(set.node) + }) + } + + return this.each(function (set) { + set.inst.action(set.node, action) + }) + }) + + // Collection control + DataTable.Api.registerPlural("buttons().collectionRebuild()", "button().collectionRebuild()", function (buttons) { + return this.each(function (set) { + for (var i = 0; i < buttons.length; i++) { + if (typeof buttons[i] === "object") { + buttons[i].parentConf = set + } + } + set.inst.collectionRebuild(set.node, buttons) + }) + }) + + // Enable / disable buttons + DataTable.Api.register(["buttons().enable()", "button().enable()"], function (flag) { + return this.each(function (set) { + set.inst.enable(set.node, flag) + }) + }) + + // Disable buttons + DataTable.Api.register(["buttons().disable()", "button().disable()"], function () { + return this.each(function (set) { + set.inst.disable(set.node) + }) + }) + + // Button index + DataTable.Api.register("button().index()", function () { + var idx = null + + this.each(function (set) { + var res = set.inst.index(set.node) + + if (res !== null) { + idx = res + } + }) + + return idx + }) + + // Get button nodes + DataTable.Api.registerPlural("buttons().nodes()", "button().node()", function () { + var jq = $() + + // jQuery will automatically reduce duplicates to a single entry + $( + this.each(function (set) { + jq = jq.add(set.inst.node(set.node)) + }) + ) + + return jq + }) + + // Get / set button processing state + DataTable.Api.registerPlural("buttons().processing()", "button().processing()", function (flag) { + if (flag === undefined) { + return this.map(function (set) { + return set.inst.processing(set.node) + }) + } + + return this.each(function (set) { + set.inst.processing(set.node, flag) + }) + }) + + // Get / set button text (i.e. the button labels) + DataTable.Api.registerPlural("buttons().text()", "button().text()", function (label) { + if (label === undefined) { + return this.map(function (set) { + return set.inst.text(set.node) + }) + } + + return this.each(function (set) { + set.inst.text(set.node, label) + }) + }) + + // Trigger a button's action + DataTable.Api.registerPlural("buttons().trigger()", "button().trigger()", function () { + return this.each(function (set) { + set.inst.node(set.node).trigger("click") + }) + }) + + // Button resolver to the popover + DataTable.Api.register("button().popover()", function (content, options) { + return this.map(function (set) { + return set.inst._popover(content, this.button(this[0].node), options) + }) + }) + + // Get the container elements + DataTable.Api.register("buttons().containers()", function () { + var jq = $() + var groupSelector = this._groupSelector + + // We need to use the group selector directly, since if there are no buttons + // the result set will be empty + this.iterator(true, "table", function (ctx) { + if (ctx._buttons) { + var insts = Buttons.instanceSelector(groupSelector, ctx._buttons) + + for (var i = 0, ien = insts.length; i < ien; i++) { + jq = jq.add(insts[i].container()) + } + } + }) + + return jq + }) + + DataTable.Api.register("buttons().container()", function () { + // API level of nesting is `buttons()` so we can zip into the containers method + return this.containers().eq(0) + }) + + // Add a new button + DataTable.Api.register("button().add()", function (idx, conf, draw) { + var ctx = this.context + + // Don't use `this` as it could be empty - select the instances directly + if (ctx.length) { + var inst = Buttons.instanceSelector(this._groupSelector, ctx[0]._buttons) + + if (inst.length) { + inst[0].add(conf, idx, draw) + } + } + + return this.button(this._groupSelector, idx) + }) + + // Destroy the button sets selected + DataTable.Api.register("buttons().destroy()", function () { + this.pluck("inst") + .unique() + .each(function (inst) { + inst.destroy() + }) + + return this + }) + + // Remove a button + DataTable.Api.registerPlural("buttons().remove()", "buttons().remove()", function () { + this.each(function (set) { + set.inst.remove(set.node) + }) + + return this + }) + + // Information box that can be used by buttons + var _infoTimer + DataTable.Api.register("buttons.info()", function (title, message, time) { + var that = this + + if (title === false) { + this.off("destroy.btn-info") + _fadeOut($("#datatables_buttons_info"), 400, function () { + $(this).remove() + }) + clearTimeout(_infoTimer) + _infoTimer = null + + return this + } + + if (_infoTimer) { + clearTimeout(_infoTimer) + } + + if ($("#datatables_buttons_info").length) { + $("#datatables_buttons_info").remove() + } + + title = title ? "<h2>" + title + "</h2>" : "" + + _fadeIn( + $('<div id="datatables_buttons_info" class="dt-button-info"/>') + .html(title) + .append($("<div/>")[typeof message === "string" ? "html" : "append"](message)) + .css("display", "none") + .appendTo("body") + ) + + if (time !== undefined && time !== 0) { + _infoTimer = setTimeout(function () { + that.buttons.info(false) + }, time) + } + + this.on("destroy.btn-info", function () { + that.buttons.info(false) + }) + + return this + }) + + // Get data from the table for export - this is common to a number of plug-in + // buttons so it is included in the Buttons core library + DataTable.Api.register("buttons.exportData()", function (options) { + if (this.context.length) { + return _exportData(new DataTable.Api(this.context[0]), options) + } + }) + + // Get information about the export that is common to many of the export data + // types (DRY) + DataTable.Api.register("buttons.exportInfo()", function (conf) { + if (!conf) { + conf = {} + } + + return { + filename: _filename(conf, this), + title: _title(conf, this), + messageTop: _message(this, conf, conf.message || conf.messageTop, "top"), + messageBottom: _message(this, conf, conf.messageBottom, "bottom") + } + }) + + /** + * Get the file name for an exported file. + * + * @param {object} config Button configuration + * @param {object} dt DataTable instance + */ + var _filename = function (config, dt) { + // Backwards compatibility + var filename = config.filename === "*" && config.title !== "*" && config.title !== undefined && config.title !== null && config.title !== "" ? config.title : config.filename + + if (typeof filename === "function") { + filename = filename(config, dt) + } + + if (filename === undefined || filename === null) { + return null + } + + if (filename.indexOf("*") !== -1) { + filename = filename.replace(/\*/g, $("head > title").text()).trim() + } + + // Strip characters which the OS will object to + filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, "") + + var extension = _stringOrFunction(config.extension, config, dt) + if (!extension) { + extension = "" + } + + return filename + extension + } + + /** + * Simply utility method to allow parameters to be given as a function + * + * @param {undefined|string|function} option Option + * @return {null|string} Resolved value + */ + var _stringOrFunction = function (option, config, dt) { + if (option === null || option === undefined) { + return null + } else if (typeof option === "function") { + return option(config, dt) + } + return option + } + + /** + * Get the title for an exported file. + * + * @param {object} config Button configuration + */ + var _title = function (config, dt) { + var title = _stringOrFunction(config.title, config, dt) + + return title === null ? null : title.indexOf("*") !== -1 ? title.replace(/\*/g, $("head > title").text() || "Exported data") : title + } + + var _message = function (dt, config, option, position) { + var message = _stringOrFunction(option, config, dt) + if (message === null) { + return null + } + + var caption = $("caption", dt.table().container()).eq(0) + if (message === "*") { + var side = caption.css("caption-side") + if (side !== position) { + return null + } + + return caption.length ? caption.text() : "" + } + + return message + } + + var _exportTextarea = $("<textarea/>")[0] + var _exportData = function (dt, inOpts) { + var config = $.extend( + true, + {}, + { + rows: null, + columns: "", + modifier: { + search: "applied", + order: "applied" + }, + orthogonal: "display", + stripHtml: true, + stripNewlines: true, + decodeEntities: true, + trim: true, + format: { + header: function (d) { + return Buttons.stripData(d, config) + }, + footer: function (d) { + return Buttons.stripData(d, config) + }, + body: function (d) { + return Buttons.stripData(d, config) + } + }, + customizeData: null, + customizeZip: null + }, + inOpts + ) + + var header = dt + .columns(config.columns) + .indexes() + .map(function (idx) { + var col = dt.column(idx) + return config.format.header(col.title(), idx, col.header()) + }) + .toArray() + + var footer = dt.table().footer() + ? dt + .columns(config.columns) + .indexes() + .map(function (idx) { + var el = dt.column(idx).footer() + var val = "" + + if (el) { + var inner = $(".dt-column-title", el) + + val = inner.length ? inner.html() : $(el).html() + } + + return config.format.footer(val, idx, el) + }) + .toArray() + : null + + // If Select is available on this table, and any rows are selected, limit the export + // to the selected rows. If no rows are selected, all rows will be exported. Specify + // a `selected` modifier to control directly. + var modifier = $.extend({}, config.modifier) + if (dt.select && typeof dt.select.info === "function" && modifier.selected === undefined) { + if (dt.rows(config.rows, $.extend({ selected: true }, modifier)).any()) { + $.extend(modifier, { selected: true }) + } + } + + var rowIndexes = dt.rows(config.rows, modifier).indexes().toArray() + var selectedCells = dt.cells(rowIndexes, config.columns, { + order: modifier.order + }) + var cells = selectedCells.render(config.orthogonal).toArray() + var cellNodes = selectedCells.nodes().toArray() + var cellIndexes = selectedCells.indexes().toArray() + + var columns = dt.columns(config.columns).count() + var rows = columns > 0 ? cells.length / columns : 0 + var body = [] + var cellCounter = 0 + + for (var i = 0, ien = rows; i < ien; i++) { + var row = [columns] + + for (var j = 0; j < columns; j++) { + row[j] = config.format.body(cells[cellCounter], cellIndexes[cellCounter].row, cellIndexes[cellCounter].column, cellNodes[cellCounter]) + cellCounter++ + } + + body[i] = row + } + + var data = { + header: header, + headerStructure: _headerFormatter(config.format.header, dt.table().header.structure(config.columns)), + footer: footer, + footerStructure: _headerFormatter(config.format.footer, dt.table().footer.structure(config.columns)), + body: body + } + + if (config.customizeData) { + config.customizeData(data) + } + + return data + } + + function _headerFormatter(formatter, struct) { + for (var i = 0; i < struct.length; i++) { + for (var j = 0; j < struct[i].length; j++) { + var item = struct[i][j] + + if (item) { + item.title = formatter(item.title, j, item.cell) + } + } + } + + return struct + } + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables interface + */ + + // Attach to DataTables objects for global access + $.fn.dataTable.Buttons = Buttons + $.fn.DataTable.Buttons = Buttons + + // DataTables creation - check if the buttons have been defined for this table, + // they will have been if the `B` option was used in `dom`, otherwise we should + // create the buttons instance here so they can be inserted into the document + // using the API. Listen for `init` for compatibility with pre 1.10.10, but to + // be removed in future. + $(document).on("init.dt plugin-init.dt", function (e, settings) { + if (e.namespace !== "dt") { + return + } + + var opts = settings.oInit.buttons || DataTable.defaults.buttons + + if (opts && !settings._buttons) { + new Buttons(settings, opts).container() + } + }) + + function _init(settings, options) { + var api = new DataTable.Api(settings) + var opts = options ? options : api.init().buttons || DataTable.defaults.buttons + + return new Buttons(api, opts).container() + } + + // DataTables 1 `dom` feature option + DataTable.ext.feature.push({ + fnInit: _init, + cFeature: "B" + }) + + // DataTables 2 layout feature + if (DataTable.feature) { + DataTable.feature.register("buttons", _init) + } + + return DataTable +}) + +/*! DataTables styling wrapper for Buttons + * © SpryMedia Ltd - datatables.net/license + */ +;(function (factory) { + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery", "datatables.net-dt", "datatables.net-buttons"], function ($) { + return factory($, window, document) + }) + } else if (typeof exports === "object") { + // CommonJS + var jq = require("jquery") + var cjsRequires = function (root, $) { + if (!$.fn.dataTable) { + require("datatables.net-dt")(root, $) + } + + if (!$.fn.dataTable.Buttons) { + require("datatables.net-buttons")(root, $) + } + } + + if (typeof window === "undefined") { + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window + } + + if (!$) { + $ = jq(root) + } + + cjsRequires(root, $) + return factory($, root, root.document) + } + } else { + cjsRequires(window, jq) + module.exports = factory(jq, window, window.document) + } + } else { + // Browser + factory(jQuery, window, document) + } +})(function ($, window, document) { + "use strict" + var DataTable = $.fn.dataTable + + return DataTable +}) + +/*! + * Column visibility buttons for Buttons and DataTables. + * © SpryMedia Ltd - datatables.net/license + */ +;(function (factory) { + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery", "datatables.net", "datatables.net-buttons"], function ($) { + return factory($, window, document) + }) + } else if (typeof exports === "object") { + // CommonJS + var jq = require("jquery") + var cjsRequires = function (root, $) { + if (!$.fn.dataTable) { + require("datatables.net")(root, $) + } + + if (!$.fn.dataTable.Buttons) { + require("datatables.net-buttons")(root, $) + } + } + + if (typeof window === "undefined") { + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window + } + + if (!$) { + $ = jq(root) + } + + cjsRequires(root, $) + return factory($, root, root.document) + } + } else { + cjsRequires(window, jq) + module.exports = factory(jq, window, window.document) + } + } else { + // Browser + factory(jQuery, window, document) + } +})(function ($, window, document) { + "use strict" + var DataTable = $.fn.dataTable + + $.extend(DataTable.ext.buttons, { + // A collection of column visibility buttons + colvis: function (dt, conf) { + var node = null + var buttonConf = { + extend: "collection", + init: function (dt, n) { + node = n + }, + text: function (dt) { + return dt.i18n("buttons.colvis", "Column visibility") + }, + className: "buttons-colvis", + closeButton: false, + buttons: [ + { + extend: "columnsToggle", + columns: conf.columns, + columnText: conf.columnText + } + ] + } + + // Rebuild the collection with the new column structure if columns are reordered + dt.on("column-reorder.dt" + conf.namespace, function () { + dt.button(null, dt.button(null, node).node()).collectionRebuild([ + { + extend: "columnsToggle", + columns: conf.columns, + columnText: conf.columnText + } + ]) + }) + + return buttonConf + }, + + // Selected columns with individual buttons - toggle column visibility + columnsToggle: function (dt, conf) { + var columns = dt + .columns(conf.columns) + .indexes() + .map(function (idx) { + return { + extend: "columnToggle", + columns: idx, + columnText: conf.columnText + } + }) + .toArray() + + return columns + }, + + // Single button to toggle column visibility + columnToggle: function (dt, conf) { + return { + extend: "columnVisibility", + columns: conf.columns, + columnText: conf.columnText + } + }, + + // Selected columns with individual buttons - set column visibility + columnsVisibility: function (dt, conf) { + var columns = dt + .columns(conf.columns) + .indexes() + .map(function (idx) { + return { + extend: "columnVisibility", + columns: idx, + visibility: conf.visibility, + columnText: conf.columnText + } + }) + .toArray() + + return columns + }, + + // Single button to set column visibility + columnVisibility: { + columns: undefined, // column selector + text: function (dt, button, conf) { + return conf._columnText(dt, conf) + }, + className: "buttons-columnVisibility", + action: function (e, dt, button, conf) { + var col = dt.columns(conf.columns) + var curr = col.visible() + + col.visible(conf.visibility !== undefined ? conf.visibility : !(curr.length ? curr[0] : false)) + }, + init: function (dt, button, conf) { + var that = this + button.attr("data-cv-idx", conf.columns) + + dt.on("column-visibility.dt" + conf.namespace, function (e, settings) { + if (!settings.bDestroying && settings.nTable == dt.settings()[0].nTable) { + that.active(dt.column(conf.columns).visible()) + } + }).on("column-reorder.dt" + conf.namespace, function () { + // Button has been removed from the DOM + if (conf.destroying) { + return + } + + if (dt.columns(conf.columns).count() !== 1) { + return + } + + // This button controls the same column index but the text for the column has + // changed + that.text(conf._columnText(dt, conf)) + + // Since its a different column, we need to check its visibility + that.active(dt.column(conf.columns).visible()) + }) + + this.active(dt.column(conf.columns).visible()) + }, + destroy: function (dt, button, conf) { + dt.off("column-visibility.dt" + conf.namespace).off("column-reorder.dt" + conf.namespace) + }, + + _columnText: function (dt, conf) { + if (typeof conf.text === "string") { + return conf.text + } + + var title = dt.column(conf.columns).title() + var idx = dt.column(conf.columns).index() + + title = title + .replace(/\n/g, " ") // remove new lines + .replace(/<br\s*\/?>/gi, " ") // replace line breaks with spaces + .replace(/<select(.*?)<\/select\s*>/gi, "") // remove select tags, including options text + + // Strip HTML comments + title = DataTable.Buttons.stripHtmlComments(title) + + // Use whatever HTML stripper DataTables is configured for + title = DataTable.util.stripHtml(title).trim() + + return conf.columnText ? conf.columnText(dt, idx, title) : title + } + }, + + colvisRestore: { + className: "buttons-colvisRestore", + + text: function (dt) { + return dt.i18n("buttons.colvisRestore", "Restore visibility") + }, + + init: function (dt, button, conf) { + // Use a private parameter on the column. This gets moved around with the + // column if ColReorder changes the order + dt.columns().every(function () { + var init = this.init() + + if (init.__visOriginal === undefined) { + init.__visOriginal = this.visible() + } + }) + }, + + action: function (e, dt, button, conf) { + dt.columns().every(function (i) { + var init = this.init() + + this.visible(init.__visOriginal) + }) + } + }, + + colvisGroup: { + className: "buttons-colvisGroup", + + action: function (e, dt, button, conf) { + dt.columns(conf.show).visible(true, false) + dt.columns(conf.hide).visible(false, false) + + dt.columns.adjust() + }, + + show: [], + + hide: [] + } + }) + + return DataTable +}) + +/*! + * HTML5 export buttons for Buttons and DataTables. + * © SpryMedia Ltd - datatables.net/license + * + * FileSaver.js (1.3.3) - MIT license + * Copyright © 2016 Eli Grey - http://eligrey.com + */ +;(function (factory) { + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery", "datatables.net", "datatables.net-buttons"], function ($) { + return factory($, window, document) + }) + } else if (typeof exports === "object") { + // CommonJS + var jq = require("jquery") + var cjsRequires = function (root, $) { + if (!$.fn.dataTable) { + require("datatables.net")(root, $) + } + + if (!$.fn.dataTable.Buttons) { + require("datatables.net-buttons")(root, $) + } + } + + if (typeof window === "undefined") { + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window + } + + if (!$) { + $ = jq(root) + } + + cjsRequires(root, $) + return factory($, root, root.document) + } + } else { + cjsRequires(window, jq) + module.exports = factory(jq, window, window.document) + } + } else { + // Browser + factory(jQuery, window, document) + } +})(function ($, window, document) { + "use strict" + var DataTable = $.fn.dataTable + + // Allow the constructor to pass in JSZip and PDFMake from external requires. + // Otherwise, use globally defined variables, if they are available. + var useJszip + var usePdfmake + + function _jsZip() { + return useJszip || window.JSZip + } + function _pdfMake() { + return usePdfmake || window.pdfMake + } + + DataTable.Buttons.pdfMake = function (_) { + if (!_) { + return _pdfMake() + } + usePdfmake = _ + } + + DataTable.Buttons.jszip = function (_) { + if (!_) { + return _jsZip() + } + useJszip = _ + } + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * FileSaver.js dependency + */ + + /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ + + var _saveAs = (function (view) { + "use strict" + // IE <10 is explicitly unsupported + if (typeof view === "undefined" || (typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent))) { + return + } + var doc = view.document, + // only get URL when necessary in case Blob.js hasn't overridden it yet + get_URL = function () { + return view.URL || view.webkitURL || view + }, + save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a"), + can_use_save_link = "download" in save_link, + click = function (node) { + var event = new MouseEvent("click") + node.dispatchEvent(event) + }, + is_safari = /constructor/i.test(view.HTMLElement) || view.safari, + is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent), + throw_outside = function (ex) { + ;(view.setImmediate || view.setTimeout)(function () { + throw ex + }, 0) + }, + force_saveable_type = "application/octet-stream", + // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to + arbitrary_revoke_timeout = 1000 * 40, // in ms + revoke = function (file) { + var revoker = function () { + if (typeof file === "string") { + // file is an object URL + get_URL().revokeObjectURL(file) + } else { + // file is a File + file.remove() + } + } + setTimeout(revoker, arbitrary_revoke_timeout) + }, + dispatch = function (filesaver, event_types, event) { + event_types = [].concat(event_types) + var i = event_types.length + while (i--) { + var listener = filesaver["on" + event_types[i]] + if (typeof listener === "function") { + try { + listener.call(filesaver, event || filesaver) + } catch (ex) { + throw_outside(ex) + } + } + } + }, + auto_bom = function (blob) { + // prepend BOM for UTF-8 XML and text/* types (including HTML) + // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF + if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { + return new Blob([String.fromCharCode(0xfeff), blob], { + type: blob.type + }) + } + return blob + }, + FileSaver = function (blob, name, no_auto_bom) { + if (!no_auto_bom) { + blob = auto_bom(blob) + } + // First try a.download, then web filesystem, then object URLs + var filesaver = this, + type = blob.type, + force = type === force_saveable_type, + object_url, + dispatch_all = function () { + dispatch(filesaver, "writestart progress write writeend".split(" ")) + }, + // on any filesys errors revert to saving with object URLs + fs_error = function () { + if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { + // Safari doesn't allow downloading of blob urls + var reader = new FileReader() + reader.onloadend = function () { + var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, "data:attachment/file;") + var popup = view.open(url, "_blank") + if (!popup) view.location.href = url + url = undefined // release reference before dispatching + filesaver.readyState = filesaver.DONE + dispatch_all() + } + reader.readAsDataURL(blob) + filesaver.readyState = filesaver.INIT + return + } + // don't create more object URLs than needed + if (!object_url) { + object_url = get_URL().createObjectURL(blob) + } + if (force) { + view.location.href = object_url + } else { + var opened = view.open(object_url, "_blank") + if (!opened) { + // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html + view.location.href = object_url + } + } + filesaver.readyState = filesaver.DONE + dispatch_all() + revoke(object_url) + } + filesaver.readyState = filesaver.INIT + + if (can_use_save_link) { + object_url = get_URL().createObjectURL(blob) + setTimeout(function () { + save_link.href = object_url + save_link.download = name + click(save_link) + dispatch_all() + revoke(object_url) + filesaver.readyState = filesaver.DONE + }) + return + } + + fs_error() + }, + FS_proto = FileSaver.prototype, + saveAs = function (blob, name, no_auto_bom) { + return new FileSaver(blob, name || blob.name || "download", no_auto_bom) + } + // IE 10+ (native saveAs) + if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { + return function (blob, name, no_auto_bom) { + name = name || blob.name || "download" + + if (!no_auto_bom) { + blob = auto_bom(blob) + } + return navigator.msSaveOrOpenBlob(blob, name) + } + } + + FS_proto.abort = function () {} + FS_proto.readyState = FS_proto.INIT = 0 + FS_proto.WRITING = 1 + FS_proto.DONE = 2 + + FS_proto.error = FS_proto.onwritestart = FS_proto.onprogress = FS_proto.onwrite = FS_proto.onabort = FS_proto.onerror = FS_proto.onwriteend = null + + return saveAs + })((typeof self !== "undefined" && self) || (typeof window !== "undefined" && window) || this.content) + + // Expose file saver on the DataTables API. Can't attach to `DataTables.Buttons` + // since this file can be loaded before Button's core! + DataTable.fileSave = _saveAs + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Local (private) functions + */ + + /** + * Get the sheet name for Excel exports. + * + * @param {object} config Button configuration + */ + var _sheetname = function (config) { + var sheetName = "Sheet1" + + if (config.sheetName) { + sheetName = config.sheetName.replace(/[\[\]\*\/\\\?\:]/g, "") + } + + return sheetName + } + + /** + * Get the newline character(s) + * + * @param {object} config Button configuration + * @return {string} Newline character + */ + var _newLine = function (config) { + return config.newline ? config.newline : navigator.userAgent.match(/Windows/) ? "\r\n" : "\n" + } + + /** + * Combine the data from the `buttons.exportData` method into a string that + * will be used in the export file. + * + * @param {DataTable.Api} dt DataTables API instance + * @param {object} config Button configuration + * @return {object} The data to export + */ + var _exportData = function (dt, config) { + var newLine = _newLine(config) + var data = dt.buttons.exportData(config.exportOptions) + var boundary = config.fieldBoundary + var separator = config.fieldSeparator + var reBoundary = new RegExp(boundary, "g") + var escapeChar = config.escapeChar !== undefined ? config.escapeChar : "\\" + var join = function (a) { + var s = "" + + // If there is a field boundary, then we might need to escape it in + // the source data + for (var i = 0, ien = a.length; i < ien; i++) { + if (i > 0) { + s += separator + } + + s += boundary ? boundary + ("" + a[i]).replace(reBoundary, escapeChar + boundary) + boundary : a[i] + } + + return s + } + + var header = "" + var footer = "" + var body = [] + + if (config.header) { + header = + data.headerStructure + .map(function (row) { + return join( + row.map(function (cell) { + return cell ? cell.title : "" + }) + ) + }) + .join(newLine) + newLine + } + + if (config.footer && data.footer) { + footer = + data.footerStructure + .map(function (row) { + return join( + row.map(function (cell) { + return cell ? cell.title : "" + }) + ) + }) + .join(newLine) + newLine + } + + for (var i = 0, ien = data.body.length; i < ien; i++) { + body.push(join(data.body[i])) + } + + return { + str: header + body.join(newLine) + newLine + footer, + rows: body.length + } + } + + /** + * Older versions of Safari (prior to tech preview 18) don't support the + * download option required. + * + * @return {Boolean} `true` if old Safari + */ + var _isDuffSafari = function () { + var safari = navigator.userAgent.indexOf("Safari") !== -1 && navigator.userAgent.indexOf("Chrome") === -1 && navigator.userAgent.indexOf("Opera") === -1 + + if (!safari) { + return false + } + + var version = navigator.userAgent.match(/AppleWebKit\/(\d+\.\d+)/) + if (version && version.length > 1 && version[1] * 1 < 603.1) { + return true + } + + return false + } + + /** + * Convert from numeric position to letter for column names in Excel + * @param {int} n Column number + * @return {string} Column letter(s) name + */ + function createCellPos(n) { + var ordA = "A".charCodeAt(0) + var ordZ = "Z".charCodeAt(0) + var len = ordZ - ordA + 1 + var s = "" + + while (n >= 0) { + s = String.fromCharCode((n % len) + ordA) + s + n = Math.floor(n / len) - 1 + } + + return s + } + + try { + var _serialiser = new XMLSerializer() + var _ieExcel + } catch (t) { + // noop + } + + /** + * Recursively add XML files from an object's structure to a ZIP file. This + * allows the XSLX file to be easily defined with an object's structure matching + * the files structure. + * + * @param {JSZip} zip ZIP package + * @param {object} obj Object to add (recursive) + */ + function _addToZip(zip, obj) { + if (_ieExcel === undefined) { + // Detect if we are dealing with IE's _awful_ serialiser by seeing if it + // drop attributes + _ieExcel = _serialiser.serializeToString(new window.DOMParser().parseFromString(excelStrings["xl/worksheets/sheet1.xml"], "text/xml")).indexOf("xmlns:r") === -1 + } + + $.each(obj, function (name, val) { + if ($.isPlainObject(val)) { + var newDir = zip.folder(name) + _addToZip(newDir, val) + } else { + if (_ieExcel) { + // IE's XML serialiser will drop some name space attributes from + // from the root node, so we need to save them. Do this by + // replacing the namespace nodes with a regular attribute that + // we convert back when serialised. Edge does not have this + // issue + var worksheet = val.childNodes[0] + var i, ien + var attrs = [] + + for (i = worksheet.attributes.length - 1; i >= 0; i--) { + var attrName = worksheet.attributes[i].nodeName + var attrValue = worksheet.attributes[i].nodeValue + + if (attrName.indexOf(":") !== -1) { + attrs.push({ name: attrName, value: attrValue }) + + worksheet.removeAttribute(attrName) + } + } + + for (i = 0, ien = attrs.length; i < ien; i++) { + var attr = val.createAttribute(attrs[i].name.replace(":", "_dt_b_namespace_token_")) + attr.value = attrs[i].value + worksheet.setAttributeNode(attr) + } + } + + var str = _serialiser.serializeToString(val) + + // Fix IE's XML + if (_ieExcel) { + // IE doesn't include the XML declaration + if (str.indexOf("<?xml") === -1) { + str = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + str + } + + // Return namespace attributes to being as such + str = str.replace(/_dt_b_namespace_token_/g, ":") + + // Remove testing name space that IE puts into the space preserve attr + str = str.replace(/xmlns:NS[\d]+="" NS[\d]+:/g, "") + } + + // Safari, IE and Edge will put empty name space attributes onto + // various elements making them useless. This strips them out + str = str.replace(/<([^<>]*?) xmlns=""([^<>]*?)>/g, "<$1 $2>") + + zip.file(name, str) + } + }) + } + + /** + * Create an XML node and add any children, attributes, etc without needing to + * be verbose in the DOM. + * + * @param {object} doc XML document + * @param {string} nodeName Node name + * @param {object} opts Options - can be `attr` (attributes), `children` + * (child nodes) and `text` (text content) + * @return {node} Created node + */ + function _createNode(doc, nodeName, opts) { + var tempNode = doc.createElement(nodeName) + + if (opts) { + if (opts.attr) { + $(tempNode).attr(opts.attr) + } + + if (opts.children) { + $.each(opts.children, function (key, value) { + tempNode.appendChild(value) + }) + } + + if (opts.text !== null && opts.text !== undefined) { + tempNode.appendChild(doc.createTextNode(opts.text)) + } + } + + return tempNode + } + + /** + * Get the width for an Excel column based on the contents of that column + * @param {object} data Data for export + * @param {int} col Column index + * @return {int} Column width + */ + function _excelColWidth(data, col) { + var max = data.header[col].length + var len, lineSplit, str + + if (data.footer && data.footer[col] && data.footer[col].length > max) { + max = data.footer[col].length + } + + for (var i = 0, ien = data.body.length; i < ien; i++) { + var point = data.body[i][col] + str = point !== null && point !== undefined ? point.toString() : "" + + // If there is a newline character, workout the width of the column + // based on the longest line in the string + if (str.indexOf("\n") !== -1) { + lineSplit = str.split("\n") + lineSplit.sort(function (a, b) { + return b.length - a.length + }) + + len = lineSplit[0].length + } else { + len = str.length + } + + if (len > max) { + max = len + } + + // Max width rather than having potentially massive column widths + if (max > 40) { + return 54 // 40 * 1.35 + } + } + + max *= 1.35 + + // And a min width + return max > 6 ? max : 6 + } + + // Excel - Pre-defined strings to build a basic XLSX file + var excelStrings = { + "_rels/.rels": + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + + '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + + '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' + + "</Relationships>", + + "xl/_rels/workbook.xml.rels": + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + + '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + + '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>' + + '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>' + + "</Relationships>", + + "[Content_Types].xml": + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + + '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' + + '<Default Extension="xml" ContentType="application/xml" />' + + '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />' + + '<Default Extension="jpeg" ContentType="image/jpeg" />' + + '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" />' + + '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" />' + + '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" />' + + "</Types>", + + "xl/workbook.xml": + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + + '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' + + '<fileVersion appName="xl" lastEdited="5" lowestEdited="5" rupBuild="24816"/>' + + '<workbookPr showInkAnnotation="0" autoCompressPictures="0"/>' + + "<bookViews>" + + '<workbookView xWindow="0" yWindow="0" windowWidth="25600" windowHeight="19020" tabRatio="500"/>' + + "</bookViews>" + + "<sheets>" + + '<sheet name="Sheet1" sheetId="1" r:id="rId1"/>' + + "</sheets>" + + "<definedNames/>" + + "</workbook>", + + "xl/worksheets/sheet1.xml": + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + + '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">' + + "<sheetData/>" + + '<mergeCells count="0"/>' + + "</worksheet>", + + "xl/styles.xml": + '<?xml version="1.0" encoding="UTF-8"?>' + + '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">' + + '<numFmts count="6">' + + '<numFmt numFmtId="164" formatCode="[$$-409]#,##0.00;-[$$-409]#,##0.00"/>' + + '<numFmt numFmtId="165" formatCode="&quot;£&quot;#,##0.00"/>' + + '<numFmt numFmtId="166" formatCode="[$€-2] #,##0.00"/>' + + '<numFmt numFmtId="167" formatCode="0.0%"/>' + + '<numFmt numFmtId="168" formatCode="#,##0;(#,##0)"/>' + + '<numFmt numFmtId="169" formatCode="#,##0.00;(#,##0.00)"/>' + + "</numFmts>" + + '<fonts count="5" x14ac:knownFonts="1">' + + "<font>" + + '<sz val="11" />' + + '<name val="Calibri" />' + + "</font>" + + "<font>" + + '<sz val="11" />' + + '<name val="Calibri" />' + + '<color rgb="FFFFFFFF" />' + + "</font>" + + "<font>" + + '<sz val="11" />' + + '<name val="Calibri" />' + + "<b />" + + "</font>" + + "<font>" + + '<sz val="11" />' + + '<name val="Calibri" />' + + "<i />" + + "</font>" + + "<font>" + + '<sz val="11" />' + + '<name val="Calibri" />' + + "<u />" + + "</font>" + + "</fonts>" + + '<fills count="6">' + + "<fill>" + + '<patternFill patternType="none" />' + + "</fill>" + + "<fill>" + // Excel appears to use this as a dotted background regardless of values but + '<patternFill patternType="none" />' + // to be valid to the schema, use a patternFill + "</fill>" + + "<fill>" + + '<patternFill patternType="solid">' + + '<fgColor rgb="FFD9D9D9" />' + + '<bgColor indexed="64" />' + + "</patternFill>" + + "</fill>" + + "<fill>" + + '<patternFill patternType="solid">' + + '<fgColor rgb="FFD99795" />' + + '<bgColor indexed="64" />' + + "</patternFill>" + + "</fill>" + + "<fill>" + + '<patternFill patternType="solid">' + + '<fgColor rgb="ffc6efce" />' + + '<bgColor indexed="64" />' + + "</patternFill>" + + "</fill>" + + "<fill>" + + '<patternFill patternType="solid">' + + '<fgColor rgb="ffc6cfef" />' + + '<bgColor indexed="64" />' + + "</patternFill>" + + "</fill>" + + "</fills>" + + '<borders count="2">' + + "<border>" + + "<left />" + + "<right />" + + "<top />" + + "<bottom />" + + "<diagonal />" + + "</border>" + + '<border diagonalUp="false" diagonalDown="false">' + + '<left style="thin">' + + '<color auto="1" />' + + "</left>" + + '<right style="thin">' + + '<color auto="1" />' + + "</right>" + + '<top style="thin">' + + '<color auto="1" />' + + "</top>" + + '<bottom style="thin">' + + '<color auto="1" />' + + "</bottom>" + + "<diagonal />" + + "</border>" + + "</borders>" + + '<cellStyleXfs count="1">' + + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />' + + "</cellStyleXfs>" + + '<cellXfs count="68">' + + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="1" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="2" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="3" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="4" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="0" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="1" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="2" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="3" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="4" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="0" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="1" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="2" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="3" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="4" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="0" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="1" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="2" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="3" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="4" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="0" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="1" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="2" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="3" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="4" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="0" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="1" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="2" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="3" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="4" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="0" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="1" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="2" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="3" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="4" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="0" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="1" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="2" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="3" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="4" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="0" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="1" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="2" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="3" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="4" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="0" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="1" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="2" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="3" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="4" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + + '<alignment horizontal="left"/>' + + "</xf>" + + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + + '<alignment horizontal="center"/>' + + "</xf>" + + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + + '<alignment horizontal="right"/>' + + "</xf>" + + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + + '<alignment horizontal="fill"/>' + + "</xf>" + + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + + '<alignment textRotation="90"/>' + + "</xf>" + + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + + '<alignment wrapText="1"/>' + + "</xf>" + + '<xf numFmtId="9" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + '<xf numFmtId="164" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + '<xf numFmtId="165" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + '<xf numFmtId="166" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + '<xf numFmtId="167" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + '<xf numFmtId="168" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + '<xf numFmtId="169" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + '<xf numFmtId="3" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + '<xf numFmtId="4" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + '<xf numFmtId="1" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + '<xf numFmtId="2" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + '<xf numFmtId="14" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + + "</cellXfs>" + + '<cellStyles count="1">' + + '<cellStyle name="Normal" xfId="0" builtinId="0" />' + + "</cellStyles>" + + '<dxfs count="0" />' + + '<tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleMedium4" />' + + "</styleSheet>" + } + // Note we could use 3 `for` loops for the styles, but when gzipped there is + // virtually no difference in size, since the above can be easily compressed + + // Pattern matching for special number formats. Perhaps this should be exposed + // via an API in future? + // Ref: section 3.8.30 - built in formatters in open spreadsheet + // https://www.ecma-international.org/news/TC45_current_work/Office%20Open%20XML%20Part%204%20-%20Markup%20Language%20Reference.pdf + var _excelSpecials = [ + { + match: /^\-?\d+\.\d%$/, + style: 60, + fmt: function (d) { + return d / 100 + } + }, // Percent with d.p. + { + match: /^\-?\d+\.?\d*%$/, + style: 56, + fmt: function (d) { + return d / 100 + } + }, // Percent + { match: /^\-?\$[\d,]+.?\d*$/, style: 57 }, // Dollars + { match: /^\-?£[\d,]+.?\d*$/, style: 58 }, // Pounds + { match: /^\-?€[\d,]+.?\d*$/, style: 59 }, // Euros + { match: /^\-?\d+$/, style: 65 }, // Numbers without thousand separators + { match: /^\-?\d+\.\d{2}$/, style: 66 }, // Numbers 2 d.p. without thousands separators + { + match: /^\([\d,]+\)$/, + style: 61, + fmt: function (d) { + return -1 * d.replace(/[\(\)]/g, "") + } + }, // Negative numbers indicated by brackets + { + match: /^\([\d,]+\.\d{2}\)$/, + style: 62, + fmt: function (d) { + return -1 * d.replace(/[\(\)]/g, "") + } + }, // Negative numbers indicated by brackets - 2d.p. + { match: /^\-?[\d,]+$/, style: 63 }, // Numbers with thousand separators + { match: /^\-?[\d,]+\.\d{2}$/, style: 64 }, + { + match: /^(19\d\d|[2-9]\d\d\d)\-(0\d|1[012])\-[0123][\d]$/, + style: 67, + fmt: function (d) { + return Math.round(25569 + Date.parse(d) / (86400 * 1000)) + } + } //Date yyyy-mm-dd + ] + + var _excelMergeCells = function (rels, row, column, rowspan, colspan) { + var mergeCells = $("mergeCells", rels) + + mergeCells[0].appendChild( + _createNode(rels, "mergeCell", { + attr: { + ref: createCellPos(column) + row + ":" + createCellPos(column + colspan - 1) + (row + rowspan - 1) + } + }) + ) + + mergeCells.attr("count", parseFloat(mergeCells.attr("count")) + 1) + } + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Buttons + */ + + // + // Copy to clipboard + // + DataTable.ext.buttons.copyHtml5 = { + className: "buttons-copy buttons-html5", + + text: function (dt) { + return dt.i18n("buttons.copy", "Copy") + }, + + action: function (e, dt, button, config, cb) { + var exportData = _exportData(dt, config) + var info = dt.buttons.exportInfo(config) + var newline = _newLine(config) + var output = exportData.str + var hiddenDiv = $("<div/>").css({ + height: 1, + width: 1, + overflow: "hidden", + position: "fixed", + top: 0, + left: 0 + }) + + // Changes made by Breck. + // Do not append stuff to data. Keep it clean. + // if (info.title) { + // output = info.title + newline + newline + output + // } + + // if (info.messageTop) { + // output = info.messageTop + newline + newline + output + // } + + // if (info.messageBottom) { + // output = output + newline + newline + info.messageBottom + // } + + // if (config.customize) { + // output = config.customize(output, config, dt) + // } + output = output.trim() + + var textarea = $("<textarea readonly/>").val(output).appendTo(hiddenDiv) + + // For browsers that support the copy execCommand, try to use it + if (document.queryCommandSupported("copy")) { + hiddenDiv.appendTo(dt.table().container()) + textarea[0].focus() + textarea[0].select() + + try { + var successful = document.execCommand("copy") + hiddenDiv.remove() + + if (successful) { + if (config.copySuccess) { + dt.buttons.info( + dt.i18n("buttons.copyTitle", "Copy to clipboard"), + dt.i18n( + "buttons.copySuccess", + { + 1: "Copied one row to clipboard", + _: "Copied %d rows to clipboard" + }, + exportData.rows + ), + 2000 + ) + } + + cb() + return + } + } catch (t) { + // noop + } + } + + // Otherwise we show the text box and instruct the user to use it + var message = $( + "<span>" + dt.i18n("buttons.copyKeys", "Press <i>ctrl</i> or <i>\u2318</i> + <i>C</i> to copy the table data<br>to your system clipboard.<br><br>" + "To cancel, click this message or press escape.") + "</span>" + ).append(hiddenDiv) + + dt.buttons.info(dt.i18n("buttons.copyTitle", "Copy to clipboard"), message, 0) + + // Select the text so when the user activates their system clipboard + // it will copy that text + textarea[0].focus() + textarea[0].select() + + // Event to hide the message when the user is done + var container = $(message).closest(".dt-button-info") + var close = function () { + container.off("click.buttons-copy") + $(document).off(".buttons-copy") + dt.buttons.info(false) + } + + container.on("click.buttons-copy", function () { + close() + cb() + }) + $(document) + .on("keydown.buttons-copy", function (e) { + if (e.keyCode === 27) { + // esc + close() + cb() + } + }) + .on("copy.buttons-copy cut.buttons-copy", function () { + close() + cb() + }) + }, + + async: 100, + + copySuccess: true, + + exportOptions: {}, + + fieldSeparator: "\t", + + fieldBoundary: "", + + header: true, + + footer: true, + + title: "*", + + messageTop: "*", + + messageBottom: "*" + } + + // + // CSV export + // + DataTable.ext.buttons.csvHtml5 = { + bom: false, + + className: "buttons-csv buttons-html5", + + available: function () { + return window.FileReader !== undefined && window.Blob + }, + + text: function (dt) { + return dt.i18n("buttons.csv", "CSV") + }, + + action: function (e, dt, button, config, cb) { + // Set the text + var output = _exportData(dt, config).str + var info = dt.buttons.exportInfo(config) + var charset = config.charset + + if (config.customize) { + output = config.customize(output, config, dt) + } + + if (charset !== false) { + if (!charset) { + charset = document.characterSet || document.charset + } + + if (charset) { + charset = ";charset=" + charset + } + } else { + charset = "" + } + + if (config.bom) { + output = String.fromCharCode(0xfeff) + output + } + + _saveAs(new Blob([output], { type: "text/csv" + charset }), info.filename, true) + + cb() + }, + + async: 100, + + filename: "*", + + extension: ".csv", + + exportOptions: {}, + + fieldSeparator: ",", + + fieldBoundary: '"', + + escapeChar: '"', + + charset: null, + + header: true, + + footer: true + } + + // + // Excel (xlsx) export + // + DataTable.ext.buttons.excelHtml5 = { + className: "buttons-excel buttons-html5", + + available: function () { + return window.FileReader !== undefined && _jsZip() !== undefined && !_isDuffSafari() && _serialiser + }, + + text: function (dt) { + return dt.i18n("buttons.excel", "Excel") + }, + + action: function (e, dt, button, config, cb) { + var rowPos = 0 + var dataStartRow, dataEndRow + var getXml = function (type) { + var str = excelStrings[type] + + //str = str.replace( /xmlns:/g, 'xmlns_' ).replace( /mc:/g, 'mc_' ); + + return $.parseXML(str) + } + var rels = getXml("xl/worksheets/sheet1.xml") + var relsGet = rels.getElementsByTagName("sheetData")[0] + + var xlsx = { + _rels: { + ".rels": getXml("_rels/.rels") + }, + xl: { + _rels: { + "workbook.xml.rels": getXml("xl/_rels/workbook.xml.rels") + }, + "workbook.xml": getXml("xl/workbook.xml"), + "styles.xml": getXml("xl/styles.xml"), + worksheets: { + "sheet1.xml": rels + } + }, + "[Content_Types].xml": getXml("[Content_Types].xml") + } + + var data = dt.buttons.exportData(config.exportOptions) + var currentRow, rowNode + var addRow = function (row) { + currentRow = rowPos + 1 + rowNode = _createNode(rels, "row", { attr: { r: currentRow } }) + + for (var i = 0, ien = row.length; i < ien; i++) { + // Concat both the Cell Columns as a letter and the Row of the cell. + var cellId = createCellPos(i) + "" + currentRow + var cell = null + + // For null, undefined of blank cell, continue so it doesn't create the _createNode + if (row[i] === null || row[i] === undefined || row[i] === "") { + if (config.createEmptyCells === true) { + row[i] = "" + } else { + continue + } + } + + var originalContent = row[i] + row[i] = typeof row[i].trim === "function" ? row[i].trim() : row[i] + + // Special number formatting options + for (var j = 0, jen = _excelSpecials.length; j < jen; j++) { + var special = _excelSpecials[j] + + // TODO Need to provide the ability for the specials to say + // if they are returning a string, since at the moment it is + // assumed to be a number + if (row[i].match && !row[i].match(/^0\d+/) && row[i].match(special.match)) { + var val = row[i].replace(/[^\d\.\-]/g, "") + + if (special.fmt) { + val = special.fmt(val) + } + + cell = _createNode(rels, "c", { + attr: { + r: cellId, + s: special.style + }, + children: [_createNode(rels, "v", { text: val })] + }) + + break + } + } + + if (!cell) { + if ( + typeof row[i] === "number" || + (row[i].match && + row[i].match(/^-?\d+(\.\d+)?([eE]\-?\d+)?$/) && // Includes exponential format + !row[i].match(/^0\d+/)) + ) { + // Detect numbers - don't match numbers with leading zeros + // or a negative anywhere but the start + cell = _createNode(rels, "c", { + attr: { + t: "n", + r: cellId + }, + children: [_createNode(rels, "v", { text: row[i] })] + }) + } else { + // String output - replace non standard characters for text output + /*eslint no-control-regex: "off"*/ + var text = !originalContent.replace ? originalContent : originalContent.replace(/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, "") + + cell = _createNode(rels, "c", { + attr: { + t: "inlineStr", + r: cellId + }, + children: { + row: _createNode(rels, "is", { + children: { + row: _createNode(rels, "t", { + text: text, + attr: { + "xml:space": "preserve" + } + }) + } + }) + } + }) + } + } + + rowNode.appendChild(cell) + } + + relsGet.appendChild(rowNode) + rowPos++ + } + + var addHeader = function (structure) { + structure.forEach(function (row) { + addRow( + row.map(function (cell) { + return cell ? cell.title : "" + }), + rowPos + ) + $("row:last c", rels).attr("s", "2") // bold + + // Add any merge cells + row.forEach(function (cell, columnCounter) { + if (cell && (cell.colSpan > 1 || cell.rowSpan > 1)) { + _excelMergeCells(rels, rowPos, columnCounter, cell.rowSpan, cell.colSpan) + } + }) + }) + } + + // Title and top messages + var exportInfo = dt.buttons.exportInfo(config) + if (exportInfo.title) { + addRow([exportInfo.title], rowPos) + _excelMergeCells(rels, rowPos, 0, 1, data.header.length) + $("row:last c", rels).attr("s", "51") // centre + } + + if (exportInfo.messageTop) { + addRow([exportInfo.messageTop], rowPos) + _excelMergeCells(rels, rowPos, 0, 1, data.header.length) + } + + // Table header + if (config.header) { + addHeader(data.headerStructure) + } + + dataStartRow = rowPos + + // Table body + for (var n = 0, ie = data.body.length; n < ie; n++) { + addRow(data.body[n], rowPos) + } + + dataEndRow = rowPos + + // Table footer + if (config.footer && data.footer) { + addHeader(data.footerStructure) + } + + // Below the table + if (exportInfo.messageBottom) { + addRow([exportInfo.messageBottom], rowPos) + _excelMergeCells(rels, rowPos, 0, 1, data.header.length) + } + + // Set column widths + var cols = _createNode(rels, "cols") + $("worksheet", rels).prepend(cols) + + for (var i = 0, ien = data.header.length; i < ien; i++) { + cols.appendChild( + _createNode(rels, "col", { + attr: { + min: i + 1, + max: i + 1, + width: _excelColWidth(data, i), + customWidth: 1 + } + }) + ) + } + + // Workbook modifications + var workbook = xlsx.xl["workbook.xml"] + + $("sheets sheet", workbook).attr("name", _sheetname(config)) + + // Auto filter for columns + if (config.autoFilter) { + $("mergeCells", rels).before( + _createNode(rels, "autoFilter", { + attr: { + ref: "A" + dataStartRow + ":" + createCellPos(data.header.length - 1) + dataEndRow + } + }) + ) + + $("definedNames", workbook).append( + _createNode(workbook, "definedName", { + attr: { + name: "_xlnm._FilterDatabase", + localSheetId: "0", + hidden: 1 + }, + text: "'" + _sheetname(config).replace(/'/g, "''") + "'!$A$" + dataStartRow + ":" + createCellPos(data.header.length - 1) + dataEndRow + }) + ) + } + + // Let the developer customise the document if they want to + if (config.customize) { + config.customize(xlsx, config, dt) + } + + // Excel doesn't like an empty mergeCells tag + if ($("mergeCells", rels).children().length === 0) { + $("mergeCells", rels).remove() + } + + var jszip = _jsZip() + var zip = new jszip() + var zipConfig = { + compression: "DEFLATE", + type: "blob", + mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + } + + _addToZip(zip, xlsx) + + // Modern Excel has a 218 character limit on the file name + path of the file (why!?) + // https://support.microsoft.com/en-us/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 + // So we truncate to allow for this. + var filename = exportInfo.filename + + if (filename > 175) { + filename = filename.substr(0, 175) + } + + // Let the developer customize the final zip file if they want to before it is generated and sent to the browser + if (config.customizeZip) { + config.customizeZip(zip, data, filename) + } + + if (zip.generateAsync) { + // JSZip 3+ + zip.generateAsync(zipConfig).then(function (blob) { + _saveAs(blob, filename) + cb() + }) + } else { + // JSZip 2.5 + _saveAs(zip.generate(zipConfig), filename) + cb() + } + }, + + async: 100, + + filename: "*", + + extension: ".xlsx", + + exportOptions: {}, + + header: true, + + footer: true, + + title: "*", + + messageTop: "*", + + messageBottom: "*", + + createEmptyCells: false, + + autoFilter: false, + + sheetName: "" + } + + // + // PDF export - using pdfMake - http://pdfmake.org + // + DataTable.ext.buttons.pdfHtml5 = { + className: "buttons-pdf buttons-html5", + + available: function () { + return window.FileReader !== undefined && _pdfMake() + }, + + text: function (dt) { + return dt.i18n("buttons.pdf", "PDF") + }, + + action: function (e, dt, button, config, cb) { + var data = dt.buttons.exportData(config.exportOptions) + var info = dt.buttons.exportInfo(config) + var rows = [] + + if (config.header) { + data.headerStructure.forEach(function (row) { + rows.push( + row.map(function (cell) { + return cell + ? { + text: cell.title, + colSpan: cell.colspan, + rowSpan: cell.rowspan, + style: "tableHeader" + } + : {} + }) + ) + }) + } + + for (var i = 0, ien = data.body.length; i < ien; i++) { + rows.push( + data.body[i].map(function (d) { + return { + text: d === null || d === undefined ? "" : typeof d === "string" ? d : d.toString() + } + }) + ) + } + + if (config.footer) { + data.footerStructure.forEach(function (row) { + rows.push( + row.map(function (cell) { + return cell + ? { + text: cell.title, + colSpan: cell.colspan, + rowSpan: cell.rowspan, + style: "tableHeader" + } + : {} + }) + ) + }) + } + + var doc = { + pageSize: config.pageSize, + pageOrientation: config.orientation, + content: [ + { + style: "table", + table: { + headerRows: data.headerStructure.length, + footerRows: data.footerStructure.length, // Used for styling, doesn't do anything in pdfmake + body: rows + }, + layout: { + hLineWidth: function (i, node) { + if (i === 0 || i === node.table.body.length) { + return 0 + } + return 0.5 + }, + vLineWidth: function () { + return 0 + }, + hLineColor: function (i, node) { + return i === node.table.headerRows || i === node.table.body.length - node.table.footerRows ? "#333" : "#ddd" + }, + fillColor: function (rowIndex) { + if (rowIndex < data.headerStructure.length) { + return "#fff" + } + return rowIndex % 2 === 0 ? "#f3f3f3" : null + }, + paddingTop: function () { + return 5 + }, + paddingBottom: function () { + return 5 + } + } + } + ], + styles: { + tableHeader: { + bold: true, + fontSize: 11, + alignment: "center" + }, + tableFooter: { + bold: true, + fontSize: 11 + }, + table: { + margin: [0, 5, 0, 5] + }, + title: { + alignment: "center", + fontSize: 13 + }, + message: {} + }, + defaultStyle: { + fontSize: 10 + } + } + + if (info.messageTop) { + doc.content.unshift({ + text: info.messageTop, + style: "message", + margin: [0, 0, 0, 12] + }) + } + + if (info.messageBottom) { + doc.content.push({ + text: info.messageBottom, + style: "message", + margin: [0, 0, 0, 12] + }) + } + + if (info.title) { + doc.content.unshift({ + text: info.title, + style: "title", + margin: [0, 0, 0, 12] + }) + } + + if (config.customize) { + config.customize(doc, config, dt) + } + + var pdf = _pdfMake().createPdf(doc) + + if (config.download === "open" && !_isDuffSafari()) { + pdf.open() + } else { + pdf.download(info.filename) + } + + cb() + }, + + async: 100, + + title: "*", + + filename: "*", + + extension: ".pdf", + + exportOptions: {}, + + orientation: "portrait", + + // This isn't perfect, but it is close + pageSize: navigator.language === "en-US" || navigator.language === "en-CA" ? "LETTER" : "A4", + + header: true, + + footer: true, + + messageTop: "*", + + messageBottom: "*", + + customize: null, + + download: "download" + } + + return DataTable +}) + +/*! ColReorder 2.0.4 + * © SpryMedia Ltd - datatables.net/license + */ +;(function (factory) { + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery", "datatables.net"], function ($) { + return factory($, window, document) + }) + } else if (typeof exports === "object") { + // CommonJS + var jq = require("jquery") + var cjsRequires = function (root, $) { + if (!$.fn.dataTable) { + require("datatables.net")(root, $) + } + } + + if (typeof window === "undefined") { + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window + } + + if (!$) { + $ = jq(root) + } + + cjsRequires(root, $) + return factory($, root, root.document) + } + } else { + cjsRequires(window, jq) + module.exports = factory(jq, window, window.document) + } + } else { + // Browser + factory(jQuery, window, document) + } +})(function ($, window, document) { + "use strict" + var DataTable = $.fn.dataTable + + /** + * Mutate an array, moving a set of elements into a new index position + * + * @param arr Array to modify + * @param from Start move index + * @param count Number of elements to move + * @param to Index where the start element will move to + */ + function arrayMove(arr, from, count, to) { + var movers = arr.splice(from, count) + // Add delete and start to the array, so we can use it for the `apply` + movers.unshift(0) // splice delete param + movers.unshift(to < from ? to : to - count + 1) // splice start param + arr.splice.apply(arr, movers) + } + /** + * Run finishing activities after one or more columns have been reordered. + * + * @param dt DataTable being operated on - must be a single table instance + */ + function finalise(dt) { + // Cache invalidation. Always read from the data object rather + // than reading back from the DOM since it could have been + // changed by a renderer + dt.rows().invalidate("data") + // Redraw the header / footer. Its a little bit of a hack this, as DT + // doesn't expose the header draw as an API method. It calls state + // saving, so we don't need to here. + dt.column(0).visible(dt.column(0).visible()) + dt.columns.adjust() + // Fire an event so other plug-ins can update + var order = dt.colReorder.order() + dt.trigger("columns-reordered", [ + { + order: order, + mapping: invertKeyValues(order) + } + ]) + } + /** + * Get the original indexes in their current order + * + * @param dt DataTable being operated on - must be a single table instance + * @returns Original indexes in current order + */ + function getOrder(dt) { + return dt.settings()[0].aoColumns.map(function (col) { + return col._crOriginalIdx + }) + } + /** + * Manipulate a header / footer array in DataTables settings to reorder + * the columns. + */ + function headerUpdate(structure, map, from, to) { + var done = [] + for (var i = 0; i < structure.length; i++) { + var headerRow = structure[i] + arrayMove(headerRow, from[0], from.length, to) + for (var j = 0; j < headerRow.length; j++) { + var cell = headerRow[j].cell + // Only work on a DOM element once, otherwise we risk remapping a + // remapped value (etc). + if (done.includes(cell)) { + continue + } + var indexes = cell.getAttribute("data-dt-column").split(",") + var mapped = indexes + .map(function (idx) { + return map[idx] + }) + .join(",") + // Update data attributes for the new column position + cell.setAttribute("data-dt-column", mapped) + done.push(cell) + } + } + } + /** + * Setup for ColReorder API operations + * + * @param dt DataTable(s) being operated on - might have multiple tables! + */ + function init(api) { + // Assign the original column index to a parameter that we can lookup. + // On the first pass (i.e. when the parameter hasn't yet been set), the + // index order will be the original order, so this is quite a simple + // assignment. + api.columns().iterator("column", function (s, idx) { + var columns = s.aoColumns + if (columns[idx]._crOriginalIdx === undefined) { + columns[idx]._crOriginalIdx = idx + } + }) + } + /** + * Switch the key value pairing of an index array to be value key (i.e. the old value is now the + * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ]. + * + * @param array arr Array to switch around + */ + function invertKeyValues(arr) { + var result = [] + for (var i = 0; i < arr.length; i++) { + result[arr[i]] = i + } + return result + } + /** + * Move one or more columns from one index to another. + * + * This method has a lot of knowledge about how DataTables works internally. + * If DataTables changes how it handles cells, columns, etc, then this + * method would need to be updated accordingly. + * + * @param dt DataTable being operated on - must be a single table instance + * @param from Column indexes to move + * @param to Destination index (starting if multiple) + */ + function move(dt, from, to) { + var i, j + var settings = dt.settings()[0] + var columns = settings.aoColumns + var newOrder = columns.map(function (col, idx) { + return idx + }) + // The to column in already inside the from column(s) (might be the same) + // no change required + if (from.includes(to)) { + return + } + // A reverse index array so we can look up new indexes from old + arrayMove(newOrder, from[0], from.length, to) + var reverseIndexes = invertKeyValues(newOrder) + // Main column + arrayMove(columns, from[0], from.length, to) + // Per row manipulations + for (i = 0; i < settings.aoData.length; i++) { + var data = settings.aoData[i] + // Allow for sparse array + if (!data) { + continue + } + var cells = data.anCells + // Not yet rendered + if (!cells) { + continue + } + // Array of cells + arrayMove(cells, from[0], from.length, to) + for (j = 0; j < cells.length; j++) { + // Reinsert into the document in the new order + if (data.nTr && cells[j] && columns[j].bVisible) { + data.nTr.appendChild(cells[j]) + } + // Update lookup index + if (cells[j] && cells[j]._DT_CellIndex) { + cells[j]._DT_CellIndex.column = j + } + } + } + // Per column manipulation + for (i = 0; i < columns.length; i++) { + var column = columns[i] + // Data column sorting + for (j = 0; j < column.aDataSort.length; j++) { + column.aDataSort[j] = reverseIndexes[column.aDataSort[j]] + } + // Update the column indexes + column.idx = reverseIndexes[column.idx] + // Reorder the colgroup > col elements for the new order + if (column.bVisible) { + settings.colgroup.append(column.colEl) + } + } + // Header and footer + headerUpdate(settings.aoHeader, reverseIndexes, from, to) + headerUpdate(settings.aoFooter, reverseIndexes, from, to) + // Search - columns + arrayMove(settings.aoPreSearchCols, from[0], from.length, to) + // Ordering indexes update - note that the sort listener on the + // header works out the index to apply on each draw, so it doesn't + // need to be updated here. + orderingIndexes(reverseIndexes, settings.aaSorting) + if (Array.isArray(settings.aaSortingFixed)) { + orderingIndexes(reverseIndexes, settings.aaSortingFixed) + } else if (settings.aaSortingFixed.pre) { + orderingIndexes(reverseIndexes, settings.aaSortingFixed.pre) + } else if (settings.aaSortingFixed.post) { + orderingIndexes(reverseIndexes, settings.aaSortingFixed.pre) + } + settings.aLastSort.forEach(function (el) { + el.src = reverseIndexes[el.src] + }) + // Fire an event so other plug-ins can update + dt.trigger("column-reorder", [ + dt.settings()[0], + { + from: from, + to: to, + mapping: reverseIndexes + } + ]) + } + /** + * Update the indexing for ordering arrays + * + * @param map Reverse index map + * @param order Array to update + */ + function orderingIndexes(map, order) { + // Can happen if the order was deleted from a saved state + if (!order) { + return + } + for (var i = 0; i < order.length; i++) { + var el = order[i] + if (typeof el === "number") { + // Just a number + order[i] = map[el] + } else if ($.isPlainObject(el) && el.idx !== undefined) { + // New index in an object style + el.idx = map[el.idx] + } else if (Array.isArray(el) && typeof el[0] === "number") { + // The good old fixes length array + el[0] = map[el[0]] + } + // No need to update if in object + .name style + } + } + /** + * Take an index array for the current positioned, reordered to what you want + * them to be. + * + * @param dt DataTable being operated on - must be a single table instance + * @param order Indexes from current order, positioned as you want them to be + */ + function setOrder(dt, order, original) { + var changed = false + var i + if (order.length !== dt.columns().count()) { + dt.error("ColReorder - column count mismatch") + return + } + // The order given is based on the original indexes, rather than the + // existing ones, so we need to translate from the original to current + // before then doing the order + if (original) { + order = transpose(dt, order, "toCurrent") + } + // The API is array index as the desired position, but our algorithm below is + // for array index as the current position. So we need to invert for it to work. + var setOrder = invertKeyValues(order) + // Move columns, one by one with validation disabled! + for (i = 0; i < setOrder.length; i++) { + var currentIndex = setOrder.indexOf(i) + if (i !== currentIndex) { + // Reorder our switching error + arrayMove(setOrder, currentIndex, 1, i) + // Do the reorder + move(dt, [currentIndex], i) + changed = true + } + } + // Reorder complete + if (changed) { + finalise(dt) + } + } + /** + * Convert the DataTables header structure array into a 2D array where each + * element has a reference to its TH/TD cell (regardless of spanning). + * + * @param structure Header / footer structure object + * @returns 2D array of header cells + */ + function structureFill(structure) { + var filledIn = [] + for (var row = 0; row < structure.length; row++) { + filledIn.push([]) + for (var col = 0; col < structure[row].length; col++) { + var cell = structure[row][col] + if (cell) { + for (var rowInner = 0; rowInner < cell.rowspan; rowInner++) { + if (!filledIn[row + rowInner]) { + filledIn[row + rowInner] = [] + } + for (var colInner = 0; colInner < cell.colspan; colInner++) { + filledIn[row + rowInner][col + colInner] = cell.cell + } + } + } + } + } + return filledIn + } + /** + * Convert the index type + * + * @param dt DataTable to work on + * @param idx Index to transform + * @param dir Transform direction + * @returns Converted number(s) + */ + function transpose(dt, idx, dir) { + var order = dt.colReorder.order() + var columns = dt.settings()[0].aoColumns + if (dir === "toCurrent" || dir === "fromOriginal") { + // Given an original index, want the current + return !Array.isArray(idx) + ? order.indexOf(idx) + : idx.map(function (index) { + return order.indexOf(index) + }) + } + // Given a current index, want the original + return !Array.isArray(idx) + ? columns[idx]._crOriginalIdx + : idx.map(function (index) { + return columns[index]._crOriginalIdx + }) + } + /** + * Validate that a requested move is okay. This includes bound checking + * and that it won't split colspan'ed cells. + * + * @param table API instance + * @param from Column indexes to move + * @param to Destination index (starting if multiple) + * @returns Validation result + */ + function validateMove(table, from, to) { + var columns = table.columns().count() + // Sanity and bound checking + if (from[0] < to && to < from[from.length]) { + return false + } + if (from[0] < 0 && from[from.length - 1] > columns) { + return false + } + if (to < 0 && to > columns) { + return false + } + // No change - it's valid + if (from.includes(to)) { + return true + } + if (!validateStructureMove(table.table().header.structure(), from, to)) { + return false + } + if (!validateStructureMove(table.table().footer.structure(), from, to)) { + return false + } + return true + } + /** + * For a given structure check that the move is valid. + * @param structure + * @param from + * @param to + * @returns + */ + function validateStructureMove(structure, from, to) { + var header = structureFill(structure) + var i + // Shuffle the header cells around + for (i = 0; i < header.length; i++) { + arrayMove(header[i], from[0], from.length, to) + } + // Sanity check that the headers are next to each other + for (i = 0; i < header.length; i++) { + var seen = [] + for (var j = 0; j < header[i].length; j++) { + var cell = header[i][j] + if (!seen.includes(cell)) { + // Hasn't been seen before + seen.push(cell) + } else if (seen[seen.length - 1] !== cell) { + // Has been seen before and is not the previous cell - validation failed + return false + } + } + } + return true + } + + /** + * This is one possible UI for column reordering in DataTables. In this case + * columns are reordered by clicking and dragging a column header. It calculates + * where columns can be dropped based on the column header used to start the drag + * and then `colReorder.move()` method to alter the DataTable. + */ + var ColReorder = /** @class */ (function () { + function ColReorder(dt, opts) { + this.dom = { + drag: null + } + this.c = { + columns: null, + enable: null, + order: null + } + this.s = { + dropZones: [], + mouse: { + absLeft: -1, + offset: { + x: -1, + y: -1 + }, + start: { + x: -1, + y: -1 + }, + target: null, + targets: [] + }, + scrollInterval: null + } + var that = this + var ctx = dt.settings()[0] + // Check if ColReorder already has been initialised on this DataTable - only + // one can exist. + if (ctx._colReorder) { + return + } + dt.settings()[0]._colReorder = this + this.dt = dt + $.extend(this.c, ColReorder.defaults, opts) + init(dt) + dt.on("stateSaveParams", function (e, s, d) { + d.colReorder = getOrder(dt) + }) + dt.on("destroy", function () { + dt.off(".colReorder") + dt.colReorder.reset() + }) + // Initial ordering / state restoring + var loaded = dt.state.loaded() + var order = this.c.order + if (loaded && loaded.colReorder) { + order = loaded.colReorder + } + if (order) { + dt.ready(function () { + setOrder(dt, order, true) + }) + } + dt.table() + .header.structure() + .forEach(function (row) { + for (var i = 0; i < row.length; i++) { + if (row[i] && row[i].cell) { + that._addListener(row[i].cell) + } + } + }) + } + ColReorder.prototype.disable = function () { + this.c.enable = false + return this + } + ColReorder.prototype.enable = function (flag) { + if (flag === void 0) { + flag = true + } + if (flag === false) { + return this.disable() + } + this.c.enable = true + return this + } + /** + * Attach the mouse down listener to an element to start a column reorder action + * + * @param el + */ + ColReorder.prototype._addListener = function (el) { + var that = this + $(el) + .on("selectstart.colReorder", function () { + return false + }) + .on("mousedown.colReorder touchstart.colReorder", function (e) { + // Ignore middle and right click + if (e.type === "mousedown" && e.which !== 1) { + return + } + // Ignore if disabled + if (!that.c.enable) { + return + } + that._mouseDown(e, this) + }) + } + /** + * Create the element that is dragged around the page + */ + ColReorder.prototype._createDragNode = function () { + var origCell = this.s.mouse.target + var origTr = origCell.parent() + var origThead = origTr.parent() + var origTable = origThead.parent() + var cloneCell = origCell.clone() + // This is a slightly odd combination of jQuery and DOM, but it is the + // fastest and least resource intensive way I could think of cloning + // the table with just a single header cell in it. + this.dom.drag = $(origTable[0].cloneNode(false)) + .addClass("dtcr-cloned") + .append( + $(origThead[0].cloneNode(false)).append($(origTr[0].cloneNode(false)).append(cloneCell[0])) // Not sure why it doesn't want to append a jQuery node + ) + .css({ + position: "absolute", + top: 0, + left: 0, + width: $(origCell).outerWidth(), + height: $(origCell).outerHeight() + }) + .appendTo("body") + } + /** + * Get cursor position regardless of mouse or touch input + * + * @param e Event + * @param prop Property name to get + * @returns Value - assuming a number here + */ + ColReorder.prototype._cursorPosition = function (e, prop) { + return e.type.indexOf("touch") !== -1 ? e.originalEvent.touches[0][prop] : e[prop] + } + /** + * Cache values at start + * + * @param e Triggering event + * @param cell Cell that the action started on + * @returns + */ + ColReorder.prototype._mouseDown = function (e, cell) { + var _this = this + var target = $(e.target).closest("th, td") + var offset = target.offset() + var moveableColumns = this.dt.columns(this.c.columns).indexes().toArray() + var moveColumnIndexes = $(cell) + .attr("data-dt-column") + .split(",") + .map(function (val) { + return parseInt(val, 10) + }) + // Don't do anything for columns which are not selected as moveable + for (var j = 0; j < moveColumnIndexes.length; j++) { + if (!moveableColumns.includes(moveColumnIndexes[j])) { + return false + } + } + this.s.mouse.start.x = this._cursorPosition(e, "pageX") + this.s.mouse.start.y = this._cursorPosition(e, "pageY") + this.s.mouse.offset.x = this._cursorPosition(e, "pageX") - offset.left + this.s.mouse.offset.y = this._cursorPosition(e, "pageY") - offset.top + this.s.mouse.target = target + this.s.mouse.targets = moveColumnIndexes + // Classes to highlight the columns being moved + for (var i = 0; i < moveColumnIndexes.length; i++) { + var cells = this.dt.cells(null, moveColumnIndexes[i], { page: "current" }).nodes().to$() + var klass = "dtcr-moving" + if (i === 0) { + klass += " dtcr-moving-first" + } + if (i === moveColumnIndexes.length - 1) { + klass += " dtcr-moving-last" + } + cells.addClass(klass) + } + this._regions(moveColumnIndexes) + this._scrollRegions() + /* Add event handlers to the document */ + $(document) + .on("mousemove.colReorder touchmove.colReorder", function (e) { + _this._mouseMove(e) + }) + .on("mouseup.colReorder touchend.colReorder", function (e) { + _this._mouseUp(e) + }) + } + ColReorder.prototype._mouseMove = function (e) { + if (this.dom.drag === null) { + // Only create the drag element if the mouse has moved a specific distance from the start + // point - this allows the user to make small mouse movements when sorting and not have a + // possibly confusing drag element showing up + if (Math.pow(Math.pow(this._cursorPosition(e, "pageX") - this.s.mouse.start.x, 2) + Math.pow(this._cursorPosition(e, "pageY") - this.s.mouse.start.y, 2), 0.5) < 5) { + return + } + $(document.body).addClass("dtcr-dragging") + this._createDragNode() + } + // Position the element - we respect where in the element the click occurred + this.dom.drag.css({ + left: this._cursorPosition(e, "pageX") - this.s.mouse.offset.x, + top: this._cursorPosition(e, "pageY") - this.s.mouse.offset.y + }) + // Find cursor's left position relative to the table + var tableOffset = $(this.dt.table().node()).offset().left + var cursorMouseLeft = this._cursorPosition(e, "pageX") - tableOffset + var dropZone = this.s.dropZones.find(function (zone) { + if (zone.left <= cursorMouseLeft && cursorMouseLeft <= zone.left + zone.width) { + return true + } + return false + }) + this.s.mouse.absLeft = this._cursorPosition(e, "pageX") + if (!dropZone) { + return + } + if (!dropZone.self) { + this._move(dropZone, cursorMouseLeft) + } + } + ColReorder.prototype._mouseUp = function (e) { + $(document).off(".colReorder") + $(document.body).removeClass("dtcr-dragging") + if (this.dom.drag) { + this.dom.drag.remove() + this.dom.drag = null + } + if (this.s.scrollInterval) { + clearInterval(this.s.scrollInterval) + } + this.dt.cells(".dtcr-moving").nodes().to$().removeClass("dtcr-moving dtcr-moving-first dtcr-moving-last") + } + /** + * Shift columns around + * + * @param dropZone Where to move to + * @param cursorMouseLeft Cursor position, relative to the left of the table + */ + ColReorder.prototype._move = function (dropZone, cursorMouseLeft) { + var that = this + this.dt.colReorder.move(this.s.mouse.targets, dropZone.colIdx) + // Update the targets + this.s.mouse.targets = $(this.s.mouse.target) + .attr("data-dt-column") + .split(",") + .map(function (val) { + return parseInt(val, 10) + }) + this._regions(this.s.mouse.targets) + var visibleTargets = this.s.mouse.targets.filter(function (val) { + return that.dt.column(val).visible() + }) + // If the column being moved is smaller than the column it is replacing, + // the drop zones might need a correction to allow for this since, otherwise + // we might immediately be changing the column order as soon as it was placed. + // Find the drop zone for the first in the list of targets - is its + // left greater than the mouse position. If so, it needs correcting + var dz = this.s.dropZones.find(function (zone) { + return zone.colIdx === visibleTargets[0] + }) + var dzIdx = this.s.dropZones.indexOf(dz) + if (dz.left > cursorMouseLeft) { + var previousDiff = dz.left - cursorMouseLeft + var previousDz = this.s.dropZones[dzIdx - 1] + dz.left -= previousDiff + dz.width += previousDiff + if (previousDz) { + previousDz.width -= previousDiff + } + } + // And for the last in the list + dz = this.s.dropZones.find(function (zone) { + return zone.colIdx === visibleTargets[visibleTargets.length - 1] + }) + if (dz.left + dz.width < cursorMouseLeft) { + var nextDiff = cursorMouseLeft - (dz.left + dz.width) + var nextDz = this.s.dropZones[dzIdx + 1] + dz.width += nextDiff + if (nextDz) { + nextDz.left += nextDiff + nextDz.width -= nextDiff + } + } + } + /** + * Determine the boundaries for where drops can happen and where they would + * insert into. + */ + ColReorder.prototype._regions = function (moveColumns) { + var that = this + var dropZones = [] + var totalWidth = 0 + var negativeCorrect = 0 + var allowedColumns = this.dt.columns(this.c.columns).indexes().toArray() + var widths = this.dt.columns().widths() + // Each column is a drop zone + this.dt.columns().every(function (colIdx, tabIdx, i) { + if (!this.visible()) { + return + } + var columnWidth = widths[colIdx] + // Check that we are allowed to move into this column - if not, need + // to offset the widths + if (!allowedColumns.includes(colIdx)) { + totalWidth += columnWidth + return + } + var valid = validateMove(that.dt, moveColumns, colIdx) + if (valid) { + // New drop zone. Note that it might have it's offset moved + // by the final condition in this logic set + dropZones.push({ + colIdx: colIdx, + left: totalWidth - negativeCorrect, + self: moveColumns[0] <= colIdx && colIdx <= moveColumns[moveColumns.length - 1], + width: columnWidth + negativeCorrect + }) + } else if (colIdx < moveColumns[0]) { + // Not valid and before the column(s) to be moved - the drop + // zone for the previous valid drop point is extended + if (dropZones.length) { + dropZones[dropZones.length - 1].width += columnWidth + } + } else if (colIdx > moveColumns[moveColumns.length - 1]) { + // Not valid and after the column(s) to be moved - the next + // drop zone to be created will be extended + negativeCorrect += columnWidth + } + totalWidth += columnWidth + }) + this.s.dropZones = dropZones + // this._drawDropZones(); + } + /** + * Check if the table is scrolling or not. It is it the `table` isn't the same for + * the header and body parents. + * + * @returns + */ + ColReorder.prototype._isScrolling = function () { + return this.dt.table().body().parentNode !== this.dt.table().header().parentNode + } + /** + * Set an interval clock that will check to see if the scrolling of the table body should be moved + * as the mouse moves on the scroll (allowing a drag and drop to columns which aren't yet visible) + */ + ColReorder.prototype._scrollRegions = function () { + if (!this._isScrolling()) { + // Not scrolling - nothing to do + return + } + var that = this + var tableLeft = $(this.dt.table().container()).position().left + var tableWidth = $(this.dt.table().container()).outerWidth() + var mouseBuffer = 75 + var scrollContainer = this.dt.table().body().parentElement.parentElement + this.s.scrollInterval = setInterval(function () { + var mouseLeft = that.s.mouse.absLeft + if (mouseLeft < tableLeft + mouseBuffer && scrollContainer.scrollLeft) { + scrollContainer.scrollLeft -= 5 + } else if (mouseLeft > tableLeft + tableWidth - mouseBuffer && scrollContainer.scrollLeft < scrollContainer.scrollWidth) { + scrollContainer.scrollLeft += 5 + } + }, 25) + } + // This is handy for debugging where the drop zones actually are! + // private _drawDropZones () { + // let dropZones = this.s.dropZones; + // $('div.allan').remove(); + // for (let i=0 ; i<dropZones.length ; i++) { + // let zone = dropZones[i]; + // $(this.dt.table().container()).append( + // $('<div>') + // .addClass('allan') + // .css({ + // position: 'absolute', + // top: 0, + // width: zone.width - 4, + // height: 20, + // left: zone.left + 2, + // border: '1px solid red', + // }) + // ); + // } + // } + ColReorder.defaults = { + columns: "", + enable: true, + order: null + } + ColReorder.version = "2.0.4" + return ColReorder + })() + + /*! ColReorder 2.0.4 + * © SpryMedia Ltd - datatables.net/license + */ + /** + * @summary ColReorder + * @description Provide the ability to reorder columns in a DataTable + * @version 2.0.4 + * @author SpryMedia Ltd + * @contact datatables.net + * @copyright SpryMedia Ltd. + * + * This source file is free software, available under the following license: + * MIT license - http://datatables.net/license/mit + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: http://www.datatables.net + */ + // declare var DataTable: any; + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * UI interaction class + */ + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables API integration + */ + /** Enable mouse column reordering */ + DataTable.Api.register("colReorder.enable()", function (flag) { + return this.iterator("table", function (ctx) { + if (ctx._colReorder) { + ctx._colReorder.enable(flag) + } + }) + }) + /** Disable mouse column reordering */ + DataTable.Api.register("colReorder.disable()", function () { + return this.iterator("table", function (ctx) { + if (ctx._colReorder) { + ctx._colReorder.disable() + } + }) + }) + /** + * Change the ordering of the columns in the DataTable. + */ + DataTable.Api.register("colReorder.move()", function (from, to) { + init(this) + if (!Array.isArray(from)) { + from = [from] + } + if (!validateMove(this, from, to)) { + this.error("ColReorder - invalid move") + return this + } + return this.tables().every(function () { + move(this, from, to) + finalise(this) + }) + }) + DataTable.Api.register("colReorder.order()", function (set, original) { + init(this) + if (!set) { + return this.context.length ? getOrder(this) : null + } + return this.tables().every(function () { + setOrder(this, set, original) + }) + }) + DataTable.Api.register("colReorder.reset()", function () { + init(this) + return this.tables().every(function () { + var order = this.columns() + .every(function (i) { + return i + }) + .flatten() + .toArray() + setOrder(this, order, true) + }) + }) + DataTable.Api.register("colReorder.transpose()", function (idx, dir) { + init(this) + if (!dir) { + dir = "toCurrent" + } + return transpose(this, idx, dir) + }) + DataTable.ColReorder = ColReorder + // Called when DataTables is going to load a state. That might be + // before the table is ready (state saving) or after (state restoring). + // Also note that it happens _before_ preInit (below). + $(document).on("stateLoadInit.dt", function (e, settings, state) { + if (e.namespace !== "dt") { + return + } + var dt = new DataTable.Api(settings) + if (state.colReorder) { + if (dt.ready()) { + // Table is fully loaded - do the column reordering here + // so that the stored indexes are in the correct place + // e.g. column visibility + setOrder(dt, state.colReorder, true) + } else { + // If the table is not ready, column reordering is done + // after it becomes fully ready. That means that saved + // column indexes need to be updated for where those columns + // currently are. Any properties which refer to column indexes + // would need to be updated here. + // State's ordering indexes + orderingIndexes(state.colReorder, state.order) + // State's columns array - sort by restore index + if (state.columns) { + for (var i = 0; i < state.columns.length; i++) { + state.columns[i]._cr_sort = state.colReorder[i] + } + state.columns.sort(function (a, b) { + return a._cr_sort - b._cr_sort + }) + } + } + } + }) + $(document).on("preInit.dt", function (e, settings) { + if (e.namespace !== "dt") { + return + } + var init = settings.oInit.colReorder + var defaults = DataTable.defaults.colReorder + if (init || defaults) { + var opts = $.extend({}, defaults, init) + if (init !== false) { + var dt = new DataTable.Api(settings) + new ColReorder(dt, opts) + } + } + }) + + return DataTable +}) + +/*! FixedColumns 5.0.2 + * © SpryMedia Ltd - datatables.net/license + */ +;(function (factory) { + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery", "datatables.net"], function ($) { + return factory($, window, document) + }) + } else if (typeof exports === "object") { + // CommonJS + var jq = require("jquery") + var cjsRequires = function (root, $) { + if (!$.fn.dataTable) { + require("datatables.net")(root, $) + } + } + + if (typeof window === "undefined") { + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window + } + + if (!$) { + $ = jq(root) + } + + cjsRequires(root, $) + return factory($, root, root.document) + } + } else { + cjsRequires(window, jq) + module.exports = factory(jq, window, window.document) + } + } else { + // Browser + factory(jQuery, window, document) + } +})(function ($, window, document) { + "use strict" + var DataTable = $.fn.dataTable + + ;(function () { + "use strict" + + var $$1 + var DataTable$1 + function setJQuery(jq) { + $$1 = jq + DataTable$1 = $$1.fn.dataTable + } + var FixedColumns = /** @class */ (function () { + function FixedColumns(settings, opts) { + var _this = this + // Check that the required version of DataTables is included + if (!DataTable$1 || !DataTable$1.versionCheck || !DataTable$1.versionCheck("2")) { + throw new Error("FixedColumns requires DataTables 2 or newer") + } + var table = new DataTable$1.Api(settings) + this.classes = $$1.extend(true, {}, FixedColumns.classes) + // Get options from user + this.c = $$1.extend(true, {}, FixedColumns.defaults, opts) + this.s = { + dt: table, + rtl: $$1(table.table().node()).css("direction") === "rtl" + } + // Backwards compatibility for deprecated options + if (opts && opts.leftColumns !== undefined) { + opts.left = opts.leftColumns + } + if (opts && opts.left !== undefined) { + this.c[this.s.rtl ? "end" : "start"] = opts.left + } + if (opts && opts.rightColumns !== undefined) { + opts.right = opts.rightColumns + } + if (opts && opts.right !== undefined) { + this.c[this.s.rtl ? "start" : "end"] = opts.right + } + this.dom = { + bottomBlocker: $$1("<div>").addClass(this.classes.bottomBlocker), + topBlocker: $$1("<div>").addClass(this.classes.topBlocker), + scroller: $$1("div.dt-scroll-body", this.s.dt.table().container()) + } + if (this.s.dt.settings()[0]._bInitComplete) { + // Fixed Columns Initialisation + this._addStyles() + this._setKeyTableListener() + } else { + table.one("init.dt.dtfc", function () { + // Fixed Columns Initialisation + _this._addStyles() + _this._setKeyTableListener() + }) + } + // Lots or reasons to redraw the column styles + table.on("column-sizing.dt.dtfc column-reorder.dt.dtfc draw.dt.dtfc", function () { + return _this._addStyles() + }) + // Column visibility can trigger a number of times quickly, so we debounce it + var debounced = DataTable$1.util.debounce(function () { + _this._addStyles() + }, 50) + table.on("column-visibility.dt.dtfc", function () { + debounced() + }) + // Add classes to indicate scrolling state for styling + this.dom.scroller.on("scroll.dtfc", function () { + return _this._scroll() + }) + this._scroll() + // Make class available through dt object + table.settings()[0]._fixedColumns = this + table.on("destroy", function () { + return _this._destroy() + }) + return this + } + FixedColumns.prototype.end = function (newVal) { + // If the value is to change + if (newVal !== undefined) { + if (newVal >= 0 && newVal <= this.s.dt.columns().count()) { + // Set the new values and redraw the columns + this.c.end = newVal + this._addStyles() + } + return this + } + return this.c.end + } + /** + * Left fix - accounting for RTL + * + * @param count Columns to fix, or undefined for getter + */ + FixedColumns.prototype.left = function (count) { + return this.s.rtl ? this.end(count) : this.start(count) + } + /** + * Right fix - accounting for RTL + * + * @param count Columns to fix, or undefined for getter + */ + FixedColumns.prototype.right = function (count) { + return this.s.rtl ? this.start(count) : this.end(count) + } + FixedColumns.prototype.start = function (newVal) { + // If the value is to change + if (newVal !== undefined) { + if (newVal >= 0 && newVal <= this.s.dt.columns().count()) { + // Set the new values and redraw the columns + this.c.start = newVal + this._addStyles() + } + return this + } + return this.c.start + } + /** + * Iterates over the columns, fixing the appropriate ones to the left and right + */ + FixedColumns.prototype._addStyles = function () { + var dt = this.s.dt + var that = this + var colCount = this.s.dt.columns(":visible").count() + var headerStruct = dt.table().header.structure(":visible") + var footerStruct = dt.table().footer.structure(":visible") + var widths = dt.columns(":visible").widths().toArray() + var wrapper = $$1(dt.table().node()).closest("div.dt-scroll") + var scroller = $$1(dt.table().node()).closest("div.dt-scroll-body")[0] + var rtl = this.s.rtl + var start = this.c.start + var end = this.c.end + var left = rtl ? end : start + var right = rtl ? start : end + var barWidth = dt.settings()[0].oBrowser.barWidth // dt internal + // Do nothing if no scrolling in the DataTable + if (wrapper.length === 0) { + return this + } + // Bar not needed - no vertical scrolling + if (scroller.offsetWidth === scroller.clientWidth) { + barWidth = 0 + } + // Loop over the visible columns, setting their state + dt.columns().every(function (colIdx) { + var visIdx = dt.column.index("toVisible", colIdx) + var offset + // Skip the hidden columns + if (visIdx === null) { + return + } + if (visIdx < start) { + // Fix to the start + offset = that._sum(widths, visIdx) + that._fixColumn(visIdx, offset, "start", headerStruct, footerStruct, barWidth) + } else if (visIdx >= colCount - end) { + // Fix to the end + offset = that._sum(widths, colCount - visIdx - 1, true) + that._fixColumn(visIdx, offset, "end", headerStruct, footerStruct, barWidth) + } else { + // Release + that._fixColumn(visIdx, 0, "none", headerStruct, footerStruct, barWidth) + } + }) + // Apply classes to table to indicate what state we are in + $$1(dt.table().node()) + .toggleClass(that.classes.tableFixedStart, start > 0) + .toggleClass(that.classes.tableFixedEnd, end > 0) + .toggleClass(that.classes.tableFixedLeft, left > 0) + .toggleClass(that.classes.tableFixedRight, right > 0) + // Blocker elements for when scroll bars are always visible + var headerEl = dt.table().header() + var footerEl = dt.table().footer() + var headerHeight = $$1(headerEl).outerHeight() + var footerHeight = $$1(footerEl).outerHeight() + this.dom.topBlocker + .appendTo(wrapper) + .css("top", 0) + .css(this.s.rtl ? "left" : "right", 0) + .css("height", headerHeight) + .css("width", barWidth + 1) + .css("display", barWidth ? "block" : "none") + if (footerEl) { + this.dom.bottomBlocker + .appendTo(wrapper) + .css("bottom", 0) + .css(this.s.rtl ? "left" : "right", 0) + .css("height", footerHeight) + .css("width", barWidth + 1) + .css("display", barWidth ? "block" : "none") + } + } + /** + * Clean up + */ + FixedColumns.prototype._destroy = function () { + this.s.dt.off(".dtfc") + this.dom.scroller.off(".dtfc") + $$1(this.s.dt.table().node()).removeClass(this.classes.tableScrollingEnd + " " + this.classes.tableScrollingLeft + " " + this.classes.tableScrollingStart + " " + this.classes.tableScrollingRight) + this.dom.bottomBlocker.remove() + this.dom.topBlocker.remove() + } + /** + * Fix or unfix a column + * + * @param idx Column visible index to operate on + * @param offset Offset from the start (pixels) + * @param side start, end or none to unfix a column + * @param header DT header structure object + * @param footer DT footer structure object + */ + FixedColumns.prototype._fixColumn = function (idx, offset, side, header, footer, barWidth) { + var _this = this + var dt = this.s.dt + var applyStyles = function (jq, part) { + if (side === "none") { + jq.css("position", "") + .css("left", "") + .css("right", "") + .removeClass(_this.classes.fixedEnd + " " + _this.classes.fixedLeft + " " + _this.classes.fixedRight + " " + _this.classes.fixedStart) + } else { + var positionSide = side === "start" ? "left" : "right" + if (_this.s.rtl) { + positionSide = side === "start" ? "right" : "left" + } + var off = offset + if (side === "end" && (part === "header" || part === "footer")) { + off += barWidth + } + jq.css("position", "sticky") + .css(positionSide, off) + .addClass(side === "start" ? _this.classes.fixedStart : _this.classes.fixedEnd) + .addClass(positionSide === "left" ? _this.classes.fixedLeft : _this.classes.fixedRight) + } + } + header.forEach(function (row) { + if (row[idx]) { + applyStyles($$1(row[idx].cell), "header") + } + }) + applyStyles( + dt + .column(idx + ":visible", { page: "current" }) + .nodes() + .to$(), + "body" + ) + if (footer) { + footer.forEach(function (row) { + if (row[idx]) { + applyStyles($$1(row[idx].cell), "footer") + } + }) + } + } + /** + * Update classes on the table to indicate if the table is scrolling or not + */ + FixedColumns.prototype._scroll = function () { + var scroller = this.dom.scroller[0] + // Not a scrolling table + if (!scroller) { + return + } + // Need to update the classes on potentially multiple table tags. There is the + // main one, the scrolling ones and if FixedHeader is active, the holding + // position ones! jQuery will deduplicate for us. + var table = $$1(this.s.dt.table().node()) + .add(this.s.dt.table().header().parentNode) + .add(this.s.dt.table().footer().parentNode) + .add("div.dt-scroll-headInner table", this.s.dt.table().container()) + .add("div.dt-scroll-footInner table", this.s.dt.table().container()) + var scrollLeft = scroller.scrollLeft // 0 when fully scrolled left + var ltr = !this.s.rtl + var scrollStart = scrollLeft !== 0 + var scrollEnd = scroller.scrollWidth > scroller.clientWidth + Math.abs(scrollLeft) + 1 // extra 1 for Chrome + table.toggleClass(this.classes.tableScrollingStart, scrollStart) + table.toggleClass(this.classes.tableScrollingEnd, scrollEnd) + table.toggleClass(this.classes.tableScrollingLeft, (scrollStart && ltr) || (scrollEnd && !ltr)) + table.toggleClass(this.classes.tableScrollingRight, (scrollEnd && ltr) || (scrollStart && !ltr)) + } + FixedColumns.prototype._setKeyTableListener = function () { + var _this = this + this.s.dt.on("key-focus.dt.dtfc", function (e, dt, cell) { + var currScroll + var cellPos = $$1(cell.node()).offset() + var scroller = _this.dom.scroller[0] + var scroll = $$1($$1(_this.s.dt.table().node()).closest("div.dt-scroll-body")) + // If there are fixed columns to the left + if (_this.c.start > 0) { + // Get the rightmost left fixed column header, it's position and it's width + var rightMost = $$1(_this.s.dt.column(_this.c.start - 1).header()) + var rightMostPos = rightMost.offset() + var rightMostWidth = rightMost.outerWidth() + // If the current highlighted cell is left of the rightmost cell on the screen + if ($$1(cell.node()).hasClass(_this.classes.fixedLeft)) { + // Fixed columns have the scrollbar at the start, always + scroll.scrollLeft(0) + } else if (cellPos.left < rightMostPos.left + rightMostWidth) { + // Scroll it into view + currScroll = scroll.scrollLeft() + scroll.scrollLeft(currScroll - (rightMostPos.left + rightMostWidth - cellPos.left)) + } + } + // If there are fixed columns to the right + if (_this.c.end > 0) { + // Get the number of columns and the width of the cell as doing right side calc + var numCols = _this.s.dt.columns().data().toArray().length + var cellWidth = $$1(cell.node()).outerWidth() + // Get the leftmost right fixed column header and it's position + var leftMost = $$1(_this.s.dt.column(numCols - _this.c.end).header()) + var leftMostPos = leftMost.offset() + // If the current highlighted cell is right of the leftmost cell on the screen + if ($$1(cell.node()).hasClass(_this.classes.fixedRight)) { + scroll.scrollLeft(scroller.scrollWidth - scroller.clientWidth) + } else if (cellPos.left + cellWidth > leftMostPos.left) { + // Scroll it into view + currScroll = scroll.scrollLeft() + scroll.scrollLeft(currScroll - (leftMostPos.left - (cellPos.left + cellWidth))) + } + } + }) + } + /** + * Sum a range of values from an array + * + * @param widths + * @param index + * @returns + */ + FixedColumns.prototype._sum = function (widths, index, reverse) { + if (reverse === void 0) { + reverse = false + } + if (reverse) { + widths = widths.slice().reverse() + } + return widths.slice(0, index).reduce(function (accum, val) { + return accum + val + }, 0) + } + FixedColumns.version = "5.0.2" + FixedColumns.classes = { + bottomBlocker: "dtfc-bottom-blocker", + fixedEnd: "dtfc-fixed-end", + fixedLeft: "dtfc-fixed-left", + fixedRight: "dtfc-fixed-right", + fixedStart: "dtfc-fixed-start", + tableFixedEnd: "dtfc-has-end", + tableFixedLeft: "dtfc-has-left", + tableFixedRight: "dtfc-has-right", + tableFixedStart: "dtfc-has-start", + tableScrollingEnd: "dtfc-scrolling-end", + tableScrollingLeft: "dtfc-scrolling-left", + tableScrollingRight: "dtfc-scrolling-right", + tableScrollingStart: "dtfc-scrolling-start", + topBlocker: "dtfc-top-blocker" + } + FixedColumns.defaults = { + i18n: { + button: "FixedColumns" + }, + start: 1, + end: 0 + } + return FixedColumns + })() + + /*! FixedColumns 5.0.2 + * © SpryMedia Ltd - datatables.net/license + */ + setJQuery($) + $.fn.dataTable.FixedColumns = FixedColumns + $.fn.DataTable.FixedColumns = FixedColumns + var apiRegister = DataTable.Api.register + apiRegister("fixedColumns()", function () { + return this + }) + apiRegister("fixedColumns().start()", function (newVal) { + var ctx = this.context[0] + if (newVal !== undefined) { + ctx._fixedColumns.start(newVal) + return this + } else { + return ctx._fixedColumns.start() + } + }) + apiRegister("fixedColumns().end()", function (newVal) { + var ctx = this.context[0] + if (newVal !== undefined) { + ctx._fixedColumns.end(newVal) + return this + } else { + return ctx._fixedColumns.end() + } + }) + apiRegister("fixedColumns().left()", function (newVal) { + var ctx = this.context[0] + if (newVal !== undefined) { + ctx._fixedColumns.left(newVal) + return this + } else { + return ctx._fixedColumns.left() + } + }) + apiRegister("fixedColumns().right()", function (newVal) { + var ctx = this.context[0] + if (newVal !== undefined) { + ctx._fixedColumns.right(newVal) + return this + } else { + return ctx._fixedColumns.right() + } + }) + DataTable.ext.buttons.fixedColumns = { + action: function (e, dt, node, config) { + if ($(node).attr("active")) { + $(node).removeAttr("active").removeClass("active") + dt.fixedColumns().start(0) + dt.fixedColumns().end(0) + } else { + $(node).attr("active", "true").addClass("active") + dt.fixedColumns().start(config.config.start) + dt.fixedColumns().end(config.config.end) + } + }, + config: { + start: 1, + end: 0 + }, + init: function (dt, node, config) { + if (dt.settings()[0]._fixedColumns === undefined) { + _init(dt.settings(), config) + } + $(node).attr("active", "true").addClass("active") + dt.button(node).text(config.text || dt.i18n("buttons.fixedColumns", dt.settings()[0]._fixedColumns.c.i18n.button)) + }, + text: null + } + function _init(settings, options) { + if (options === void 0) { + options = null + } + var api = new DataTable.Api(settings) + var opts = options ? options : api.init().fixedColumns || DataTable.defaults.fixedColumns + var fixedColumns = new FixedColumns(api, opts) + return fixedColumns + } + // Attach a listener to the document which listens for DataTables initialisation + // events so we can automatically initialise + $(document).on("plugin-init.dt", function (e, settings) { + if (e.namespace !== "dt") { + return + } + if (settings.oInit.fixedColumns || DataTable.defaults.fixedColumns) { + if (!settings._fixedColumns) { + _init(settings, null) + } + } + }) + })() + + return DataTable +}) + +/*! FixedHeader 4.0.1 + * © SpryMedia Ltd - datatables.net/license + */ +;(function (factory) { + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery", "datatables.net"], function ($) { + return factory($, window, document) + }) + } else if (typeof exports === "object") { + // CommonJS + var jq = require("jquery") + var cjsRequires = function (root, $) { + if (!$.fn.dataTable) { + require("datatables.net")(root, $) + } + } + + if (typeof window === "undefined") { + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window + } + + if (!$) { + $ = jq(root) + } + + cjsRequires(root, $) + return factory($, root, root.document) + } + } else { + cjsRequires(window, jq) + module.exports = factory(jq, window, window.document) + } + } else { + // Browser + factory(jQuery, window, document) + } +})(function ($, window, document) { + "use strict" + var DataTable = $.fn.dataTable + + /** + * @summary FixedHeader + * @description Fix a table's header or footer, so it is always visible while + * scrolling + * @version 4.0.1 + * @author SpryMedia Ltd + * @contact datatables.net + * + * This source file is free software, available under the following license: + * MIT license - http://datatables.net/license/mit + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: http://www.datatables.net + */ + + var _instCounter = 0 + + var FixedHeader = function (dt, config) { + if (!DataTable.versionCheck("2")) { + throw "Warning: FixedHeader requires DataTables 2 or newer" + } + + // Sanity check - you just know it will happen + if (!(this instanceof FixedHeader)) { + throw "FixedHeader must be initialised with the 'new' keyword." + } + + // Allow a boolean true for defaults + if (config === true) { + config = {} + } + + dt = new DataTable.Api(dt) + + this.c = $.extend(true, {}, FixedHeader.defaults, config) + + this.s = { + dt: dt, + position: { + theadTop: 0, + tbodyTop: 0, + tfootTop: 0, + tfootBottom: 0, + width: 0, + left: 0, + tfootHeight: 0, + theadHeight: 0, + windowHeight: $(window).height(), + visible: true + }, + headerMode: null, + footerMode: null, + autoWidth: dt.settings()[0].oFeatures.bAutoWidth, + namespace: ".dtfc" + _instCounter++, + scrollLeft: { + header: -1, + footer: -1 + }, + enable: true, + autoDisable: false + } + + this.dom = { + floatingHeader: null, + thead: $(dt.table().header()), + tbody: $(dt.table().body()), + tfoot: $(dt.table().footer()), + header: { + host: null, + floating: null, + floatingParent: $('<div class="dtfh-floatingparent"><div></div></div>'), + placeholder: null + }, + footer: { + host: null, + floating: null, + floatingParent: $('<div class="dtfh-floatingparent"><div></div></div>'), + placeholder: null + } + } + + this.dom.header.host = this.dom.thead.parent() + this.dom.footer.host = this.dom.tfoot.parent() + + var dtSettings = dt.settings()[0] + if (dtSettings._fixedHeader) { + throw "FixedHeader already initialised on table " + dtSettings.nTable.id + } + + dtSettings._fixedHeader = this + + this._constructor() + } + + /* + * Variable: FixedHeader + * Purpose: Prototype for FixedHeader + * Scope: global + */ + $.extend(FixedHeader.prototype, { + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * API methods + */ + + /** + * Kill off FH and any events + */ + destroy: function () { + var dom = this.dom + + this.s.dt.off(".dtfc") + $(window).off(this.s.namespace) + + // Remove clones of FC blockers + if (dom.header.rightBlocker) { + dom.header.rightBlocker.remove() + } + if (dom.header.leftBlocker) { + dom.header.leftBlocker.remove() + } + if (dom.footer.rightBlocker) { + dom.footer.rightBlocker.remove() + } + if (dom.footer.leftBlocker) { + dom.footer.leftBlocker.remove() + } + + if (this.c.header) { + this._modeChange("in-place", "header", true) + } + + if (this.c.footer && dom.tfoot.length) { + this._modeChange("in-place", "footer", true) + } + }, + + /** + * Enable / disable the fixed elements + * + * @param {boolean} enable `true` to enable, `false` to disable + */ + enable: function (enable, update, type) { + this.s.enable = enable + + this.s.enableType = type + + if (update || update === undefined) { + this._positions() + this._scroll(true) + } + }, + + /** + * Get enabled status + */ + enabled: function () { + return this.s.enable + }, + + /** + * Set header offset + * + * @param {int} new value for headerOffset + */ + headerOffset: function (offset) { + if (offset !== undefined) { + this.c.headerOffset = offset + this.update() + } + + return this.c.headerOffset + }, + + /** + * Set footer offset + * + * @param {int} new value for footerOffset + */ + footerOffset: function (offset) { + if (offset !== undefined) { + this.c.footerOffset = offset + this.update() + } + + return this.c.footerOffset + }, + + /** + * Recalculate the position of the fixed elements and force them into place + */ + update: function (force) { + var table = this.s.dt.table().node() + + // Update should only do something if enabled by the dev. + if (!this.s.enable && !this.s.autoDisable) { + return + } + + if ($(table).is(":visible")) { + this.s.autoDisable = false + this.enable(true, false) + } else { + this.s.autoDisable = true + this.enable(false, false) + } + + // Don't update if header is not in the document atm (due to + // async events) + if ($(table).children("thead").length === 0) { + return + } + + this._positions() + this._scroll(force !== undefined ? force : true) + this._widths(this.dom.header) + this._widths(this.dom.footer) + }, + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Constructor + */ + + /** + * FixedHeader constructor - adding the required event listeners and + * simple initialisation + * + * @private + */ + _constructor: function () { + var that = this + var dt = this.s.dt + + $(window) + .on("scroll" + this.s.namespace, function () { + that._scroll() + }) + .on( + "resize" + this.s.namespace, + DataTable.util.throttle(function () { + that.s.position.windowHeight = $(window).height() + that.update() + }, 50) + ) + + var autoHeader = $(".fh-fixedHeader") + if (!this.c.headerOffset && autoHeader.length) { + this.c.headerOffset = autoHeader.outerHeight() + } + + var autoFooter = $(".fh-fixedFooter") + if (!this.c.footerOffset && autoFooter.length) { + this.c.footerOffset = autoFooter.outerHeight() + } + + dt.on("column-reorder.dt.dtfc column-visibility.dt.dtfc column-sizing.dt.dtfc responsive-display.dt.dtfc", function (e, ctx) { + that.update() + }).on("draw.dt.dtfc", function (e, ctx) { + // For updates from our own table, don't reclone, but for all others, do + that.update(ctx === dt.settings()[0] ? false : true) + }) + + dt.on("destroy.dtfc", function () { + that.destroy() + }) + + this._positions() + this._scroll() + }, + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Private methods + */ + + /** + * Clone a fixed item to act as a place holder for the original element + * which is moved into a clone of the table element, and moved around the + * document to give the fixed effect. + * + * @param {string} item 'header' or 'footer' + * @param {boolean} force Force the clone to happen, or allow automatic + * decision (reuse existing if available) + * @private + */ + _clone: function (item, force) { + var that = this + var dt = this.s.dt + var itemDom = this.dom[item] + var itemElement = item === "header" ? this.dom.thead : this.dom.tfoot + + // If footer and scrolling is enabled then we don't clone + // Instead the table's height is decreased accordingly - see `_scroll()` + if (item === "footer" && this._scrollEnabled()) { + return + } + + if (!force && itemDom.floating) { + // existing floating element - reuse it + itemDom.floating.removeClass("fixedHeader-floating fixedHeader-locked") + } else { + if (itemDom.floating) { + if (itemDom.placeholder !== null) { + itemDom.placeholder.remove() + } + + itemDom.floating.children().detach() + itemDom.floating.remove() + } + + var tableNode = $(dt.table().node()) + var scrollBody = $(tableNode.parent()) + var scrollEnabled = this._scrollEnabled() + + itemDom.floating = $(dt.table().node().cloneNode(false)) + .attr("aria-hidden", "true") + .css({ + top: 0, + left: 0 + }) + .removeAttr("id") + + itemDom.floatingParent + .css({ + width: scrollBody[0].offsetWidth, + overflow: "hidden", + height: "fit-content", + position: "fixed", + left: scrollEnabled ? tableNode.offset().left + scrollBody.scrollLeft() : 0 + }) + .css( + item === "header" + ? { + top: this.c.headerOffset, + bottom: "" + } + : { + top: "", + bottom: this.c.footerOffset + } + ) + .addClass(item === "footer" ? "dtfh-floatingparent-foot" : "dtfh-floatingparent-head") + .appendTo("body") + .children() + .eq(0) + .append(itemDom.floating) + + this._stickyPosition(itemDom.floating, "-") + + var scrollLeftUpdate = function () { + var scrollLeft = scrollBody.scrollLeft() + that.s.scrollLeft = { footer: scrollLeft, header: scrollLeft } + itemDom.floatingParent.scrollLeft(that.s.scrollLeft.header) + } + + scrollLeftUpdate() + scrollBody.off("scroll.dtfh").on("scroll.dtfh", scrollLeftUpdate) + + // Need padding on the header's container to allow for a scrollbar, + // just like how DataTables handles it + itemDom.floatingParent.children().css({ + width: "fit-content", + paddingRight: that.s.dt.settings()[0].oBrowser.barWidth + }) + + // Blocker to hide the table behind the scrollbar - this needs to use + // fixed positioning in the container since we don't have an outer wrapper + let blocker = $(item === "footer" ? "div.dtfc-bottom-blocker" : "div.dtfc-top-blocker", dt.table().container()) + + if (blocker.length) { + blocker.clone().appendTo(itemDom.floatingParent).css({ + position: "fixed", + right: blocker.width() + }) + } + + // Insert a fake thead/tfoot into the DataTable to stop it jumping around + itemDom.placeholder = itemElement.clone(false) + itemDom.placeholder.find("*[id]").removeAttr("id") + + // Move the thead / tfoot elements around - original into the floating + // element and clone into the original table + itemDom.host.prepend(itemDom.placeholder) + itemDom.floating.append(itemElement) + + this._widths(itemDom) + } + }, + + /** + * This method sets the sticky position of the header elements to match fixed columns + * @param {JQuery<HTMLElement>} el + * @param {string} sign + */ + _stickyPosition: function (el, sign) { + if (this._scrollEnabled()) { + var that = this + var rtl = $(that.s.dt.table().node()).css("direction") === "rtl" + + el.find("th").each(function () { + // Find out if fixed header has previously set this column + if ($(this).css("position") === "sticky") { + var right = $(this).css("right") + var left = $(this).css("left") + var potential + + if (right !== "auto" && !rtl) { + potential = +right.replace(/px/g, "") + + $(this).css("right", potential > 0 ? potential : 0) + } else if (left !== "auto" && rtl) { + potential = +left.replace(/px/g, "") + + $(this).css("left", potential > 0 ? potential : 0) + } + } + }) + } + }, + + /** + * Reposition the floating elements to take account of horizontal page + * scroll + * + * @param {string} item The `header` or `footer` + * @param {int} scrollLeft Document scrollLeft + * @private + */ + _horizontal: function (item, scrollLeft) { + var itemDom = this.dom[item] + var lastScrollLeft = this.s.scrollLeft + + if (itemDom.floating && lastScrollLeft[item] !== scrollLeft) { + // If scrolling is enabled we need to match the floating header to the body + if (this._scrollEnabled()) { + var newScrollLeft = $($(this.s.dt.table().node()).parent()).scrollLeft() + itemDom.floating.scrollLeft(newScrollLeft) + itemDom.floatingParent.scrollLeft(newScrollLeft) + } + + lastScrollLeft[item] = scrollLeft + } + }, + + /** + * Change from one display mode to another. Each fixed item can be in one + * of: + * + * * `in-place` - In the main DataTable + * * `in` - Floating over the DataTable + * * `below` - (Header only) Fixed to the bottom of the table body + * * `above` - (Footer only) Fixed to the top of the table body + * + * @param {string} mode Mode that the item should be shown in + * @param {string} item 'header' or 'footer' + * @param {boolean} forceChange Force a redraw of the mode, even if already + * in that mode. + * @private + */ + _modeChange: function (mode, item, forceChange) { + var itemDom = this.dom[item] + var position = this.s.position + + // Just determine if scroll is enabled once + var scrollEnabled = this._scrollEnabled() + + // If footer and scrolling is enabled then we don't clone + // Instead the table's height is decreased accordingly - see `_scroll()` + if (item === "footer" && scrollEnabled) { + return + } + + // It isn't trivial to add a !important css attribute... + var importantWidth = function (w) { + itemDom.floating[0].style.setProperty("width", w + "px", "important") + + // If not scrolling also have to update the floatingParent + if (!scrollEnabled) { + itemDom.floatingParent[0].style.setProperty("width", w + "px", "important") + } + } + + // Record focus. Browser's will cause input elements to loose focus if + // they are inserted else where in the doc + var tablePart = this.dom[item === "footer" ? "tfoot" : "thead"] + var focus = $.contains(tablePart[0], document.activeElement) ? document.activeElement : null + var scrollBody = $($(this.s.dt.table().node()).parent()) + + if (mode === "in-place") { + // Insert the header back into the table's real header + if (itemDom.placeholder) { + itemDom.placeholder.remove() + itemDom.placeholder = null + } + + if (item === "header") { + itemDom.host.prepend(tablePart) + } else { + itemDom.host.append(tablePart) + } + + if (itemDom.floating) { + itemDom.floating.remove() + itemDom.floating = null + this._stickyPosition(itemDom.host, "+") + } + + if (itemDom.floatingParent) { + itemDom.floatingParent.find("div.dtfc-top-blocker").remove() + itemDom.floatingParent.remove() + } + + $($(itemDom.host.parent()).parent()).scrollLeft(scrollBody.scrollLeft()) + } else if (mode === "in") { + // Remove the header from the real table and insert into a fixed + // positioned floating table clone + this._clone(item, forceChange) + + // Get useful position values + var scrollOffset = scrollBody.offset() + var windowTop = $(document).scrollTop() + var windowHeight = $(window).height() + var windowBottom = windowTop + windowHeight + var bodyTop = scrollEnabled ? scrollOffset.top : position.tbodyTop + var bodyBottom = scrollEnabled ? scrollOffset.top + scrollBody.outerHeight() : position.tfootTop + + // Calculate the amount that the footer or header needs to be shuffled + var shuffle + + if (item === "footer") { + shuffle = + bodyTop > windowBottom + ? position.tfootHeight // Yes - push the footer below + : bodyTop + position.tfootHeight - windowBottom // No + } else { + // Otherwise must be a header so get the difference from the bottom of the + // desired floating header and the bottom of the table body + shuffle = windowTop + this.c.headerOffset + position.theadHeight - bodyBottom + } + + // Set the top or bottom based off of the offset and the shuffle value + var prop = item === "header" ? "top" : "bottom" + var val = this.c[item + "Offset"] - (shuffle > 0 ? shuffle : 0) + + itemDom.floating.addClass("fixedHeader-floating") + itemDom.floatingParent.css(prop, val).css({ + left: position.left, + "z-index": 3 + }) + + importantWidth(position.width) + + if (item === "footer") { + itemDom.floating.css("top", "") + } + } else if (mode === "below") { + // only used for the header + // Fix the position of the floating header at base of the table body + this._clone(item, forceChange) + + itemDom.floating.addClass("fixedHeader-locked") + itemDom.floatingParent.css({ + position: "absolute", + top: position.tfootTop - position.theadHeight, + left: position.left + "px" + }) + + importantWidth(position.width) + } else if (mode === "above") { + // only used for the footer + // Fix the position of the floating footer at top of the table body + this._clone(item, forceChange) + + itemDom.floating.addClass("fixedHeader-locked") + itemDom.floatingParent.css({ + position: "absolute", + top: position.tbodyTop, + left: position.left + "px" + }) + + importantWidth(position.width) + } + + // Restore focus if it was lost + if (focus && focus !== document.activeElement) { + setTimeout(function () { + focus.focus() + }, 10) + } + + this.s.scrollLeft.header = -1 + this.s.scrollLeft.footer = -1 + this.s[item + "Mode"] = mode + }, + + /** + * Cache the positional information that is required for the mode + * calculations that FixedHeader performs. + * + * @private + */ + _positions: function () { + var dt = this.s.dt + var table = dt.table() + var position = this.s.position + var dom = this.dom + var tableNode = $(table.node()) + var scrollEnabled = this._scrollEnabled() + + // Need to use the header and footer that are in the main table, + // regardless of if they are clones, since they hold the positions we + // want to measure from + var thead = $(dt.table().header()) + var tfoot = $(dt.table().footer()) + var tbody = dom.tbody + var scrollBody = tableNode.parent() + + position.visible = tableNode.is(":visible") + position.width = tableNode.outerWidth() + position.left = tableNode.offset().left + position.theadTop = thead.offset().top + position.tbodyTop = scrollEnabled ? scrollBody.offset().top : tbody.offset().top + position.tbodyHeight = scrollEnabled ? scrollBody.outerHeight() : tbody.outerHeight() + position.theadHeight = thead.outerHeight() + position.theadBottom = position.theadTop + position.theadHeight + position.tfootTop = position.tbodyTop + position.tbodyHeight //tfoot.offset().top; + + if (tfoot.length) { + position.tfootBottom = position.tfootTop + tfoot.outerHeight() + position.tfootHeight = tfoot.outerHeight() + } else { + position.tfootBottom = position.tfootTop + position.tfootHeight = 0 + } + }, + + /** + * Mode calculation - determine what mode the fixed items should be placed + * into. + * + * @param {boolean} forceChange Force a redraw of the mode, even if already + * in that mode. + * @private + */ + _scroll: function (forceChange) { + if (this.s.dt.settings()[0].bDestroying) { + return + } + + // ScrollBody details + var scrollEnabled = this._scrollEnabled() + var scrollBody = $(this.s.dt.table().node()).parent() + var scrollOffset = scrollBody.offset() + var scrollHeight = scrollBody.outerHeight() + + // Window details + var windowLeft = $(document).scrollLeft() + var windowTop = $(document).scrollTop() + var windowHeight = $(window).height() + var windowBottom = windowHeight + windowTop + + var position = this.s.position + var headerMode, footerMode + + // Body Details + var bodyTop = scrollEnabled ? scrollOffset.top : position.tbodyTop + var bodyLeft = scrollEnabled ? scrollOffset.left : position.left + var bodyBottom = scrollEnabled ? scrollOffset.top + scrollHeight : position.tfootTop + var bodyWidth = scrollEnabled ? scrollBody.outerWidth() : position.tbodyWidth + + if (this.c.header) { + if (!this.s.enable) { + headerMode = "in-place" + } + // The header is in it's normal place if the body top is lower than + // the scroll of the window plus the headerOffset and the height of the header + else if (!position.visible || windowTop + this.c.headerOffset + position.theadHeight <= bodyTop) { + headerMode = "in-place" + } + // The header should be floated if + else if ( + // The scrolling plus the header offset plus the height of the header is lower than the top of the body + windowTop + this.c.headerOffset + position.theadHeight > bodyTop && + // And the scrolling at the top plus the header offset is above the bottom of the body + windowTop + this.c.headerOffset + position.theadHeight < bodyBottom + ) { + headerMode = "in" + + // Further to the above, If the scrolling plus the header offset plus the header height is lower + // than the bottom of the table a shuffle is required so have to force the calculation + if (windowTop + this.c.headerOffset + position.theadHeight > bodyBottom || this.dom.header.floatingParent === undefined) { + forceChange = true + } else { + this.dom.header.floatingParent + .css({ + top: this.c.headerOffset, + position: "fixed" + }) + .children() + .eq(0) + .append(this.dom.header.floating) + } + } + // Anything else and the view is below the table + else { + headerMode = "below" + } + + if (forceChange || headerMode !== this.s.headerMode) { + this._modeChange(headerMode, "header", forceChange) + } + + this._horizontal("header", windowLeft) + } + + var header = { + offset: { top: 0, left: 0 }, + height: 0 + } + var footer = { + offset: { top: 0, left: 0 }, + height: 0 + } + + if (this.c.footer && this.dom.tfoot.length && this.dom.tfoot.find("th, td").length) { + if (!this.s.enable) { + footerMode = "in-place" + } else if (!position.visible || position.tfootBottom + this.c.footerOffset <= windowBottom) { + footerMode = "in-place" + } else if (bodyBottom + position.tfootHeight + this.c.footerOffset > windowBottom && bodyTop + this.c.footerOffset < windowBottom) { + footerMode = "in" + forceChange = true + } else { + footerMode = "above" + } + + if (forceChange || footerMode !== this.s.footerMode) { + this._modeChange(footerMode, "footer", forceChange) + } + + this._horizontal("footer", windowLeft) + + var getOffsetHeight = function (el) { + return { + offset: el.offset(), + height: el.outerHeight() + } + } + + header = this.dom.header.floating ? getOffsetHeight(this.dom.header.floating) : getOffsetHeight(this.dom.thead) + footer = this.dom.footer.floating ? getOffsetHeight(this.dom.footer.floating) : getOffsetHeight(this.dom.tfoot) + + // If scrolling is enabled and the footer is off the screen + if (scrollEnabled && footer.offset.top > windowTop) { + // && footer.offset.top >= windowBottom) { + // Calculate the gap between the top of the scrollBody and the top of the window + var overlap = windowTop - scrollOffset.top + // The new height is the bottom of the window + var newHeight = + windowBottom + + // If the gap between the top of the scrollbody and the window is more than + // the height of the header then the top of the table is still visible so add that gap + // Doing this has effectively calculated the height from the top of the table to the bottom of the current page + (overlap > -header.height ? overlap : 0) - + // Take from that + // The top of the header plus + (header.offset.top + + // The header height if the standard header is present + (overlap < -header.height ? header.height : 0) + + // And the height of the footer + footer.height) + + // Don't want a negative height + if (newHeight < 0) { + newHeight = 0 + } + + // At the end of the above calculation the space between the header (top of the page if floating) + // and the point just above the footer should be the new value for the height of the table. + scrollBody.outerHeight(newHeight) + + // Need some rounding here as sometimes very small decimal places are encountered + // If the actual height is bigger or equal to the height we just applied then the footer is "Floating" + if (Math.round(scrollBody.outerHeight()) >= Math.round(newHeight)) { + $(this.dom.tfoot.parent()).addClass("fixedHeader-floating") + } + // Otherwise max-width has kicked in so it is not floating + else { + $(this.dom.tfoot.parent()).removeClass("fixedHeader-floating") + } + } + } + + if (this.dom.header.floating) { + this.dom.header.floatingParent.css("left", bodyLeft - windowLeft) + } + if (this.dom.footer.floating) { + this.dom.footer.floatingParent.css("left", bodyLeft - windowLeft) + } + + // If fixed columns is being used on this table then the blockers need to be copied across + // Cloning these is cleaner than creating as our own as it will keep consistency with fixedColumns automatically + // ASSUMING that the class remains the same + if (this.s.dt.settings()[0]._fixedColumns !== undefined) { + var adjustBlocker = function (side, end, el) { + if (el === undefined) { + var blocker = $("div.dtfc-" + side + "-" + end + "-blocker") + + el = blocker.length === 0 ? null : blocker.clone().css("z-index", 1) + } + + if (el !== null) { + if (headerMode === "in" || headerMode === "below") { + el.appendTo("body").css({ + top: end === "top" ? header.offset.top : footer.offset.top, + left: side === "right" ? bodyLeft + bodyWidth - el.width() : bodyLeft + }) + } else { + el.detach() + } + } + + return el + } + + // Adjust all blockers + this.dom.header.rightBlocker = adjustBlocker("right", "top", this.dom.header.rightBlocker) + this.dom.header.leftBlocker = adjustBlocker("left", "top", this.dom.header.leftBlocker) + this.dom.footer.rightBlocker = adjustBlocker("right", "bottom", this.dom.footer.rightBlocker) + this.dom.footer.leftBlocker = adjustBlocker("left", "bottom", this.dom.footer.leftBlocker) + } + }, + + /** + * Function to check if scrolling is enabled on the table or not + * @returns Boolean value indicating if scrolling on the table is enabled or not + */ + _scrollEnabled: function () { + var oScroll = this.s.dt.settings()[0].oScroll + if (oScroll.sY !== "" || oScroll.sX !== "") { + return true + } + return false + }, + + /** + * Realign columns by using the colgroup tag and + * checking column widths + */ + _widths: function (itemDom) { + if (!itemDom || !itemDom.placeholder) { + return + } + + // Match the table overall width + var tableNode = $(this.s.dt.table().node()) + var scrollBody = $(tableNode.parent()) + + itemDom.floatingParent.css("width", scrollBody[0].offsetWidth) + itemDom.floating.css("width", tableNode[0].offsetWidth) + + // Strip out the old colgroup + $("colgroup", itemDom.floating).remove() + + // Copy the `colgroup` element to define the number of columns - needed + // for complex header cases where a column might not have a unique + // header + var cols = itemDom.placeholder.parent().find("colgroup").clone().appendTo(itemDom.floating).find("col") + + // However, the widths defined in the colgroup from the DataTable might + // not exactly reflect the actual widths of the columns (content can + // force it to stretch). So we need to copy the actual widths into the + // colgroup / col's used for the floating header. + var widths = this.s.dt.columns(":visible").widths() + + for (var i = 0; i < widths.length; i++) { + cols.eq(i).css("width", widths[i]) + } + } + }) + + /** + * Version + * @type {String} + * @static + */ + FixedHeader.version = "4.0.1" + + /** + * Defaults + * @type {Object} + * @static + */ + FixedHeader.defaults = { + header: true, + footer: false, + headerOffset: 0, + footerOffset: 0 + } + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables interfaces + */ + + // Attach for constructor access + $.fn.dataTable.FixedHeader = FixedHeader + $.fn.DataTable.FixedHeader = FixedHeader + + // DataTables creation - check if the FixedHeader option has been defined on the + // table and if so, initialise + $(document).on("init.dt.dtfh", function (e, settings, json) { + if (e.namespace !== "dt") { + return + } + + var init = settings.oInit.fixedHeader + var defaults = DataTable.defaults.fixedHeader + + if ((init || defaults) && !settings._fixedHeader) { + var opts = $.extend({}, defaults, init) + + if (init !== false) { + new FixedHeader(settings, opts) + } + } + }) + + // DataTables API methods + DataTable.Api.register("fixedHeader()", function () {}) + + DataTable.Api.register("fixedHeader.adjust()", function () { + return this.iterator("table", function (ctx) { + var fh = ctx._fixedHeader + + if (fh) { + fh.update() + } + }) + }) + + DataTable.Api.register("fixedHeader.enable()", function (flag) { + return this.iterator("table", function (ctx) { + var fh = ctx._fixedHeader + + flag = flag !== undefined ? flag : true + if (fh && flag !== fh.enabled()) { + fh.enable(flag) + } + }) + }) + + DataTable.Api.register("fixedHeader.enabled()", function () { + if (this.context.length) { + var fh = this.context[0]._fixedHeader + + if (fh) { + return fh.enabled() + } + } + + return false + }) + + DataTable.Api.register("fixedHeader.disable()", function () { + return this.iterator("table", function (ctx) { + var fh = ctx._fixedHeader + + if (fh && fh.enabled()) { + fh.enable(false) + } + }) + }) + + $.each(["header", "footer"], function (i, el) { + DataTable.Api.register("fixedHeader." + el + "Offset()", function (offset) { + var ctx = this.context + + if (offset === undefined) { + return ctx.length && ctx[0]._fixedHeader ? ctx[0]._fixedHeader[el + "Offset"]() : undefined + } + + return this.iterator("table", function (ctx) { + var fh = ctx._fixedHeader + + if (fh) { + fh[el + "Offset"](offset) + } + }) + }) + }) + + return DataTable +}) + +/*! SearchBuilder 1.8.0 + * ©SpryMedia Ltd - datatables.net/license/mit + */ +;(function (factory) { + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery", "datatables.net"], function ($) { + return factory($, window, document) + }) + } else if (typeof exports === "object") { + // CommonJS + var jq = require("jquery") + var cjsRequires = function (root, $) { + if (!$.fn.dataTable) { + require("datatables.net")(root, $) + } + } + + if (typeof window === "undefined") { + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window + } + + if (!$) { + $ = jq(root) + } + + cjsRequires(root, $) + return factory($, root, root.document) + } + } else { + cjsRequires(window, jq) + module.exports = factory(jq, window, window.document) + } + } else { + // Browser + factory(jQuery, window, document) + } +})(function ($, window, document) { + "use strict" + var DataTable = $.fn.dataTable + + ;(function () { + "use strict" + + var $$3 + var dataTable$2 + /** Get a moment object. Attempt to get from DataTables for module loading first. */ + function moment() { + var used = DataTable.use("moment") + return used ? used : window.moment + } + /** Get a luxon object. Attempt to get from DataTables for module loading first. */ + function luxon() { + var used = DataTable.use("luxon") + return used ? used : window.luxon + } + /** + * Sets the value of jQuery for use in the file + * + * @param jq the instance of jQuery to be set + */ + function setJQuery$2(jq) { + $$3 = jq + dataTable$2 = jq.fn.dataTable + } + /** + * The Criteria class is used within SearchBuilder to represent a search criteria + */ + var Criteria = /** @class */ (function () { + function Criteria(table, opts, topGroup, index, depth, serverData, liveSearch) { + if (index === void 0) { + index = 0 + } + if (depth === void 0) { + depth = 1 + } + if (serverData === void 0) { + serverData = undefined + } + if (liveSearch === void 0) { + liveSearch = false + } + var _this = this + this.classes = $$3.extend(true, {}, Criteria.classes) + // Get options from user and any extra conditions/column types defined by plug-ins + this.c = $$3.extend(true, {}, Criteria.defaults, $$3.fn.dataTable.ext.searchBuilder, opts) + var i18n = this.c.i18n + this.s = { + condition: undefined, + conditions: {}, + data: undefined, + dataIdx: -1, + dataPoints: [], + dateFormat: false, + depth: depth, + dt: table, + filled: false, + index: index, + liveSearch: liveSearch, + origData: undefined, + preventRedraw: false, + serverData: serverData, + topGroup: topGroup, + type: "", + value: [] + } + this.dom = { + buttons: $$3("<div/>").addClass(this.classes.buttonContainer), + condition: $$3("<select disabled/>").addClass(this.classes.condition).addClass(this.classes.dropDown).addClass(this.classes.italic).attr("autocomplete", "hacking"), + conditionTitle: $$3('<option value="" disabled selected hidden/>').html(this.s.dt.i18n("searchBuilder.condition", i18n.condition)), + container: $$3("<div/>").addClass(this.classes.container), + data: $$3("<select/>").addClass(this.classes.data).addClass(this.classes.dropDown).addClass(this.classes.italic), + dataTitle: $$3('<option value="" disabled selected hidden/>').html(this.s.dt.i18n("searchBuilder.data", i18n.data)), + defaultValue: $$3("<select disabled/>").addClass(this.classes.value).addClass(this.classes.dropDown).addClass(this.classes.select).addClass(this.classes.italic), + delete: $$3("<button/>") + .html(this.s.dt.i18n("searchBuilder.delete", i18n["delete"])) + .addClass(this.classes["delete"]) + .addClass(this.classes.button) + .attr("title", this.s.dt.i18n("searchBuilder.deleteTitle", i18n.deleteTitle)) + .attr("type", "button"), + inputCont: $$3("<div/>").addClass(this.classes.inputCont), + // eslint-disable-next-line no-useless-escape + left: $$3("<button/>") + .html(this.s.dt.i18n("searchBuilder.left", i18n.left)) + .addClass(this.classes.left) + .addClass(this.classes.button) + .attr("title", this.s.dt.i18n("searchBuilder.leftTitle", i18n.leftTitle)) + .attr("type", "button"), + // eslint-disable-next-line no-useless-escape + right: $$3("<button/>") + .html(this.s.dt.i18n("searchBuilder.right", i18n.right)) + .addClass(this.classes.right) + .addClass(this.classes.button) + .attr("title", this.s.dt.i18n("searchBuilder.rightTitle", i18n.rightTitle)) + .attr("type", "button"), + value: [$$3("<select disabled/>").addClass(this.classes.value).addClass(this.classes.dropDown).addClass(this.classes.italic).addClass(this.classes.select)], + valueTitle: $$3('<option value="--valueTitle--" disabled selected hidden/>').html(this.s.dt.i18n("searchBuilder.value", i18n.value)) + } + // If the greyscale option is selected then add the class to add the grey colour to SearchBuilder + if (this.c.greyscale) { + this.dom.data.addClass(this.classes.greyscale) + this.dom.condition.addClass(this.classes.greyscale) + this.dom.defaultValue.addClass(this.classes.greyscale) + for (var _i = 0, _a = this.dom.value; _i < _a.length; _i++) { + var val = _a[_i] + val.addClass(this.classes.greyscale) + } + } + $$3(window).on( + "resize.dtsb", + dataTable$2.util.throttle(function () { + _this.s.topGroup.trigger("dtsb-redrawLogic") + }) + ) + this._buildCriteria() + return this + } + /** + * Escape html characters within a string + * + * @param txt the string to be escaped + * @returns the escaped string + */ + Criteria._escapeHTML = function (txt) { + return txt + .toString() + .replace(/&lt;/g, "<") + .replace(/&gt;/g, ">") + .replace(/&quot;/g, '"') + .replace(/&amp;/g, "&") + } + /** + * Redraw the DataTable with the current search parameters + */ + Criteria.prototype.doSearch = function () { + // Only do the search if live search is disabled, otherwise the search + // is triggered by the button at the top level group. + if (this.c.liveSearch) { + this.s.dt.draw() + } + } + /** + * Parses formatted numbers down to a form where they can be compared. + * Note that this does not account for different decimal characters. Use + * parseNumber instead on the instance. + * + * @param val the value to convert + * @returns the converted value + */ + Criteria.parseNumFmt = function (val) { + return +val.replace(/(?!^-)[^0-9.]/g, "") + } + /** + * Adds the left button to the criteria + */ + Criteria.prototype.updateArrows = function (hasSiblings) { + if (hasSiblings === void 0) { + hasSiblings = false + } + // Empty the container and append all of the elements in the correct order + this.dom.container.children().detach() + this.dom.container.append(this.dom.data).append(this.dom.condition).append(this.dom.inputCont) + this.setListeners() + // Trigger the inserted events for the value elements as they are inserted + if (this.dom.value[0] !== undefined) { + $$3(this.dom.value[0]).trigger("dtsb-inserted") + } + for (var i = 1; i < this.dom.value.length; i++) { + this.dom.inputCont.append(this.dom.value[i]) + $$3(this.dom.value[i]).trigger("dtsb-inserted") + } + // If this is a top level criteria then don't let it move left + if (this.s.depth > 1) { + this.dom.buttons.append(this.dom.left) + } + // If the depthLimit of the query has been hit then don't add the right button + if ((this.c.depthLimit === false || this.s.depth < this.c.depthLimit) && hasSiblings) { + this.dom.buttons.append(this.dom.right) + } else { + this.dom.right.remove() + } + this.dom.buttons.append(this.dom["delete"]) + this.dom.container.append(this.dom.buttons) + } + /** + * Destroys the criteria, removing listeners and container from the dom + */ + Criteria.prototype.destroy = function () { + // Turn off listeners + this.dom.data.off(".dtsb") + this.dom.condition.off(".dtsb") + this.dom["delete"].off(".dtsb") + for (var _i = 0, _a = this.dom.value; _i < _a.length; _i++) { + var val = _a[_i] + val.off(".dtsb") + } + // Remove container from the dom + this.dom.container.remove() + } + /** + * Passes in the data for the row and compares it against this single criteria + * + * @param rowData The data for the row to be compared + * @returns boolean Whether the criteria has passed + */ + Criteria.prototype.search = function (rowData, rowIdx) { + var settings = this.s.dt.settings()[0] + var condition = this.s.conditions[this.s.condition] + if (this.s.condition !== undefined && condition !== undefined) { + var filter = rowData[this.s.dataIdx] + // This check is in place for if a custom decimal character is in place + if (this.s.type && this.s.type.includes("num") && (settings.oLanguage.sDecimal !== "" || settings.oLanguage.sThousands !== "")) { + var splitRD = [rowData[this.s.dataIdx]] + if (settings.oLanguage.sDecimal !== "") { + splitRD = rowData[this.s.dataIdx].split(settings.oLanguage.sDecimal) + } + if (settings.oLanguage.sThousands !== "") { + for (var i = 0; i < splitRD.length; i++) { + splitRD[i] = splitRD[i].replace(settings.oLanguage.sThousands, ",") + } + } + filter = splitRD.join(".") + } + // If orthogonal data is in place we need to get it's values for searching + if (this.c.orthogonal.search !== "filter") { + filter = settings.fastData(rowIdx, this.s.dataIdx, typeof this.c.orthogonal === "string" ? this.c.orthogonal : this.c.orthogonal.search) + } + if (this.s.type === "array") { + // Make sure we are working with an array + if (!Array.isArray(filter)) { + filter = [filter] + } + filter.sort() + for (var _i = 0, filter_1 = filter; _i < filter_1.length; _i++) { + var filt = filter_1[_i] + if (filt && typeof filt === "string") { + filt = filt.replace(/[\r\n\u2028]/g, " ") + } + } + } else if (filter !== null && typeof filter === "string") { + filter = filter.replace(/[\r\n\u2028]/g, " ") + } + if (this.s.type.includes("html") && typeof filter === "string") { + filter = filter.replace(/(<([^>]+)>)/gi, "") + } + // Not ideal, but jqueries .val() returns an empty string even + // when the value set is null, so we shall assume the two are equal + if (filter === null) { + filter = "" + } + return condition.search(filter, this.s.value, this) + } + } + /** + * Gets the details required to rebuild the criteria + */ + Criteria.prototype.getDetails = function (deFormatDates) { + if (deFormatDates === void 0) { + deFormatDates = false + } + var i + var settings = this.s.dt.settings()[0] + // This check is in place for if a custom decimal character is in place + if (this.s.type !== null && this.s.type.includes("num") && (settings.oLanguage.sDecimal !== "" || settings.oLanguage.sThousands !== "")) { + for (i = 0; i < this.s.value.length; i++) { + var splitRD = [this.s.value[i].toString()] + if (settings.oLanguage.sDecimal !== "") { + splitRD = this.s.value[i].split(settings.oLanguage.sDecimal) + } + if (settings.oLanguage.sThousands !== "") { + for (var j = 0; j < splitRD.length; j++) { + splitRD[j] = splitRD[j].replace(settings.oLanguage.sThousands, ",") + } + } + this.s.value[i] = splitRD.join(".") + } + } else if (this.s.type !== null && deFormatDates) { + if (this.s.type.includes("date") || this.s.type.includes("time")) { + for (i = 0; i < this.s.value.length; i++) { + if (this.s.value[i].match(/^\d{4}-([0]\d|1[0-2])-([0-2]\d|3[01])$/g) === null) { + this.s.value[i] = "" + } + } + } else if (this.s.type.includes("moment")) { + for (i = 0; i < this.s.value.length; i++) { + if (this.s.value[i] && this.s.value[i].length > 0 && moment()(this.s.value[i], this.s.dateFormat, true).isValid()) { + this.s.value[i] = moment()(this.s.value[i], this.s.dateFormat).format("YYYY-MM-DD HH:mm:ss") + } + } + } else if (this.s.type.includes("luxon")) { + for (i = 0; i < this.s.value.length; i++) { + if (this.s.value[i] && this.s.value[i].length > 0 && luxon().DateTime.fromFormat(this.s.value[i], this.s.dateFormat).invalid === null) { + this.s.value[i] = luxon().DateTime.fromFormat(this.s.value[i], this.s.dateFormat).toFormat("yyyy-MM-dd HH:mm:ss") + } + } + } + } + if (this.s.type && this.s.type.includes("num") && this.s.dt.page.info().serverSide) { + for (i = 0; i < this.s.value.length; i++) { + this.s.value[i] = this.s.value[i].replace(/[^0-9.\-]/g, "") + } + } + return { + condition: this.s.condition, + data: this.s.data, + origData: this.s.origData, + type: this.s.type, + value: this.s.value.map(function (a) { + return a !== null && a !== undefined ? a.toString() : a + }) + } + } + /** + * Getter for the node for the container of the criteria + * + * @returns JQuery<HTMLElement> the node for the container + */ + Criteria.prototype.getNode = function () { + return this.dom.container + } + /** + * Parses formatted numbers down to a form where they can be compared + * + * @param val the value to convert + * @returns the converted value + */ + Criteria.prototype.parseNumber = function (val) { + var decimal = this.s.dt.i18n("decimal") + // Remove any periods and then replace the decimal with a period + if (decimal && decimal !== ".") { + val = val.replace(/\./g, "").replace(decimal, ".") + } + return +val.replace(/(?!^-)[^0-9.]/g, "") + } + /** + * Populates the criteria data, condition and value(s) as far as has been selected + */ + Criteria.prototype.populate = function () { + this._populateData() + // If the column index has been found attempt to select a condition + if (this.s.dataIdx !== -1) { + this._populateCondition() + // If the condittion has been found attempt to select the values + if (this.s.condition !== undefined) { + this._populateValue() + } + } + } + /** + * Rebuilds the criteria based upon the details passed in + * + * @param loadedCriteria the details required to rebuild the criteria + */ + Criteria.prototype.rebuild = function (loadedCriteria) { + // Check to see if the previously selected data exists, if so select it + var foundData = false + var dataIdx, i + this._populateData() + // If a data selection has previously been made attempt to find and select it + if (loadedCriteria.data !== undefined) { + var italic_1 = this.classes.italic + var data_1 = this.dom.data + this.dom.data.children("option").each(function () { + if (!foundData && ($$3(this).text() === loadedCriteria.data || (loadedCriteria.origData && $$3(this).prop("origData") === loadedCriteria.origData))) { + $$3(this).prop("selected", true) + data_1.removeClass(italic_1) + foundData = true + dataIdx = parseInt($$3(this).val(), 10) + } else { + $$3(this).removeProp("selected") + } + }) + } + // If the data has been found and selected then the condition can be populated and searched + if (foundData) { + this.s.data = loadedCriteria.data + this.s.origData = loadedCriteria.origData + this.s.dataIdx = dataIdx + this.c.orthogonal = this._getOptions().orthogonal + this.dom.dataTitle.remove() + this._populateCondition() + this.dom.conditionTitle.remove() + var condition = void 0 + // Check to see if the previously selected condition exists, if so select it + var options = this.dom.condition.children("option") + for (i = 0; i < options.length; i++) { + var option = $$3(options[i]) + if (loadedCriteria.condition !== undefined && option.val() === loadedCriteria.condition && typeof loadedCriteria.condition === "string") { + option.prop("selected", true) + condition = option.val() + } else { + option.removeProp("selected") + } + } + this.s.condition = condition + // If the condition has been found and selected then the value can be populated and searched + if (this.s.condition !== undefined) { + this.dom.conditionTitle.removeProp("selected") + this.dom.conditionTitle.remove() + this.dom.condition.removeClass(this.classes.italic) + for (i = 0; i < options.length; i++) { + var opt = $$3(options[i]) + if (opt.val() !== this.s.condition) { + opt.removeProp("selected") + } + } + this._populateValue(loadedCriteria) + } else { + this.dom.conditionTitle.prependTo(this.dom.condition).prop("selected", true) + } + } + } + /** + * Sets the listeners for the criteria + */ + Criteria.prototype.setListeners = function () { + var _this = this + this.dom.data.unbind("change").on("change.dtsb", function () { + _this.dom.dataTitle.removeProp("selected") + // Need to go over every option to identify the correct selection + var options = _this.dom.data.children("option." + _this.classes.option) + for (var i = 0; i < options.length; i++) { + var option = $$3(options[i]) + if (option.val() === _this.dom.data.val()) { + _this.dom.data.removeClass(_this.classes.italic) + option.prop("selected", true) + _this.s.dataIdx = +option.val() + _this.s.data = option.text() + _this.s.origData = option.prop("origData") + _this.c.orthogonal = _this._getOptions().orthogonal + // When the data is changed, the values in condition and + // value may also change so need to renew them + _this._clearCondition() + _this._clearValue() + _this._populateCondition() + // If this criteria was previously active in the search then + // remove it from the search and trigger a new search + if (_this.s.filled) { + _this.s.filled = false + _this.doSearch() + _this.setListeners() + } + _this.s.dt.state.save() + } else { + option.removeProp("selected") + } + } + }) + this.dom.condition.unbind("change").on("change.dtsb", function () { + _this.dom.conditionTitle.removeProp("selected") + // Need to go over every option to identify the correct selection + var options = _this.dom.condition.children("option." + _this.classes.option) + for (var i = 0; i < options.length; i++) { + var option = $$3(options[i]) + if (option.val() === _this.dom.condition.val()) { + _this.dom.condition.removeClass(_this.classes.italic) + option.prop("selected", true) + var condDisp = option.val() + // Find the condition that has been selected and store it internally + for (var _i = 0, _a = Object.keys(_this.s.conditions); _i < _a.length; _i++) { + var cond = _a[_i] + if (cond === condDisp) { + _this.s.condition = condDisp + break + } + } + // When the condition is changed, the value selector may switch between + // a select element and an input element + _this._clearValue() + _this._populateValue() + for (var _b = 0, _c = _this.dom.value; _b < _c.length; _b++) { + var val = _c[_b] + // If this criteria was previously active in the search then remove + // it from the search and trigger a new search + if (_this.s.filled && val !== undefined && _this.dom.inputCont.has(val[0]).length !== 0) { + _this.s.filled = false + _this.doSearch() + _this.setListeners() + } + } + if (_this.dom.value.length === 0 || (_this.dom.value.length === 1 && _this.dom.value[0] === undefined)) { + _this.doSearch() + } + } else { + option.removeProp("selected") + } + } + }) + } + Criteria.prototype.setupButtons = function () { + if (window.innerWidth > 550) { + this.dom.container.removeClass(this.classes.vertical) + this.dom.buttons.css("left", null) + this.dom.buttons.css("top", null) + return + } + this.dom.container.addClass(this.classes.vertical) + this.dom.buttons.css("left", this.dom.data.innerWidth()) + this.dom.buttons.css("top", this.dom.data.position().top) + } + /** + * Builds the elements of the dom together + */ + Criteria.prototype._buildCriteria = function () { + // Append Titles for select elements + this.dom.data.append(this.dom.dataTitle) + this.dom.condition.append(this.dom.conditionTitle) + // Add elements to container + this.dom.container.append(this.dom.data).append(this.dom.condition) + this.dom.inputCont.empty() + for (var _i = 0, _a = this.dom.value; _i < _a.length; _i++) { + var val = _a[_i] + val.append(this.dom.valueTitle) + this.dom.inputCont.append(val) + } + // Add buttons to container + this.dom.buttons.append(this.dom["delete"]).append(this.dom.right) + this.dom.container.append(this.dom.inputCont).append(this.dom.buttons) + this.setListeners() + } + /** + * Clears the condition select element + */ + Criteria.prototype._clearCondition = function () { + this.dom.condition.empty() + this.dom.conditionTitle.prop("selected", true).attr("disabled", "true") + this.dom.condition.prepend(this.dom.conditionTitle).prop("selectedIndex", 0) + this.s.conditions = {} + this.s.condition = undefined + } + /** + * Clears the value elements + */ + Criteria.prototype._clearValue = function () { + var val + if (this.s.condition !== undefined) { + if (this.dom.value.length > 0 && this.dom.value[0] !== undefined) { + // Remove all of the value elements + for (var _i = 0, _a = this.dom.value; _i < _a.length; _i++) { + val = _a[_i] + if (val !== undefined) { + // Timeout is annoying but because of IOS + setTimeout(function () { + val.remove() + }, 50) + } + } + } + // Call the init function to get the value elements for this condition + this.dom.value = [].concat(this.s.conditions[this.s.condition].init(this, Criteria.updateListener)) + if (this.dom.value.length > 0 && this.dom.value[0] !== undefined) { + this.dom.inputCont.empty().append(this.dom.value[0]).insertAfter(this.dom.condition) + $$3(this.dom.value[0]).trigger("dtsb-inserted") + // Insert all of the value elements + for (var i = 1; i < this.dom.value.length; i++) { + this.dom.inputCont.append(this.dom.value[i]) + $$3(this.dom.value[i]).trigger("dtsb-inserted") + } + } + } else { + // Remove all of the value elements + for (var _b = 0, _c = this.dom.value; _b < _c.length; _b++) { + val = _c[_b] + if (val !== undefined) { + // Timeout is annoying but because of IOS + setTimeout(function () { + val.remove() + }, 50) + } + } + // Append the default valueTitle to the default select element + this.dom.valueTitle.prop("selected", true) + this.dom.defaultValue.append(this.dom.valueTitle).insertAfter(this.dom.condition) + } + this.s.value = [] + this.dom.value = [$$3("<select disabled/>").addClass(this.classes.value).addClass(this.classes.dropDown).addClass(this.classes.italic).addClass(this.classes.select).append(this.dom.valueTitle.clone())] + } + /** + * Gets the options for the column + * + * @returns {object} The options for the column + */ + Criteria.prototype._getOptions = function () { + var table = this.s.dt + return $$3.extend(true, {}, Criteria.defaults, table.settings()[0].aoColumns[this.s.dataIdx].searchBuilder) + } + /** + * Populates the condition dropdown + */ + Criteria.prototype._populateCondition = function () { + var conditionOpts = [] + var conditionsLength = Object.keys(this.s.conditions).length + var dt = this.s.dt + var colInits = dt.settings()[0].aoColumns + var column = +this.dom.data.children("option:selected").val() + var condition, condName + // If there are no conditions stored then we need to get them from the appropriate type + if (conditionsLength === 0) { + this.s.type = dt.column(column).type() + if (colInits !== undefined) { + var colInit = colInits[column] + if (colInit.searchBuilderType !== undefined && colInit.searchBuilderType !== null) { + this.s.type = colInit.searchBuilderType + } else if (this.s.type === undefined || this.s.type === null) { + this.s.type = colInit.sType + } + } + // If the column type is still unknown use the internal API to detect type + if (this.s.type === null || this.s.type === undefined) { + // This can only happen in DT1 - DT2 will do the invalidation of the type itself + if ($$3.fn.dataTable.ext.oApi) { + $$3.fn.dataTable.ext.oApi._fnColumnTypes(dt.settings()[0]) + } + this.s.type = dt.column(column).type() + } + // Enable the condition element + this.dom.condition.removeAttr("disabled").empty().append(this.dom.conditionTitle).addClass(this.classes.italic) + this.dom.conditionTitle.prop("selected", true) + var decimal = dt.settings()[0].oLanguage.sDecimal + // This check is in place for if a custom decimal character is in place + if (decimal !== "" && this.s.type && this.s.type.indexOf(decimal) === this.s.type.length - decimal.length) { + if (this.s.type.includes("num-fmt")) { + this.s.type = this.s.type.replace(decimal, "") + } else if (this.s.type.includes("num")) { + this.s.type = this.s.type.replace(decimal, "") + } + } + // Select which conditions are going to be used based on the column type + var conditionObj = void 0 + if (this.c.conditions[this.s.type] !== undefined) { + conditionObj = this.c.conditions[this.s.type] + } else if (this.s.type && this.s.type.includes("datetime-")) { + // Date / time data types in DataTables are driven by Luxon or + // Moment.js. + conditionObj = DataTable.use("moment") ? this.c.conditions.moment : this.c.conditions.luxon + this.s.dateFormat = this.s.type.replace(/datetime-/g, "") + } else if (this.s.type && this.s.type.includes("moment")) { + conditionObj = this.c.conditions.moment + this.s.dateFormat = this.s.type.replace(/moment-/g, "") + } else if (this.s.type && this.s.type.includes("luxon")) { + conditionObj = this.c.conditions.luxon + this.s.dateFormat = this.s.type.replace(/luxon-/g, "") + } else { + conditionObj = this.c.conditions.string + } + // Add all of the conditions to the select element + for (var _i = 0, _a = Object.keys(conditionObj); _i < _a.length; _i++) { + condition = _a[_i] + if (conditionObj[condition] !== null) { + // Serverside processing does not supply the options for the select elements + // Instead input elements need to be used for these instead + if (dt.page.info().serverSide && conditionObj[condition].init === Criteria.initSelect) { + var col = colInits[column] + if (this.s.serverData && this.s.serverData[col.data]) { + conditionObj[condition].init = Criteria.initSelectSSP + conditionObj[condition].inputValue = Criteria.inputValueSelect + conditionObj[condition].isInputValid = Criteria.isInputValidSelect + } else { + conditionObj[condition].init = Criteria.initInput + conditionObj[condition].inputValue = Criteria.inputValueInput + conditionObj[condition].isInputValid = Criteria.isInputValidInput + } + } + this.s.conditions[condition] = conditionObj[condition] + condName = conditionObj[condition].conditionName + if (typeof condName === "function") { + condName = condName(dt, this.c.i18n) + } + conditionOpts.push( + $$3("<option>", { + text: condName, + value: condition + }) + .addClass(this.classes.option) + .addClass(this.classes.notItalic) + ) + } + } + } + // Otherwise we can just load them in + else if (conditionsLength > 0) { + this.dom.condition.empty().removeAttr("disabled").addClass(this.classes.italic) + for (var _b = 0, _c = Object.keys(this.s.conditions); _b < _c.length; _b++) { + condition = _c[_b] + var name_1 = this.s.conditions[condition].conditionName + if (typeof name_1 === "function") { + name_1 = name_1(dt, this.c.i18n) + } + var newOpt = $$3("<option>", { + text: name_1, + value: condition + }) + .addClass(this.classes.option) + .addClass(this.classes.notItalic) + if (this.s.condition !== undefined && this.s.condition === name_1) { + newOpt.prop("selected", true) + this.dom.condition.removeClass(this.classes.italic) + } + conditionOpts.push(newOpt) + } + } else { + this.dom.condition.attr("disabled", "true").addClass(this.classes.italic) + return + } + for (var _d = 0, conditionOpts_1 = conditionOpts; _d < conditionOpts_1.length; _d++) { + var opt = conditionOpts_1[_d] + this.dom.condition.append(opt) + } + // Selecting a default condition if one is set + if (colInits[column].searchBuilder && colInits[column].searchBuilder.defaultCondition) { + var defaultCondition = colInits[column].searchBuilder.defaultCondition + // If it is a number just use it as an index + if (typeof defaultCondition === "number") { + this.dom.condition.prop("selectedIndex", defaultCondition) + this.dom.condition.trigger("change") + } + // If it is a string then things get slightly more tricly + else if (typeof defaultCondition === "string") { + // We need to check each condition option to see if any will match + for (var i = 0; i < conditionOpts.length; i++) { + // Need to check against the stored conditions so we can match the token "cond" to the option + for (var _e = 0, _f = Object.keys(this.s.conditions); _e < _f.length; _e++) { + var cond = _f[_e] + condName = this.s.conditions[cond].conditionName + if ( + // If the conditionName matches the text of the option + (typeof condName === "string" ? condName : condName(dt, this.c.i18n)) === conditionOpts[i].text() && + // and the tokens match + cond === defaultCondition + ) { + // Select that option + this.dom.condition.prop("selectedIndex", this.dom.condition.children().toArray().indexOf(conditionOpts[i][0])).removeClass(this.classes.italic) + this.dom.condition.trigger("change") + i = conditionOpts.length + break + } + } + } + } + } + // If not default set then default to 0, the title + else { + this.dom.condition.prop("selectedIndex", 0) + } + } + /** + * Populates the data / column select element + */ + Criteria.prototype._populateData = function () { + var columns = this.s.dt.settings()[0].aoColumns + var includeColumns = this.s.dt.columns(this.c.columns).indexes().toArray() + this.dom.data.empty().append(this.dom.dataTitle) + for (var index = 0; index < columns.length; index++) { + // Need to check that the column can be filtered on before adding it + if (this.c.columns === true || includeColumns.includes(index)) { + var col = columns[index] + var opt = { + index: index, + origData: col.data, + text: (col.searchBuilderTitle || col.sTitle).replace(/(<([^>]+)>)/gi, "") + } + this.dom.data.append( + $$3("<option>", { + text: opt.text, + value: opt.index + }) + .addClass(this.classes.option) + .addClass(this.classes.notItalic) + .prop("origData", col.data) + .prop("selected", this.s.dataIdx === opt.index ? true : false) + ) + if (this.s.dataIdx === opt.index) { + this.dom.dataTitle.removeProp("selected") + } + } + } + } + /** + * Populates the Value select element + * + * @param loadedCriteria optional, used to reload criteria from predefined filters + */ + Criteria.prototype._populateValue = function (loadedCriteria) { + var _this = this + var prevFilled = this.s.filled + var i + this.s.filled = false + // Remove any previous value elements + // Timeout is annoying but because of IOS + setTimeout(function () { + _this.dom.defaultValue.remove() + }, 50) + var _loop_1 = function (val) { + // Timeout is annoying but because of IOS + setTimeout(function () { + if (val !== undefined) { + val.remove() + } + }, 50) + } + for (var _i = 0, _a = this.dom.value; _i < _a.length; _i++) { + var val = _a[_i] + _loop_1(val) + } + var children = this.dom.inputCont.children() + if (children.length > 1) { + for (i = 0; i < children.length; i++) { + $$3(children[i]).remove() + } + } + // Find the column with the title matching the data for the criteria and take note of the index + if (loadedCriteria !== undefined) { + this.s.dt.columns().every(function (index) { + if (_this.s.dt.settings()[0].aoColumns[index].sTitle === loadedCriteria.data) { + _this.s.dataIdx = index + } + }) + } + // Initialise the value elements based on the condition + this.dom.value = [].concat(this.s.conditions[this.s.condition].init(this, Criteria.updateListener, loadedCriteria !== undefined ? loadedCriteria.value : undefined)) + if (loadedCriteria !== undefined && loadedCriteria.value !== undefined) { + this.s.value = loadedCriteria.value + } + this.dom.inputCont.empty() + // Insert value elements and trigger the inserted event + if (this.dom.value[0] !== undefined) { + $$3(this.dom.value[0]).appendTo(this.dom.inputCont).trigger("dtsb-inserted") + } + for (i = 1; i < this.dom.value.length; i++) { + $$3(this.dom.value[i]) + .insertAfter(this.dom.value[i - 1]) + .trigger("dtsb-inserted") + } + // Check if the criteria can be used in a search + this.s.filled = this.s.conditions[this.s.condition].isInputValid(this.dom.value, this) + this.setListeners() + // If it can and this is different to before then trigger a draw + if (!this.s.preventRedraw && prevFilled !== this.s.filled) { + // If using SSP we want to restrict the amount of server calls that take place + // and this will already have taken place + if (!this.s.dt.page.info().serverSide) { + this.doSearch() + } + this.setListeners() + } + } + /** + * Provides throttling capabilities to SearchBuilder without having to use dt's _fnThrottle function + * This is because that function is not quite suitable for our needs as it runs initially rather than waiting + * + * @param args arguments supplied to the throttle function + * @returns Function that is to be run that implements the throttling + */ + Criteria.prototype._throttle = function (fn, frequency) { + if (frequency === void 0) { + frequency = 200 + } + var last = null + var timer = null + var that = this + if (frequency === null) { + frequency = 200 + } + return function () { + var args = [] + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i] + } + var now = +new Date() + if (last !== null && now < last + frequency) { + clearTimeout(timer) + } else { + last = now + } + timer = setTimeout(function () { + last = null + fn.apply(that, args) + }, frequency) + } + } + Criteria.version = "1.1.0" + Criteria.classes = { + button: "dtsb-button", + buttonContainer: "dtsb-buttonContainer", + condition: "dtsb-condition", + container: "dtsb-criteria", + data: "dtsb-data", + delete: "dtsb-delete", + dropDown: "dtsb-dropDown", + greyscale: "dtsb-greyscale", + input: "dtsb-input", + inputCont: "dtsb-inputCont", + italic: "dtsb-italic", + joiner: "dtsb-joiner", + left: "dtsb-left", + notItalic: "dtsb-notItalic", + option: "dtsb-option", + right: "dtsb-right", + select: "dtsb-select", + value: "dtsb-value", + vertical: "dtsb-vertical" + } + /** + * Default initialisation function for select conditions + */ + Criteria.initSelect = function (that, fn, preDefined, array) { + if (preDefined === void 0) { + preDefined = null + } + if (array === void 0) { + array = false + } + var column = that.dom.data.children("option:selected").val() + var indexArray = that.s.dt.rows().indexes().toArray() + var fastData = that.s.dt.settings()[0].fastData + that.dom.valueTitle.prop("selected", true) + // Declare select element to be used with all of the default classes and listeners. + var el = $$3("<select/>") + .addClass(Criteria.classes.value) + .addClass(Criteria.classes.dropDown) + .addClass(Criteria.classes.italic) + .addClass(Criteria.classes.select) + .append(that.dom.valueTitle) + .on("change.dtsb", function () { + $$3(this).removeClass(Criteria.classes.italic) + fn(that, this) + }) + if (that.c.greyscale) { + el.addClass(Criteria.classes.greyscale) + } + var added = [] + var options = [] + // Add all of the options from the table to the select element. + // Only add one option for each possible value + for (var _i = 0, indexArray_1 = indexArray; _i < indexArray_1.length; _i++) { + var index = indexArray_1[_i] + var filter = fastData(index, column, typeof that.c.orthogonal === "string" ? that.c.orthogonal : that.c.orthogonal.search) + var value = { + filter: + typeof filter === "string" + ? filter.replace(/[\r\n\u2028]/g, " ") // Need to replace certain characters to match search values + : filter, + index: index, + text: fastData(index, column, typeof that.c.orthogonal === "string" ? that.c.orthogonal : that.c.orthogonal.display) + } + // If we are dealing with an array type, either make sure we are working with arrays, or sort them + if (that.s.type === "array") { + value.filter = !Array.isArray(value.filter) ? [value.filter] : value.filter + value.text = !Array.isArray(value.text) ? [value.text] : value.text + } + // Function to add an option to the select element + var addOption = function (filt, text) { + if (that.s.type.includes("html") && filt !== null && typeof filt === "string") { + filt.replace(/(<([^>]+)>)/gi, "") + } + // Add text and value, stripping out any html if that is the column type + var opt = $$3("<option>", { + type: Array.isArray(filt) ? "Array" : "String", + value: filt + }) + .data("sbv", filt) + .addClass(that.classes.option) + .addClass(that.classes.notItalic) + // Have to add the text this way so that special html characters are not escaped - &amp; etc. + .html(typeof text === "string" ? text.replace(/(<([^>]+)>)/gi, "") : text) + var val = opt.val() + // Check that this value has not already been added + if (added.indexOf(val) === -1) { + added.push(val) + options.push(opt) + if (preDefined !== null && Array.isArray(preDefined[0])) { + preDefined[0] = preDefined[0].sort().join(",") + } + // If this value was previously selected as indicated by preDefined, then select it again + if (preDefined !== null && opt.val() === preDefined[0]) { + opt.prop("selected", true) + el.removeClass(Criteria.classes.italic) + that.dom.valueTitle.removeProp("selected") + } + } + } + // If this is to add the individual values within the array we need to loop over the array + if (array) { + for (var i = 0; i < value.filter.length; i++) { + addOption(value.filter[i], value.text[i]) + } + } + // Otherwise the value that is in the cell is to be added + else { + addOption(value.filter, Array.isArray(value.text) ? value.text.join(", ") : value.text) + } + } + options.sort(function (a, b) { + if (that.s.type === "array" || that.s.type === "string" || that.s.type === "html") { + if (a.val() < b.val()) { + return -1 + } else if (a.val() > b.val()) { + return 1 + } else { + return 0 + } + } else if (that.s.type === "num" || that.s.type === "html-num") { + if (+a.val().replace(/(<([^>]+)>)/gi, "") < +b.val().replace(/(<([^>]+)>)/gi, "")) { + return -1 + } else if (+a.val().replace(/(<([^>]+)>)/gi, "") > +b.val().replace(/(<([^>]+)>)/gi, "")) { + return 1 + } else { + return 0 + } + } else if (that.s.type === "num-fmt" || that.s.type === "html-num-fmt") { + if (+a.val().replace(/[^0-9.]/g, "") < +b.val().replace(/[^0-9.]/g, "")) { + return -1 + } else if (+a.val().replace(/[^0-9.]/g, "") > +b.val().replace(/[^0-9.]/g, "")) { + return 1 + } else { + return 0 + } + } + }) + for (var _a = 0, options_1 = options; _a < options_1.length; _a++) { + var opt = options_1[_a] + el.append(opt) + } + return el + } + /** + * Default initialisation function for select conditions + */ + Criteria.initSelectSSP = function (that, fn, preDefined) { + if (preDefined === void 0) { + preDefined = null + } + that.dom.valueTitle.prop("selected", true) + // Declare select element to be used with all of the default classes and listeners. + var el = $$3("<select/>") + .addClass(Criteria.classes.value) + .addClass(Criteria.classes.dropDown) + .addClass(Criteria.classes.italic) + .addClass(Criteria.classes.select) + .append(that.dom.valueTitle) + .on("change.dtsb", function () { + $$3(this).removeClass(Criteria.classes.italic) + fn(that, this) + }) + if (that.c.greyscale) { + el.addClass(Criteria.classes.greyscale) + } + var options = [] + for (var _i = 0, _a = that.s.serverData[that.s.origData]; _i < _a.length; _i++) { + var option = _a[_i] + var value = option.value + var label = option.label + // Function to add an option to the select element + var addOption = function (filt, text) { + if (that.s.type.includes("html") && filt !== null && typeof filt === "string") { + filt.replace(/(<([^>]+)>)/gi, "") + } + // Add text and value, stripping out any html if that is the column type + var opt = $$3("<option>", { + type: Array.isArray(filt) ? "Array" : "String", + value: filt + }) + .data("sbv", filt) + .addClass(that.classes.option) + .addClass(that.classes.notItalic) + // Have to add the text this way so that special html characters are not escaped - &amp; etc. + .html(typeof text === "string" ? text.replace(/(<([^>]+)>)/gi, "") : text) + options.push(opt) + // If this value was previously selected as indicated by preDefined, then select it again + if (preDefined !== null && opt.val() === preDefined[0]) { + opt.prop("selected", true) + el.removeClass(Criteria.classes.italic) + that.dom.valueTitle.removeProp("selected") + } + } + addOption(value, label) + } + for (var _b = 0, options_2 = options; _b < options_2.length; _b++) { + var opt = options_2[_b] + el.append(opt) + } + return el + } + /** + * Default initialisation function for select array conditions + * + * This exists because there needs to be different select functionality for contains/without and equals/not + */ + Criteria.initSelectArray = function (that, fn, preDefined) { + if (preDefined === void 0) { + preDefined = null + } + return Criteria.initSelect(that, fn, preDefined, true) + } + /** + * Default initialisation function for input conditions + */ + Criteria.initInput = function (that, fn, preDefined) { + if (preDefined === void 0) { + preDefined = null + } + // Declare the input element + var searchDelay = that.s.dt.settings()[0].searchDelay + var el = $$3("<input/>") + .addClass(Criteria.classes.value) + .addClass(Criteria.classes.input) + .on( + "input.dtsb keypress.dtsb", + that._throttle( + function (e) { + var code = e.keyCode || e.which + return fn(that, this, code) + }, + searchDelay === null ? 100 : searchDelay + ) + ) + if (that.c.greyscale) { + el.addClass(Criteria.classes.greyscale) + } + // If there is a preDefined value then add it + if (preDefined !== null) { + el.val(preDefined[0]) + } + // This is add responsive functionality to the logic button without redrawing everything else + that.s.dt.one("draw.dtsb", function () { + that.s.topGroup.trigger("dtsb-redrawLogic") + }) + return el + } + /** + * Default initialisation function for conditions requiring 2 inputs + */ + Criteria.init2Input = function (that, fn, preDefined) { + if (preDefined === void 0) { + preDefined = null + } + // Declare all of the necessary jQuery elements + var searchDelay = that.s.dt.settings()[0].searchDelay + var els = [ + $$3("<input/>") + .addClass(Criteria.classes.value) + .addClass(Criteria.classes.input) + .on( + "input.dtsb keypress.dtsb", + that._throttle( + function (e) { + var code = e.keyCode || e.which + return fn(that, this, code) + }, + searchDelay === null ? 100 : searchDelay + ) + ), + $$3("<span>").addClass(that.classes.joiner).html(that.s.dt.i18n("searchBuilder.valueJoiner", that.c.i18n.valueJoiner)), + $$3("<input/>") + .addClass(Criteria.classes.value) + .addClass(Criteria.classes.input) + .on( + "input.dtsb keypress.dtsb", + that._throttle( + function (e) { + var code = e.keyCode || e.which + return fn(that, this, code) + }, + searchDelay === null ? 100 : searchDelay + ) + ) + ] + if (that.c.greyscale) { + els[0].addClass(Criteria.classes.greyscale) + els[2].addClass(Criteria.classes.greyscale) + } + // If there is a preDefined value then add it + if (preDefined !== null) { + els[0].val(preDefined[0]) + els[2].val(preDefined[1]) + } + // This is add responsive functionality to the logic button without redrawing everything else + that.s.dt.one("draw.dtsb", function () { + that.s.topGroup.trigger("dtsb-redrawLogic") + }) + return els + } + /** + * Default initialisation function for date conditions + */ + Criteria.initDate = function (that, fn, preDefined) { + if (preDefined === void 0) { + preDefined = null + } + var searchDelay = that.s.dt.settings()[0].searchDelay + var i18n = that.s.dt.i18n("datetime", {}) + // Declare date element using DataTables dateTime plugin + var el = $$3("<input/>") + .addClass(Criteria.classes.value) + .addClass(Criteria.classes.input) + .dtDateTime({ + attachTo: "input", + format: that.s.dateFormat ? that.s.dateFormat : undefined, + i18n: i18n + }) + .on( + "change.dtsb", + that._throttle( + function () { + return fn(that, this) + }, + searchDelay === null ? 100 : searchDelay + ) + ) + .on("input.dtsb keypress.dtsb", function (e) { + that._throttle( + function () { + var code = e.keyCode || e.which + return fn(that, this, code) + }, + searchDelay === null ? 100 : searchDelay + ) + }) + if (that.c.greyscale) { + el.addClass(Criteria.classes.greyscale) + } + // If there is a preDefined value then add it + if (preDefined !== null) { + el.val(preDefined[0]) + } + // This is add responsive functionality to the logic button without redrawing everything else + that.s.dt.one("draw.dtsb", function () { + that.s.topGroup.trigger("dtsb-redrawLogic") + }) + return el + } + Criteria.initNoValue = function (that) { + // This is add responsive functionality to the logic button without redrawing everything else + that.s.dt.one("draw.dtsb", function () { + that.s.topGroup.trigger("dtsb-redrawLogic") + }) + return [] + } + Criteria.init2Date = function (that, fn, preDefined) { + var _this = this + if (preDefined === void 0) { + preDefined = null + } + var searchDelay = that.s.dt.settings()[0].searchDelay + var i18n = that.s.dt.i18n("datetime", {}) + // Declare all of the date elements that are required using DataTables dateTime plugin + var els = [ + $$3("<input/>") + .addClass(Criteria.classes.value) + .addClass(Criteria.classes.input) + .dtDateTime({ + attachTo: "input", + format: that.s.dateFormat ? that.s.dateFormat : undefined, + i18n: i18n + }) + .on( + "change.dtsb", + searchDelay !== null + ? DataTable.util.throttle(function () { + return fn(that, this) + }, searchDelay) + : function () { + fn(that, _this) + } + ) + .on("input.dtsb keypress.dtsb", function (e) { + DataTable.util.throttle( + function () { + var code = e.keyCode || e.which + return fn(that, this, code) + }, + searchDelay === null ? 0 : searchDelay + ) + }), + $$3("<span>").addClass(that.classes.joiner).html(that.s.dt.i18n("searchBuilder.valueJoiner", that.c.i18n.valueJoiner)), + $$3("<input/>") + .addClass(Criteria.classes.value) + .addClass(Criteria.classes.input) + .dtDateTime({ + attachTo: "input", + format: that.s.dateFormat ? that.s.dateFormat : undefined, + i18n: i18n + }) + .on( + "change.dtsb", + searchDelay !== null + ? DataTable.util.throttle(function () { + return fn(that, this) + }, searchDelay) + : function () { + fn(that, _this) + } + ) + .on( + "input.dtsb keypress.dtsb", + !that.c.enterSearch && !(that.s.dt.settings()[0].oInit.search !== undefined && that.s.dt.settings()[0].oInit.search["return"]) && searchDelay !== null + ? DataTable.util.throttle(function () { + return fn(that, this) + }, searchDelay) + : function (e) { + var code = e.keyCode || e.which + fn(that, _this, code) + } + ) + ] + if (that.c.greyscale) { + els[0].addClass(Criteria.classes.greyscale) + els[2].addClass(Criteria.classes.greyscale) + } + // If there are and preDefined values then add them + if (preDefined !== null && preDefined.length > 0) { + els[0].val(preDefined[0]) + els[2].val(preDefined[1]) + } + // This is add responsive functionality to the logic button without redrawing everything else + that.s.dt.one("draw.dtsb", function () { + that.s.topGroup.trigger("dtsb-redrawLogic") + }) + return els + } + /** + * Default function for select elements to validate condition + */ + Criteria.isInputValidSelect = function (el) { + var allFilled = true + // Check each element to make sure that the selections are valid + for (var _i = 0, el_1 = el; _i < el_1.length; _i++) { + var element = el_1[_i] + if ( + element.children("option:selected").length === element.children("option").length - element.children("option." + Criteria.classes.notItalic).length && + element.children("option:selected").length === 1 && + element.children("option:selected")[0] === element.children("option")[0] + ) { + allFilled = false + } + } + return allFilled + } + /** + * Default function for input and date elements to validate condition + */ + Criteria.isInputValidInput = function (el) { + var allFilled = true + // Check each element to make sure that the inputs are valid + for (var _i = 0, el_2 = el; _i < el_2.length; _i++) { + var element = el_2[_i] + if (element.is("input") && element.val().length === 0) { + allFilled = false + } + } + return allFilled + } + /** + * Default function for getting select conditions + */ + Criteria.inputValueSelect = function (el) { + var values = [] + // Go through the select elements and push each selected option to the return array + for (var _i = 0, el_3 = el; _i < el_3.length; _i++) { + var element = el_3[_i] + if (element.is("select")) { + values.push(Criteria._escapeHTML(element.children("option:selected").data("sbv"))) + } + } + return values + } + /** + * Default function for getting input conditions + */ + Criteria.inputValueInput = function (el) { + var values = [] + // Go through the input elements and push each value to the return array + for (var _i = 0, el_4 = el; _i < el_4.length; _i++) { + var element = el_4[_i] + if (element.is("input")) { + values.push(Criteria._escapeHTML(element.val())) + } + } + return values + } + /** + * Function that is run on each element as a call back when a search should be triggered + */ + Criteria.updateListener = function (that, el, code) { + // When the value is changed the criteria is now complete so can be included in searches + // Get the condition from the map based on the key that has been selected for the condition + var condition = that.s.conditions[that.s.condition] + var i + that.s.filled = condition.isInputValid(that.dom.value, that) + that.s.value = condition.inputValue(that.dom.value, that) + if (!that.s.filled) { + if ((!that.c.enterSearch && !(that.s.dt.settings()[0].oInit.search !== undefined && that.s.dt.settings()[0].oInit.search["return"])) || code === 13) { + that.doSearch() + } + return + } + if (!Array.isArray(that.s.value)) { + that.s.value = [that.s.value] + } + for (i = 0; i < that.s.value.length; i++) { + // If the value is an array we need to sort it + if (Array.isArray(that.s.value[i])) { + that.s.value[i].sort() + } + } + // Take note of the cursor position so that we can refocus there later + var idx = null + var cursorPos = null + for (i = 0; i < that.dom.value.length; i++) { + if (el === that.dom.value[i][0]) { + idx = i + if (el.selectionStart !== undefined) { + cursorPos = el.selectionStart + } + } + } + if ((!that.c.enterSearch && !(that.s.dt.settings()[0].oInit.search !== undefined && that.s.dt.settings()[0].oInit.search["return"])) || code === 13) { + // Trigger a search + that.doSearch() + } + // Refocus the element and set the correct cursor position + if (idx !== null) { + that.dom.value[idx].removeClass(that.classes.italic) + that.dom.value[idx].focus() + if (cursorPos !== null) { + that.dom.value[idx][0].setSelectionRange(cursorPos, cursorPos) + } + } + } + // The order of the conditions will make eslint sad :( + // Has to be in this order so that they are displayed correctly in select elements + // Also have to disable member ordering for this as the private methods used are not yet declared otherwise + Criteria.dateConditions = { + "=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.equals", i18n.conditions.date.equals) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + value = value.replace(/(\/|-|,)/g, "-") + return value === comparison[0] + } + }, + "!=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.not", i18n.conditions.date.not) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + value = value.replace(/(\/|-|,)/g, "-") + return value !== comparison[0] + } + }, + "<": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.before", i18n.conditions.date.before) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + value = value.replace(/(\/|-|,)/g, "-") + return value < comparison[0] + } + }, + ">": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.after", i18n.conditions.date.after) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + value = value.replace(/(\/|-|,)/g, "-") + return value > comparison[0] + } + }, + between: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.between", i18n.conditions.date.between) + }, + init: Criteria.init2Date, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + value = value.replace(/(\/|-|,)/g, "-") + if (comparison[0] < comparison[1]) { + return comparison[0] <= value && value <= comparison[1] + } else { + return comparison[1] <= value && value <= comparison[0] + } + } + }, + "!between": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.notBetween", i18n.conditions.date.notBetween) + }, + init: Criteria.init2Date, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + value = value.replace(/(\/|-|,)/g, "-") + if (comparison[0] < comparison[1]) { + return !(comparison[0] <= value && value <= comparison[1]) + } else { + return !(comparison[1] <= value && value <= comparison[0]) + } + } + }, + null: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.empty", i18n.conditions.date.empty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return value === null || value === undefined || value.length === 0 + } + }, + "!null": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.notEmpty", i18n.conditions.date.notEmpty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return !(value === null || value === undefined || value.length === 0) + } + } + } + // The order of the conditions will make eslint sad :( + // Has to be in this order so that they are displayed correctly in select elements + // Also have to disable member ordering for this as the private methods used are not yet declared otherwise + Criteria.momentDateConditions = { + "=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.equals", i18n.conditions.date.equals) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + return moment()(value, that.s.dateFormat).valueOf() === moment()(comparison[0], that.s.dateFormat).valueOf() + } + }, + "!=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.not", i18n.conditions.date.not) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + return moment()(value, that.s.dateFormat).valueOf() !== moment()(comparison[0], that.s.dateFormat).valueOf() + } + }, + "<": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.before", i18n.conditions.date.before) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + return moment()(value, that.s.dateFormat).valueOf() < moment()(comparison[0], that.s.dateFormat).valueOf() + } + }, + ">": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.after", i18n.conditions.date.after) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + return moment()(value, that.s.dateFormat).valueOf() > moment()(comparison[0], that.s.dateFormat).valueOf() + } + }, + between: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.between", i18n.conditions.date.between) + }, + init: Criteria.init2Date, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + var val = moment()(value, that.s.dateFormat).valueOf() + var comp0 = moment()(comparison[0], that.s.dateFormat).valueOf() + var comp1 = moment()(comparison[1], that.s.dateFormat).valueOf() + if (comp0 < comp1) { + return comp0 <= val && val <= comp1 + } else { + return comp1 <= val && val <= comp0 + } + } + }, + "!between": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.notBetween", i18n.conditions.date.notBetween) + }, + init: Criteria.init2Date, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + var val = moment()(value, that.s.dateFormat).valueOf() + var comp0 = moment()(comparison[0], that.s.dateFormat).valueOf() + var comp1 = moment()(comparison[1], that.s.dateFormat).valueOf() + if (comp0 < comp1) { + return !(+comp0 <= +val && +val <= +comp1) + } else { + return !(+comp1 <= +val && +val <= +comp0) + } + } + }, + null: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.empty", i18n.conditions.date.empty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return value === null || value === undefined || value.length === 0 + } + }, + "!null": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.notEmpty", i18n.conditions.date.notEmpty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return !(value === null || value === undefined || value.length === 0) + } + } + } + // The order of the conditions will make eslint sad :( + // Has to be in this order so that they are displayed correctly in select elements + // Also have to disable member ordering for this as the private methods used are not yet declared otherwise + Criteria.luxonDateConditions = { + "=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.equals", i18n.conditions.date.equals) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + return luxon().DateTime.fromFormat(value, that.s.dateFormat).ts === luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts + } + }, + "!=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.not", i18n.conditions.date.not) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + return luxon().DateTime.fromFormat(value, that.s.dateFormat).ts !== luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts + } + }, + "<": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.before", i18n.conditions.date.before) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + return luxon().DateTime.fromFormat(value, that.s.dateFormat).ts < luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts + } + }, + ">": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.after", i18n.conditions.date.after) + }, + init: Criteria.initDate, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + return luxon().DateTime.fromFormat(value, that.s.dateFormat).ts > luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts + } + }, + between: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.between", i18n.conditions.date.between) + }, + init: Criteria.init2Date, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + var val = luxon().DateTime.fromFormat(value, that.s.dateFormat).ts + var comp0 = luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts + var comp1 = luxon().DateTime.fromFormat(comparison[1], that.s.dateFormat).ts + if (comp0 < comp1) { + return comp0 <= val && val <= comp1 + } else { + return comp1 <= val && val <= comp0 + } + } + }, + "!between": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.notBetween", i18n.conditions.date.notBetween) + }, + init: Criteria.init2Date, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, that) { + var val = luxon().DateTime.fromFormat(value, that.s.dateFormat).ts + var comp0 = luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts + var comp1 = luxon().DateTime.fromFormat(comparison[1], that.s.dateFormat).ts + if (comp0 < comp1) { + return !(+comp0 <= +val && +val <= +comp1) + } else { + return !(+comp1 <= +val && +val <= +comp0) + } + } + }, + null: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.empty", i18n.conditions.date.empty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return value === null || value === undefined || value.length === 0 + } + }, + "!null": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.date.notEmpty", i18n.conditions.date.notEmpty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return !(value === null || value === undefined || value.length === 0) + } + } + } + // The order of the conditions will make eslint sad :( + // Has to be in this order so that they are displayed correctly in select elements + // Also have to disable member ordering for this as the private methods used are not yet declared otherwise + Criteria.numConditions = { + "=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.equals", i18n.conditions.number.equals) + }, + init: Criteria.initSelect, + inputValue: Criteria.inputValueSelect, + isInputValid: Criteria.isInputValidSelect, + search: function (value, comparison) { + return +value === +comparison[0] + } + }, + "!=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.not", i18n.conditions.number.not) + }, + init: Criteria.initSelect, + inputValue: Criteria.inputValueSelect, + isInputValid: Criteria.isInputValidSelect, + search: function (value, comparison) { + return +value !== +comparison[0] + } + }, + "<": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.lt", i18n.conditions.number.lt) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + return +value < +comparison[0] + } + }, + "<=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.lte", i18n.conditions.number.lte) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + return +value <= +comparison[0] + } + }, + ">=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.gte", i18n.conditions.number.gte) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + return +value >= +comparison[0] + } + }, + ">": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.gt", i18n.conditions.number.gt) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + return +value > +comparison[0] + } + }, + between: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.between", i18n.conditions.number.between) + }, + init: Criteria.init2Input, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + if (+comparison[0] < +comparison[1]) { + return +comparison[0] <= +value && +value <= +comparison[1] + } else { + return +comparison[1] <= +value && +value <= +comparison[0] + } + } + }, + "!between": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.notBetween", i18n.conditions.number.notBetween) + }, + init: Criteria.init2Input, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + if (+comparison[0] < +comparison[1]) { + return !(+comparison[0] <= +value && +value <= +comparison[1]) + } else { + return !(+comparison[1] <= +value && +value <= +comparison[0]) + } + } + }, + null: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.empty", i18n.conditions.number.empty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return value === null || value === undefined || value.length === 0 + } + }, + "!null": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.notEmpty", i18n.conditions.number.notEmpty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return !(value === null || value === undefined || value.length === 0) + } + } + } + // The order of the conditions will make eslint sad :( + // Has to be in this order so that they are displayed correctly in select elements + // Also have to disable member ordering for this as the private methods used are not yet declared otherwise + Criteria.numFmtConditions = { + "=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.equals", i18n.conditions.number.equals) + }, + init: Criteria.initSelect, + inputValue: Criteria.inputValueSelect, + isInputValid: Criteria.isInputValidSelect, + search: function (value, comparison, criteria) { + return criteria.parseNumber(value) === criteria.parseNumber(comparison[0]) + } + }, + "!=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.not", i18n.conditions.number.not) + }, + init: Criteria.initSelect, + inputValue: Criteria.inputValueSelect, + isInputValid: Criteria.isInputValidSelect, + search: function (value, comparison, criteria) { + return criteria.parseNumber(value) !== criteria.parseNumber(comparison[0]) + } + }, + "<": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.lt", i18n.conditions.number.lt) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, criteria) { + return criteria.parseNumber(value) < criteria.parseNumber(comparison[0]) + } + }, + "<=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.lte", i18n.conditions.number.lte) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, criteria) { + return criteria.parseNumber(value) <= criteria.parseNumber(comparison[0]) + } + }, + ">=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.gte", i18n.conditions.number.gte) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, criteria) { + return criteria.parseNumber(value) >= criteria.parseNumber(comparison[0]) + } + }, + ">": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.gt", i18n.conditions.number.gt) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, criteria) { + return criteria.parseNumber(value) > criteria.parseNumber(comparison[0]) + } + }, + between: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.between", i18n.conditions.number.between) + }, + init: Criteria.init2Input, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, criteria) { + var val = criteria.parseNumber(value) + var comp0 = criteria.parseNumber(comparison[0]) + var comp1 = criteria.parseNumber(comparison[1]) + if (+comp0 < +comp1) { + return +comp0 <= +val && +val <= +comp1 + } else { + return +comp1 <= +val && +val <= +comp0 + } + } + }, + "!between": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.notBetween", i18n.conditions.number.notBetween) + }, + init: Criteria.init2Input, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison, criteria) { + var val = criteria.parseNumber(value) + var comp0 = criteria.parseNumber(comparison[0]) + var comp1 = criteria.parseNumber(comparison[1]) + if (+comp0 < +comp1) { + return !(+comp0 <= +val && +val <= +comp1) + } else { + return !(+comp1 <= +val && +val <= +comp0) + } + } + }, + null: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.empty", i18n.conditions.number.empty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return value === null || value === undefined || value.length === 0 + } + }, + "!null": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.number.notEmpty", i18n.conditions.number.notEmpty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return !(value === null || value === undefined || value.length === 0) + } + } + } + // The order of the conditions will make eslint sad :( + // Has to be in this order so that they are displayed correctly in select elements + // Also have to disable member ordering for this as the private methods used are not yet declared otherwise + Criteria.stringConditions = { + "=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.string.equals", i18n.conditions.string.equals) + }, + init: Criteria.initSelect, + inputValue: Criteria.inputValueSelect, + isInputValid: Criteria.isInputValidSelect, + search: function (value, comparison) { + return value === comparison[0] + } + }, + "!=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.string.not", i18n.conditions.string.not) + }, + init: Criteria.initSelect, + inputValue: Criteria.inputValueSelect, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + return value !== comparison[0] + } + }, + starts: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.string.startsWith", i18n.conditions.string.startsWith) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + return value.toLowerCase().indexOf(comparison[0].toLowerCase()) === 0 + } + }, + "!starts": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.string.notStartsWith", i18n.conditions.string.notStartsWith) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + return value.toLowerCase().indexOf(comparison[0].toLowerCase()) !== 0 + } + }, + contains: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.string.contains", i18n.conditions.string.contains) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + return value.toLowerCase().includes(comparison[0].toLowerCase()) + } + }, + "!contains": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.string.notContains", i18n.conditions.string.notContains) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + return !value.toLowerCase().includes(comparison[0].toLowerCase()) + } + }, + ends: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.string.endsWith", i18n.conditions.string.endsWith) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + return value.toLowerCase().endsWith(comparison[0].toLowerCase()) + } + }, + "!ends": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.string.notEndsWith", i18n.conditions.string.notEndsWith) + }, + init: Criteria.initInput, + inputValue: Criteria.inputValueInput, + isInputValid: Criteria.isInputValidInput, + search: function (value, comparison) { + return !value.toLowerCase().endsWith(comparison[0].toLowerCase()) + } + }, + null: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.string.empty", i18n.conditions.string.empty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return value === null || value === undefined || value.length === 0 + } + }, + "!null": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.string.notEmpty", i18n.conditions.string.notEmpty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return !(value === null || value === undefined || value.length === 0) + } + } + } + // The order of the conditions will make eslint sad :( + // Also have to disable member ordering for this as the private methods used are not yet declared otherwise + Criteria.arrayConditions = { + contains: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.array.contains", i18n.conditions.array.contains) + }, + init: Criteria.initSelectArray, + inputValue: Criteria.inputValueSelect, + isInputValid: Criteria.isInputValidSelect, + search: function (value, comparison) { + return value.includes(comparison[0]) + } + }, + without: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.array.without", i18n.conditions.array.without) + }, + init: Criteria.initSelectArray, + inputValue: Criteria.inputValueSelect, + isInputValid: Criteria.isInputValidSelect, + search: function (value, comparison) { + return value.indexOf(comparison[0]) === -1 + } + }, + "=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.array.equals", i18n.conditions.array.equals) + }, + init: Criteria.initSelect, + inputValue: Criteria.inputValueSelect, + isInputValid: Criteria.isInputValidSelect, + search: function (value, comparison) { + if (value.length === comparison[0].length) { + for (var i = 0; i < value.length; i++) { + if (value[i] !== comparison[0][i]) { + return false + } + } + return true + } + return false + } + }, + "!=": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.array.not", i18n.conditions.array.not) + }, + init: Criteria.initSelect, + inputValue: Criteria.inputValueSelect, + isInputValid: Criteria.isInputValidSelect, + search: function (value, comparison) { + if (value.length === comparison[0].length) { + for (var i = 0; i < value.length; i++) { + if (value[i] !== comparison[0][i]) { + return true + } + } + return false + } + return true + } + }, + null: { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.array.empty", i18n.conditions.array.empty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return value === null || value === undefined || value.length === 0 + } + }, + "!null": { + conditionName: function (dt, i18n) { + return dt.i18n("searchBuilder.conditions.array.notEmpty", i18n.conditions.array.notEmpty) + }, + init: Criteria.initNoValue, + inputValue: function () { + return + }, + isInputValid: function () { + return true + }, + search: function (value) { + return value !== null && value !== undefined && value.length !== 0 + } + } + } + // eslint will be sad because we have to disable member ordering for this as the + // private static properties used are not yet declared otherwise + Criteria.defaults = { + columns: true, + conditions: { + array: Criteria.arrayConditions, + date: Criteria.dateConditions, + html: Criteria.stringConditions, + "html-num": Criteria.numConditions, + "html-num-fmt": Criteria.numFmtConditions, + luxon: Criteria.luxonDateConditions, + moment: Criteria.momentDateConditions, + num: Criteria.numConditions, + "num-fmt": Criteria.numFmtConditions, + string: Criteria.stringConditions + }, + depthLimit: false, + enterSearch: false, + filterChanged: undefined, + greyscale: false, + i18n: { + add: "Add Condition", + button: { + 0: "Search Builder", + _: "Search Builder (%d)" + }, + clearAll: "Clear All", + condition: "Condition", + data: "Data", + delete: "&times", + deleteTitle: "Delete filtering rule", + left: "<", + leftTitle: "Outdent criteria", + logicAnd: "And", + logicOr: "Or", + right: ">", + rightTitle: "Indent criteria", + search: "Search", + title: { + 0: "Custom Search Builder", + _: "Custom Search Builder (%d)" + }, + value: "Value", + valueJoiner: "and" + }, + liveSearch: true, + logic: "AND", + orthogonal: { + display: "display", + search: "filter" + }, + preDefined: false + } + return Criteria + })() + + var $$2 + /** + * Sets the value of jQuery for use in the file + * + * @param jq the instance of jQuery to be set + */ + function setJQuery$1(jq) { + $$2 = jq + jq.fn.dataTable + } + /** + * The Group class is used within SearchBuilder to represent a group of criteria + */ + var Group = /** @class */ (function () { + function Group(table, opts, topGroup, index, isChild, depth, serverData) { + if (index === void 0) { + index = 0 + } + if (isChild === void 0) { + isChild = false + } + if (depth === void 0) { + depth = 1 + } + if (serverData === void 0) { + serverData = undefined + } + this.classes = $$2.extend(true, {}, Group.classes) + // Get options from user + this.c = $$2.extend(true, {}, Group.defaults, opts) + this.s = { + criteria: [], + depth: depth, + dt: table, + index: index, + isChild: isChild, + logic: undefined, + opts: opts, + preventRedraw: false, + serverData: serverData, + toDrop: undefined, + topGroup: topGroup + } + this.dom = { + add: $$2("<button/>").addClass(this.classes.add).addClass(this.classes.button).attr("type", "button"), + clear: $$2("<button>&times</button>").addClass(this.classes.button).addClass(this.classes.clearGroup).attr("type", "button"), + container: $$2("<div/>").addClass(this.classes.group), + logic: $$2("<button><div/></button>").addClass(this.classes.logic).addClass(this.classes.button).attr("type", "button"), + logicContainer: $$2("<div/>").addClass(this.classes.logicContainer), + search: $$2("<button/>").addClass(this.classes.search).addClass(this.classes.button).attr("type", "button").css("display", "none") + } + // A reference to the top level group is maintained throughout any subgroups and criteria that may be created + if (this.s.topGroup === undefined) { + this.s.topGroup = this.dom.container + } + this._setup() + return this + } + /** + * Destroys the groups buttons, clears the internal criteria and removes it from the dom + */ + Group.prototype.destroy = function () { + // Turn off listeners + this.dom.add.off(".dtsb") + this.dom.logic.off(".dtsb") + this.dom.search.off(".dtsb") + // Trigger event for groups at a higher level to pick up on + this.dom.container.trigger("dtsb-destroy").remove() + this.s.criteria = [] + } + /** + * Gets the details required to rebuild the group + */ + // Eslint upset at empty object but needs to be done + Group.prototype.getDetails = function (deFormatDates) { + if (deFormatDates === void 0) { + deFormatDates = false + } + if (this.s.criteria.length === 0) { + return {} + } + var details = { + criteria: [], + logic: this.s.logic + } + // NOTE here crit could be either a subgroup or a criteria + for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { + var crit = _a[_i] + details.criteria.push(crit.criteria.getDetails(deFormatDates)) + } + return details + } + /** + * Getter for the node for the container of the group + * + * @returns Node for the container of the group + */ + Group.prototype.getNode = function () { + return this.dom.container + } + /** + * Rebuilds the group based upon the details passed in + * + * @param loadedDetails the details required to rebuild the group + */ + Group.prototype.rebuild = function (loadedDetails) { + var crit + // If no criteria are stored then just return + if (loadedDetails.criteria === undefined || loadedDetails.criteria === null || (Array.isArray(loadedDetails.criteria) && loadedDetails.criteria.length === 0)) { + return + } + this.s.logic = loadedDetails.logic + this.dom.logic + .children() + .first() + .html(this.s.logic === "OR" ? this.s.dt.i18n("searchBuilder.logicOr", this.c.i18n.logicOr) : this.s.dt.i18n("searchBuilder.logicAnd", this.c.i18n.logicAnd)) + // Add all of the criteria, be it a sub group or a criteria + if (Array.isArray(loadedDetails.criteria)) { + for (var _i = 0, _a = loadedDetails.criteria; _i < _a.length; _i++) { + crit = _a[_i] + if (crit.logic !== undefined) { + this._addPrevGroup(crit) + } else if (crit.logic === undefined) { + this._addPrevCriteria(crit) + } + } + } + // For all of the criteria children, update the arrows incase they require changing and set the listeners + for (var _b = 0, _c = this.s.criteria; _b < _c.length; _b++) { + crit = _c[_b] + if (crit.criteria instanceof Criteria) { + crit.criteria.updateArrows(this.s.criteria.length > 1) + this._setCriteriaListeners(crit.criteria) + } + } + } + /** + * Redraws the Contents of the searchBuilder Groups and Criteria + */ + Group.prototype.redrawContents = function () { + if (this.s.preventRedraw) { + return + } + // Clear the container out and add the basic elements + this.dom.container.children().detach() + this.dom.container.append(this.dom.logicContainer).append(this.dom.add) + if (!this.c.liveSearch) { + this.dom.container.append(this.dom.search) + } + // Sort the criteria by index so that they appear in the correct order + this.s.criteria.sort(function (a, b) { + if (a.criteria.s.index < b.criteria.s.index) { + return -1 + } else if (a.criteria.s.index > b.criteria.s.index) { + return 1 + } + return 0 + }) + this.setListeners() + for (var i = 0; i < this.s.criteria.length; i++) { + var crit = this.s.criteria[i].criteria + if (crit instanceof Criteria) { + // Reset the index to the new value + this.s.criteria[i].index = i + this.s.criteria[i].criteria.s.index = i + // Add to the group + this.s.criteria[i].criteria.dom.container.insertBefore(this.dom.add) + // Set listeners for various points + this._setCriteriaListeners(crit) + this.s.criteria[i].criteria.s.preventRedraw = this.s.preventRedraw + this.s.criteria[i].criteria.rebuild(this.s.criteria[i].criteria.getDetails()) + this.s.criteria[i].criteria.s.preventRedraw = false + } else if (crit instanceof Group && crit.s.criteria.length > 0) { + // Reset the index to the new value + this.s.criteria[i].index = i + this.s.criteria[i].criteria.s.index = i + // Add the sub group to the group + this.s.criteria[i].criteria.dom.container.insertBefore(this.dom.add) + // Redraw the contents of the group + crit.s.preventRedraw = this.s.preventRedraw + crit.redrawContents() + crit.s.preventRedraw = false + this._setGroupListeners(crit) + } else { + // The group is empty so remove it + this.s.criteria.splice(i, 1) + i-- + } + } + this.setupLogic() + } + /** + * Resizes the logic button only rather than the entire dom. + */ + Group.prototype.redrawLogic = function () { + for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { + var crit = _a[_i] + if (crit.criteria instanceof Group) { + crit.criteria.redrawLogic() + } + } + this.setupLogic() + } + /** + * Search method, checking the row data against the criteria in the group + * + * @param rowData The row data to be compared + * @returns boolean The result of the search + */ + Group.prototype.search = function (rowData, rowIdx) { + if (this.s.logic === "AND") { + return this._andSearch(rowData, rowIdx) + } else if (this.s.logic === "OR") { + return this._orSearch(rowData, rowIdx) + } + return true + } + /** + * Locates the groups logic button to the correct location on the page + */ + Group.prototype.setupLogic = function () { + // Remove logic button + this.dom.logicContainer.remove() + this.dom.clear.remove() + // If there are no criteria in the group then keep the logic removed and return + if (this.s.criteria.length < 1) { + if (!this.s.isChild) { + this.dom.container.trigger("dtsb-destroy") + // Set criteria left margin + this.dom.container.css("margin-left", 0) + } + this.dom.search.css("display", "none") + return + } + this.dom.clear.height("0px") + this.dom.logicContainer.append(this.dom.clear) + if (!this.s.isChild) { + this.dom.search.css("display", "inline-block") + } + // Prepend logic button + this.dom.container.prepend(this.dom.logicContainer) + for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { + var crit = _a[_i] + if (crit.criteria instanceof Criteria) { + crit.criteria.setupButtons() + } + } + // Set width, take 2 for the border + var height = this.dom.container.outerHeight() - 1 + this.dom.logicContainer.width(height) + this._setLogicListener() + // Set criteria left margin + this.dom.container.css("margin-left", this.dom.logicContainer.outerHeight(true)) + var logicOffset = this.dom.logicContainer.offset() + // Set horizontal alignment + var currentLeft = logicOffset.left + var groupLeft = this.dom.container.offset().left + var shuffleLeft = currentLeft - groupLeft + var newPos = currentLeft - shuffleLeft - this.dom.logicContainer.outerHeight(true) + this.dom.logicContainer.offset({ left: newPos }) + // Set vertical alignment + var firstCrit = this.dom.logicContainer.next() + var currentTop = logicOffset.top + var firstTop = $$2(firstCrit).offset().top + var shuffleTop = currentTop - firstTop + var newTop = currentTop - shuffleTop + this.dom.logicContainer.offset({ top: newTop }) + this.dom.clear.outerHeight(this.dom.logicContainer.height()) + this._setClearListener() + } + /** + * Sets listeners on the groups elements + */ + Group.prototype.setListeners = function () { + var _this = this + this.dom.add.unbind("click") + this.dom.add.on("click.dtsb", function () { + // If this is the parent group then the logic button has not been added yet + if (!_this.s.isChild) { + _this.dom.container.prepend(_this.dom.logicContainer) + } + _this.addCriteria() + _this.dom.container.trigger("dtsb-add") + _this.s.dt.state.save() + return false + }) + this.dom.search.off("click.dtsb").on("click.dtsb", function () { + _this.s.dt.draw() + }) + for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { + var crit = _a[_i] + crit.criteria.setListeners() + } + this._setClearListener() + this._setLogicListener() + } + /** + * Adds a criteria to the group + * + * @param crit Instance of Criteria to be added to the group + */ + Group.prototype.addCriteria = function (crit) { + if (crit === void 0) { + crit = null + } + var index = crit === null ? this.s.criteria.length : crit.s.index + var criteria = new Criteria(this.s.dt, this.s.opts, this.s.topGroup, index, this.s.depth, this.s.serverData, this.c.liveSearch) + // If a Criteria has been passed in then set the values to continue that + if (crit !== null) { + criteria.c = crit.c + criteria.s = crit.s + criteria.s.depth = this.s.depth + criteria.classes = crit.classes + } + criteria.populate() + var inserted = false + for (var i = 0; i < this.s.criteria.length; i++) { + if (i === 0 && this.s.criteria[i].criteria.s.index > criteria.s.index) { + // Add the node for the criteria at the start of the group + criteria.getNode().insertBefore(this.s.criteria[i].criteria.dom.container) + inserted = true + } else if (i < this.s.criteria.length - 1 && this.s.criteria[i].criteria.s.index < criteria.s.index && this.s.criteria[i + 1].criteria.s.index > criteria.s.index) { + // Add the node for the criteria in the correct location + criteria.getNode().insertAfter(this.s.criteria[i].criteria.dom.container) + inserted = true + } + } + if (!inserted) { + criteria.getNode().insertBefore(this.dom.add) + } + // Add the details for this criteria to the array + this.s.criteria.push({ + criteria: criteria, + index: index + }) + this.s.criteria = this.s.criteria.sort(function (a, b) { + return a.criteria.s.index - b.criteria.s.index + }) + for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { + var opt = _a[_i] + if (opt.criteria instanceof Criteria) { + opt.criteria.updateArrows(this.s.criteria.length > 1) + } + } + this._setCriteriaListeners(criteria) + criteria.setListeners() + this.setupLogic() + } + /** + * Checks the group to see if it has any filled criteria + */ + Group.prototype.checkFilled = function () { + for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { + var crit = _a[_i] + if ((crit.criteria instanceof Criteria && crit.criteria.s.filled) || (crit.criteria instanceof Group && crit.criteria.checkFilled())) { + return true + } + } + return false + } + /** + * Gets the count for the number of criteria in this group and any sub groups + */ + Group.prototype.count = function () { + var count = 0 + for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { + var crit = _a[_i] + if (crit.criteria instanceof Group) { + count += crit.criteria.count() + } else { + count++ + } + } + return count + } + /** + * Rebuilds a sub group that previously existed + * + * @param loadedGroup The details of a group within this group + */ + Group.prototype._addPrevGroup = function (loadedGroup) { + var idx = this.s.criteria.length + var group = new Group(this.s.dt, this.c, this.s.topGroup, idx, true, this.s.depth + 1, this.s.serverData) + // Add the new group to the criteria array + this.s.criteria.push({ + criteria: group, + index: idx, + logic: group.s.logic + }) + // Rebuild it with the previous conditions for that group + group.rebuild(loadedGroup) + this.s.criteria[idx].criteria = group + this.s.topGroup.trigger("dtsb-redrawContents") + this._setGroupListeners(group) + } + /** + * Rebuilds a criteria of this group that previously existed + * + * @param loadedCriteria The details of a criteria within the group + */ + Group.prototype._addPrevCriteria = function (loadedCriteria) { + var idx = this.s.criteria.length + var criteria = new Criteria(this.s.dt, this.s.opts, this.s.topGroup, idx, this.s.depth, this.s.serverData) + criteria.populate() + // Add the new criteria to the criteria array + this.s.criteria.push({ + criteria: criteria, + index: idx + }) + // Rebuild it with the previous conditions for that criteria + criteria.s.preventRedraw = this.s.preventRedraw + criteria.rebuild(loadedCriteria) + criteria.s.preventRedraw = false + this.s.criteria[idx].criteria = criteria + if (!this.s.preventRedraw) { + this.s.topGroup.trigger("dtsb-redrawContents") + } + } + /** + * Checks And the criteria using AND logic + * + * @param rowData The row data to be checked against the search criteria + * @returns boolean The result of the AND search + */ + Group.prototype._andSearch = function (rowData, rowIdx) { + // If there are no criteria then return true for this group + if (this.s.criteria.length === 0) { + return true + } + for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { + var crit = _a[_i] + // If the criteria is not complete then skip it + if (crit.criteria instanceof Criteria && !crit.criteria.s.filled) { + continue + } + // Otherwise if a single one fails return false + else if (!crit.criteria.search(rowData, rowIdx)) { + return false + } + } + // If we get to here then everything has passed, so return true for the group + return true + } + /** + * Checks And the criteria using OR logic + * + * @param rowData The row data to be checked against the search criteria + * @returns boolean The result of the OR search + */ + Group.prototype._orSearch = function (rowData, rowIdx) { + // If there are no criteria in the group then return true + if (this.s.criteria.length === 0) { + return true + } + // This will check to make sure that at least one criteria in the group is complete + var filledfound = false + for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { + var crit = _a[_i] + if (crit.criteria instanceof Criteria && crit.criteria.s.filled) { + // A completed criteria has been found so set the flag + filledfound = true + // If the search passes then return true + if (crit.criteria.search(rowData, rowIdx)) { + return true + } + } else if (crit.criteria instanceof Group && crit.criteria.checkFilled()) { + filledfound = true + if (crit.criteria.search(rowData, rowIdx)) { + return true + } + } + } + // If we get here we need to return the inverse of filledfound, + // as if any have been found and we are here then none have passed + return !filledfound + } + /** + * Removes a criteria from the group + * + * @param criteria The criteria instance to be removed + */ + Group.prototype._removeCriteria = function (criteria, group) { + if (group === void 0) { + group = false + } + var i + // If removing a criteria and there is only then then just destroy the group + if (this.s.criteria.length <= 1 && this.s.isChild) { + this.destroy() + } else { + // Otherwise splice the given criteria out and redo the indexes + var last = void 0 + for (i = 0; i < this.s.criteria.length; i++) { + if (this.s.criteria[i].index === criteria.s.index && (!group || this.s.criteria[i].criteria instanceof Group)) { + last = i + } + } + // We want to remove the last element with the desired index, as its replacement will be inserted before it + if (last !== undefined) { + this.s.criteria.splice(last, 1) + } + for (i = 0; i < this.s.criteria.length; i++) { + this.s.criteria[i].index = i + this.s.criteria[i].criteria.s.index = i + } + } + } + /** + * Sets the listeners in group for a criteria + * + * @param criteria The criteria for the listeners to be set on + */ + Group.prototype._setCriteriaListeners = function (criteria) { + var _this = this + criteria.dom["delete"].unbind("click").on("click.dtsb", function () { + _this._removeCriteria(criteria) + criteria.dom.container.remove() + for (var _i = 0, _a = _this.s.criteria; _i < _a.length; _i++) { + var crit = _a[_i] + if (crit.criteria instanceof Criteria) { + crit.criteria.updateArrows(_this.s.criteria.length > 1) + } + } + criteria.destroy() + _this.s.dt.draw() + _this.s.topGroup.trigger("dtsb-redrawContents") + return false + }) + criteria.dom.right.unbind("click").on("click.dtsb", function () { + var idx = criteria.s.index + var group = new Group(_this.s.dt, _this.s.opts, _this.s.topGroup, criteria.s.index, true, _this.s.depth + 1, _this.s.serverData) + // Add the criteria that is to be moved to the new group + group.addCriteria(criteria) + // Update the details in the current groups criteria array + _this.s.criteria[idx].criteria = group + _this.s.criteria[idx].logic = "AND" + _this.s.topGroup.trigger("dtsb-redrawContents") + _this._setGroupListeners(group) + return false + }) + criteria.dom.left.unbind("click").on("click.dtsb", function () { + _this.s.toDrop = new Criteria(_this.s.dt, _this.s.opts, _this.s.topGroup, criteria.s.index, undefined, _this.s.serverData) + _this.s.toDrop.s = criteria.s + _this.s.toDrop.c = criteria.c + _this.s.toDrop.classes = criteria.classes + _this.s.toDrop.populate() + // The dropCriteria event mutates the reference to the index so need to store it + var index = _this.s.toDrop.s.index + _this.dom.container.trigger("dtsb-dropCriteria") + criteria.s.index = index + _this._removeCriteria(criteria) + // By tracking the top level group we can directly trigger a redraw on it, + // bubbling is also possible, but that is slow with deep levelled groups + _this.s.topGroup.trigger("dtsb-redrawContents") + _this.s.dt.draw() + return false + }) + } + /** + * Set's the listeners for the group clear button + */ + Group.prototype._setClearListener = function () { + var _this = this + this.dom.clear.unbind("click").on("click.dtsb", function () { + if (!_this.s.isChild) { + _this.dom.container.trigger("dtsb-clearContents") + return false + } + _this.destroy() + _this.s.topGroup.trigger("dtsb-redrawContents") + return false + }) + } + /** + * Sets listeners for sub groups of this group + * + * @param group The sub group that the listeners are to be set on + */ + Group.prototype._setGroupListeners = function (group) { + var _this = this + // Set listeners for the new group + group.dom.add.unbind("click").on("click.dtsb", function () { + _this.setupLogic() + _this.dom.container.trigger("dtsb-add") + return false + }) + group.dom.container.unbind("dtsb-add").on("dtsb-add.dtsb", function () { + _this.setupLogic() + _this.dom.container.trigger("dtsb-add") + return false + }) + group.dom.container.unbind("dtsb-destroy").on("dtsb-destroy.dtsb", function () { + _this._removeCriteria(group, true) + group.dom.container.remove() + _this.setupLogic() + return false + }) + group.dom.container.unbind("dtsb-dropCriteria").on("dtsb-dropCriteria.dtsb", function () { + var toDrop = group.s.toDrop + toDrop.s.index = group.s.index + toDrop.updateArrows(_this.s.criteria.length > 1) + _this.addCriteria(toDrop) + return false + }) + group.setListeners() + } + /** + * Sets up the Group instance, setting listeners and appending elements + */ + Group.prototype._setup = function () { + this.setListeners() + this.dom.add.html(this.s.dt.i18n("searchBuilder.add", this.c.i18n.add)) + this.dom.search.html(this.s.dt.i18n("searchBuilder.search", this.c.i18n.search)) + this.dom.logic + .children() + .first() + .html(this.c.logic === "OR" ? this.s.dt.i18n("searchBuilder.logicOr", this.c.i18n.logicOr) : this.s.dt.i18n("searchBuilder.logicAnd", this.c.i18n.logicAnd)) + this.s.logic = this.c.logic === "OR" ? "OR" : "AND" + if (this.c.greyscale) { + this.dom.logic.addClass(this.classes.greyscale) + } + this.dom.logicContainer.append(this.dom.logic).append(this.dom.clear) + // Only append the logic button immediately if this is a sub group, + // otherwise it will be prepended later when adding a criteria + if (this.s.isChild) { + this.dom.container.append(this.dom.logicContainer) + } + this.dom.container.append(this.dom.add) + if (!this.c.liveSearch) { + this.dom.container.append(this.dom.search) + } + } + /** + * Sets the listener for the logic button + */ + Group.prototype._setLogicListener = function () { + var _this = this + this.dom.logic.unbind("click").on("click.dtsb", function () { + _this._toggleLogic() + _this.s.dt.draw() + for (var _i = 0, _a = _this.s.criteria; _i < _a.length; _i++) { + var crit = _a[_i] + crit.criteria.setListeners() + } + }) + } + /** + * Toggles the logic for the group + */ + Group.prototype._toggleLogic = function () { + if (this.s.logic === "OR") { + this.s.logic = "AND" + this.dom.logic.children().first().html(this.s.dt.i18n("searchBuilder.logicAnd", this.c.i18n.logicAnd)) + } else if (this.s.logic === "AND") { + this.s.logic = "OR" + this.dom.logic.children().first().html(this.s.dt.i18n("searchBuilder.logicOr", this.c.i18n.logicOr)) + } + } + Group.version = "1.1.0" + Group.classes = { + add: "dtsb-add", + button: "dtsb-button", + clearGroup: "dtsb-clearGroup", + greyscale: "dtsb-greyscale", + group: "dtsb-group", + inputButton: "dtsb-iptbtn", + logic: "dtsb-logic", + logicContainer: "dtsb-logicContainer", + search: "dtsb-search" + } + Group.defaults = { + columns: true, + conditions: { + date: Criteria.dateConditions, + html: Criteria.stringConditions, + "html-num": Criteria.numConditions, + "html-num-fmt": Criteria.numFmtConditions, + luxon: Criteria.luxonDateConditions, + moment: Criteria.momentDateConditions, + num: Criteria.numConditions, + "num-fmt": Criteria.numFmtConditions, + string: Criteria.stringConditions + }, + depthLimit: false, + enterSearch: false, + filterChanged: undefined, + greyscale: false, + liveSearch: true, + i18n: { + add: "Add Condition", + button: { + 0: "Search Builder", + _: "Search Builder (%d)" + }, + clearAll: "Clear All", + condition: "Condition", + data: "Data", + delete: "&times", + deleteTitle: "Delete filtering rule", + left: "<", + leftTitle: "Outdent criteria", + logicAnd: "And", + logicOr: "Or", + right: ">", + rightTitle: "Indent criteria", + search: "Search", + title: { + 0: "Custom Search Builder", + _: "Custom Search Builder (%d)" + }, + value: "Value", + valueJoiner: "and" + }, + logic: "AND", + orthogonal: { + display: "display", + search: "filter" + }, + preDefined: false + } + return Group + })() + + var $$1 + var dataTable$1 + /** + * Sets the value of jQuery for use in the file + * + * @param jq the instance of jQuery to be set + */ + function setJQuery(jq) { + $$1 = jq + dataTable$1 = jq.fn.DataTable + } + /** + * SearchBuilder class for DataTables. + * Allows for complex search queries to be constructed and implemented on a DataTable + */ + var SearchBuilder = /** @class */ (function () { + function SearchBuilder(builderSettings, opts) { + var _this = this + // Check that the required version of DataTables is included + if (!dataTable$1 || !dataTable$1.versionCheck || !dataTable$1.versionCheck("2")) { + throw new Error("SearchBuilder requires DataTables 2 or newer") + } + var table = new dataTable$1.Api(builderSettings) + this.classes = $$1.extend(true, {}, SearchBuilder.classes) + // Get options from user + this.c = $$1.extend(true, {}, SearchBuilder.defaults, opts) + this.dom = { + clearAll: $$1('<button type="button">' + table.i18n("searchBuilder.clearAll", this.c.i18n.clearAll) + "</button>") + .addClass(this.classes.clearAll) + .addClass(this.classes.button) + .attr("type", "button"), + container: $$1("<div/>").addClass(this.classes.container), + title: $$1("<div/>").addClass(this.classes.title), + titleRow: $$1("<div/>").addClass(this.classes.titleRow), + topGroup: undefined + } + this.s = { + dt: table, + opts: opts, + search: undefined, + serverData: undefined, + topGroup: undefined + } + // If searchbuilder is already defined for this table then return + if (table.settings()[0]._searchBuilder !== undefined) { + return + } + table.settings()[0]._searchBuilder = this + // If using SSP we want to include the previous state in the very first server call + if (this.s.dt.page.info().serverSide) { + this.s.dt.on("preXhr.dtsb", function (e, settings, data) { + var loadedState = _this.s.dt.state.loaded() + if (loadedState && loadedState.searchBuilder) { + data.searchBuilder = _this._collapseArray(loadedState.searchBuilder) + } + }) + this.s.dt.on("xhr.dtsb", function (e, settings, json) { + if (json && json.searchBuilder && json.searchBuilder.options) { + _this.s.serverData = json.searchBuilder.options + } + }) + } + // Run the remaining setup when the table is initialised + if (this.s.dt.settings()[0]._bInitComplete) { + this._setUp() + } else { + table.one("init.dt", function () { + _this._setUp() + }) + } + return this + } + /** + * Gets the details required to rebuild the SearchBuilder as it currently is + */ + // eslint upset at empty object but that is what it is + SearchBuilder.prototype.getDetails = function (deFormatDates) { + if (deFormatDates === void 0) { + deFormatDates = false + } + return this.s.topGroup.getDetails(deFormatDates) + } + /** + * Getter for the node of the container for the searchBuilder + * + * @returns JQuery<HTMLElement> the node of the container + */ + SearchBuilder.prototype.getNode = function () { + return this.dom.container + } + /** + * Rebuilds the SearchBuilder to a state that is provided + * + * @param details The details required to perform a rebuild + */ + SearchBuilder.prototype.rebuild = function (details) { + this.dom.clearAll.click() + // If there are no details to rebuild then return + if (details === undefined || details === null) { + return this + } + this.s.topGroup.s.preventRedraw = true + this.s.topGroup.rebuild(details) + this.s.topGroup.s.preventRedraw = false + this._checkClear() + this._updateTitle(this.s.topGroup.count()) + this.s.topGroup.redrawContents() + this.s.dt.draw(false) + this.s.topGroup.setListeners() + return this + } + /** + * Applies the defaults to preDefined criteria + * + * @param preDef the array of criteria to be processed. + */ + SearchBuilder.prototype._applyPreDefDefaults = function (preDef) { + var _this = this + if (preDef.criteria !== undefined && preDef.logic === undefined) { + preDef.logic = "AND" + } + var _loop_1 = function (crit) { + // Apply the defaults to any further criteria + if (crit.criteria !== undefined) { + crit = this_1._applyPreDefDefaults(crit) + } else { + this_1.s.dt.columns().every(function (index) { + if (_this.s.dt.settings()[0].aoColumns[index].sTitle === crit.data) { + crit.dataIdx = index + } + }) + } + } + var this_1 = this + for (var _i = 0, _a = preDef.criteria; _i < _a.length; _i++) { + var crit = _a[_i] + _loop_1(crit) + } + return preDef + } + /** + * Set's up the SearchBuilder + */ + SearchBuilder.prototype._setUp = function (loadState) { + var _this = this + if (loadState === void 0) { + loadState = true + } + // Register an Api method for getting the column type. DataTables 2 has + // this built in + if (typeof this.s.dt.column().type !== "function") { + DataTable.Api.registerPlural("columns().types()", "column().type()", function () { + return this.iterator( + "column", + function (settings, column) { + return settings.aoColumns[column].sType + }, + 1 + ) + }) + } + // Check that DateTime is included, If not need to check if it could be used + // eslint-disable-next-line no-extra-parens + if (!dataTable$1.DateTime) { + var types = this.s.dt.columns().types().toArray() + if (types === undefined || types.includes(undefined) || types.includes(null)) { + types = [] + for (var _i = 0, _a = this.s.dt.settings()[0].aoColumns; _i < _a.length; _i++) { + var colInit = _a[_i] + types.push(colInit.searchBuilderType !== undefined ? colInit.searchBuilderType : colInit.sType) + } + } + var columnIdxs = this.s.dt.columns().toArray() + // If the column type is still unknown use the internal API to detect type + if (types === undefined || types.includes(undefined) || types.includes(null)) { + // This can only happen in DT1 - DT2 will do the invalidation of the type itself + if ($$1.fn.dataTable.ext.oApi) { + $$1.fn.dataTable.ext.oApi._fnColumnTypes(this.s.dt.settings()[0]) + } + types = this.s.dt.columns().types().toArray() + } + for (var i = 0; i < columnIdxs[0].length; i++) { + var column = columnIdxs[0][i] + var type = types[column] + if ( + // Check if this column can be filtered + (this.c.columns === true || (Array.isArray(this.c.columns) && this.c.columns.includes(i))) && + // Check if the type is one of the restricted types + (type.includes("date") || type.includes("moment") || type.includes("luxon")) + ) { + alert("SearchBuilder Requires DateTime when used with dates.") + throw new Error("SearchBuilder requires DateTime") + } + } + } + this.s.topGroup = new Group(this.s.dt, this.c, undefined, undefined, undefined, undefined, this.s.serverData) + this._setClearListener() + this.s.dt.on("stateSaveParams.dtsb", function (e, settings, data) { + data.searchBuilder = _this.getDetails() + if (!data.scroller) { + data.page = _this.s.dt.page() + } else { + data.start = _this.s.dt.state().start + } + }) + this.s.dt.on("stateLoadParams.dtsb", function (e, settings, data) { + _this.rebuild(data.searchBuilder) + }) + this._build() + this.s.dt.on("preXhr.dtsb", function (e, settings, data) { + if (_this.s.dt.page.info().serverSide) { + data.searchBuilder = _this._collapseArray(_this.getDetails(true)) + } + }) + this.s.dt.on("columns-reordered", function () { + _this.rebuild(_this.getDetails()) + }) + if (loadState) { + var loadedState = this.s.dt.state.loaded() + // If the loaded State is not null rebuild based on it for statesave + if (loadedState !== null && loadedState.searchBuilder !== undefined) { + this.s.topGroup.rebuild(loadedState.searchBuilder) + this.s.topGroup.dom.container.trigger("dtsb-redrawContents") + // If using SSP we want to restrict the amount of server calls that take place + // and this information will already have been processed + if (!this.s.dt.page.info().serverSide) { + if (loadedState.page) { + this.s.dt.page(loadedState.page).draw("page") + } else if (this.s.dt.scroller && loadedState.scroller) { + this.s.dt.scroller().scrollToRow(loadedState.scroller.topRow) + } + } + this.s.topGroup.setListeners() + } + // Otherwise load any predefined options + else if (this.c.preDefined !== false) { + this.c.preDefined = this._applyPreDefDefaults(this.c.preDefined) + this.rebuild(this.c.preDefined) + } + } + this._setEmptyListener() + this.s.dt.state.save() + } + SearchBuilder.prototype._collapseArray = function (criteria) { + if (criteria.logic === undefined) { + if (criteria.value !== undefined) { + criteria.value.sort(function (a, b) { + if (!isNaN(+a)) { + a = +a + b = +b + } + if (a < b) { + return -1 + } else if (b < a) { + return 1 + } else { + return 0 + } + }) + criteria.value1 = criteria.value[0] + criteria.value2 = criteria.value[1] + } + } else { + for (var i = 0; i < criteria.criteria.length; i++) { + criteria.criteria[i] = this._collapseArray(criteria.criteria[i]) + } + } + return criteria + } + /** + * Updates the title of the SearchBuilder + * + * @param count the number of filters in the SearchBuilder + */ + SearchBuilder.prototype._updateTitle = function (count) { + this.dom.title.html(this.s.dt.i18n("searchBuilder.title", this.c.i18n.title, count)) + } + /** + * Builds all of the dom elements together + */ + SearchBuilder.prototype._build = function () { + var _this = this + // Empty and setup the container + this.dom.clearAll.remove() + this.dom.container.empty() + var count = this.s.topGroup.count() + this._updateTitle(count) + this.dom.titleRow.append(this.dom.title) + this.dom.container.append(this.dom.titleRow) + this.dom.topGroup = this.s.topGroup.getNode() + this.dom.container.append(this.dom.topGroup) + this._setRedrawListener() + var tableNode = this.s.dt.table(0).node() + if (!$$1.fn.dataTable.ext.search.includes(this.s.search)) { + // Custom search function for SearchBuilder + this.s.search = function (settings, searchData, dataIndex) { + if (settings.nTable !== tableNode) { + return true + } + return _this.s.topGroup.search(searchData, dataIndex) + } + // Add SearchBuilder search function to the dataTables search array + $$1.fn.dataTable.ext.search.push(this.s.search) + } + this.s.dt.on("destroy.dtsb", function () { + _this.dom.container.remove() + _this.dom.clearAll.remove() + var searchIdx = $$1.fn.dataTable.ext.search.indexOf(_this.s.search) + while (searchIdx !== -1) { + $$1.fn.dataTable.ext.search.splice(searchIdx, 1) + searchIdx = $$1.fn.dataTable.ext.search.indexOf(_this.s.search) + } + _this.s.dt.off(".dtsb") + $$1(_this.s.dt.table().node()).off(".dtsb") + }) + } + /** + * Checks if the clearAll button should be added or not + */ + SearchBuilder.prototype._checkClear = function () { + if (this.s.topGroup.s.criteria.length > 0) { + this.dom.clearAll.insertAfter(this.dom.title) + this._setClearListener() + } else { + this.dom.clearAll.remove() + } + } + /** + * Update the count in the title/button + * + * @param count Number of filters applied + */ + SearchBuilder.prototype._filterChanged = function (count) { + var fn = this.c.filterChanged + if (typeof fn === "function") { + fn(count, this.s.dt.i18n("searchBuilder.button", this.c.i18n.button, count)) + } + } + /** + * Set the listener for the clear button + */ + SearchBuilder.prototype._setClearListener = function () { + var _this = this + this.dom.clearAll.unbind("click") + this.dom.clearAll.on("click.dtsb", function () { + _this.s.topGroup = new Group(_this.s.dt, _this.c, undefined, undefined, undefined, undefined, _this.s.serverData) + _this._build() + _this.s.dt.draw() + _this.s.topGroup.setListeners() + _this.dom.clearAll.remove() + _this._setEmptyListener() + _this._filterChanged(0) + return false + }) + } + /** + * Set the listener for the Redraw event + */ + SearchBuilder.prototype._setRedrawListener = function () { + var _this = this + this.s.topGroup.dom.container.unbind("dtsb-redrawContents") + this.s.topGroup.dom.container.on("dtsb-redrawContents.dtsb", function () { + _this._checkClear() + _this.s.topGroup.redrawContents() + _this.s.topGroup.setupLogic() + _this._setEmptyListener() + var count = _this.s.topGroup.count() + _this._updateTitle(count) + _this._filterChanged(count) + // If using SSP we want to restrict the amount of server calls that take place + // and this information will already have been processed + if (!_this.s.dt.page.info().serverSide) { + _this.s.dt.draw() + } + _this.s.dt.state.save() + }) + this.s.topGroup.dom.container.unbind("dtsb-redrawContents-noDraw") + this.s.topGroup.dom.container.on("dtsb-redrawContents-noDraw.dtsb", function () { + _this._checkClear() + _this.s.topGroup.s.preventRedraw = true + _this.s.topGroup.redrawContents() + _this.s.topGroup.s.preventRedraw = false + _this.s.topGroup.setupLogic() + _this._setEmptyListener() + var count = _this.s.topGroup.count() + _this._updateTitle(count) + _this._filterChanged(count) + }) + this.s.topGroup.dom.container.unbind("dtsb-redrawLogic") + this.s.topGroup.dom.container.on("dtsb-redrawLogic.dtsb", function () { + _this.s.topGroup.redrawLogic() + var count = _this.s.topGroup.count() + _this._updateTitle(count) + _this._filterChanged(count) + }) + this.s.topGroup.dom.container.unbind("dtsb-add") + this.s.topGroup.dom.container.on("dtsb-add.dtsb", function () { + var count = _this.s.topGroup.count() + _this._updateTitle(count) + _this._filterChanged(count) + _this._checkClear() + }) + this.s.dt.on("postEdit.dtsb postCreate.dtsb postRemove.dtsb", function () { + _this.s.topGroup.redrawContents() + }) + this.s.topGroup.dom.container.unbind("dtsb-clearContents") + this.s.topGroup.dom.container.on("dtsb-clearContents.dtsb", function () { + _this._setUp(false) + _this._filterChanged(0) + _this.s.dt.draw() + }) + } + /** + * Sets listeners to check whether clearAll should be added or removed + */ + SearchBuilder.prototype._setEmptyListener = function () { + var _this = this + this.s.topGroup.dom.add.on("click.dtsb", function () { + _this._checkClear() + }) + this.s.topGroup.dom.container.on("dtsb-destroy.dtsb", function () { + _this.dom.clearAll.remove() + }) + } + SearchBuilder.version = "1.8.0" + SearchBuilder.classes = { + button: "dtsb-button", + clearAll: "dtsb-clearAll", + container: "dtsb-searchBuilder", + inputButton: "dtsb-iptbtn", + title: "dtsb-title", + titleRow: "dtsb-titleRow" + } + SearchBuilder.defaults = { + columns: true, + conditions: { + date: Criteria.dateConditions, + html: Criteria.stringConditions, + "html-num": Criteria.numConditions, + "html-num-fmt": Criteria.numFmtConditions, + luxon: Criteria.luxonDateConditions, + moment: Criteria.momentDateConditions, + num: Criteria.numConditions, + "num-fmt": Criteria.numFmtConditions, + string: Criteria.stringConditions + }, + depthLimit: false, + enterSearch: false, + filterChanged: undefined, + greyscale: false, + liveSearch: true, + i18n: { + add: "Add Condition", + button: { + 0: "Search Builder", + _: "Search Builder (%d)" + }, + clearAll: "Clear All", + condition: "Condition", + conditions: { + array: { + contains: "Contains", + empty: "Empty", + equals: "Equals", + not: "Not", + notEmpty: "Not Empty", + without: "Without" + }, + date: { + after: "After", + before: "Before", + between: "Between", + empty: "Empty", + equals: "Equals", + not: "Not", + notBetween: "Not Between", + notEmpty: "Not Empty" + }, + // eslint-disable-next-line id-blacklist + number: { + between: "Between", + empty: "Empty", + equals: "Equals", + gt: "Greater Than", + gte: "Greater Than Equal To", + lt: "Less Than", + lte: "Less Than Equal To", + not: "Not", + notBetween: "Not Between", + notEmpty: "Not Empty" + }, + // eslint-disable-next-line id-blacklist + string: { + contains: "Contains", + empty: "Empty", + endsWith: "Ends With", + equals: "Equals", + not: "Not", + notContains: "Does Not Contain", + notEmpty: "Not Empty", + notEndsWith: "Does Not End With", + notStartsWith: "Does Not Start With", + startsWith: "Starts With" + } + }, + data: "Data", + delete: "&times", + deleteTitle: "Delete filtering rule", + left: "<", + leftTitle: "Outdent criteria", + logicAnd: "And", + logicOr: "Or", + right: ">", + rightTitle: "Indent criteria", + search: "Search", + title: { + 0: "Custom Search Builder", + _: "Custom Search Builder (%d)" + }, + value: "Value", + valueJoiner: "and" + }, + logic: "AND", + orthogonal: { + display: "display", + search: "filter" + }, + preDefined: false + } + return SearchBuilder + })() + + /*! SearchBuilder 1.8.0 + * ©SpryMedia Ltd - datatables.net/license/mit + */ + setJQuery($) + setJQuery$1($) + setJQuery$2($) + var dataTable = $.fn.dataTable + // eslint-disable-next-line no-extra-parens + DataTable.SearchBuilder = SearchBuilder + // eslint-disable-next-line no-extra-parens + dataTable.SearchBuilder = SearchBuilder + // eslint-disable-next-line no-extra-parens + DataTable.Group = Group + // eslint-disable-next-line no-extra-parens + dataTable.Group = Group + // eslint-disable-next-line no-extra-parens + DataTable.Criteria = Criteria + // eslint-disable-next-line no-extra-parens + dataTable.Criteria = Criteria + // eslint-disable-next-line no-extra-parens + var apiRegister = DataTable.Api.register + // Set up object for plugins + DataTable.ext.searchBuilder = { + conditions: {} + } + DataTable.ext.buttons.searchBuilder = { + action: function (e, dt, node, config) { + this.popover(config._searchBuilder.getNode(), { + align: "container", + span: "container" + }) + var topGroup = config._searchBuilder.s.topGroup + // Need to redraw the contents to calculate the correct positions for the elements + if (topGroup !== undefined) { + topGroup.dom.container.trigger("dtsb-redrawContents-noDraw") + } + if (topGroup.s.criteria.length === 0) { + $("." + $.fn.dataTable.Group.classes.add.replace(/ /g, ".")).click() + } + }, + config: {}, + init: function (dt, node, config) { + var sb = new DataTable.SearchBuilder( + dt, + $.extend( + { + filterChanged: function (count, text) { + dt.button(node).text(text) + } + }, + config.config + ) + ) + dt.button(node).text(config.text || dt.i18n("searchBuilder.button", sb.c.i18n.button, 0)) + config._searchBuilder = sb + }, + text: null + } + apiRegister("searchBuilder.getDetails()", function (deFormatDates) { + if (deFormatDates === void 0) { + deFormatDates = false + } + var ctx = this.context[0] + // If SearchBuilder has not been initialised on this instance then return + return ctx._searchBuilder ? ctx._searchBuilder.getDetails(deFormatDates) : null + }) + apiRegister("searchBuilder.rebuild()", function (details) { + var ctx = this.context[0] + // If SearchBuilder has not been initialised on this instance then return + if (ctx._searchBuilder === undefined) { + return null + } + ctx._searchBuilder.rebuild(details) + return this + }) + apiRegister("searchBuilder.container()", function () { + var ctx = this.context[0] + // If SearchBuilder has not been initialised on this instance then return + return ctx._searchBuilder ? ctx._searchBuilder.getNode() : null + }) + /** + * Init function for SearchBuilder + * + * @param settings the settings to be applied + * @param options the options for SearchBuilder + * @returns JQUERY<HTMLElement> Returns the node of the SearchBuilder + */ + function _init(settings, options) { + var api = new DataTable.Api(settings) + var opts = options ? options : api.init().searchBuilder || DataTable.defaults.searchBuilder + var searchBuilder = new SearchBuilder(api, opts) + var node = searchBuilder.getNode() + return node + } + // Attach a listener to the document which listens for DataTables initialisation + // events so we can automatically initialise + $(document).on("preInit.dt.dtsp", function (e, settings) { + if (e.namespace !== "dt") { + return + } + if (settings.oInit.searchBuilder || DataTable.defaults.searchBuilder) { + if (!settings._searchBuilder) { + _init(settings) + } + } + }) + // DataTables `dom` feature option + DataTable.ext.feature.push({ + cFeature: "Q", + fnInit: _init + }) + // DataTables 2 layout feature + if (DataTable.feature) { + DataTable.feature.register("searchBuilder", _init) + } + })() + + return DataTable +}) + +/*! DataTables integration for DataTables' SearchBuilder + * © SpryMedia Ltd - datatables.net/license + */ +;(function (factory) { + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery", "datatables.net-dt", "datatables.net-searchbuilder"], function ($) { + return factory($, window, document) + }) + } else if (typeof exports === "object") { + // CommonJS + var jq = require("jquery") + var cjsRequires = function (root, $) { + if (!$.fn.dataTable) { + require("datatables.net-dt")(root, $) + } + + if (!$.fn.dataTable.SearchBuilder) { + require("datatables.net-searchbuilder")(root, $) + } + } + + if (typeof window === "undefined") { + module.exports = function (root, $) { + if (!root) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window + } + + if (!$) { + $ = jq(root) + } + + cjsRequires(root, $) + return factory($, root, root.document) + } + } else { + cjsRequires(window, jq) + module.exports = factory(jq, window, window.document) + } + } else { + // Browser + factory(jQuery, window, document) + } +})(function ($, window, document) { + "use strict" + var DataTable = $.fn.dataTable + + return DataTable +}) diff --git a/gazette.css b/gazette.css new file mode 100644 index 0000000..cebd198 --- /dev/null +++ b/gazette.css @@ -0,0 +1,388 @@ +html, +body, +div, +span, +p, +ol, +ul, +li, +table, +figure { + margin: 0; + padding: 0; + border: 0; + vertical-align: baseline; + border-spacing: 0; +} +figure { + margin: 0; + padding: 0; +} +.dropcap:first-letter { + font-size: 3rem; + line-height: 0.9em; + margin-right: 0.125rem; + display: block; + float: left; +} +.dinkus { + text-align: center; + padding: 1rem; +} +.dinkus span { + vertical-align: sub; +} +details { + margin-top: 10px; +} +summary { + font-family: "SF Pro", "Helvetica Neue", "Segoe UI", "Arial"; + cursor: pointer; +} +.scrollCaptionedFigure { + display: block; + break-inside: avoid; + max-width: 100%; + text-align: center; +} +.scrollCaptionedFigure img { + max-width: 100%; + height: auto; + margin-top: 0.1875rem; +} +.scrollCaptionedFigure figcaption { + font-family: "SF Pro", "Helvetica Neue", "Segoe UI", "Arial"; + font-size: 0.8rem; +} +.scrollCaptionedFigure figcaption .scrollParagraph { + margin-top: 0; +} +.scrollCodeBlock { + overflow: auto; + font-size: 0.8rem; + hyphens: none; + white-space: pre; + break-inside: avoid; + display: block; + margin: 0.5rem 0; + padding: 0.5rem; + border-radius: 0; + position: relative; +} +.codeWithHeader { + break-inside: avoid-column; + margin: 10px 0; +} +.codeHeader { + font-size: 80%; + text-align: center; + background: rgba(224, 224, 224, 0.4); + border: 1px solid rgba(204, 204, 204, 0.8); + border-bottom: 0; + margin-bottom: -7px; + padding: 4px 2px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.scrollCodeBlock:hover .scrollCopyButton { + opacity: 0.5; +} +.scrollCodeBlock:hover .scrollCopyButton:hover { + opacity: 0.8; +} +.scrollCodeBlock:hover .scrollCopyButton:active { + opacity: 1; +} +.scrollCopyButton { + position: absolute; + top: 0.125rem; + right: 0.125rem; + font-size: 0.875rem; + cursor: pointer; + opacity: 0; +} +.scrollCopyButton::after { + content: "[ ]"; +} +.scrollCopiedButton::after { + content: "[✓]"; +} +ol, +ul { + padding-left: 1rem; +} +li { + margin-top: 0.4rem; + line-height: 1.4; +} +a { + text-decoration-color: transparent; + color: #36c; +} +a:hover { + text-decoration-color: initial; +} +.scrollButton { + background-color: rgba(10, 92, 202, 0.8); + border-radius: 6px; + color: white; + padding: 10px 20px; + display: inline-block; + border: 0; + cursor: pointer; +} +.scrollButton a { + color: white; + text-decoration: none; +} +.scrollButton:hover { + background-color: rgb(10, 92, 202, 0.9); +} +.scrollButton:active { + background-color: rgb(10, 92, 202, 1); +} +sup, +sub { + vertical-align: baseline; + position: relative; + top: -0.375rem; +} +sub { + top: 0.375rem; +} +html { + padding: 0.25rem; + background-color: rgb(244, 244, 244); + font-family: Exchange, Georgia, serif; + color: #000; + font-size: var(--base-font-size, 16px); + hyphens: auto; +} +p { + margin-top: 0.4rem; + line-height: 1.4rem; +} +.scrollQuote { + break-inside: avoid; + display: block; + margin: 0.5rem 0; + padding: 0.5rem; + background: rgba(204, 204, 204, 0.5); + white-space: pre-line; + border-left: 0.5rem solid rgba(204, 204, 204, 0.8); +} +code { + font-size: 0.9rem; + background-color: rgba(204, 204, 204, 0.5); + padding: 0.125rem 0.25rem; + border-radius: 0.25rem; +} +.scrollParagraph { + text-align: justify; +} +center .scrollParagraph { + text-align: center; +} +.subdued { + color: rgb(150, 150, 150); +} +.scrollColumns { + column-count: auto; + column-fill: balance; + column-width: 35ch; + column-gap: 1.5rem; + padding-left: 1.25rem; + padding-right: 1.25rem; + margin: auto; +} +.scrollSnippetContainer { + padding: 1ch 0; + break-inside: avoid; + text-align: justify; +} +h1, +h2, +h3, +h4 { + margin: 0.625rem 0; +} +h1 { + font-size: 1.25rem; +} +h2 { + font-size: 1.125rem; +} +h3, +h4 { + font-size: 1rem; +} +h1.scrollTitle { + text-align: center; + margin: auto; + margin-bottom: 0.15625rem; + margin-top: 0; + font-size: 1.75rem; + max-width: calc(100vw - 2 * (1.5625rem + 1.875rem)); +} +h1.scrollTitle a { + color: #000; +} +.scrollDateline { + font-style: italic; + line-height: 1.4rem; + font-size: 0.75rem; +} +.scrollSection { + break-inside: avoid; +} +.scrollSection h1, +.scrollSection h2, +.scrollSection h3, +.scrollSection h4 { + text-align: center; +} +h4.scrollQuestion { + text-align: left; + margin: 1.4rem 0 0 0; +} +.scrollSection:first-child h1, +.scrollSection:first-child h2, +.scrollSection:first-child h3, +.scrollSection:first-child h4 { + margin-top: 0; +} +.scrollSection:first-child h4.scrollQuestion { + margin-top: 0; +} +.scrollNoteLink { + opacity: 0.4; + text-decoration: none; +} +.scrollNoteLink:hover { + opacity: 1; +} +.scrollFootNoteUsageLink { + opacity: 0.7; + text-decoration: none; +} +.scrollFootNoteUsageLink:hover { + opacity: 1; +} +.scrollHoverNote { + text-decoration: underline dashed 1px rgba(0, 0, 0, 0.1); + cursor: default; +} +.scrollCodeBlock { + border-left: 0.5rem solid rgba(204, 204, 204, 0.8); +} +.scrollTable { + table-layout: fixed; + font-family: "SF Pro", "Helvetica Neue", "Segoe UI", "Arial"; + margin: 0.5rem 0; + overflow: hidden; + font-size: 0.8rem; + width: 100%; + hyphens: none; + border: 1px solid rgba(224, 224, 224, 0.8); +} +.scrollTable td, +.scrollTable th { + padding: 0.1875rem; + overflow: hidden; + white-space: nowrap; +} +.scrollTable th { + text-transform: capitalize; + border-bottom: 2px solid rgba(0, 0, 0, 0.6); + text-align: left; +} +.scrollTable tr:nth-child(even) { + background: rgba(224, 224, 224, 0.6); +} +.scrollTable pre { + white-space: nowrap; + overflow: hidden; + margin: 0; +} +.scrollTable.expandedTable { + table-layout: unset; + background: white; + position: relative; + z-index: 10; + overflow: unset; +} +.scrollTable.expandedTable pre { + white-space: unset; + overflow: unset; +} +.scrollTable.expandedTable td, +.scrollTable.expandedTable th { + overflow: unset; + white-space: unset; +} +.scrollByLine { + font-size: 0.875rem; + font-style: italic; + margin: 0.25rem 0; + text-align: center; +} +.abstractTextLinkParser { + text-align: center; + margin: 0.5em auto; + font-family: Verdana; + font-weight: 100; +} +.abstractTextLinkParser a { + color: rgba(204, 204, 204, 0.5); +} +.abstractTextLinkParser a:hover { + color: #333; +} +.scrollContinueReadingLink { + display: block; + text-align: center; +} +.scrollDashboard { + width: 100%; + font-size: 1.875rem; + text-align: center; + font-weight: bold; + break-inside: avoid; + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} +.scrollDashboard td { + width: 33.3%; + border: 1px solid #e8e8e8; +} +.scrollDashboard span { + font-size: 1.25rem; + display: block; +} +.scrollChat span { + font-family: Verdana; + margin-top: 0.3125rem; + padding: 0.3125rem 1.25rem; + border-radius: 0.9375rem; + display: inline-block; +} +.scrollChatLeft span { + background: rgba(204, 204, 204, 0.5); +} +.scrollChatRight span { + color: white; + background: rgb(0, 132, 255); +} +.scrollYouTubeHolder { + position: relative; + width: 100%; + height: 0; + padding-bottom: 56.25%; +} +.scrollYouTubeEmbed { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} diff --git a/index.scroll b/index.scroll index 1c82de1..569b499 100644 --- a/index.scroll +++ b/index.scroll @@ -1,7 +1,20 @@ -title I CAN PISS REALLY HARD - -header.scroll - -printTitle - -footer.scroll +title RejectedByYC + +header.scroll + +printTitle + +Original by Leo. This version by Breck. + https://rejectedbyyc.com/ Original + https://twitter.com/leonagano/ Leo + https://twitter.com/breckyunits Breck + +A curated list of companies and founders rejected by YC* + +table rejects.tsv + rename twitter nameLink + rename source storyLink + printTable +tableSearch + +footer.scroll diff --git a/jquery-3.7.1.min.js b/jquery-3.7.1.min.js new file mode 100644 index 0000000..7f37b5d --- /dev/null +++ b/jquery-3.7.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}function fe(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}ce.fn=ce.prototype={jquery:t,constructor:ce,length:0,toArray:function(){return ae.call(this)},get:function(e){return null==e?ae.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=ce.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return ce.each(this,e)},map:function(n){return this.pushStack(ce.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(ae.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(ce.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(ce.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:oe.sort,splice:oe.splice},ce.extend=ce.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||v(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(ce.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||ce.isPlainObject(n)?n:{},i=!1,a[t]=ce.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},ce.extend({expando:"jQuery"+(t+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==i.call(e))&&(!(t=r(e))||"function"==typeof(n=ue.call(t,"constructor")&&t.constructor)&&o.call(n)===a)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){m(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(c(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},text:function(e){var t,n="",r=0,i=e.nodeType;if(!i)while(t=e[r++])n+=ce.text(t);return 1===i||11===i?e.textContent:9===i?e.documentElement.textContent:3===i||4===i?e.nodeValue:n},makeArray:function(e,t){var n=t||[];return null!=e&&(c(Object(e))?ce.merge(n,"string"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:se.call(t,e,n)},isXMLDoc:function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!l.test(t||n&&n.nodeName||"HTML")},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(c(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:le}),"function"==typeof Symbol&&(ce.fn[Symbol.iterator]=oe[Symbol.iterator]),ce.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var pe=oe.pop,de=oe.sort,he=oe.splice,ge="[\\x20\\t\\r\\n\\f]",ve=new RegExp("^"+ge+"+|((?:^|[^\\\\])(?:\\\\.)*)"+ge+"+$","g");ce.contains=function(e,t){var n=t&&t.parentNode;return e===n||!(!n||1!==n.nodeType||!(e.contains?e.contains(n):e.compareDocumentPosition&&16&e.compareDocumentPosition(n)))};var f=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;function p(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e}ce.escapeSelector=function(e){return(e+"").replace(f,p)};var ye=C,me=s;!function(){var e,b,w,o,a,T,r,C,d,i,k=me,S=ce.expando,E=0,n=0,s=W(),c=W(),u=W(),h=W(),l=function(e,t){return e===t&&(a=!0),0},f="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",t="(?:\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",p="\\["+ge+"*("+t+")(?:"+ge+"*([*^$|!~]?=)"+ge+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+t+"))|)"+ge+"*\\]",g=":("+t+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+p+")*)|.*)\\)|)",v=new RegExp(ge+"+","g"),y=new RegExp("^"+ge+"*,"+ge+"*"),m=new RegExp("^"+ge+"*([>+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="<a id='"+S+"' href='' disabled='disabled'></a><select id='"+S+"-\r\\' disabled='disabled'><option selected=''></option></select>",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0<I(t,T,null,[e]).length},I.contains=function(e,t){return(e.ownerDocument||e)!=T&&V(e),ce.contains(e,t)},I.attr=function(e,t){(e.ownerDocument||e)!=T&&V(e);var n=b.attrHandle[t.toLowerCase()],r=n&&ue.call(b.attrHandle,t.toLowerCase())?n(e,t,!C):void 0;return void 0!==r?r:e.getAttribute(t)},I.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},ce.uniqueSort=function(e){var t,n=[],r=0,i=0;if(a=!le.sortStable,o=!le.sortStable&&ae.call(e,0),de.call(e,l),a){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)he.call(e,n[r],1)}return o=null,e},ce.fn.uniqueSort=function(){return this.pushStack(ce.uniqueSort(ae.apply(this)))},(b=ce.expr={cacheLength:50,createPseudo:F,match:D,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(v," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(d,e,t,h,g){var v="nth"!==d.slice(0,3),y="last"!==d.slice(-4),m="of-type"===e;return 1===h&&0===g?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u=v!==y?"nextSibling":"previousSibling",l=e.parentNode,c=m&&e.nodeName.toLowerCase(),f=!n&&!m,p=!1;if(l){if(v){while(u){o=e;while(o=o[u])if(m?fe(o,c):1===o.nodeType)return!1;s=u="only"===d&&!s&&"nextSibling"}return!0}if(s=[y?l.firstChild:l.lastChild],y&&f){p=(a=(r=(i=l[S]||(l[S]={}))[d]||[])[0]===E&&r[1])&&r[2],o=a&&l.childNodes[a];while(o=++a&&o&&o[u]||(p=a=0)||s.pop())if(1===o.nodeType&&++p&&o===e){i[d]=[E,a,p];break}}else if(f&&(p=a=(r=(i=e[S]||(e[S]={}))[d]||[])[0]===E&&r[1]),!1===p)while(o=++a&&o&&o[u]||(p=a=0)||s.pop())if((m?fe(o,c):1===o.nodeType)&&++p&&(f&&((i=o[S]||(o[S]={}))[d]=[E,p]),o===e))break;return(p-=g)===h||p%h==0&&0<=p/h}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||I.error("unsupported pseudo: "+e);return a[S]?a(o):1<a.length?(t=[e,e,"",o],b.setFilters.hasOwnProperty(e.toLowerCase())?F(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=se.call(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:F(function(e){var r=[],i=[],s=ne(e.replace(ve,"$1"));return s[S]?F(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:F(function(t){return function(e){return 0<I(t,e).length}}),contains:F(function(t){return t=t.replace(O,P),function(e){return-1<(e.textContent||ce.text(e)).indexOf(t)}}),lang:F(function(n){return A.test(n||"")||I.error("unsupported lang: "+n),n=n.replace(O,P).toLowerCase(),function(e){var t;do{if(t=C?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=ie.location&&ie.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===r},focus:function(e){return e===function(){try{return T.activeElement}catch(e){}}()&&T.hasFocus()&&!!(e.type||e.href||~e.tabIndex)},enabled:z(!1),disabled:z(!0),checked:function(e){return fe(e,"input")&&!!e.checked||fe(e,"option")&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return q.test(e.nodeName)},input:function(e){return N.test(e.nodeName)},button:function(e){return fe(e,"input")&&"button"===e.type||fe(e,"button")},text:function(e){var t;return fe(e,"input")&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:X(function(){return[0]}),last:X(function(e,t){return[t-1]}),eq:X(function(e,t,n){return[n<0?n+t:n]}),even:X(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:X(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:X(function(e,t,n){var r;for(r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:X(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=B(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=_(e);function G(){}function Y(e,t){var n,r,i,o,a,s,u,l=c[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=y.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=m.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace(ve," ")}),a=a.slice(n.length)),b.filter)!(r=D[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?I.error(e):c(e,s).slice(0)}function Q(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function J(a,e,t){var s=e.dir,u=e.next,l=u||s,c=t&&"parentNode"===l,f=n++;return e.first?function(e,t,n){while(e=e[s])if(1===e.nodeType||c)return a(e,t,n);return!1}:function(e,t,n){var r,i,o=[E,f];if(n){while(e=e[s])if((1===e.nodeType||c)&&a(e,t,n))return!0}else while(e=e[s])if(1===e.nodeType||c)if(i=e[S]||(e[S]={}),u&&fe(e,u))e=e[s]||e;else{if((r=i[l])&&r[0]===E&&r[1]===f)return o[2]=r[2];if((i[l]=o)[2]=a(e,t,n))return!0}return!1}}function K(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Z(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function ee(d,h,g,v,y,e){return v&&!v[S]&&(v=ee(v)),y&&!y[S]&&(y=ee(y,e)),F(function(e,t,n,r){var i,o,a,s,u=[],l=[],c=t.length,f=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)I(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),p=!d||!e&&h?f:Z(f,u,d,n,r);if(g?g(p,s=y||(e?d:c||v)?[]:t,n,r):s=p,v){i=Z(s,l),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(s[l[o]]=!(p[l[o]]=a))}if(e){if(y||d){if(y){i=[],o=s.length;while(o--)(a=s[o])&&i.push(p[o]=a);y(null,s=[],i,r)}o=s.length;while(o--)(a=s[o])&&-1<(i=y?se.call(e,a):u[o])&&(e[i]=!(t[i]=a))}}else s=Z(s===t?s.splice(c,s.length):s),y?y(null,t,s,r):k.apply(t,s)})}function te(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[" "],s=o?1:0,u=J(function(e){return e===i},a,!0),l=J(function(e){return-1<se.call(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!=w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[J(K(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[S]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return ee(1<s&&K(c),1<s&&Q(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(ve,"$1"),t,s<n&&te(e.slice(s,n)),n<r&&te(e=e.slice(n)),n<r&&Q(e))}c.push(t)}return K(c)}function ne(e,t){var n,v,y,m,x,r,i=[],o=[],a=u[e+" "];if(!a){t||(t=Y(e)),n=t.length;while(n--)(a=te(t[n]))[S]?i.push(a):o.push(a);(a=u(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG("*",i),h=E+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t==T||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument==T||(V(o),n=!C);while(s=v[a++])if(s(o,t||T,n)){k.call(r,o);break}i&&(E=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=pe.call(r));f=Z(f)}k.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&ce.uniqueSort(r)}return i&&(E=h,w=p),c},m?F(r):r))).selector=e}return a}function re(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&Y(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&C&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(O,P),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=D.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(O,P),H.test(o[0].type)&&U(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&Q(o)))return k.apply(n,r),n;break}}}return(l||ne(e,c))(r,t,!C,n,!t||H.test(e)&&U(t.parentNode)||t),n}G.prototype=b.filters=b.pseudos,b.setFilters=new G,le.sortStable=S.split("").sort(l).join("")===S,V(),le.sortDetached=$(function(e){return 1&e.compareDocumentPosition(T.createElement("fieldset"))}),ce.find=I,ce.expr[":"]=ce.expr.pseudos,ce.unique=ce.uniqueSort,I.compile=ne,I.select=re,I.setDocument=V,I.tokenize=Y,I.escape=ce.escapeSelector,I.getText=ce.text,I.isXML=ce.isXMLDoc,I.selectors=ce.expr,I.support=ce.support,I.uniqueSort=ce.uniqueSort}();var d=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&ce(e).is(n))break;r.push(e)}return r},h=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},b=ce.expr.match.needsContext,w=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1<se.call(n,e)!==r}):ce.filter(n,e,r)}ce.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?ce.find.matchesSelector(r,e)?[r]:[]:ce.find.matches(e,ce.grep(t,function(e){return 1===e.nodeType}))},ce.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(ce(e).filter(function(){for(t=0;t<r;t++)if(ce.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)ce.find(e,i[t],n);return 1<r?ce.uniqueSort(n):n},filter:function(e){return this.pushStack(T(this,e||[],!1))},not:function(e){return this.pushStack(T(this,e||[],!0))},is:function(e){return!!T(this,"string"==typeof e&&b.test(e)?ce(e):e||[],!1).length}});var k,S=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(ce.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&ce(e);if(!b.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&ce.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?ce.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?se.call(ce(e),this[0]):se.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(ce.uniqueSort(ce.merge(this.get(),ce(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),ce.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return d(e,"parentNode")},parentsUntil:function(e,t,n){return d(e,"parentNode",n)},next:function(e){return A(e,"nextSibling")},prev:function(e){return A(e,"previousSibling")},nextAll:function(e){return d(e,"nextSibling")},prevAll:function(e){return d(e,"previousSibling")},nextUntil:function(e,t,n){return d(e,"nextSibling",n)},prevUntil:function(e,t,n){return d(e,"previousSibling",n)},siblings:function(e){return h((e.parentNode||{}).firstChild,e)},children:function(e){return h(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(fe(e,"template")&&(e=e.content||e),ce.merge([],e.childNodes))}},function(r,i){ce.fn[r]=function(e,t){var n=ce.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=ce.filter(t,n)),1<this.length&&(j[r]||ce.uniqueSort(n),E.test(r)&&n.reverse()),this.pushStack(n)}});var D=/[^\x20\t\r\n\f]+/g;function N(e){return e}function q(e){throw e}function L(e,t,n,r){var i;try{e&&v(i=e.promise)?i.call(e).done(t).fail(n):e&&v(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}ce.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},ce.each(e.match(D)||[],function(e,t){n[t]=!0}),n):ce.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){ce.each(e,function(e,t){v(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==x(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return ce.each(arguments,function(e,t){var n;while(-1<(n=ce.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<ce.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},ce.extend({Deferred:function(e){var o=[["notify","progress",ce.Callbacks("memory"),ce.Callbacks("memory"),2],["resolve","done",ce.Callbacks("once memory"),ce.Callbacks("once memory"),0,"resolved"],["reject","fail",ce.Callbacks("once memory"),ce.Callbacks("once memory"),1,"rejected"]],i="pending",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return ce.Deferred(function(r){ce.each(o,function(e,t){var n=v(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&v(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,v(t)?s?t.call(e,l(u,o,N,s),l(u,o,q,s)):(u++,t.call(e,l(u,o,N,s),l(u,o,q,s),l(u,o,N,o.notifyWith))):(a!==N&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){ce.Deferred.exceptionHook&&ce.Deferred.exceptionHook(e,t.error),u<=i+1&&(a!==q&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(ce.Deferred.getErrorHook?t.error=ce.Deferred.getErrorHook():ce.Deferred.getStackHook&&(t.error=ce.Deferred.getStackHook()),ie.setTimeout(t))}}return ce.Deferred(function(e){o[0][3].add(l(0,e,v(r)?r:N,e.notifyWith)),o[1][3].add(l(0,e,v(t)?t:N)),o[2][3].add(l(0,e,v(n)?n:q))}).promise()},promise:function(e){return null!=e?ce.extend(e,a):a}},s={};return ce.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=ae.call(arguments),o=ce.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?ae.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(L(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||v(i[t]&&i[t].then)))return o.then();while(t--)L(i[t],a(t),o.reject);return o.promise()}});var H=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;ce.Deferred.exceptionHook=function(e,t){ie.console&&ie.console.warn&&e&&H.test(e.name)&&ie.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},ce.readyException=function(e){ie.setTimeout(function(){throw e})};var O=ce.Deferred();function P(){C.removeEventListener("DOMContentLoaded",P),ie.removeEventListener("load",P),ce.ready()}ce.fn.ready=function(e){return O.then(e)["catch"](function(e){ce.readyException(e)}),this},ce.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--ce.readyWait:ce.isReady)||(ce.isReady=!0)!==e&&0<--ce.readyWait||O.resolveWith(C,[ce])}}),ce.ready.then=O.then,"complete"===C.readyState||"loading"!==C.readyState&&!C.documentElement.doScroll?ie.setTimeout(ce.ready):(C.addEventListener("DOMContentLoaded",P),ie.addEventListener("load",P));var M=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n))for(s in i=!0,n)M(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,v(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(ce(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},R=/^-ms-/,I=/-([a-z])/g;function W(e,t){return t.toUpperCase()}function F(e){return e.replace(R,"ms-").replace(I,W)}var $=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function B(){this.expando=ce.expando+B.uid++}B.uid=1,B.prototype={cache:function(e){var t=e[this.expando];return t||(t={},$(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[F(t)]=n;else for(r in t)i[F(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][F(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(F):(t=F(t))in r?[t]:t.match(D)||[]).length;while(n--)delete r[t[n]]}(void 0===t||ce.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!ce.isEmptyObject(t)}};var _=new B,z=new B,X=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,U=/[A-Z]/g;function V(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(U,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:X.test(i)?JSON.parse(i):i)}catch(e){}z.set(e,t,n)}else n=void 0;return n}ce.extend({hasData:function(e){return z.hasData(e)||_.hasData(e)},data:function(e,t,n){return z.access(e,t,n)},removeData:function(e,t){z.remove(e,t)},_data:function(e,t,n){return _.access(e,t,n)},_removeData:function(e,t){_.remove(e,t)}}),ce.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=z.get(o),1===o.nodeType&&!_.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=F(r.slice(5)),V(o,r,i[r]));_.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){z.set(this,n)}):M(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=z.get(o,n))?t:void 0!==(t=V(o,n))?t:void 0;this.each(function(){z.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){z.remove(this,e)})}}),ce.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=_.get(e,t),n&&(!r||Array.isArray(n)?r=_.access(e,t,ce.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=ce.queue(e,t),r=n.length,i=n.shift(),o=ce._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){ce.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return _.get(e,n)||_.access(e,n,{empty:ce.Callbacks("once memory").add(function(){_.remove(e,[t+"queue",n])})})}}),ce.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?ce.queue(this[0],t):void 0===n?this:this.each(function(){var e=ce.queue(this,t,n);ce._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&ce.dequeue(this,t)})},dequeue:function(e){return this.each(function(){ce.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=ce.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=_.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var G=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,Y=new RegExp("^(?:([+-])=|)("+G+")([a-z%]*)$","i"),Q=["Top","Right","Bottom","Left"],J=C.documentElement,K=function(e){return ce.contains(e.ownerDocument,e)},Z={composed:!0};J.getRootNode&&(K=function(e){return ce.contains(e.ownerDocument,e)||e.getRootNode(Z)===e.ownerDocument});var ee=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&K(e)&&"none"===ce.css(e,"display")};function te(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return ce.css(e,t,"")},u=s(),l=n&&n[3]||(ce.cssNumber[t]?"":"px"),c=e.nodeType&&(ce.cssNumber[t]||"px"!==l&&+u)&&Y.exec(ce.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)ce.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,ce.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ne={};function re(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=_.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&ee(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ne[s])||(o=a.body.appendChild(a.createElement(s)),u=ce.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),ne[s]=u)))):"none"!==n&&(l[c]="none",_.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}ce.fn.extend({show:function(){return re(this,!0)},hide:function(){return re(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ee(this)?ce(this).show():ce(this).hide()})}});var xe,be,we=/^(?:checkbox|radio)$/i,Te=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="<textarea>x</textarea>",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="<option></option>",le.option=!!xe.lastChild;var ke={thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n<r;n++)_.set(e[n],"globalEval",!t||_.get(t[n],"globalEval"))}ke.tbody=ke.tfoot=ke.colgroup=ke.caption=ke.thead,ke.th=ke.td,le.option||(ke.optgroup=ke.option=[1,"<select multiple='multiple'>","</select>"]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===x(o))ce.merge(p,o.nodeType?[o]:o);else if(je.test(o)){a=a||f.appendChild(t.createElement("div")),s=(Te.exec(o)||["",""])[1].toLowerCase(),u=ke[s]||ke._default,a.innerHTML=u[1]+ce.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;ce.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&-1<ce.inArray(o,r))i&&i.push(o);else if(l=K(o),a=Se(f.appendChild(o),"script"),l&&Ee(a),n){c=0;while(o=a[c++])Ce.test(o.type||"")&&n.push(o)}return f}var De=/^([^.]*)(?:\.(.+)|)/;function Ne(){return!0}function qe(){return!1}function Le(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Le(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=qe;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return ce().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=ce.guid++)),e.each(function(){ce.event.add(this,t,i,r,n)})}function He(e,r,t){t?(_.set(e,r,!1),ce.event.add(e,r,{namespace:!1,handler:function(e){var t,n=_.get(this,r);if(1&e.isTrigger&&this[r]){if(n)(ce.event.special[r]||{}).delegateType&&e.stopPropagation();else if(n=ae.call(arguments),_.set(this,r,n),this[r](),t=_.get(this,r),_.set(this,r,!1),n!==t)return e.stopImmediatePropagation(),e.preventDefault(),t}else n&&(_.set(this,r,ce.event.trigger(n[0],n.slice(1),this)),e.stopPropagation(),e.isImmediatePropagationStopped=Ne)}})):void 0===_.get(e,r)&&ce.event.add(e,r,Ne)}ce.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=_.get(t);if($(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&ce.find.matchesSelector(J,i),n.guid||(n.guid=ce.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof ce&&ce.event.triggered!==e.type?ce.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(D)||[""]).length;while(l--)d=g=(s=De.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=ce.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=ce.event.special[d]||{},c=ce.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&ce.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),ce.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=_.hasData(e)&&_.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(D)||[""]).length;while(l--)if(d=g=(s=De.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=ce.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||ce.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)ce.event.remove(e,d+t[l],n,r,!0);ce.isEmptyObject(u)&&_.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=ce.event.fix(e),l=(_.get(this,"events")||Object.create(null))[u.type]||[],c=ce.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=ce.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((ce.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<ce(i,this).index(l):ce.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(ce.Event.prototype,t,{enumerable:!0,configurable:!0,get:v(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[ce.expando]?e:new ce.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return we.test(t.type)&&t.click&&fe(t,"input")&&He(t,"click",!0),!1},trigger:function(e){var t=this||e;return we.test(t.type)&&t.click&&fe(t,"input")&&He(t,"click"),!0},_default:function(e){var t=e.target;return we.test(t.type)&&t.click&&fe(t,"input")&&_.get(t,"click")||fe(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},ce.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},ce.Event=function(e,t){if(!(this instanceof ce.Event))return new ce.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ne:qe,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&ce.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[ce.expando]=!0},ce.Event.prototype={constructor:ce.Event,isDefaultPrevented:qe,isPropagationStopped:qe,isImmediatePropagationStopped:qe,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ne,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ne,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ne,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},ce.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:!0},ce.event.addProp),ce.each({focus:"focusin",blur:"focusout"},function(r,i){function o(e){if(C.documentMode){var t=_.get(this,"handle"),n=ce.event.fix(e);n.type="focusin"===e.type?"focus":"blur",n.isSimulated=!0,t(e),n.target===n.currentTarget&&t(n)}else ce.event.simulate(i,e.target,ce.event.fix(e))}ce.event.special[r]={setup:function(){var e;if(He(this,r,!0),!C.documentMode)return!1;(e=_.get(this,i))||this.addEventListener(i,o),_.set(this,i,(e||0)+1)},trigger:function(){return He(this,r),!0},teardown:function(){var e;if(!C.documentMode)return!1;(e=_.get(this,i)-1)?_.set(this,i,e):(this.removeEventListener(i,o),_.remove(this,i))},_default:function(e){return _.get(e.target,r)},delegateType:i},ce.event.special[i]={setup:function(){var e=this.ownerDocument||this.document||this,t=C.documentMode?this:e,n=_.get(t,i);n||(C.documentMode?this.addEventListener(i,o):e.addEventListener(r,o,!0)),_.set(t,i,(n||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=C.documentMode?this:e,n=_.get(t,i)-1;n?_.set(t,i,n):(C.documentMode?this.removeEventListener(i,o):e.removeEventListener(r,o,!0),_.remove(t,i))}}}),ce.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){ce.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||ce.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),ce.fn.extend({on:function(e,t,n,r){return Le(this,e,t,n,r)},one:function(e,t,n,r){return Le(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,ce(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=qe),this.each(function(){ce.event.remove(this,e,n,t)})}});var Oe=/<script|<style|<link/i,Pe=/checked\s*(?:[^=]|=\s*.checked.)/i,Me=/^\s*<!\[CDATA\[|\]\]>\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n<r;n++)ce.event.add(t,i,s[i][n]);z.hasData(e)&&(o=z.access(e),a=ce.extend({},o),z.set(t,a))}}function $e(n,r,i,o){r=g(r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=v(d);if(h||1<f&&"string"==typeof d&&!le.checkClone&&Pe.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),$e(t,r,i,o)});if(f&&(t=(e=Ae(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=ce.map(Se(e,"script"),Ie)).length;c<f;c++)u=e,c!==p&&(u=ce.clone(u,!0,!0),s&&ce.merge(a,Se(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,ce.map(a,We),c=0;c<s;c++)u=a[c],Ce.test(u.type||"")&&!_.access(u,"globalEval")&&ce.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?ce._evalUrl&&!u.noModule&&ce._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")},l):m(u.textContent.replace(Me,""),u,l))}return n}function Be(e,t,n){for(var r,i=t?ce.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||ce.cleanData(Se(r)),r.parentNode&&(n&&K(r)&&Ee(Se(r,"script")),r.parentNode.removeChild(r));return e}ce.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=K(e);if(!(le.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||ce.isXMLDoc(e)))for(a=Se(c),r=0,i=(o=Se(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&we.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||Se(e),a=a||Se(c),r=0,i=o.length;r<i;r++)Fe(o[r],a[r]);else Fe(e,c);return 0<(a=Se(c,"script")).length&&Ee(a,!f&&Se(e,"script")),c},cleanData:function(e){for(var t,n,r,i=ce.event.special,o=0;void 0!==(n=e[o]);o++)if($(n)){if(t=n[_.expando]){if(t.events)for(r in t.events)i[r]?ce.event.remove(n,r):ce.removeEvent(n,r,t.handle);n[_.expando]=void 0}n[z.expando]&&(n[z.expando]=void 0)}}}),ce.fn.extend({detach:function(e){return Be(this,e,!0)},remove:function(e){return Be(this,e)},text:function(e){return M(this,function(e){return void 0===e?ce.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return $e(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Re(this,e).appendChild(e)})},prepend:function(){return $e(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Re(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return $e(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return $e(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(ce.cleanData(Se(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return ce.clone(this,e,t)})},html:function(e){return M(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Oe.test(e)&&!ke[(Te.exec(e)||["",""])[1].toLowerCase()]){e=ce.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(ce.cleanData(Se(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return $e(this,arguments,function(e){var t=this.parentNode;ce.inArray(this,n)<0&&(ce.cleanData(Se(this)),t&&t.replaceChild(e,this))},n)}}),ce.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){ce.fn[e]=function(e){for(var t,n=[],r=ce(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),ce(r[o])[a](t),s.apply(n,t.get());return this.pushStack(n)}});var _e=new RegExp("^("+G+")(?!px)[a-z%]+$","i"),ze=/^--/,Xe=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=ie),t.getComputedStyle(e)},Ue=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Ve=new RegExp(Q.join("|"),"i");function Ge(e,t,n){var r,i,o,a,s=ze.test(t),u=e.style;return(n=n||Xe(e))&&(a=n.getPropertyValue(t)||n[t],s&&a&&(a=a.replace(ve,"$1")||void 0),""!==a||K(e)||(a=ce.style(e,t)),!le.pixelBoxStyles()&&_e.test(a)&&Ve.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=n.width,u.width=r,u.minWidth=i,u.maxWidth=o)),void 0!==a?a+"":a}function Ye(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",l.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",J.appendChild(u).appendChild(l);var e=ie.getComputedStyle(l);n="1%"!==e.top,s=12===t(e.marginLeft),l.style.right="60%",o=36===t(e.right),r=36===t(e.width),l.style.position="absolute",i=12===t(l.offsetWidth/3),J.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=C.createElement("div"),l=C.createElement("div");l.style&&(l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",le.clearCloneStyle="content-box"===l.style.backgroundClip,ce.extend(le,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=C.createElement("table"),t=C.createElement("tr"),n=C.createElement("div"),e.style.cssText="position:absolute;left:-11111px;border-collapse:separate",t.style.cssText="box-sizing:content-box;border:1px solid",t.style.height="1px",n.style.height="9px",n.style.display="block",J.appendChild(e).appendChild(t).appendChild(n),r=ie.getComputedStyle(t),a=parseInt(r.height,10)+parseInt(r.borderTopWidth,10)+parseInt(r.borderBottomWidth,10)===t.offsetHeight,J.removeChild(e)),a}}))}();var Qe=["Webkit","Moz","ms"],Je=C.createElement("div").style,Ke={};function Ze(e){var t=ce.cssProps[e]||Ke[e];return t||(e in Je?e:Ke[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=Qe.length;while(n--)if((e=Qe[n]+t)in Je)return e}(e)||e)}var et=/^(none|table(?!-c[ea]).+)/,tt={position:"absolute",visibility:"hidden",display:"block"},nt={letterSpacing:"0",fontWeight:"400"};function rt(e,t,n){var r=Y.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function it(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0,l=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(l+=ce.css(e,n+Q[a],!0,i)),r?("content"===n&&(u-=ce.css(e,"padding"+Q[a],!0,i)),"margin"!==n&&(u-=ce.css(e,"border"+Q[a]+"Width",!0,i))):(u+=ce.css(e,"padding"+Q[a],!0,i),"padding"!==n?u+=ce.css(e,"border"+Q[a]+"Width",!0,i):s+=ce.css(e,"border"+Q[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u+l}function ot(e,t,n){var r=Xe(e),i=(!le.boxSizingReliable()||n)&&"border-box"===ce.css(e,"boxSizing",!1,r),o=i,a=Ge(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(_e.test(a)){if(!n)return a;a="auto"}return(!le.boxSizingReliable()&&i||!le.reliableTrDimensions()&&fe(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===ce.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===ce.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+it(e,t,n||(i?"border":"content"),o,r,a)+"px"}function at(e,t,n,r,i){return new at.prototype.init(e,t,n,r,i)}ce.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Ge(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,aspectRatio:!0,borderImageSlice:!0,columnCount:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,scale:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeMiterlimit:!0,strokeOpacity:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=F(t),u=ze.test(t),l=e.style;if(u||(t=Ze(s)),a=ce.cssHooks[t]||ce.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=Y.exec(n))&&i[1]&&(n=te(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(ce.cssNumber[s]?"":"px")),le.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=F(t);return ze.test(t)||(t=Ze(s)),(a=ce.cssHooks[t]||ce.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Ge(e,t,r)),"normal"===i&&t in nt&&(i=nt[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),ce.each(["height","width"],function(e,u){ce.cssHooks[u]={get:function(e,t,n){if(t)return!et.test(ce.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?ot(e,u,n):Ue(e,tt,function(){return ot(e,u,n)})},set:function(e,t,n){var r,i=Xe(e),o=!le.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===ce.css(e,"boxSizing",!1,i),s=n?it(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-it(e,u,"border",!1,i)-.5)),s&&(r=Y.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=ce.css(e,u)),rt(0,t,s)}}}),ce.cssHooks.marginLeft=Ye(le.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Ge(e,"marginLeft"))||e.getBoundingClientRect().left-Ue(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),ce.each({margin:"",padding:"",border:"Width"},function(i,o){ce.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+Q[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(ce.cssHooks[i+o].set=rt)}),ce.fn.extend({css:function(e,t){return M(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Xe(e),i=t.length;a<i;a++)o[t[a]]=ce.css(e,t[a],!1,r);return o}return void 0!==n?ce.style(e,t,n):ce.css(e,t)},e,t,1<arguments.length)}}),((ce.Tween=at).prototype={constructor:at,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||ce.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(ce.cssNumber[n]?"":"px")},cur:function(){var e=at.propHooks[this.prop];return e&&e.get?e.get(this):at.propHooks._default.get(this)},run:function(e){var t,n=at.propHooks[this.prop];return this.options.duration?this.pos=t=ce.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):at.propHooks._default.set(this),this}}).init.prototype=at.prototype,(at.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=ce.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){ce.fx.step[e.prop]?ce.fx.step[e.prop](e):1!==e.elem.nodeType||!ce.cssHooks[e.prop]&&null==e.elem.style[Ze(e.prop)]?e.elem[e.prop]=e.now:ce.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=at.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},ce.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},ce.fx=at.prototype.init,ce.fx.step={};var st,ut,lt,ct,ft=/^(?:toggle|show|hide)$/,pt=/queueHooks$/;function dt(){ut&&(!1===C.hidden&&ie.requestAnimationFrame?ie.requestAnimationFrame(dt):ie.setTimeout(dt,ce.fx.interval),ce.fx.tick())}function ht(){return ie.setTimeout(function(){st=void 0}),st=Date.now()}function gt(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=Q[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function vt(e,t,n){for(var r,i=(yt.tweeners[t]||[]).concat(yt.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function yt(o,e,t){var n,a,r=0,i=yt.prefilters.length,s=ce.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=st||ht(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:ce.extend({},e),opts:ce.extend(!0,{specialEasing:{},easing:ce.easing._default},t),originalProperties:e,originalOptions:t,startTime:st||ht(),duration:t.duration,tweens:[],createTween:function(e,t){var n=ce.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=F(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=ce.cssHooks[r])&&"expand"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=yt.prefilters[r].call(l,o,c,l.opts))return v(n.stop)&&(ce._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return ce.map(c,vt,l),v(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),ce.fx.timer(ce.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}ce.Animation=ce.extend(yt,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return te(n.elem,e,Y.exec(t),n),n}]},tweener:function(e,t){v(e)?(t=e,e=["*"]):e=e.match(D);for(var n,r=0,i=e.length;r<i;r++)n=e[r],yt.tweeners[n]=yt.tweeners[n]||[],yt.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&ee(e),v=_.get(e,"fxshow");for(r in n.queue||(null==(a=ce._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,ce.queue(e,"fx").length||a.empty.fire()})})),t)if(i=t[r],ft.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||ce.style(e,r)}if((u=!ce.isEmptyObject(t))||!ce.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=_.get(e,"display")),"none"===(c=ce.css(e,"display"))&&(l?c=l:(re([e],!0),l=e.style.display||l,c=ce.css(e,"display"),re([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===ce.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?"hidden"in v&&(g=v.hidden):v=_.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&re([e],!0),p.done(function(){for(r in g||re([e]),_.remove(e,"fxshow"),d)ce.style(e,r,d[r])})),u=vt(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?yt.prefilters.unshift(e):yt.prefilters.push(e)}}),ce.speed=function(e,t,n){var r=e&&"object"==typeof e?ce.extend({},e):{complete:n||!n&&t||v(e)&&e,duration:e,easing:n&&t||t&&!v(t)&&t};return ce.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in ce.fx.speeds?r.duration=ce.fx.speeds[r.duration]:r.duration=ce.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){v(r.old)&&r.old.call(this),r.queue&&ce.dequeue(this,r.queue)},r},ce.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ee).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=ce.isEmptyObject(t),o=ce.speed(e,n,r),a=function(){var e=yt(this,ce.extend({},t),o);(i||_.get(this,"finish"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return"string"!=typeof i&&(o=e,e=i,i=void 0),e&&this.queue(i||"fx",[]),this.each(function(){var e=!0,t=null!=i&&i+"queueHooks",n=ce.timers,r=_.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&pt.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||ce.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||"fx"),this.each(function(){var e,t=_.get(this),n=t[a+"queue"],r=t[a+"queueHooks"],i=ce.timers,o=n?n.length:0;for(t.finish=!0,ce.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),ce.each(["toggle","show","hide"],function(e,r){var i=ce.fn[r];ce.fn[r]=function(e,t,n){return null==e||"boolean"==typeof e?i.apply(this,arguments):this.animate(gt(r,!0),e,t,n)}}),ce.each({slideDown:gt("show"),slideUp:gt("hide"),slideToggle:gt("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,r){ce.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),ce.timers=[],ce.fx.tick=function(){var e,t=0,n=ce.timers;for(st=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||ce.fx.stop(),st=void 0},ce.fx.timer=function(e){ce.timers.push(e),ce.fx.start()},ce.fx.interval=13,ce.fx.start=function(){ut||(ut=!0,dt())},ce.fx.stop=function(){ut=null},ce.fx.speeds={slow:600,fast:200,_default:400},ce.fn.delay=function(r,e){return r=ce.fx&&ce.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=ie.setTimeout(e,r);t.stop=function(){ie.clearTimeout(n)}})},lt=C.createElement("input"),ct=C.createElement("select").appendChild(C.createElement("option")),lt.type="checkbox",le.checkOn=""!==lt.value,le.optSelected=ct.selected,(lt=C.createElement("input")).value="t",lt.type="radio",le.radioValue="t"===lt.value;var mt,xt=ce.expr.attrHandle;ce.fn.extend({attr:function(e,t){return M(this,ce.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){ce.removeAttr(this,e)})}}),ce.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?ce.prop(e,t,n):(1===o&&ce.isXMLDoc(e)||(i=ce.attrHooks[t.toLowerCase()]||(ce.expr.match.bool.test(t)?mt:void 0)),void 0!==n?null===n?void ce.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=ce.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!le.radioValue&&"radio"===t&&fe(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(D);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),mt={set:function(e,t,n){return!1===t?ce.removeAttr(e,n):e.setAttribute(n,n),n}},ce.each(ce.expr.match.bool.source.match(/\w+/g),function(e,t){var a=xt[t]||ce.find.attr;xt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=xt[o],xt[o]=r,r=null!=a(e,t,n)?o:null,xt[o]=i),r}});var bt=/^(?:input|select|textarea|button)$/i,wt=/^(?:a|area)$/i;function Tt(e){return(e.match(D)||[]).join(" ")}function Ct(e){return e.getAttribute&&e.getAttribute("class")||""}function kt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(D)||[]}ce.fn.extend({prop:function(e,t){return M(this,ce.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[ce.propFix[e]||e]})}}),ce.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&ce.isXMLDoc(e)||(t=ce.propFix[t]||t,i=ce.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=ce.find.attr(e,"tabindex");return t?parseInt(t,10):bt.test(e.nodeName)||wt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),le.optSelected||(ce.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),ce.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){ce.propFix[this.toLowerCase()]=this}),ce.fn.extend({addClass:function(t){var e,n,r,i,o,a;return v(t)?this.each(function(e){ce(this).addClass(t.call(this,e,Ct(this)))}):(e=kt(t)).length?this.each(function(){if(r=Ct(this),n=1===this.nodeType&&" "+Tt(r)+" "){for(o=0;o<e.length;o++)i=e[o],n.indexOf(" "+i+" ")<0&&(n+=i+" ");a=Tt(n),r!==a&&this.setAttribute("class",a)}}):this},removeClass:function(t){var e,n,r,i,o,a;return v(t)?this.each(function(e){ce(this).removeClass(t.call(this,e,Ct(this)))}):arguments.length?(e=kt(t)).length?this.each(function(){if(r=Ct(this),n=1===this.nodeType&&" "+Tt(r)+" "){for(o=0;o<e.length;o++){i=e[o];while(-1<n.indexOf(" "+i+" "))n=n.replace(" "+i+" "," ")}a=Tt(n),r!==a&&this.setAttribute("class",a)}}):this:this.attr("class","")},toggleClass:function(t,n){var e,r,i,o,a=typeof t,s="string"===a||Array.isArray(t);return v(t)?this.each(function(e){ce(this).toggleClass(t.call(this,e,Ct(this),n),n)}):"boolean"==typeof n&&s?n?this.addClass(t):this.removeClass(t):(e=kt(t),this.each(function(){if(s)for(o=ce(this),i=0;i<e.length;i++)r=e[i],o.hasClass(r)?o.removeClass(r):o.addClass(r);else void 0!==t&&"boolean"!==a||((r=Ct(this))&&_.set(this,"__className__",r),this.setAttribute&&this.setAttribute("class",r||!1===t?"":_.get(this,"__className__")||""))}))},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+Tt(Ct(n))+" ").indexOf(t))return!0;return!1}});var St=/\r/g;ce.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=v(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,ce(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=ce.map(t,function(e){return null==e?"":e+""})),(r=ce.valHooks[this.type]||ce.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=ce.valHooks[t.type]||ce.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(St,""):null==e?"":e:void 0}}),ce.extend({valHooks:{option:{get:function(e){var t=ce.find.attr(e,"value");return null!=t?t:Tt(ce.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!fe(n.parentNode,"optgroup"))){if(t=ce(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=ce.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<ce.inArray(ce.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),ce.each(["radio","checkbox"],function(){ce.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<ce.inArray(ce(e).val(),t)}},le.checkOn||(ce.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Et=ie.location,jt={guid:Date.now()},At=/\?/;ce.parseXML=function(e){var t,n;if(!e||"string"!=typeof e)return null;try{t=(new ie.DOMParser).parseFromString(e,"text/xml")}catch(e){}return n=t&&t.getElementsByTagName("parsererror")[0],t&&!n||ce.error("Invalid XML: "+(n?ce.map(n.childNodes,function(e){return e.textContent}).join("\n"):e)),t};var Dt=/^(?:focusinfocus|focusoutblur)$/,Nt=function(e){e.stopPropagation()};ce.extend(ce.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||C],d=ue.call(e,"type")?e.type:e,h=ue.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||C,3!==n.nodeType&&8!==n.nodeType&&!Dt.test(d+ce.event.triggered)&&(-1<d.indexOf(".")&&(d=(h=d.split(".")).shift(),h.sort()),u=d.indexOf(":")<0&&"on"+d,(e=e[ce.expando]?e:new ce.Event(d,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:ce.makeArray(t,[e]),c=ce.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!y(n)){for(s=c.delegateType||d,Dt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||C)&&p.push(a.defaultView||a.parentWindow||ie)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(_.get(o,"events")||Object.create(null))[e.type]&&_.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&$(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!$(n)||u&&v(n[d])&&!y(n)&&((a=n[u])&&(n[u]=null),ce.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,Nt),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,Nt),ce.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=ce.extend(new ce.Event,n,{type:e,isSimulated:!0});ce.event.trigger(r,null,t)}}),ce.fn.extend({trigger:function(e,t){return this.each(function(){ce.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return ce.event.trigger(e,t,n,!0)}});var qt=/\[\]$/,Lt=/\r?\n/g,Ht=/^(?:submit|button|image|reset|file)$/i,Ot=/^(?:input|select|textarea|keygen)/i;function Pt(n,e,r,i){var t;if(Array.isArray(e))ce.each(e,function(e,t){r||qt.test(n)?i(n,t):Pt(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==x(e))i(n,e);else for(t in e)Pt(n+"["+t+"]",e[t],r,i)}ce.param=function(e,t){var n,r=[],i=function(e,t){var n=v(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!ce.isPlainObject(e))ce.each(e,function(){i(this.name,this.value)});else for(n in e)Pt(n,e[n],t,i);return r.join("&")},ce.fn.extend({serialize:function(){return ce.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=ce.prop(this,"elements");return e?ce.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!ce(this).is(":disabled")&&Ot.test(this.nodeName)&&!Ht.test(e)&&(this.checked||!we.test(e))}).map(function(e,t){var n=ce(this).val();return null==n?null:Array.isArray(n)?ce.map(n,function(e){return{name:t.name,value:e.replace(Lt,"\r\n")}}):{name:t.name,value:n.replace(Lt,"\r\n")}}).get()}});var Mt=/%20/g,Rt=/#.*$/,It=/([?&])_=[^&]*/,Wt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Ft=/^(?:GET|HEAD)$/,$t=/^\/\//,Bt={},_t={},zt="*/".concat("*"),Xt=C.createElement("a");function Ut(o){return function(e,t){"string"!=typeof e&&(t=e,e="*");var n,r=0,i=e.toLowerCase().match(D)||[];if(v(t))while(n=i[r++])"+"===n[0]?(n=n.slice(1)||"*",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function Vt(t,i,o,a){var s={},u=t===_t;function l(e){var r;return s[e]=!0,ce.each(t[e]||[],function(e,t){var n=t(i,o,a);return"string"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s["*"]&&l("*")}function Gt(e,t){var n,r,i=ce.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&ce.extend(!0,e,r),e}Xt.href=Et.href,ce.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Et.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Et.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":zt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":ce.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Gt(Gt(e,ce.ajaxSettings),t):Gt(ce.ajaxSettings,e)},ajaxPrefilter:Ut(Bt),ajaxTransport:Ut(_t),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=ce.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?ce(y):ce.event,x=ce.Deferred(),b=ce.Callbacks("once memory"),w=v.statusCode||{},a={},s={},u="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=Wt.exec(p))n[t[1].toLowerCase()+" "]=(n[t[1].toLowerCase()+" "]||[]).concat(t[2])}t=n[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||Et.href)+"").replace($t,Et.protocol+"//"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||"*").toLowerCase().match(D)||[""],null==v.crossDomain){r=C.createElement("a");try{r.href=v.url,r.href=r.href,v.crossDomain=Xt.protocol+"//"+Xt.host!=r.protocol+"//"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&"string"!=typeof v.data&&(v.data=ce.param(v.data,v.traditional)),Vt(Bt,v,t,T),h)return T;for(i in(g=ce.event&&v.global)&&0==ce.active++&&ce.event.trigger("ajaxStart"),v.type=v.type.toUpperCase(),v.hasContent=!Ft.test(v.type),f=v.url.replace(Rt,""),v.hasContent?v.data&&v.processData&&0===(v.contentType||"").indexOf("application/x-www-form-urlencoded")&&(v.data=v.data.replace(Mt,"+")):(o=v.url.slice(f.length),v.data&&(v.processData||"string"==typeof v.data)&&(f+=(At.test(f)?"&":"?")+v.data,delete v.data),!1===v.cache&&(f=f.replace(It,"$1"),o=(At.test(f)?"&":"?")+"_="+jt.guid+++o),v.url=f+o),v.ifModified&&(ce.lastModified[f]&&T.setRequestHeader("If-Modified-Since",ce.lastModified[f]),ce.etag[f]&&T.setRequestHeader("If-None-Match",ce.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader("Content-Type",v.contentType),T.setRequestHeader("Accept",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+("*"!==v.dataTypes[0]?", "+zt+"; q=0.01":""):v.accepts["*"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u="abort",b.add(v.complete),T.done(v.success),T.fail(v.error),c=Vt(_t,v,t,T)){if(T.readyState=1,g&&m.trigger("ajaxSend",[T,v]),h)return T;v.async&&0<v.timeout&&(d=ie.setTimeout(function(){T.abort("timeout")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,"No Transport");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&ie.clearTimeout(d),c=void 0,p=r||"",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),!i&&-1<ce.inArray("script",v.dataTypes)&&ce.inArray("json",v.dataTypes)<0&&(v.converters["text script"]=function(){}),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader("Last-Modified"))&&(ce.lastModified[f]=u),(u=T.getResponseHeader("etag"))&&(ce.etag[f]=u)),204===e||"HEAD"===v.type?l="nocontent":304===e?l="notmodified":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l="error",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+"",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?"ajaxSuccess":"ajaxError",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger("ajaxComplete",[T,v]),--ce.active||ce.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return ce.get(e,t,n,"json")},getScript:function(e,t){return ce.get(e,void 0,t,"script")}}),ce.each(["get","post"],function(e,i){ce[i]=function(e,t,n,r){return v(t)&&(r=r||n,n=t,t=void 0),ce.ajax(ce.extend({url:e,type:i,dataType:r,data:t,success:n},ce.isPlainObject(e)&&e))}}),ce.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),ce._evalUrl=function(e,t,n){return ce.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){ce.globalEval(e,t,n)}})},ce.fn.extend({wrapAll:function(e){var t;return this[0]&&(v(e)&&(e=e.call(this[0])),t=ce(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return v(n)?this.each(function(e){ce(this).wrapInner(n.call(this,e))}):this.each(function(){var e=ce(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=v(t);return this.each(function(e){ce(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){ce(this).replaceWith(this.childNodes)}),this}}),ce.expr.pseudos.hidden=function(e){return!ce.expr.pseudos.visible(e)},ce.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},ce.ajaxSettings.xhr=function(){try{return new ie.XMLHttpRequest}catch(e){}};var Yt={0:200,1223:204},Qt=ce.ajaxSettings.xhr();le.cors=!!Qt&&"withCredentials"in Qt,le.ajax=Qt=!!Qt,ce.ajaxTransport(function(i){var o,a;if(le.cors||Qt&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,"abort"===e?r.abort():"error"===e?"number"!=typeof r.status?t(0,"error"):t(r.status,r.statusText):t(Yt[r.status]||r.status,r.statusText,"text"!==(r.responseType||"text")||"string"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o("error"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&ie.setTimeout(function(){o&&a()})},o=o("abort");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),ce.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),ce.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return ce.globalEval(e),e}}}),ce.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),ce.ajaxTransport("script",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=ce("<script>").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1<s&&(r=Tt(e.slice(s)),e=e.slice(0,s)),v(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),0<a.length&&ce.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?ce("<div>").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var en=/^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g;ce.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),v(e))return r=ae.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(ae.call(arguments)))}).guid=e.guid=e.guid||ce.guid++,i},ce.holdReady=function(e){e?ce.readyWait++:ce.ready(!0)},ce.isArray=Array.isArray,ce.parseJSON=JSON.parse,ce.nodeName=fe,ce.isFunction=v,ce.isWindow=y,ce.camelCase=F,ce.type=x,ce.now=Date.now,ce.isNumeric=function(e){var t=ce.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},ce.trim=function(e){return null==e?"":(e+"").replace(en,"$1")},"function"==typeof define&&define.amd&&define("jquery",[],function(){return ce});var tn=ie.jQuery,nn=ie.$;return ce.noConflict=function(e){return ie.$===ce&&(ie.$=nn),e&&ie.jQuery===ce&&(ie.jQuery=tn),ce},"undefined"==typeof e&&(ie.jQuery=ie.$=ce),ce}); diff --git a/rejects.csv b/rejects.csv new file mode 100644 index 0000000..dd04c27 --- /dev/null +++ b/rejects.csv @@ -0,0 +1,3 @@ +name,twitter,story,source,now +Fredrik,https://twitter.com/@hagaetc/,"The story of when @DuneAnalytics applied to @ycombinator in 2018 . Was on the video call 9PM Friday night and sat there for 2 hours without it working. Never got rescheduled, then got bulk rejected a few days later. Took us another 6 months after this to close our pre-seed",https://twitter.com/hagaetc/status/1562452240019132419/,https://dune.com/ +Joel Gascoigne,https://twitter.com/@joelgascoigne/,"When Buffer was in its very early stages, just a couple months after the product launched, we applied to be part of the prestigious Y Combinator accelerator program. We were rejected...",https://buffer.com/resources/buffers-y-combinator-application/,https://buffer.com/ \ No newline at end of file diff --git a/rejects.json b/rejects.json new file mode 100644 index 0000000..2635baf --- /dev/null +++ b/rejects.json @@ -0,0 +1,16 @@ +[ + { + "name": "Fredrik", + "twitter": "https://twitter.com/@hagaetc/", + "story": "The story of when @DuneAnalytics applied to @ycombinator in 2018 . Was on the video call 9PM Friday night and sat there for 2 hours without it working. Never got rescheduled, then got bulk rejected a few days later. Took us another 6 months after this to close our pre-seed", + "source": "https://twitter.com/hagaetc/status/1562452240019132419/", + "now": "https://dune.com/" + }, + { + "name": "Joel Gascoigne", + "twitter": "https://twitter.com/@joelgascoigne/", + "story": "When Buffer was in its very early stages, just a couple months after the product launched, we applied to be part of the prestigious Y Combinator accelerator program. We were rejected...", + "source": "https://buffer.com/resources/buffers-y-combinator-application/", + "now": "https://buffer.com/" + } +] \ No newline at end of file diff --git a/rejects.tsv b/rejects.tsv new file mode 100644 index 0000000..ec24f50 --- /dev/null +++ b/rejects.tsv @@ -0,0 +1,3 @@ +name twitter story source now +Fredrik https://twitter.com/@hagaetc/ The story of when @DuneAnalytics applied to @ycombinator in 2018 . Was on the video call 9PM Friday night and sat there for 2 hours without it working. Never got rescheduled, then got bulk rejected a few days later. Took us another 6 months after this to close our pre-seed https://twitter.com/hagaetc/status/1562452240019132419/ https://dune.com/ +Joel Gascoigne https://twitter.com/@joelgascoigne/ When Buffer was in its very early stages, just a couple months after the product launched, we applied to be part of the prestigious Y Combinator accelerator program. We were rejected... https://buffer.com/resources/buffers-y-combinator-application/ https://buffer.com/ \ No newline at end of file diff --git a/roboto.css b/roboto.css new file mode 100644 index 0000000..8dbb091 --- /dev/null +++ b/roboto.css @@ -0,0 +1,39 @@ +body { + font-family: "Roboto", sans-serif; + line-height: 1.6; + max-width: 800px; + margin: 0 auto; + padding: 20px; + background-color: #f0f0f0; + color: #333; +} +h1, +h2, +h3 { + margin-top: 1.5em; +} +a { + color: #0066cc; + text-decoration: none; + background-image: linear-gradient(currentColor, currentColor); + background-position: 0% 100%; + background-repeat: no-repeat; + background-size: 0% 2px; + transition: background-size 0.3s; +} +a:hover, +a:focus { + background-size: 100% 2px; +} +iframe { + width: 300px; + height: 300px; + border-radius: 15px; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); + padding: 20px; + display: inline-block; +} +code { + background: rgba(224, 224, 224); + padding: 3px; +} diff --git a/tableSearch.js b/tableSearch.js new file mode 100644 index 0000000..665d174 --- /dev/null +++ b/tableSearch.js @@ -0,0 +1,180 @@ +class Patch { + constructor( + patchInput = "", + grammar = { + rowDelimiter: "&", + columnDelimiter: "=", + encodedRowDelimiter: "%2E%2E%2E", + encodedColumnDelimiter: "%7E" + } + ) { + // The pipeline of encodings. Operations will be run in order for encoding (and reveresed for decoding). + this.encoders = [ + { + encode: str => encodeURIComponent(str), + decode: str => decodeURIComponent(str) + }, + { + encode: str => this.replaceAll(str, this.grammar.columnDelimiter, this.grammar.encodedColumnDelimiter), + decode: str => this.replaceAll(str, this.grammar.encodedColumnDelimiter, this.grammar.columnDelimiter) + }, + { + encode: str => this.replaceAll(str, this.grammar.rowDelimiter, this.grammar.encodedRowDelimiter), + decode: str => this.replaceAll(str, this.grammar.encodedRowDelimiter, this.grammar.rowDelimiter) + }, + { + // Turn "%20" into "+" for prettier urls. + encode: str => str.replace(/\%20/g, "+"), + decode: str => str.replace(/\+/g, "%20") + } + ] + this.grammar = grammar + if (typeof patchInput === "string") this.uriEncodedString = patchInput + else if (Array.isArray(patchInput)) this.uriEncodedString = this.arrayToEncodedString(patchInput) + else this.uriEncodedString = this.objectToEncodedString(patchInput) + } + replaceAll(str, search, replace) { + return str.split(search).join(replace) + } + objectToEncodedString(obj) { + return Object.keys(obj) + .map(identifierCell => { + const value = obj[identifierCell] + const valueCells = value instanceof Array ? value : [value] + const row = [identifierCell, ...valueCells].map(cell => this.encodeCell(cell)) + return row.join(this.grammar.columnDelimiter) + }) + .join(this.grammar.rowDelimiter) + } + arrayToEncodedString(arr) { + return arr.map(line => line.map(cell => this.encodeCell(cell)).join(this.grammar.columnDelimiter)).join(this.grammar.rowDelimiter) + } + get array() { + return this.uriEncodedString.split(this.grammar.rowDelimiter).map(line => line.split(this.grammar.columnDelimiter).map(cell => this.decodeCell(cell))) + } + get object() { + const patchObj = {} + if (!this.uriEncodedString) return patchObj + this.array.forEach(cells => { + const identifierCell = cells.shift() + patchObj[identifierCell] = cells.length > 1 ? cells : cells[0] // If a single value, collapse to a simple tuple. todo: sure about this design? + }) + return patchObj + } + encodeCell(unencodedCell) { + return this.encoders.reduce((str, encoder) => encoder.encode(str), unencodedCell) + } + decodeCell(encodedCell) { + return this.encoders + .slice() + .reverse() + .reduce((str, encoder) => encoder.decode(str), encodedCell) + } +} + +class TableSearchApp { + constructor() { + this.createDatatable() + this.bindToHashChange() + } + + get windowHash() { + return window.location.hash.replace(/^#/, "") + } + + get objectFromHash() { + return new Patch(this.windowHash).object + } + + setObjectOnHash(obj) { + if (obj.order === "0.asc") delete obj.order + Object.keys(obj).forEach(key => { + if (obj[key] === "") delete obj[key] + }) + const newHash = new Patch(obj).uriEncodedString + if (this.windowHash !== newHash) window.location.hash = newHash + } + + bindToHashChange() { + // Listen for hash changes to update the DataTable search + window.addEventListener("hashchange", () => { + this.dataTables.search(this.searchFromHash).order(this.orderFromHash).draw(false) + }) + } + + createDatatable() { + this.dataTables = jQuery("table.scrollTable").DataTable({ + paging: false, + stateSave: true, + stateSaveCallback: (settings, data) => { + const order = data.order.map(([column, direction]) => `${column}.${direction}`).join(this.columnDelimiter) + const patch = { + q: data.search.search, + order + } + + this.setObjectOnHash(patch) + }, + layout: { + topStart: { + buttons: ["copy"] + }, + topEnd: { + search: { + placeholder: "Search" + } + } + }, + // Set the search input to the initial value extracted from the URL + search: { search: this.searchFromHash }, + order: this.orderFromHash, + stateLoadCallback: settings => { + return { + search: { + order: this.orderFromHash, + search: this.searchFromHash + } + } + } + }) + this.addExpandButtons() + } + + addExpandButtons() { + let buttons = document.querySelectorAll(".buttons-copy") + + buttons.forEach(button => { + let ariaControls = button.getAttribute("aria-controls") + let newHTML = `<button id="expandToggle${ariaControls}" aria-controls="${ariaControls}" class="dt-button buttons-html5 expandCollapseButton" tabindex="1" type="button"><span>Expand</span></button>` + + button.insertAdjacentHTML("afterend", newHTML) + + // Get the newly added element by ID + let newButton = document.getElementById(`expandToggle${ariaControls}`) + + // Add an onclick handler (in case you want to do more complex actions) + newButton.onclick = function () { + this.innerHTML = this.innerHTML.includes("Expand") ? "Collapse" : "Expand" + document.querySelector("#" + ariaControls).classList.toggle("expandedTable") + } + }) + } + + get orderFromHash() { + const order = this.objectFromHash.order + return order + ? order.split(this.columnDelimiter).map(o => { + const parts = o.split(".") + return [parseInt(parts[0]), parts[1]] + }) + : [] + } + + columnDelimiter = "~" + + get searchFromHash() { + return this.objectFromHash.q || "" + } +} + +document.addEventListener("DOMContentLoaded", () => (window.tableSearchApp = new TableSearchApp())) ------------------------------------------------------------
commit 22e50e1a99890c903d8e6a35352aaada3566cfc2
Author: ffff:172.59.227.72 <ffff:172.59.227.72@hub.scroll.pub> Date: Fri Oct 4 01:09:43 2024 +0000 Updated index.scroll diff --git a/index.scroll b/index.scroll index 2ab5f1e..1c82de1 100644 --- a/index.scroll +++ b/index.scroll @@ -1,21 +1,7 @@ -title RejectedByYC +title I CAN PISS REALLY HARD header.scroll printTitle -SCROLLHUB IS FUNDAMENTALLY FLAWED BECAUSE ANYONE FROM ANYWHERE CAN EDIT ANY FOLDER. ADD AUTHENTICATION. -Original by Leo. This version by Breck. - https://rejectedbyyc.com/ Original - https://twitter.com/leonagano/ Leo - https://twitter.com/breckyunits Breck - -A curated list of companies and founders rejected by YC* - -table rejects.tsv - rename twitter nameLink - rename source storyLink - printTable -tableSearch - footer.scroll ------------------------------------------------------------
commit ea4d09240d6e5508373ef4aa03225519a5f88b2e
Author: ffff:100.6.57.73 <ffff:100.6.57.73@hub.scroll.pub> Date: Thu Oct 3 23:20:38 2024 +0000 Updated index.scroll diff --git a/index.scroll b/index.scroll index 569b499..2ab5f1e 100644 --- a/index.scroll +++ b/index.scroll @@ -1,20 +1,21 @@ -title RejectedByYC - -header.scroll - -printTitle - -Original by Leo. This version by Breck. - https://rejectedbyyc.com/ Original - https://twitter.com/leonagano/ Leo - https://twitter.com/breckyunits Breck - -A curated list of companies and founders rejected by YC* - -table rejects.tsv - rename twitter nameLink - rename source storyLink - printTable -tableSearch - -footer.scroll +title RejectedByYC + +header.scroll + +printTitle + +SCROLLHUB IS FUNDAMENTALLY FLAWED BECAUSE ANYONE FROM ANYWHERE CAN EDIT ANY FOLDER. ADD AUTHENTICATION. +Original by Leo. This version by Breck. + https://rejectedbyyc.com/ Original + https://twitter.com/leonagano/ Leo + https://twitter.com/breckyunits Breck + +A curated list of companies and founders rejected by YC* + +table rejects.tsv + rename twitter nameLink + rename source storyLink + printTable +tableSearch + +footer.scroll ------------------------------------------------------------
commit 444b71e752c76a616fd36b63cc4751114c12b921
Author: ffff:167.224.113.210 <ffff:167.224.113.210@hub.scroll.pub> Date: Thu Oct 3 20:33:54 2024 +0000 Updated rejects.scroll diff --git a/rejects.scroll b/rejects.scroll index 2755baa..48d6a0b 100644 --- a/rejects.scroll +++ b/rejects.scroll @@ -7,3 +7,9 @@ twitter https://twitter.com/@hagaetc/ story The story of when @DuneAnalytics applied to @ycombinator in 2018 . Was on the video call 9PM Friday night and sat there for 2 hours without it working. Never got rescheduled, then got bulk rejected a few days later. Took us another 6 months after this to close our pre-seed source https://twitter.com/hagaetc/status/1562452240019132419/ now https://dune.com/ + +name Joel Gascoigne +twitter https://twitter.com/@joelgascoigne/ +story When Buffer was in its very early stages, just a couple months after the product launched, we applied to be part of the prestigious Y Combinator accelerator program. We were rejected... +source https://buffer.com/resources/buffers-y-combinator-application/ +now https://buffer.com/ ------------------------------------------------------------
commit 9d630762344fd90d61a92da94396455e6528f1c1
Author: ffff:167.224.113.210 <ffff:167.224.113.210@hub.scroll.pub> Date: Thu Oct 3 20:32:42 2024 +0000 Updated footer.scroll diff --git a/footer.scroll b/footer.scroll index e371b40..aee022c 100644 --- a/footer.scroll +++ b/footer.scroll @@ -1 +1,4 @@ *This website is not associated with YCombinator + +JSON + link rejects.json ------------------------------------------------------------
commit 2fa2f3b8378a3a9ce7f452b82e64ec81a9cf7aaf
Author: ffff:167.224.113.210 <ffff:167.224.113.210@hub.scroll.pub> Date: Thu Oct 3 20:32:21 2024 +0000 Updated header.scroll diff --git a/header.scroll b/header.scroll index c573bc5..40397de 100644 --- a/header.scroll +++ b/header.scroll @@ -4,4 +4,6 @@ buildTxt metaTags +theme gazette roboto + homeButton ------------------------------------------------------------
commit ad8571a1fef1c5ce05e4e85c669f5bd5d9201e41
Author: ffff:167.224.113.210 <ffff:167.224.113.210@hub.scroll.pub> Date: Thu Oct 3 20:32:14 2024 +0000 Updated index.scroll diff --git a/index.scroll b/index.scroll index f7040c9..569b499 100644 --- a/index.scroll +++ b/index.scroll @@ -1,7 +1,6 @@ -buildHtml title RejectedByYC -theme gazette roboto +header.scroll printTitle ------------------------------------------------------------
commit 2529ca280a325b2b297e1b855a2ef05e3175aabf
Author: ffff:167.224.113.210 <ffff:167.224.113.210@hub.scroll.pub> Date: Thu Oct 3 20:32:04 2024 +0000 Updated header.scroll diff --git a/header.scroll b/header.scroll index ecec9ad..c573bc5 100644 --- a/header.scroll +++ b/header.scroll @@ -3,3 +3,5 @@ buildHtml buildTxt metaTags + +homeButton ------------------------------------------------------------
commit 9930ba3b034c16b6028cc544ddac584f042624d1
Author: ffff:167.224.113.210 <ffff:167.224.113.210@hub.scroll.pub> Date: Thu Oct 3 20:31:57 2024 +0000 Updated header.scroll diff --git a/header.scroll b/header.scroll index e16b4dd..ecec9ad 100644 --- a/header.scroll +++ b/header.scroll @@ -1,4 +1,5 @@ importOnly buildHtml +buildTxt metaTags ------------------------------------------------------------
commit 329911952833fb7419c461b19d2b8e59c374914d
Author: ffff:167.224.113.210 <ffff:167.224.113.210@hub.scroll.pub> Date: Thu Oct 3 20:31:44 2024 +0000 Updated header.scroll diff --git a/header.scroll b/header.scroll index 8b13789..e16b4dd 100644 --- a/header.scroll +++ b/header.scroll @@ -1 +1,4 @@ +importOnly +buildHtml +metaTags