UNPKG

12 kBJavaScriptView Raw
1import angular from 'angular';
2
3import 'dom4';
4import debounce from 'just-debounce-it';
5
6import {getRect, getStyles, getWindowHeight} from '../global/dom';
7import PlaceUnder from '../place-under-ng/place-under-ng';
8import Checkbox from '../checkbox-ng/checkbox-ng';
9
10import Selection from './table-legacy-ng__selection';
11import SelectionNavigateActions from './table-legacy-ng__selection-navigate-actions';
12import TableToolbar from './table-legacy-ng__toolbar';
13import TablePager from './table-legacy-ng__pager';
14
15import '../table-legacy/table-legacy.scss';
16
17/**
18 * @name Table Legacy Ng
19 */
20
21const angularModule = angular.module(
22 'Ring.table-legacy',
23 [TableToolbar, TablePager, Checkbox, PlaceUnder]
24);
25
26angularModule.directive('rgLegacyTable', function rgLegacyTableDirective() {
27 return {
28 restrict: 'E',
29 transclude: true,
30 template: require('./table-legacy-ng.html'),
31 controllerAs: 'ctrl',
32
33 /**
34 * {{
35 * items: array, items of table
36 * selection: {Selection}?, a selection object link can be provided to use it outside the table
37 * }}
38 */
39 scope: {
40 items: '=',
41 selection: '=?',
42 disableSelection: '@'
43 },
44
45 bindToController: true,
46
47 controller: function controller($scope) {
48 this.$onInit = () => {
49 if (this.disableSelection) {
50 return;
51 }
52
53 /**
54 * Create Selection instance first to make sure it is always available
55 * @type {Selection}
56 */
57 this.selection = new Selection(this.items, (name, item, index) => {
58 $scope.$emit(name, item, index);
59 $scope.$broadcast(name, item, index);
60 });
61
62 /**
63 * Updating items when data is initiated or updated
64 */
65 $scope.$watch(() => this.items, newItems => {
66 if (newItems) {
67 this.selection.setItems(newItems);
68 }
69 });
70 };
71 }
72 };
73});
74
75angularModule.directive('rgLegacyTableHeader',
76 function rgLegacyTableHeaderDirective(getClosestElementWithCommonParent) {
77 const HEADER_RESIZE_DEBOUNCE = 50;
78 const HEADER_SCROLL_DEBOUNCE = 10;
79 const TOOLBAR_FIXED_CLASSNAME = 'ring-table__toolbar-controls_fixed';
80
81 return {
82 restrict: 'E',
83 template: require('./table-legacy-ng__header.html'),
84 transclude: true,
85 replace: true,
86 link: function link(scope, iElement, iAttrs) {
87 const element = iElement[0];
88 let stickToElement = null;
89
90 scope.stickToSelector = iAttrs.stickTo;
91
92 //Shortcut for placing under table toolbar
93 if (iAttrs.stickToToolbar !== undefined) {
94 scope.stickToSelector = '.ring-table__toolbar';
95 }
96
97 const scrollableHeader = element.query('.ring-table__header:not(.ring-table__header_sticky)');
98 const fixedHeader = element.query('.ring-table__header_sticky');
99
100 const toolbarFixed = () => stickToElement.query(`.${TOOLBAR_FIXED_CLASSNAME}`) !== null;
101
102 /**
103 * Sync header columns width with real table
104 */
105 const resizeFixedHeader = debounce(() => {
106 fixedHeader.style.width = `${scrollableHeader.offsetWidth}px`;
107 const titles = fixedHeader.queryAll('.ring-table__title');
108
109 titles.forEach((titleElement, index) => {
110 const targetHeaderTitle = scrollableHeader.queryAll('.ring-table__title')[index];
111 titleElement.style.width = getStyles(targetHeaderTitle).width;
112 });
113
114 }, HEADER_RESIZE_DEBOUNCE, true);
115
116 /**
117 * Toggle headers on scroll. Also resize header columns with some big interval
118 */
119 const scrollListener = debounce(() => {
120 if (toolbarFixed()) {
121 fixedHeader.style.display = 'block';
122 scrollableHeader.style.visibility = 'hidden';
123 } else {
124 fixedHeader.style.display = 'none';
125 scrollableHeader.style.visibility = 'visible';
126 }
127
128 resizeFixedHeader();
129 }, HEADER_SCROLL_DEBOUNCE);
130
131 function startSticking() {
132 scope.$evalAsync(() => {
133 window.addEventListener('resize', resizeFixedHeader);
134 window.addEventListener('scroll', scrollListener);
135 scope.$on('rgLegacyTable:itemsChanged', scrollListener);
136 });
137 }
138
139 if (scope.stickToSelector) {
140 stickToElement = getClosestElementWithCommonParent(element, scope.stickToSelector);
141 startSticking();
142 }
143 }
144 };
145 }
146);
147
148angularModule.directive('rgLegacyTableBody', function rgLegacyTableBodyDirective() {
149 return {
150 restrict: 'E',
151 template: '<tbody ng-transclude></tbody>',
152 transclude: true,
153 replace: true
154 };
155});
156
157angularModule.directive('rgLegacyTableRow', function rgLegacyTableRowDirective() {
158 return {
159 template: require('./table-legacy-ng__row.html'),
160 restrict: 'E',
161 transclude: true,
162 replace: true,
163 require: ['^rgLegacyTable', 'rgLegacyTableRow'],
164
165 scope: {
166 rowItem: '='
167 },
168
169 link: function link(scope, iElement, iAttrs, ctrls) {
170 const rgLegacyTableCtrl = ctrls[0];
171 const rgLegacyTableRowCtrl = ctrls[1];
172 rgLegacyTableRowCtrl.setSelection(rgLegacyTableCtrl.selection);
173 },
174
175 controllerAs: 'rowCtrl',
176 bindToController: true,
177
178 controller: function controller($scope, $element) {
179 const element = $element[0];
180
181 let watchRowCheckFlag;
182 this.setSelection = selection => {
183 if (!selection) {
184 return;
185 }
186
187 this.selection = selection;
188
189 if (!watchRowCheckFlag) {
190 watchRowCheckFlag = $scope.$watch('rowCtrl.rowItem.checked', newValue => {
191 if (newValue !== undefined) {
192 this.selection.triggerSelectionChanged(this.rowItem);
193 }
194 });
195 }
196 };
197
198 this.setActiveItem = item => {
199 item && !item.unselectable && this.selection && this.selection.activateItem(item);
200 };
201
202 this.onMouseOver = item => {
203 item && !item.unselectable && this.selection && this.selection.setSuggestedItem(item);
204 };
205
206 this.onMouseOut = item => {
207 item &&
208 this.selection &&
209 item === this.selection.suggestedItem && this.selection.setSuggestedItem(null);
210 };
211
212 this.hasCheckedItems = () => {
213 if (!this.selection) {
214 return false;
215 }
216 //TODO: cache this operation if there are performance issues
217 const checkedItems = this.selection.getCheckedItems();
218 return checkedItems && checkedItems.length > 0;
219 };
220
221 function getRowOutOfViewInfo(el, offsetInRows) {
222 const rect = getRect(el);
223 const offset = rect.height * offsetInRows;
224
225 const isGoneUp = rect.top < offset;
226 const isGoneDown = rect.bottom > (getWindowHeight() - offset);
227
228 return {
229 offset,
230 isOutOfView: isGoneDown || isGoneUp,
231 isGoneUp,
232 isGoneDown
233 };
234 }
235
236 function addSpacingAfterScroll(offset) {
237 if (window.scrollY) {
238 window.scrollBy(0, offset);
239 }
240 }
241
242 $scope.$on('rgLegacyTable:activateItem', (e, item) => {
243 if (item === this.rowItem) {
244 const scrollInfo = getRowOutOfViewInfo(element, 2);
245 if (scrollInfo.isOutOfView) {
246 element.scrollIntoView(scrollInfo.isGoneUp);
247 addSpacingAfterScroll(scrollInfo.isGoneDown ? scrollInfo.offset : -scrollInfo.offset);
248 }
249 }
250 });
251 }
252 };
253});
254
255angularModule.
256 directive('rgLegacyTableHeaderCheckbox', function rgLegacyTableHeaderCheckboxDirective() {
257 return {
258 restrict: 'E',
259 require: '^rgLegacyTable',
260 replace: true,
261 template: '<span class="ring-table__header-checkbox"><rg-checkbox ng-click="onClickChange()" ng-model="allChecked"/></span>',
262
263 link: function link(scope, iElement, iAttrs, tableCtrl) {
264 // TODO: reduce number of recheckSelection() calls
265 scope.allChecked = false;
266
267 function recheckSelection() {
268 if (tableCtrl.items && tableCtrl.items.length) {
269 scope.allChecked = tableCtrl.items.every(item => item.checked);
270 } else {
271 scope.allChecked = false;
272 }
273 }
274
275 function markAllItemsAs(state) {
276 tableCtrl.items.forEach(item => {
277 item.checked = state;
278 });
279 }
280
281 scope.$on('rgLegacyTable:itemsChanged', () => {
282 if (scope.allChecked) {
283 markAllItemsAs(true);
284 }
285 recheckSelection();
286 });
287 scope.$on('rgLegacyTable:selectionChanged', recheckSelection);
288
289 scope.onClickChange = () => {
290 markAllItemsAs(scope.allChecked);
291 };
292 }
293 };
294 });
295
296/**
297 * A checkbox cell for table. Uses rg-table-row parent directive as model host
298 */
299angularModule.directive('rgLegacyTableCheckboxCell', function rgLegacyTableCheckboxCellDirective() {
300 return {
301 restrict: 'E',
302 transclude: true,
303 require: '^rgLegacyTableRow',
304 replace: true,
305 template: '<td class="ring-table__selector ring-table__column_selector" ng-class="{\'ring-table__column\': !isEmbedded}"><rg-checkbox ng-model="getRowItem().checked"/></td>',
306
307 link: function link(scope, iElement, iAttrs, rowCtrl) {
308 /**
309 * rowItem getter to use it as ng-model for checkbox
310 */
311 scope.getRowItem = () => rowCtrl.rowItem;
312 scope.isEmbedded = angular.isDefined(iAttrs.embedded);
313 }
314 };
315});
316
317/**
318 * Table title wrapper, receive next attributes:
319 * {{
320 border: whether or not title contain right border
321 active: makes title more bolder
322 }}
323 */
324angularModule.directive('rgLegacyTableTitle', function rgLegacyTableTitleDirective() {
325 return {
326 restrict: 'E',
327 transclude: true,
328 replace: true,
329 scope: true,
330 template: require('./table-legacy-ng__title.html'),
331
332 link: function link(scope, iElement, iAttrs) {
333 /**
334 * One time property assigning without watching through isolated scope helps to improve performance
335 */
336 scope.isBorder = angular.isDefined(iAttrs.border);
337 scope.isActive = angular.isDefined(iAttrs.active);
338 scope.isPullRight = angular.isDefined(iAttrs.pullRight);
339 scope.isAlignRight = angular.isDefined(iAttrs.alignRight);
340 scope.isPullLeft = angular.isDefined(iAttrs.pullLeft);
341 }
342 };
343});
344
345/**
346 * Column wrapper, receive next attributes:
347 * {{
348 limited: is column width should be limited,
349 wide: for wide columns
350 avatar: for columns contains avatar
351 }}
352 */
353angularModule.directive('rgLegacyTableColumn', function rgLegacyTableColumnDirective() {
354 return {
355 restrict: 'E',
356 transclude: true,
357 replace: true,
358 scope: true,
359 template: require('./table-legacy-ng__column.html'),
360
361 link: function link(scope, iElement, iAttrs) {
362 const element = iElement[0];
363 const FULL_WIDTH = 100;
364
365 scope.isLimited = angular.isDefined(iAttrs.limited);
366 scope.isUnlimited = angular.isDefined(iAttrs.unlimited);
367 scope.isAvatar = angular.isDefined(iAttrs.avatar);
368 scope.isWide = angular.isDefined(iAttrs.wide);
369 scope.isAlignRight = angular.isDefined(iAttrs.alignRight);
370 scope.isGray = angular.isDefined(iAttrs.gray);
371 scope.isPullRight = angular.isDefined(iAttrs.pullRight);
372 scope.isPullLeft = angular.isDefined(iAttrs.pullLeft);
373
374 function adjustUnlimitedColumnWidths() {
375 const unlimitedColumnsCount = element.parentNode.
376 queryAll('.ring-table__column[unlimited]').length;
377 if (unlimitedColumnsCount > 1) {
378 element.style.width = `${(FULL_WIDTH / unlimitedColumnsCount).toFixed()}%`;
379 }
380 }
381
382 if (scope.isUnlimited) {
383 adjustUnlimitedColumnWidths();
384 }
385 }
386 };
387});
388
389/**
390 * Class with default hotkeys navigation actions (e.g. select, clear selection, move up/down)
391 */
392angularModule.constant('SelectionNavigateActions', SelectionNavigateActions);
393
394export default angularModule.name;