UNPKG

35.3 kBPlain TextView Raw
1import Component, { ComponentEvent } from "@egjs/component";
2import Grid, {
3 ContainerManager,
4 DEFAULT_GRID_OPTIONS,
5 Properties,
6 RenderOptions,
7 MOUNT_STATE,
8 OnContentError,
9 ItemRenderer,
10 GridItem,
11 ResizeWatcherResizeEvent,
12 getUpdatedItems,
13} from "@egjs/grid";
14import {
15 DIRECTION,
16 GROUP_TYPE,
17 INFINITEGRID_EVENTS, INFINITEGRID_PROPERTY_TYPES,
18 ITEM_TYPE, STATUS_TYPE,
19} from "./consts";
20import { GroupManager } from "./GroupManager";
21import {
22 Infinite,
23 OnInfiniteChange,
24 OnInfiniteRequestAppend,
25 OnInfiniteRequestPrepend,
26} from "./Infinite";
27import { InfiniteGridItem, InfiniteGridItemStatus } from "./InfiniteGridItem";
28import { OnRendererUpdated } from "./Renderer/Renderer";
29import { GridRendererItem, VanillaGridRenderer } from "./Renderer/VanillaGridRenderer";
30import { ScrollManager } from "./ScrollManager";
31import {
32 InfiniteGridEvents, InfiniteGridGroup,
33 InfiniteGridInsertedItems, InfiniteGridItemInfo,
34 InfiniteGridOptions,
35 InfiniteGridStatus,
36 InsertedPlaceholdersResult,
37 OnPickedRenderComplete,
38 OnRequestInsert,
39 OnChangeScroll,
40} from "./types";
41import {
42 InfiniteGridGetterSetter, toArray, convertInsertedItems, findIndex,
43 findLastIndex, isString,
44} from "./utils";
45
46
47/**
48 * A module used to arrange items including content infinitely according to layout type. With this module, you can implement various layouts composed of different items whose sizes vary. It guarantees performance by maintaining the number of DOMs the module is handling under any circumstance
49 * @ko 콘텐츠가 있는 아이템을 레이아웃 타입에 따라 무한으로 배치하는 모듈. 다양한 크기의 아이템을 다양한 레이아웃으로 배치할 수 있다. 아이템의 개수가 계속 늘어나도 모듈이 처리하는 DOM의 개수를 일정하게 유지해 최적의 성능을 보장한다
50 * @extends Component
51 * @support {"ie": "9+(with polyfill)", "ch" : "latest", "ff" : "latest", "sf" : "latest", "edge" : "latest", "ios" : "7+", "an" : "4.X+"}
52 * @example
53```html
54<ul id="grid">
55 <li class="card">
56 <div>test1</div>
57 </li>
58 <li class="card">
59 <div>test2</div>
60 </li>
61 <li class="card">
62 <div>test3</div>
63 </li>
64 <li class="card">
65 <div>test4</div>
66 </li>
67 <li class="card">
68 <div>test5</div>
69 </li>
70 <li class="card">
71 <div>test6</div>
72 </li>
73</ul>
74<script>
75import { MasonryInfiniteGrid } from "@egjs/infinitegrid";
76var some = new MasonryInfiniteGrid("#grid").on("renderComplete", function(e) {
77 // ...
78});
79// If you already have items in the container, call "layout" method.
80some.renderItems();
81</script>
82```
83 */
84@InfiniteGridGetterSetter
85class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> extends Component<InfiniteGridEvents> {
86 public static defaultOptions = {
87 ...DEFAULT_GRID_OPTIONS,
88 container: false,
89 containerTag: "div",
90 renderer: null,
91 threshold: 100,
92 useRecycle: true,
93 scrollContainer: null,
94 appliedItemChecker: (() => false) as (item: InfiniteGridItem, grid: Grid) => boolean,
95 } as Required<InfiniteGridOptions>;
96 public static propertyTypes = INFINITEGRID_PROPERTY_TYPES;
97 protected wrapperElement: HTMLElement;
98 protected scrollManager: ScrollManager;
99 protected itemRenderer: ItemRenderer;
100 protected containerManager: ContainerManager;
101 protected infinite: Infinite;
102 protected groupManager: GroupManager;
103 protected options: Required<Options>;
104 private _waitType: "" | "start" | "end" = "";
105 /**
106 * @param - A base element for a module <ko>모듈을 적용할 기준 엘리먼트</ko>
107 * @param - The option object of the InfiniteGrid module <ko>eg.InfiniteGrid 모듈의 옵션 객체</ko>
108 */
109 constructor(wrapper: HTMLElement | string, options: Options) {
110 super();
111 this.options = {
112 ...((this.constructor as typeof InfiniteGrid).defaultOptions as Required<Options>),
113 renderer: new VanillaGridRenderer().on("requestUpdate", () => this._render()),
114 ...options,
115 };
116
117 const {
118 gridConstructor,
119 containerTag,
120 container,
121 renderer,
122 threshold,
123 useRecycle,
124 scrollContainer,
125 appliedItemChecker,
126 ...gridOptions
127 } = this.options;
128 // options.container === false, wrapper = container, scrollContainer = document.body
129 // options.container === true, wrapper = scrollContainer, container = wrapper's child
130 // options.container === string,
131 const {
132 horizontal,
133 attributePrefix,
134 useTransform,
135 percentage,
136 isConstantSize,
137 isEqualSize,
138 autoResize,
139 useResizeObserver,
140 resizeDebounce,
141 maxResizeDebounce,
142 defaultDirection,
143 } = gridOptions;
144 const wrapperElement = isString(wrapper) ? document.querySelector(wrapper) as HTMLElement : wrapper;
145 const scrollManager = new ScrollManager(wrapperElement, {
146 scrollContainer,
147 container,
148 containerTag,
149 horizontal,
150 }).on({
151 scroll: this._onScroll,
152 });
153 const containerElement = scrollManager.getContainer();
154 const containerManager = new ContainerManager(containerElement, {
155 horizontal,
156 autoResize,
157 resizeDebounce,
158 maxResizeDebounce,
159 useResizeObserver,
160 }).on("resize", this._onResize);
161 const itemRenderer = new ItemRenderer({
162 attributePrefix,
163 horizontal,
164 useTransform,
165 percentage,
166 isEqualSize,
167 isConstantSize,
168 });
169 const infinite = new Infinite({
170 defaultDirection,
171 useRecycle,
172 threshold,
173 }).on({
174 "change": this._onChange,
175 "requestAppend": this._onRequestAppend,
176 "requestPrepend": this._onRequestPrepend,
177 });
178
179 infinite.setSize(scrollManager.getContentSize());
180 const groupManager = new GroupManager(containerElement, {
181 appliedItemChecker: appliedItemChecker!,
182 gridConstructor: gridConstructor!,
183 externalItemRenderer: itemRenderer,
184 externalContainerManager: containerManager,
185 gridOptions,
186 });
187
188 groupManager.on({
189 "renderComplete": this._onRenderComplete,
190 "contentError": this._onContentError,
191 });
192
193 renderer!.setContainer(containerElement);
194 renderer!.on("updated", this._onRendererUpdated);
195
196 this.itemRenderer = itemRenderer;
197 this.groupManager = groupManager;
198 this.wrapperElement = wrapperElement;
199 this.scrollManager = scrollManager;
200 this.containerManager = containerManager;
201 this.infinite = infinite;
202
203 this.containerManager.resize();
204 }
205 /**
206 * Rearrange items to fit the grid and render them. When rearrange is complete, the `renderComplete` event is fired.
207 * @ko grid에 맞게 아이템을 재배치하고 렌더링을 한다. 배치가 완료되면 `renderComplete` 이벤트가 발생한다.
208 * @param - Options for rendering. <ko>렌더링을 하기 위한 옵션.</ko>
209 * @example
210 * ```ts
211 * import { MasonryInfiniteGrid } from "@egjs/infinitegrid";
212 * const grid = new MasonryInfiniteGrid();
213 *
214 * grid.on("renderComplete", e => {
215 * console.log(e);
216 * });
217 * grid.renderItems();
218 * ```
219 */
220 public renderItems(options: RenderOptions = {}) {
221 this._renderItems(options);
222 return this;
223 }
224 /**
225 * Returns the wrapper element specified by the user.
226 * @ko 컨테이너 엘리먼트를 반환한다.
227 */
228 public getWrapperElement() {
229 return this.scrollManager.getWrapper();
230 }
231 /**
232 * Returns the container element corresponding to the scroll area.
233 * @ko 스크롤 영역에 해당하는 컨테이너 엘리먼트를 반환한다.
234 */
235 public getScrollContainerElement() {
236 return this.scrollManager.getScrollContainer();
237 }
238 /**
239 * Returns the container element containing item elements.
240 * @ko 아이템 엘리먼트들을 담긴 컨테이너 엘리먼트를 반환한다.
241 */
242 public getContainerElement() {
243 return this.scrollManager.getContainer();
244 }
245 /**
246 * When items change, it synchronizes and renders items.
247 * @ko items가 바뀐 경우 동기화를 하고 렌더링을 한다.
248 * @param - Options for rendering. <ko>렌더링을 하기 위한 옵션.</ko>
249 */
250 public syncItems(items: InfiniteGridItemInfo[]): this {
251 this.groupManager.syncItems(items);
252 this._syncGroups();
253
254 return this;
255 }
256 /**
257 * Change the currently visible groups.
258 * @ko 현재 보이는 그룹들을 바꾼다.
259 * @param - first index of visible groups. <ko>보이는 그룹의 첫번째 index.</ko>
260 * @param - last index of visible groups. <ko>보이는 그룹의 마지막 index.</ko>
261 * @param - Whether the first rendering has already been done. <ko>첫 렌더링이 이미 되어있는지 여부.</ko>
262 */
263 public setCursors(startCursor: number, endCursor: number, useFirstRender?: boolean): this {
264 this.groupManager.setCursors(startCursor, endCursor);
265 this.infinite.setCursors(startCursor, endCursor);
266
267 if (useFirstRender) {
268 this._syncItems();
269 } else {
270 this._update();
271 this._checkEndLoading();
272 }
273 return this;
274 }
275 /**
276 * Returns the first index of visible groups.
277 * @ko 보이는 그룹들의 첫번째 index를 반환한다.
278 */
279 public getStartCursor(): number {
280 return this.infinite.getStartCursor();
281 }
282 /**
283 * Returns the last index of visible groups.
284 * @ko 보이는 그룹들의 마지막 index를 반환한다.
285 */
286 public getEndCursor(): number {
287 return this.infinite.getEndCursor();
288 }
289 /**
290 * Add items at the bottom(right) of the grid.
291 * @ko 아이템들을 grid 아래(오른쪽)에 추가한다.
292 * @param - items to be added <ko>추가할 아이템들</ko>
293 * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
294 * @return - An instance of a module itself<ko>모듈 자신의 인스턴스</ko>
295 * @example
296 * ```js
297 * ig.append(`<div class="item">test1</div><div class="item">test2</div>`);
298 * ig.append([`<div class="item">test1</div>`, `<div class="item">test2</div>`]);
299 * ig.append([HTMLElement1, HTMLElement2]);
300 * ```
301 */
302 public append(items: InfiniteGridInsertedItems, groupKey?: string | number): this {
303 return this.insert(-1, items, groupKey);
304 }
305 /**
306 * Add items at the top(left) of the grid.
307 * @ko 아이템들을 grid 위(왼쪽)에 추가한다.
308 * @param - items to be added <ko>추가할 아이템들</ko>
309 * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
310 * @return - An instance of a module itself<ko>모듈 자신의 인스턴스</ko>
311 * @example
312 * ```ts
313 * ig.prepend(`<div class="item">test1</div><div class="item">test2</div>`);
314 * ig.prepend([`<div class="item">test1</div>`, `<div class="item">test2</div>`]);
315 * ig.prepend([HTMLElement1, HTMLElement2]);
316 * ```
317 */
318 public prepend(items: InfiniteGridInsertedItems, groupKey?: string | number): this {
319 return this.insert(0, items, groupKey);
320 }
321 /**
322 * Add items to a specific index.
323 * @ko 아이템들을 특정 index에 추가한다.
324 * @param - index to add <ko>추가하기 위한 index</ko>
325 * @param - items to be added <ko>추가할 아이템들</ko>
326 * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
327 * @return - An instance of a module itself<ko>모듈 자신의 인스턴스</ko>
328 * @example
329 * ```ts
330 * ig.insert(2, `<div class="item">test1</div><div class="item">test2</div>`);
331 * ig.insert(3, [`<div class="item">test1</div>`, `<div class="item">test2</div>`]);
332 * ig.insert(4, [HTMLElement1, HTMLElement2]);
333 * ```
334 */
335 public insert(index: number, items: InfiniteGridInsertedItems, groupKey?: string | number): this {
336 const nextItemInfos: InfiniteGridItemInfo[] = this.groupManager.getGroupItems();
337 const itemInfos = convertInsertedItems(items, groupKey);
338
339 if (index === -1) {
340 nextItemInfos.push(...itemInfos);
341 } else {
342 nextItemInfos.splice(index, 0, ...itemInfos);
343 }
344 return this.syncItems(nextItemInfos);
345 }
346 /**
347 * Add items based on group index.
348 * @ko group의 index 기준으로 item들을 추가한다.
349 * @param - group index to add <ko>추가하기 위한 group의 index</ko>
350 * @param - items to be added <ko>추가할 아이템들</ko>
351 * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
352 * @return - An instance of a module itself<ko>모듈 자신의 인스턴스</ko>
353 * @example
354 * ```ts
355 * ig.insertByGroupIndex(2, `<div class="item">test1</div><div class="item">test2</div>`);
356 * ig.insertByGroupIndex(3, [`<div class="item">test1</div>`, `<div class="item">test2</div>`]);
357 * ig.insertByGroupIndex(4, [HTMLElement1, HTMLElement2]);
358 * ```
359 */
360 public insertByGroupIndex(groupIndex: number, items: InfiniteGridInsertedItems, groupKey?: string | number): this {
361 const nextGroupInfos: InfiniteGridGroup[] = this.groupManager.getGroups();
362 const rightGroup = nextGroupInfos[groupIndex];
363
364 if (!rightGroup) {
365 return this.append(items, groupKey);
366 }
367 const nextItemInfos: InfiniteGridItemInfo[] = this.groupManager.getGroupItems();
368 const rightGroupKey = rightGroup.groupKey;
369 const rightItemIndex = findIndex(nextItemInfos, (item) => item.groupKey === rightGroupKey);
370
371 return this.insert(rightItemIndex, items, groupKey);
372 }
373 /**
374 * Returns the current state of a module such as location information. You can use the setStatus() method to restore the information returned through a call to this method.
375 * @ko 아이템의 위치 정보 등 모듈의 현재 상태 정보를 반환한다. 이 메서드가 반환한 정보를 저장해 두었다가 setStatus() 메서드로 복원할 수 있다
376 * @param - STATUS_TYPE.NOT_REMOVE = Get all information about items. STATUS_TYPE.REMOVE_INVISIBLE_ITEMS = Get information on visible items only. STATUS_TYPE.MINIMIZE_INVISIBLE_ITEMS = Compress invisible items. You can replace it with a placeholder. STATUS_TYPE.MINIMIZE_INVISIBLE_GROUPS = Compress invisible groups. <ko> STATUS_TYPE.NOT_REMOVE = 모든 아이템들의 정보를 가져온다. STATUS_TYPE.REMOVE_INVISIBLE_ITEMS = 보이는 아이템들의 정보만 가져온다. STATUS_TYPE.MINIMIZE_INVISIBLE_ITEMS = 안보이는 아이템들을 압축한다. placeholder로 대체가 가능하다. STATUS_TYPE.MINIMIZE_INVISIBLE_GROUPS = 안보이는 그룹을 압축한다.</ko>
377 * @param - Whether to include items corresponding to placeholders. <ko>placeholder에 해당하는 아이템들을 포함할지 여부.</ko>
378 */
379 public getStatus(type?: STATUS_TYPE, includePlaceholders?: boolean): InfiniteGridStatus {
380 return {
381 containerManager: this.containerManager.getStatus(),
382 itemRenderer: this.itemRenderer.getStatus(),
383 groupManager: this.groupManager.getGroupStatus(type, includePlaceholders),
384 scrollManager: this.scrollManager.getStatus(),
385 };
386 }
387
388 /**
389 * You can set placeholders to restore status or wait for items to be added.
390 * @ko status 복구 또는 아이템 추가 대기를 위한 placeholder를 설정할 수 있다.
391 * @param - The placeholder status. <ko>placeholder의 status</ko>
392 */
393 public setPlaceholder(info: Partial<InfiniteGridItemStatus> | null): this {
394 this.groupManager.setPlaceholder(info);
395 return this;
396 }
397 /**
398 * You can set placeholders to restore status or wait for items to be added.
399 * @ko status 복구 또는 아이템 추가 대기를 위한 placeholder를 설정할 수 있다.
400 * @param - The placeholder status. <ko>placeholder의 status</ko>
401 */
402 public setLoading(info: Partial<InfiniteGridItemStatus> | null): this {
403 this.groupManager.setLoading(info);
404 return this;
405 }
406 /**
407 * Add the placeholder at the end.
408 * @ko placeholder들을 마지막에 추가한다.
409 * @param - Items that correspond to placeholders. If it is a number, it duplicates the number of copies. <ko>placeholder에 해당하는 아이템들. 숫자면 갯수만큼 복제를 한다.</ko>
410 * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
411 */
412 public appendPlaceholders(
413 items: number | InfiniteGridItemStatus[],
414 groupKey?: string | number,
415 ): InsertedPlaceholdersResult {
416 const result = this.groupManager.appendPlaceholders(items, groupKey);
417
418 this._syncGroups(true);
419 return {
420 ...result,
421 remove: () => {
422 this.removePlaceholders({ groupKey: result.group.groupKey });
423 },
424 };
425 }
426 /**
427 * Add the placeholder at the start.
428 * @ko placeholder들을 처음에 추가한다.
429 * @param - Items that correspond to placeholders. If it is a number, it duplicates the number of copies. <ko>placeholder에 해당하는 아이템들. 숫자면 갯수만큼 복제를 한다.</ko>
430 * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
431 */
432 public prependPlaceholders(
433 items: number | InfiniteGridItemStatus[],
434 groupKey?: string | number,
435 ): InsertedPlaceholdersResult {
436 const result = this.groupManager.prependPlaceholders(items, groupKey);
437
438 this._syncGroups(true);
439 return {
440 ...result,
441 remove: () => {
442 this.removePlaceholders({ groupKey: result.group.groupKey });
443 },
444 };
445 }
446
447 /**
448 * Remove placeholders
449 * @ko placeholder들을 삭제한다.
450 * @param type - Remove the placeholders corresponding to the groupkey. When "start" or "end", remove all placeholders in that direction. <ko>groupkey에 해당하는 placeholder들을 삭제한다. "start" 또는 "end" 일 때 해당 방향의 모든 placeholder들을 삭제한다.</ko>
451 */
452 public removePlaceholders(type: "start" | "end" | { groupKey: string | number }) {
453 this.groupManager.removePlaceholders(type);
454 this._syncGroups(true);
455 }
456
457 /**
458 * Sets the status of the InfiniteGrid module with the information returned through a call to the getStatus() method.
459 * @ko getStatus() 메서드가 저장한 정보로 InfiniteGrid 모듈의 상태를 설정한다.
460 * @param - status object of the InfiniteGrid module. <ko>InfiniteGrid 모듈의 status 객체.</ko>
461 * @param - Whether the first rendering has already been done. <ko>첫 렌더링이 이미 되어있는지 여부.</ko>
462 */
463 public setStatus(status: InfiniteGridStatus, useFirstRender?: boolean): this {
464 this.itemRenderer.setStatus(status.itemRenderer);
465 this.containerManager.setStatus(status.containerManager);
466 this.scrollManager.setStatus(status.scrollManager);
467 const groupManager = this.groupManager;
468 const prevInlineSize = this.containerManager.getInlineSize();
469
470 groupManager.setGroupStatus(status.groupManager);
471 this._syncInfinite();
472 this.infinite.setCursors(groupManager.getStartCursor(), groupManager.getEndCursor());
473
474 this._getRenderer().updateKey();
475
476 const state = {
477 isResize: this.containerManager.getInlineSize() !== prevInlineSize,
478 isRestore: true,
479 };
480 if (useFirstRender) {
481 this._syncItems(state);
482 } else {
483 this._update(state);
484 }
485 return this;
486 }
487 /**
488 * Removes the group corresponding to index.
489 * @ko index에 해당하는 그룹을 제거 한다.
490 */
491 public removeGroupByIndex(index: number): this {
492 const nextGroups = this.getGroups();
493
494 return this.removeGroupByKey(nextGroups[index].groupKey);
495 }
496 /**
497 * Removes the group corresponding to key.
498 * @ko key에 해당하는 그룹을 제거 한다.
499 */
500 public removeGroupByKey(key: number | string): this {
501 const nextItemInfos = this.getItems();
502
503 const firstIndex = findIndex(nextItemInfos, (item) => item.groupKey === key);
504 const lastIndex = findLastIndex(nextItemInfos, (item) => item.groupKey === key);
505
506 if (firstIndex === -1) {
507 return this;
508 }
509 nextItemInfos.splice(firstIndex, lastIndex - firstIndex + 1);
510 return this.syncItems(nextItemInfos);
511 }
512 /**
513 * Removes the item corresponding to index.
514 * @ko index에 해당하는 아이템을 제거 한다.
515 */
516 public removeByIndex(index: number): this {
517 const nextItemInfos = this.getItems(true);
518
519 nextItemInfos.splice(index, 1);
520
521 return this.syncItems(nextItemInfos);
522 }
523 /**
524 * Removes the item corresponding to key.
525 * @ko key에 해당하는 아이템을 제거 한다.
526 */
527 public removeByKey(key: string | number): this {
528 const nextItemInfos = this.getItems(true);
529 const index = findIndex(nextItemInfos, (item) => item.key === key);
530
531 return this.removeByIndex(index);
532 }
533 /**
534 * Update the size of the items and render them.
535 * @ko 아이템들의 사이즈를 업데이트하고 렌더링을 한다.
536 * @param - Items to be updated. <ko>업데이트할 아이템들.</ko>
537 * @param - Options for rendering. <ko>렌더링을 하기 위한 옵션.</ko>
538 */
539 public updateItems(items?: InfiniteGridItem[], options: RenderOptions = {}) {
540 this.groupManager.updateItems(items, options);
541 return this;
542 }
543 /**
544 * Return all items of InfiniteGrid.
545 * @ko InfiniteGrid의 모든 아이템들을 반환한다.
546 * @param - Whether to include items corresponding to placeholders. <ko>placeholder에 해당하는 아이템들을 포함할지 여부.</ko>
547 */
548 public getItems(includePlaceholders?: boolean): InfiniteGridItem[] {
549 return this.groupManager.getGroupItems(includePlaceholders);
550 }
551 /**
552 * Return visible items of InfiniteGrid.
553 * @ko InfiniteGrid의 보이는 아이템들을 반환한다.
554 * @param - Whether to include items corresponding to placeholders. <ko>placeholder에 해당하는 아이템들을 포함할지 여부.</ko>
555 */
556 public getVisibleItems(includePlaceholders?: boolean): InfiniteGridItem[] {
557 return this.groupManager.getVisibleItems(includePlaceholders);
558 }
559
560 /**
561 * Return rendering items of InfiniteGrid.
562 * @ko InfiniteGrid의 렌더링 아이템들을 반환한다.
563 */
564 public getRenderingItems(): InfiniteGridItem[] {
565 return this.groupManager.getRenderingItems();
566 }
567 /**
568 * Return all groups of InfiniteGrid.
569 * @ko InfiniteGrid의 모든 그룹들을 반환한다.
570 * @param - Whether to include groups corresponding to placeholders. <ko>placeholder에 해당하는 그룹들을 포함할지 여부.</ko>
571 */
572 public getGroups(includePlaceholders?: boolean): InfiniteGridGroup[] {
573 return this.groupManager.getGroups(includePlaceholders);
574 }
575 /**
576 * Return visible groups of InfiniteGrid.
577 * @ko InfiniteGrid의 보이는 그룹들을 반환한다.
578 * @param - Whether to include groups corresponding to placeholders. <ko>placeholder에 해당하는 그룹들을 포함할지 여부.</ko>
579 */
580 public getVisibleGroups(includePlaceholders?: boolean): InfiniteGridGroup[] {
581 return this.groupManager.getVisibleGroups(includePlaceholders);
582 }
583 /**
584 * Set to wait to request data.
585 * @ko 데이터를 요청하기 위해 대기 상태로 설정한다.
586 * @param direction - direction in which data will be added. <ko>데이터를 추가하기 위한 방향.</ko>
587 */
588 public wait(direction: "start" | "end" = DIRECTION.END) {
589 this._waitType = direction;
590 this._checkStartLoading(direction);
591 }
592 /**
593 * When the data request is complete, it is set to ready state.
594 * @ko 데이터 요청이 끝났다면 준비 상태로 설정한다.
595 */
596 public ready() {
597 this._waitType = "";
598 }
599 /**
600 * Returns whether it is set to wait to request data.
601 * @ko 데이터를 요청하기 위해 대기 상태로 설정되어 있는지 여부를 반환한다.
602 */
603 public isWait() {
604 return !!this._waitType;
605 }
606 /**
607 * Releases the instnace and events and returns the CSS of the container and elements.
608 * @ko 인스턴스와 이벤트를 해제하고 컨테이너와 엘리먼트들의 CSS를 되돌린다.
609 */
610 public destroy(): void {
611 this.off();
612 this._getRenderer().destroy();
613 this.containerManager.destroy();
614 this.groupManager.destroy();
615 this.scrollManager.destroy();
616 this.infinite.destroy();
617 }
618
619 private _getRenderer() {
620 return this.options.renderer!;
621 }
622 private _getRendererItems() {
623 return this.getRenderingItems().map((item) => {
624 return {
625 element: item.element,
626 key: `${item.type}_${item.key}`,
627 orgItem: item,
628 };
629 });
630 }
631 private _syncItems(state?: Record<string, any>): void {
632 this._getRenderer().syncItems(this._getRendererItems(), state);
633 }
634 private _render(state?: Record<string, any>): void {
635 this._getRenderer().render(this._getRendererItems(), state);
636 }
637 private _update(state: Record<string, any> = {}): void {
638 this._getRenderer().update(state);
639 }
640 private _resizeScroll() {
641 const scrollManager = this.scrollManager;
642
643 scrollManager.resize();
644
645 this.infinite.setSize(scrollManager.getContentSize());
646 }
647 private _syncGroups(isUpdate?: boolean) {
648 const infinite = this.infinite;
649 const scrollManager = this.scrollManager;
650
651 if (!scrollManager.getContentSize()) {
652 this._resizeScroll();
653 }
654 this._syncInfinite();
655 this.groupManager.setCursors(infinite.getStartCursor(), infinite.getEndCursor());
656 if (isUpdate) {
657 this._update();
658 } else {
659 this._render();
660 }
661 }
662 private _syncInfinite() {
663 this.infinite.syncItems(this.getGroups(true).map(({ groupKey, grid, type }) => {
664 const outlines = grid.getOutlines();
665
666 return {
667 key: groupKey,
668 isVirtual: type === GROUP_TYPE.VIRTUAL,
669 startOutline: outlines.start,
670 endOutline: outlines.end,
671 };
672 }));
673 }
674 private _scroll() {
675 this.infinite.scroll(this.scrollManager.getRelativeScrollPos());
676 }
677 private _onScroll = ({ direction, scrollPos, relativeScrollPos }: OnChangeScroll): void => {
678 this._scroll();
679 /**
680 * This event is fired when scrolling.
681 * @ko 스크롤하면 발생하는 이벤트이다.
682 * @event InfiniteGrid#changeScroll
683 * @param {InfiniteGrid.OnChangeScroll} e - The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
684 */
685 this.trigger(new ComponentEvent(INFINITEGRID_EVENTS.CHANGE_SCROLL, {
686 direction,
687 scrollPos,
688 relativeScrollPos,
689 }));
690 }
691
692 private _onChange = (e: OnInfiniteChange): void => {
693 this.setCursors(e.nextStartCursor, e.nextEndCursor);
694 }
695 private _onRendererUpdated = (e: OnRendererUpdated<GridRendererItem>): void => {
696 const renderedItems = e.items;
697
698 renderedItems.forEach((item) => {
699 // set grid element
700 const gridItem = item.orgItem;
701
702 gridItem.element = item.element as HTMLElement;
703 });
704
705 if (!e.isChanged) {
706 this._checkEndLoading();
707 this._scroll();
708 return;
709 }
710
711 const {
712 added,
713 removed,
714 prevList,
715 list,
716 } = e.diffResult;
717
718 removed.forEach((index) => {
719 const orgItem = prevList[index].orgItem;
720
721 if (orgItem.mountState !== MOUNT_STATE.UNCHECKED) {
722 orgItem.mountState = MOUNT_STATE.UNMOUNTED;
723 }
724 });
725
726
727 const horizontal = this.options.horizontal;
728 const addedItems = added.map((index) => {
729 const gridItem = list[index].orgItem;
730 const element = gridItem.element!;
731
732 if (gridItem.type === ITEM_TYPE.VIRTUAL) {
733 const cssRect = { ...gridItem.cssRect };
734 const rect = gridItem.rect;
735
736 if (!cssRect.width && rect.width) {
737 cssRect.width = rect.width;
738 }
739 if (!cssRect.height && rect.height) {
740 cssRect.height = rect.height;
741 }
742 // virtual item
743 return new GridItem(horizontal!, {
744 element,
745 cssRect,
746 });
747 }
748 return gridItem;
749 });
750
751 const containerManager = this.containerManager;
752 if (this.options.observeChildren) {
753 containerManager.unobserveChildren(removed.map((index) => prevList[index].element!));
754 containerManager.observeChildren(added.map((index) => list[index].element!));
755 }
756
757 const {
758 isRestore,
759 isResize,
760 } = e.state;
761
762 this.itemRenderer.renderItems(addedItems);
763
764 if (isRestore) {
765 this._onRenderComplete({
766 mounted: added.map((index) => list[index].orgItem),
767 updated: [],
768 isResize: false,
769 direction: this.defaultDirection,
770 });
771 }
772 if (!isRestore || isResize || e.isItemChanged) {
773 this.groupManager.renderItems();
774 }
775 }
776
777 private _onResize = (e: ResizeWatcherResizeEvent) => {
778 if (e.isResizeContainer) {
779 this._renderItems({ useResize: true }, true);
780 } else {
781 const updatedItems = getUpdatedItems(this.getVisibleItems(), e.childEntries) as InfiniteGridItem[];
782
783 if (updatedItems.length > 0) {
784 this.updateItems(updatedItems);
785 }
786 }
787 }
788
789 private _onRequestAppend = (e: OnRequestInsert): void => {
790 /**
791 * The event is fired when scrolling reaches the end or when data for a virtual group is required.
792 * @ko 스크롤이 끝에 도달하거나 virtual 그룹에 대한 데이터가 필요한 경우 이벤트가 발생한다.
793 * @event InfiniteGrid#requestAppend
794 * @param {InfiniteGrid.OnRequestAppend} e - The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
795 */
796 this._onRequestInsert(DIRECTION.END, INFINITEGRID_EVENTS.REQUEST_APPEND, e);
797 }
798
799 private _onRequestPrepend = (e: OnInfiniteRequestPrepend): void => {
800 /**
801 * The event is fired when scrolling reaches the start or when data for a virtual group is required.
802 * @ko 스크롤이 끝에 도달하거나 virtual 그룹에 대한 데이터가 필요한 경우 이벤트가 발생한다.
803 * @event InfiniteGrid#requestPrepend
804 * @param {InfiniteGrid.OnRequestPrepend} e - The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
805 */
806 this._onRequestInsert(DIRECTION.START, INFINITEGRID_EVENTS.REQUEST_PREPEND, e);
807 }
808
809 private _onRequestInsert(
810 direction: "start" | "end",
811 eventType: "requestAppend" | "requestPrepend",
812 e: OnInfiniteRequestAppend | OnInfiniteRequestPrepend,
813 ) {
814 if (this._waitType) {
815 this._checkStartLoading(this._waitType);
816 return;
817 }
818 this.trigger(new ComponentEvent(eventType, {
819 groupKey: e.key,
820 nextGroupKey: e.nextKey,
821 nextGroupKeys: e.nextKeys || [],
822 isVirtual: e.isVirtual,
823 wait: () => {
824 this.wait(direction);
825 },
826 ready: () => {
827 this.ready();
828 },
829 }));
830 }
831
832 private _onContentError = ({ element, target, item, update }: OnContentError): void => {
833 /**
834 * The event is fired when scrolling reaches the start or when data for a virtual group is required.
835 * @ko 스크롤이 끝에 도달하거나 virtual 그룹에 대한 데이터가 필요한 경우 이벤트가 발생한다.
836 * @event InfiniteGrid#contentError
837 * @param {InfiniteGrid.OnContentError} e - The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
838 */
839 this.trigger(new ComponentEvent(INFINITEGRID_EVENTS.CONTENT_ERROR, {
840 element,
841 target,
842 item: item as InfiniteGridItem,
843 update,
844 remove: () => {
845 this.removeByKey(item.key!);
846 },
847 }));
848 }
849
850 private _onRenderComplete = ({ isResize, mounted, updated, direction }: OnPickedRenderComplete): void => {
851 const infinite = this.infinite;
852 const prevRenderedGroups = infinite.getRenderedVisibleItems();
853 const length = prevRenderedGroups.length;
854 const isDirectionEnd = direction === DIRECTION.END;
855
856 this._syncInfinite();
857
858 if (length) {
859 const prevStandardGroup = prevRenderedGroups[isDirectionEnd ? 0 : length - 1];
860 const nextStandardGroup = infinite.getItemByKey(prevStandardGroup.key);
861 const offset = isDirectionEnd
862 ? Math.min(...nextStandardGroup.startOutline) - Math.min(...prevStandardGroup.startOutline)
863 : Math.max(...nextStandardGroup.endOutline) - Math.max(...prevStandardGroup.endOutline);
864
865 this.scrollManager.scrollBy(offset);
866 }
867
868 /**
869 * This event is fired when the InfiniteGrid has completed rendering.
870 * @ko InfiniteGrid가 렌더링이 완료됐을 때 이벤트가 발생한다.
871 * @event InfiniteGrid#renderComplete
872 * @param {InfiniteGrid.OnRenderComplete} e - The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
873 */
874 this.trigger(new ComponentEvent(INFINITEGRID_EVENTS.RENDER_COMPLETE, {
875 isResize,
876 direction,
877 mounted: (mounted as InfiniteGridItem[]).filter((item) => item.type !== ITEM_TYPE.LOADING),
878 updated: (updated as InfiniteGridItem[]).filter((item) => item.type !== ITEM_TYPE.LOADING),
879 startCursor: this.getStartCursor(),
880 endCursor: this.getEndCursor(),
881 items: this.getVisibleItems(true),
882 groups: this.getVisibleGroups(true),
883 }));
884
885 if (this.groupManager.shouldRerenderItems()) {
886 this._update();
887 } else {
888 this._checkEndLoading();
889 this._scroll();
890 }
891 }
892 private _renderItems(options: RenderOptions = {}, isTrusted?: boolean) {
893 if (!isTrusted && options.useResize) {
894 this.containerManager.resize();
895 }
896 this._resizeScroll();
897 if (!this.getRenderingItems().length) {
898 const children = toArray(this.getContainerElement().children);
899 if (children.length > 0) {
900 // no items, but has children
901 this.groupManager.syncItems(convertInsertedItems(children));
902 this._syncInfinite();
903 this.setCursors(0, 0, true);
904 this._getRenderer().updated();
905 } else {
906 this.infinite.scroll(0);
907 }
908 return this;
909 }
910 if (!this.getVisibleGroups(true).length) {
911 this.setCursors(0, 0);
912 } else {
913 this.groupManager.renderItems(options);
914 }
915 return this;
916 }
917 private _checkStartLoading(direction: "start" | "end") {
918 const groupManager = this.groupManager;
919 const infinite = this.infinite;
920
921 if (
922 !groupManager.getLoadingType()
923 && infinite.isLoading(direction)
924 && groupManager.startLoading(direction)
925 && groupManager.hasLoadingItem()
926 ) {
927 this._update();
928 }
929 }
930 private _checkEndLoading() {
931 const groupManager = this.groupManager;
932 const loadingType = this.groupManager.getLoadingType();
933
934 if (
935 loadingType
936 && (!this._waitType || !this.infinite.isLoading(loadingType))
937 && groupManager.endLoading()
938 && groupManager.hasLoadingItem()
939 ) {
940 this._update();
941 }
942 }
943}
944
945interface InfiniteGrid extends Properties<typeof InfiniteGrid> { }
946
947export default InfiniteGrid;
948
\No newline at end of file