ninegrid2 6.316.0 → 6.317.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11036,6 +11036,18 @@ class ninegrid {
11036
11036
  static nvl = (v, v2) => {
11037
11037
  return ninegrid.isNvl(v) ? v2 : v;
11038
11038
  };
11039
+
11040
+ static isEllipsis = (element) => {
11041
+ const clone = element.cloneNode(true);
11042
+ clone.style.width = "auto";
11043
+ clone.style.visibility = "hidden";
11044
+ document.body.appendChild(clone);
11045
+
11046
+ const isTruncated = clone.scrollWidth > element.clientWidth;
11047
+ document.body.removeChild(clone);
11048
+
11049
+ return isTruncated;
11050
+ }
11039
11051
 
11040
11052
  static oppositeColor = (bgColor, lightColor, darkColor) => {
11041
11053
  var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
@@ -17824,509 +17836,565 @@ class ngFields
17824
17836
  };
17825
17837
  }
17826
17838
 
17827
- /**
17828
- * button.filterOptions = [{
17829
- * colnm: "userId",
17830
- * data: [],
17831
- * }]
17832
- * button.
17833
- */
17834
- class ngFiltering
17839
+ class ngFoot extends HTMLElement
17835
17840
  {
17836
17841
  #owner;
17837
- #isFiltering;
17838
-
17839
- constructor(owner) {
17840
- this.#owner = owner;
17841
- this.#isFiltering = false;
17842
-
17843
- const filterPanel = document.createElement("ng-filter-panel"); // ✅ 필터 패널 생성
17844
- filterPanel.style.display = "none"; // ✅ 숨김 처리
17845
- this.#owner.shadowRoot.appendChild(filterPanel); // ✅ Shadow DOM 내부에 추가
17846
- }
17847
-
17848
- initialize = () => {
17849
- /**
17850
- * 1. grid attr == filter : on();
17851
- */
17852
- this.#isFiltering = false;
17853
- this.#owner.data.clearFilter();
17854
- };
17855
-
17856
- isFiltering = () => {
17857
- return this.#isFiltering;
17858
- };
17859
17842
 
17860
- on = (v) => {
17843
+ constructor () {
17844
+ super();
17861
17845
 
17862
- this.#isFiltering = true;
17863
- const lastRowIndex = this.#getLastRowIndex();
17864
-
17865
- this.#owner.body.querySelectorAll(".ng-table thead th, .ng-table thead td").forEach((td) => {
17866
- const rowIndex = td.closest("tr")?.sectionRowIndex;
17867
- if (rowIndex !== undefined && rowIndex + td.rowSpan - 1 === lastRowIndex) {
17868
- const options = Array.from(this.#owner.activeTmpl.querySelectorAll(`[data-col="${td.dataset.col}"]`))
17869
- .map(el => el.dataset.bind ? { colnm: el.dataset.bind, data: [] } : null)
17870
- .filter(Boolean); // ✅ `null` 제거
17871
-
17872
- if (options.length > 0) {
17873
- td.querySelector("ng-filter-button")?.remove();
17874
-
17875
- const filterButton = document.createElement("ng-filter-button");
17876
- td.appendChild(filterButton);
17877
- filterButton.filterOptions = options;
17878
- }
17879
- }
17880
- });
17881
- };
17882
-
17883
- off = () => {
17884
- this.#isFiltering = false;
17885
-
17886
- this.#owner.body.querySelectorAll(".ng-table ng-filter-button").forEach((el) => {
17887
- el.remove(); // ✅ 요소 삭제
17888
- });
17889
- };
17890
-
17891
- /**
17892
- clear = () => {
17893
- console.log(this.#owner.dataManager.rawRecords);
17894
- this.#owner.dataManager.rawRecords.map(item => { item.__ng.filtered = false; });
17895
-
17896
-
17897
- this.#records = this.#owner.dataManager.rawRecords.filter(item => { return !item.__ng.deleted && !item.__ng.filtered; } );
17898
- return this.#records;
17846
+ this.attachShadow({ mode: 'open' });
17899
17847
  }
17900
17848
 
17901
- refresh = () => {
17902
- this.#records = this.#owner.dataManager.rawRecords.filter(item => { return !item.__ng.deleted && !item.__ng.filtered; } );
17903
- //this.#records.forEach((o,i) => { o.__ng.rowidx = i; });
17904
- //this.getValidData().forEach((o,i) => { o.__ng._[ninegrid.ROW.ORDER] = i + 1; });
17905
- this.#resetOrder();
17906
- return this.#records;
17907
- } */
17908
-
17909
-
17910
- /**
17911
- * oFilter : {
17912
- * col1 : [],
17913
- * col2 : []
17914
- * }
17915
- */
17916
- set = (oFilter) => {
17917
-
17918
- this.on();
17919
-
17920
- // ✅ JSON 변환 (배열 → 객체)
17921
- let jsonFilter = Array.isArray(oFilter)
17922
- ? Object.fromEntries(Object.keys(oFilter[0]).map(key => [key, [...new Set(oFilter.map(item => item[key]))]]))
17923
- : oFilter;
17924
-
17925
- this.#owner.data.clearFilter();
17926
-
17927
- Object.entries(jsonFilter).forEach(([key, arr]) => {
17928
- const idx = this.#owner.fields.indexOf(key);
17929
-
17930
- // ✅ 숫자 판별 및 변환
17931
- if (this.#owner.data.getValidData().some(o => typeof o.v[idx] === "number")) {
17932
- arr = arr.map(Number);
17933
- }
17934
-
17935
- // ✅ 필터 적용
17936
- this.#owner.data.getValidData()
17937
- .filter(m => arr.nineBinarySearch(m.v[idx] || '') < 0)
17938
- .forEach(m => { m.__ng.filtered = true; });
17939
- });
17940
-
17941
-
17942
- this.#owner.data.refreshFilter();
17943
-
17944
- // ✅ 필터 버튼 초기화
17945
- this.#owner.shadowRoot.querySelectorAll("ng-filter-button").forEach(el => {
17946
- el.filterOptions.forEach(opt => { opt.data = []; });
17947
- });
17948
-
17949
- Object.entries(jsonFilter).forEach(([key, arr]) => {
17950
- this.#owner.shadowRoot.querySelectorAll("ng-filter-button").forEach(el => {
17951
-
17952
- let options = el.filterOptions;
17953
-
17954
- options.forEach(opt => {
17955
- if (opt.colnm === key) opt.data = arr;
17956
- });
17957
-
17958
- el.filterOptions = options;
17959
- });
17960
- });
17849
+ connectedCallback() {
17961
17850
 
17851
+ this.#owner = this.getRootNode().host;//this.closest("nine-grid");
17962
17852
 
17963
- this.#owner.scrollTo_V1(0);
17853
+ this.shadowRoot.innerHTML = `
17854
+ <style>
17855
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngFoot.css";
17856
+ ${ninegrid.getCustomPath(this,"ngFoot.css")}
17857
+ </style>
17858
+
17859
+ <ng-layout></ng-layout>
17860
+ <ng-paging></ng-paging>
17861
+ `;
17964
17862
 
17965
- this.#owner.paging.reset();
17966
17863
  };
17967
-
17968
- #getLastRowIndex = (v = "thead") =>
17969
- [...this.#owner.body.querySelectorAll(`.ng-table ${v}`)]
17970
- .reduce((maxIndex, el) => Math.max(el.rows.length - 1, maxIndex), 0);
17971
-
17972
17864
  }
17973
17865
 
17974
- class ngFilterButton extends HTMLElement
17866
+ class ngLayout extends HTMLElement
17975
17867
  {
17976
17868
  #owner;
17977
- #filterOptions;
17869
+ #tables;
17870
+ #wrap;
17871
+ #at;
17978
17872
 
17979
17873
  constructor () {
17980
17874
  super();
17875
+
17876
+ this.#tables = [];
17877
+ this.#at = 0;
17981
17878
  }
17982
-
17879
+
17983
17880
  connectedCallback() {
17984
- this.#owner = this.getRootNode().host;//this.closest("nine-grid");
17881
+ this.#owner = this.getRootNode().host.getRootNode().host;//this.closest("nine-grid");
17882
+
17883
+ this.innerHTML =`<div class="ng-wrap"></div>`;
17884
+ this.#wrap = this.querySelector(".ng-wrap");
17885
+ };
17886
+
17887
+ add = (arr) => {
17985
17888
 
17986
- this.removeEventListener("click", this.#onClick);
17987
- this.addEventListener("click", this.#onClick);
17889
+ this.#tables.push(...arr);
17890
+
17891
+ var i = 0;
17892
+ for (const tbl of this.#tables) {
17893
+ i++;
17894
+ const caption = tbl.querySelector("caption");
17895
+ const sheetName = caption ? caption.innerHTML : `Sheet${i}`;
17896
+
17897
+ $(this.#wrap).append(`<button class="ng-button" value="${i-1}" title="${sheetName.replaceAll('"', "'")}">${sheetName}</button>`);
17898
+ }
17899
+
17900
+ if (this.#tables.length <= 1) $(this.#wrap).hide();
17901
+
17902
+ //$("button", this.#wrap).eq(0).addClass("ng-active");
17903
+ ninegrid.j.querySelectorAll(this.#wrap.querySelector("button")).addClass("ng-active");
17904
+
17905
+ $("button", this.#wrap).on("click", this.#onClick);
17988
17906
  };
17989
17907
 
17990
- get filterOptions() {
17991
- return this.#filterOptions;
17908
+ get tables() {
17909
+ return this.#tables;
17910
+ };
17911
+ get currentTable() {
17912
+ return this.#tables[this.#at];
17992
17913
  };
17993
- set filterOptions(v) {
17994
- this.#filterOptions = v;
17995
- this.classList.toggle('filtered', this.#filterOptions.some(item => item.data.length > 0));
17996
- }
17997
17914
 
17998
- #onClick = (e) => {
17999
- e.preventDefault();
18000
- e.stopPropagation();
18001
-
18002
- const panel = this.#owner.shadowRoot.querySelector("ng-filter-panel");
18003
- const col = this.closest("th,td")?.dataset.col; // ✅ `?.` 활용하여 안전한 접근
17915
+ resize = () => {
17916
+ const overflow = $(this.#wrap).width() > $(this).width();
18004
17917
 
18005
- panel.col === col ? panel.close() : panel.open(this);
18006
- };
17918
+ $(this.#wrap).css({
17919
+ "width": overflow ? "100%" : "unset",
17920
+ "justify-content": overflow ? "space-evenly" : "unset",
17921
+ });
17922
+ };
17923
+
17924
+ #onClick = e => {
17925
+
17926
+ if (ninegrid.j.querySelectorAll(e.currentTarget).hasClass("ng-active")) return;
17927
+
17928
+ const oldIndex = this.#at;
17929
+
17930
+ this.#at = parseInt(e.currentTarget.value);
17931
+
17932
+ this.#owner.changeRayout(this.#at);
17933
+
17934
+ ninegrid.j.querySelectorAll("button", this.#wrap).removeClass("ng-active");
17935
+ ninegrid.j.querySelectorAll(e.currentTarget).addClass("ng-active");
17936
+
17937
+ var customEvent = new CustomEvent(ninegrid.EVENT.LAYOUT_CHANGED, { bubbles: true, detail: {} });
17938
+ customEvent.oldIndex = oldIndex;
17939
+ customEvent.newIndex = this.#at;
17940
+
17941
+ this.#owner.dispatchEvent(customEvent);
17942
+ };
18007
17943
  }
18008
17944
 
18009
- class ngFilterPanel extends HTMLElement
18010
- {
18011
- #owner;
18012
- #button;
18013
- #timer;
18014
-
18015
- constructor () {
18016
- super();
18017
- this.attachShadow({ mode: 'open' });
18018
- }
17945
+ customElements.define("ng-layout", ngLayout);
17946
+ customElements.define("ng-foot", ngFoot);
18019
17947
 
18020
- connectedCallback() {
18021
-
18022
- this.#owner = this.getRootNode().host;
17948
+ class ngHead extends HTMLElement
17949
+ {
17950
+ #owner;
17951
+
17952
+ constructor () {
17953
+ super();
17954
+
17955
+ this.attachShadow({ mode: 'open' });
17956
+ }
17957
+
17958
+ connectedCallback() {
17959
+
17960
+ this.#owner = this.getRootNode().host;
17961
+
17962
+ if (ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) return;
17963
+
17964
+ this.shadowRoot.innerHTML = `
17965
+ <style>
17966
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngHead.css";
17967
+ ${ninegrid.getCustomPath(this,"ngHead.css")}
17968
+ </style>
17969
+
17970
+ <ng-menu></ng-menu>
17971
+ <ng-title></ng-title>
17972
+ <ng-custom></ng-custom>
17973
+ `;
17974
+ };
17975
+ }
17976
+
17977
+ class ngTitle extends HTMLElement
17978
+ {
17979
+ #owner;
17980
+
17981
+ constructor () {
17982
+ super();
17983
+ }
17984
+
17985
+ connectedCallback() {
17986
+ if (!this.getRootNode().host) return;
17987
+
17988
+ this.#owner = this.getRootNode().host.getRootNode().host;
17989
+
17990
+ this.innerHTML = `<span>${this.#owner.getAttribute("caption")}</span>`;
17991
+
17992
+ $("span", this).on("dblclick", e => {
17993
+ const link = '<font color="green">https://www.ninegrid.net</font>';
17994
+ e.currentTarget.innerHTML = e.currentTarget.innerHTML == link ? `<span>${this.#owner.getAttribute("caption")}</span>` : link;
17995
+ });
17996
+ };
17997
+ }
17998
+
17999
+ class ngCustom extends HTMLElement
18000
+ {
18001
+ #owner;
18002
+
18003
+ constructor () {
18004
+ super();
18005
+ }
18006
+
18007
+ connectedCallback() {
18008
+ if (!this.getRootNode().host) return;
18009
+
18010
+ this.#owner = this.getRootNode().host.getRootNode().host;
18011
+
18012
+ this.innerHTML = `<div></div>`;
18013
+ };
18014
+
18015
+ add = (element) => {
18016
+ this.querySelector("div:first-child").append(element);
18017
+ };
18018
+ }
18019
+
18020
+ customElements.define("ng-title", ngTitle);
18021
+ customElements.define("ng-custom", ngCustom);
18022
+ customElements.define("ng-head", ngHead);
18023
18023
 
18024
- const cssPath = this.getRootNode().host.closest("nine-grid").getAttribute("css-path") || "";
18024
+ class ngIcon extends HTMLElement
18025
+ {
18026
+ constructor() {
18027
+ super();
18028
+
18029
+ this.attachShadow({ mode: 'open' });
18030
+ }
18031
+
18032
+ connectedCallback() {
18033
+
18034
+ new MutationObserver((entries, observer) => {
18035
+ for (var entry of entries) {
18036
+ if (entry.type != "attributes") continue;
18037
+ switch (entry.attributeName) {
18038
+ case "stroke":
18039
+ this.shadowRoot.querySelector("polyline").setAttribute(entry.attributeName, this.getAttribute(entry.attributeName));
18040
+ break;
18041
+
18042
+ case "fill":
18043
+ this.shadowRoot.querySelector("circle,rect,svg").setAttribute(entry.attributeName, this.getAttribute(entry.attributeName));
18044
+ break;
18045
+
18046
+ case "start-fill":
18047
+ case "end-fill":
18048
+ var startFill = this.getAttribute("start-fill") || "rgba(255, 255, 255, 0.85)";
18049
+ var endFill = this.getAttribute("end-fill") || "rgba(30, 30, 40, 0.85)";
18050
+ $(entry.target).css({
18051
+ //background: `radial-gradient(circle farthest-corner at 33% 33%, ${startFill} 0%, ${endFill} 80%), radial-gradient(circle farthest-corner at 45% 45%, rgba(0, 0, 0, 0) 50%, #000000 80%)`
18052
+ background: `radial-gradient(circle farthest-corner at 33% 33%, ${startFill} 0%, ${endFill} 80%), radial-gradient(circle farthest-corner at 45% 45%, rgba(0, 0, 0, 0) 50%, #000000 80%)`
18053
+ });
18054
+ break;
18055
+ }
18056
+ }
18057
+ }).observe(this, {
18058
+ childList: false,
18059
+ subtree: false,
18060
+ attributeOldValue: true,
18061
+ });
18062
+ };
18063
+ }
18064
+
18065
+ class ngCheck extends ngIcon
18066
+ {
18067
+ constructor() {
18068
+ super();
18069
+ }
18070
+
18071
+ connectedCallback() {
18072
+ var size = this.getAttribute("size") || "12";
18073
+ this.getAttribute("stroke") || "black";
18074
+
18075
+ this.shadowRoot.innerHTML = `
18076
+ <style>
18077
+ :host {
18078
+ width: ${size}px;
18079
+ --border: 1px solid
18080
+ }
18081
+
18082
+ polyline {
18083
+ fill: none;
18084
+ stroke-width: 2px;
18085
+ }
18086
+ </style>
18087
+
18088
+ <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="currentColor" viewBox="0 0 16 16">
18089
+ <path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425z"/>
18090
+ </svg>
18091
+ `;
18092
+
18093
+ super.connectedCallback();
18094
+ };
18095
+ }
18096
+
18097
+ class ngRect extends ngIcon
18098
+ {
18099
+ constructor() {
18100
+ super();
18101
+ }
18102
+
18103
+ connectedCallback() {
18104
+ var size = this.getAttribute("size") || "12";
18105
+ this.getAttribute("fill") || "black";
18106
+
18107
+ this.shadowRoot.innerHTML = `
18108
+ <style>
18109
+ :host {
18110
+ }
18111
+ </style>
18112
+
18113
+ <svg viewBox="0 0 ${size} ${size}" width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
18114
+ <rect width="${size}" height="${size}" x="0" y="0" rx="0" ry="0" fill="#ccc" />
18115
+ </svg>
18116
+ `;
18117
+
18118
+ super.connectedCallback();
18119
+ };
18120
+ }
18121
+
18122
+ class ngCircle extends ngIcon
18123
+ {
18124
+ constructor() {
18125
+ super();
18126
+ }
18127
+
18128
+ connectedCallback() {
18129
+ var size = Number(this.getAttribute("size") || 12);
18130
+ var round = size / 2;
18131
+ var fill = this.getAttribute("fill") || "black";
18132
+ var stroke = this.getAttribute("stroke") || "#666";
18133
+ var strokeWidth = Number(this.getAttribute("stroke-width") || 0);
18134
+ var cx = round + strokeWidth;
18135
+ var cy = round + strokeWidth;
18136
+
18137
+ this.shadowRoot.innerHTML = `
18138
+ <style>
18139
+ :host {
18140
+ }
18141
+ </style>
18142
+
18143
+ <svg viewBox="0 0 ${size} ${size}" width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
18144
+ <circle fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" cx="${cx}" cy="${cy}" r="${round}"/>
18145
+ </svg>
18146
+ `;
18147
+
18148
+ super.connectedCallback();
18149
+ };
18150
+ }
18151
+
18152
+ class ngSphere extends ngIcon
18153
+ {
18154
+ constructor() {
18155
+ super();
18156
+ }
18157
+
18158
+ connectedCallback() {
18159
+ var size = this.getAttribute("size") || "12";
18160
+ var startFill = this.getAttribute("start-fill") || "rgba(255, 255, 255, 0.85)";
18161
+ var endFill = this.getAttribute("end-fill") || "rgba(0, 0, 0, 0.85)";
18162
+
18163
+ this.shadowRoot.innerHTML = `
18164
+ <style>
18165
+ :host {
18166
+ position: absolute;
18167
+ width: ${size}px;
18168
+ height: ${size}px;
18169
+ border-radius: 50%;
18170
+ background: radial-gradient(circle farthest-corner at 33% 33%, ${startFill} 0%, ${endFill} 80%), radial-gradient(circle farthest-corner at 45% 45%, rgba(0, 0, 0, 0) 50%, #000000 80%);
18171
+ }
18172
+ </style>
18173
+ `;
18174
+
18175
+ super.connectedCallback();
18176
+ };
18177
+ }
18178
+
18179
+ customElements.define("ng-check", ngCheck);
18180
+ customElements.define("ng-rect", ngRect);
18181
+ customElements.define("ng-circle", ngCircle);
18182
+ customElements.define("ng-sphere", ngSphere);
18025
18183
 
18026
- this.shadowRoot.innerHTML = `
18027
- <style>
18028
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngFilterPanel.css";
18029
- ${ninegrid.getCustomPath(this,"ngFilterPanel.css")}
18030
- </style>
18031
-
18032
- <div class="head">
18033
- <input type="text">
18034
- </div>
18035
- <nine-grid class="simple filter" css-path="${cssPath}" select-type="row" auto-fit-col="true" col-indicator-type="collapse">
18036
- <table style="display: none;">
18037
- <colgroup>
18038
- <col width="180" />
18039
- <col width="30" />
18040
- </colgroup>
18041
- <tbody>
18042
- <tr style="height: 24px;">
18043
- <td data-bind="LVL" data-expr="data.DATA2">
18044
- <ng-tree-item />
18045
- </td>
18046
- <td data-bind="CHK">
18047
- <ng-checkbox update-row-state="false" visible="data.LVL!=1" selected-border-color="white" />
18048
- </td>
18049
- </tr>
18050
- </tbody>
18051
- </table>
18052
- </nine-grid>
18053
-
18054
- <div>
18055
- <label><input type="checkbox" checked>Select All</label>
18056
- <button id="btnOk">OK</button>
18057
- <button id="btnCancel">Cancel</button>
18058
- </div>
18059
- `;
18060
-
18061
- this.shadowRoot.querySelector("input[type=text]").addEventListener("input", this.#onInput);
18062
- this.shadowRoot.querySelector("input[type=checkbox]").addEventListener("change", this.#onSelectAll);
18063
- this.shadowRoot.querySelector("#btnOk").addEventListener("click", this.#onOk);
18064
- this.shadowRoot.querySelector("#btnCancel").addEventListener("click", this.#onCancel);
18065
- };
18066
-
18067
- #onOk = (e) => {
18068
-
18069
- const grd = this.shadowRoot.querySelector("nine-grid");
18070
-
18071
- this.classList.add("loading");
18072
-
18073
- setTimeout(() => {
18074
- const [LVL_IDX, CHK_IDX, COLNM_IDX, DATA_IDX] = ["LVL", "CHK", "COLNM", "DATA"].map(k => grd.fields.indexOf(k));
18075
- const checked = grd.data.getValidDataNF().filter(m => m.v[LVL_IDX] === 2 && m.v[CHK_IDX] === "Y");
18076
- const filterOptions = checked.length > 0 ? [...new Set(checked.map(m => m.v[COLNM_IDX]))].map(v => ({
18077
- colnm: v,
18078
- data: [...new Set(grd.data.getValidData().filter(m => m.v[CHK_IDX] === "Y" && m.v[COLNM_IDX] === v).map(m => m.v[DATA_IDX] || ''))].sort()
18079
- })) : [];
18080
-
18081
-
18082
- this.#button.filterOptions = filterOptions;
18184
+ class ngImg extends ngCellEx
18185
+ {
18186
+ #target;
18187
+ //#src;
18188
+
18189
+ constructor() {
18190
+ super();
18191
+
18192
+ //this.#src = this.getAttribute("src");
18193
+ }
18194
+
18195
+ connectedCallback() {
18196
+ const border = this.getAttribute("border") || "unset";
18197
+ const borderRadius = this.getAttribute("border-radius") || "unset";
18198
+ const link = this.getAttribute("link");
18199
+
18200
+ var attr = [];
18201
+ for (var i = 0; i < this.attributes.length; i++) {
18202
+ if (["alt","width","height"].includes(this.attributes[i].name)) attr.push(`${this.attributes[i].name}="${this.attributes[i].value}"`);
18203
+ }
18204
+
18205
+ this.shadowRoot.innerHTML = `
18206
+ <style>
18207
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngImg.css";
18208
+ ${ninegrid.getCustomPath(this,"ngImg.css")}
18209
+
18210
+ img {
18211
+ border: ${border};
18212
+ border-radius: ${borderRadius};
18213
+ cursor: ${link == "true"? "pointer" : "unset"};
18214
+ }
18215
+ img:hover {
18216
+ filter: ${link == "true"? "brightness(80%)" : "unset"};
18217
+ }
18218
+ </style>
18219
+
18220
+ <div class="ng-wrap">
18221
+ <img ${attr.join(" ")} class="renderer" />
18222
+ </div>
18223
+ `;
18224
+
18225
+ /** img,div class="renderer" 일때 ngCell에서 link로 연결 */
18226
+
18227
+ super.connectedCallback();
18228
+
18229
+ this.#target = this.shadowRoot.querySelector("img");
18230
+ };
18231
+
18232
+ dataRefresh = (v) => {
18233
+
18234
+ var src = this.getAttribute("src");// || this.cell.getAttribute("src");
18235
+
18236
+ var exprSrc = (src) ? this.getExprValue(src, this.cell.closest("tr").data, this.cell.dataset.row) : "";
18237
+
18238
+ src = exprSrc || this.getDisplayText();
18239
+ this.#target.src = src;
18240
+
18241
+ src ? $(this.#target).show() : $(this.#target).hide();
18242
+
18243
+ if (!v) this.reset();
18244
+ };
18245
+
18246
+ r = (owner, cell) => {
18247
+
18248
+ const src = this.getAttribute("icon-src") || cell.getAttribute("icon-src");
18249
+ var exprSrc = (src) ? this.getExprValue(src, cell.closest("tr").data, cell.dataset.row) : "";
18250
+
18251
+ this.#target.src = exprSrc || this.value;
18252
+ };
18253
+ }
18254
+
18255
+ class ngSvg extends ngCellEx
18256
+ {
18257
+ #target;
18258
+ #src;
18259
+
18260
+ constructor() {
18261
+ super();
18262
+
18263
+ this.#src = this.getAttribute("src");
18264
+ }
18265
+
18266
+ connectedCallback() {
18267
+ const border = this.getAttribute("border") || "unset";
18268
+ const borderRadius = this.getAttribute("border-radius") || "unset";
18269
+ const link = this.getAttribute("link");
18270
+
18271
+ this.shadowRoot.innerHTML = `
18272
+ <style>
18273
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngSvg.css";
18274
+ ${ninegrid.getCustomPath(this,"ngSvg.css")}
18275
+
18276
+ div {
18277
+ border: ${border};
18278
+ border-radius: ${borderRadius};
18279
+ cursor: ${link == "true"? "pointer" : "unset"};
18280
+ }
18281
+ div:hover {
18282
+ filter: ${link == "true"? "brightness(80%)" : "unset"};
18283
+ }
18284
+ </style>
18285
+
18286
+ <div class="ng-wrap"></div>
18287
+ `;
18288
+
18289
+ /** img,div class="renderer" 일때 ngCell에서 link로 연결 */
18290
+
18291
+ super.connectedCallback();
18292
+
18293
+ this.#target = this.shadowRoot.querySelector("div");
18294
+ };
18295
+
18296
+ dataRefresh = (v) => {
18297
+
18298
+ const src = this.getAttribute("src");// || this.cell.getAttribute("src");
18299
+
18300
+ var exprSrc = (src) ? this.getExprValue(src, this.cell.closest("tr").data, this.cell.dataset.row) : "";
18301
+
18302
+ this.#target.innerHTML = exprSrc || this.getDisplayText();
18303
+
18304
+ if (!v) this.reset();
18305
+ };
18306
+
18307
+
18308
+ r = (owner, cell) => {
18309
+
18310
+ const src = this.getAttribute("icon-src") || cell.getAttribute("icon-src");
18311
+ var exprSrc = (src) ? this.getExprValue(src, cell.closest("tr").data, cell.dataset.row) : "";
18312
+
18313
+ this.#target.innerHTML = exprSrc || this.value;
18314
+ };
18315
+ }
18316
+
18317
+ customElements.define("ng-img", ngImg);
18318
+ customElements.define("ng-svg", ngSvg);
18083
18319
 
18084
- const oParam = Object.fromEntries(
18085
- [...this.#owner.body.querySelectorAll("ng-filter-button")]
18086
- .flatMap(el => el.filterOptions)
18087
- .filter(opt => opt.data.length > 0)
18088
- .map(opt => [opt.colnm, opt.data])
18089
- );
18090
-
18091
- this.#owner.filtering.set(oParam);
18320
+ class ngImport
18321
+ {
18322
+ #owner;
18323
+
18324
+ constructor (owner) {
18325
+ //super();
18326
+
18327
+ this.#owner = owner;
18328
+ };
18329
+
18330
+ importExcel = () => {
18331
+ this.#openFileDialog();
18332
+ };
18333
+
18334
+ #openFileDialog = () => {
18335
+ if (!this.input) {
18336
+ this.input = document.createElement('input');
18337
+ this.input.type = 'file';
18338
+ this.input.onchange = this.#fileSelectedHandler;
18339
+ }
18340
+
18341
+ this.input.click();
18342
+ };
18343
+
18344
+ #fileSelectedHandler = (e) => {
18345
+ var files = e.target.files;
18346
+ if (files.length < 1) {
18347
+ alert('select a file...');
18348
+ return;
18349
+ }
18350
+ var file = files[0];
18351
+ var reader = new FileReader();
18352
+
18353
+ reader.onload = this.#fileLoadedHandler;
18354
+ reader.readAsDataURL(file);
18355
+ reader.onloadend = this.#fileLoadEndHandler;
18356
+ }
18357
+
18358
+ #fileLoadedHandler = e => {
18359
+
18360
+ var match = /^data:(.*);base64,(.*)$/.exec(e.target.result);
18361
+ if (match == null) {
18362
+ throw 'Could not parse result'; // should not happen
18363
+ }
18364
+
18365
+ match[1];
18366
+ match[2];
18367
+ //alert(mimeType);
18368
+ //alert(content);
18369
+
18370
+
18371
+ //var fileReader = new FileReader();
18372
+
18373
+ }
18374
+
18375
+ #fileLoadEndHandler = (e) => {
18376
+
18377
+ //var arrayBuffer = fileReader.result;
18378
+ var arrayBuffer = e.target.result;
18379
+
18380
+ const workbook = new ExcelJS.Workbook();
18381
+
18382
+ workbook.xlsx.load(arrayBuffer).then(() => {
18383
+
18384
+ workbook.worksheets.forEach((sheet) => {
18385
+ sheet.eachRow((row,rowNumber) => {
18386
+
18387
+ if (rowNumber % 10 == 0) console.log(rowNumber);
18388
+ });
18389
+ });
18390
+ });
18391
+ }
18392
+ }
18092
18393
 
18093
- this.classList.remove("loading");
18094
- this.style.display = 'none';
18095
- });
18096
- };
18097
-
18098
-
18099
-
18100
- #onCancel = (e) => {
18101
- this.style.display = 'none';
18102
- };
18103
-
18104
- #onSelectAll = (e) => {
18105
- const grd = this.shadowRoot.querySelector("nine-grid");
18106
- const idx = grd.fields.indexOf("CHK");
18107
- const isChecked = e.target.checked; // ✅ jQuery 없이 `checked` 값 가져오기
18108
-
18109
- grd.data.getValidData().forEach(m => {
18110
- m.v[idx] = isChecked ? "Y" : "N"; // ✅ `Y` 또는 `N` 값 설정
18111
- });
18112
-
18113
- grd.refreshData();
18114
- };
18115
-
18116
-
18117
-
18118
- #onInput = (e) => {
18119
- const grd = this.shadowRoot.querySelector("nine-grid");
18120
-
18121
- grd.classList.add("loading");
18122
-
18123
- const data = grd.dataManager.rawRecords;
18124
- data.forEach(m => { m.__ng.filtered = false; });
18125
-
18126
- const v = e.target.value.toLowerCase(); // ✅ jQuery 없이 값 가져오기
18127
- const [LVL_IDX, DATA_IDX] = ["LVL", "DATA"].map(field => grd.fields.indexOf(field));
18128
-
18129
- data.forEach(m => {
18130
- m.__ng.filtered = m.v[LVL_IDX] === 2 && !String(m.v[DATA_IDX] || "").toLowerCase().includes(v);
18131
- });
18132
-
18133
- grd.data.resetRecords();
18134
-
18135
- clearTimeout(this.#timer);
18136
- this.#timer = setTimeout(() => {
18137
- grd.dataManager.viewRecords.reset();
18138
- grd.dataManager.viewRecords.touch();
18139
- }, 200);
18140
- };
18141
-
18142
- close = () => {
18143
- this.col = null;
18144
- this.style.display = 'none';
18145
- };
18146
-
18147
- open = (filterButton) => {
18148
-
18149
- /** 위치 */
18150
- const cell = filterButton.closest("th,td");
18151
-
18152
- const { left: btnLeft } = filterButton.getBoundingClientRect();
18153
- const { left: ownerLeft, width: ownerWidth, top: ownerTop } = this.#owner.getBoundingClientRect();
18154
- const { top: cellTop, height: cellHeight } = cell.getBoundingClientRect();
18155
- const { width: targetWidth } = this.getBoundingClientRect();
18156
-
18157
- let l = Math.max(0, btnLeft - ownerLeft);
18158
- l = Math.min(l, ownerWidth - targetWidth - 5);
18159
-
18160
- const t = cellTop + cellHeight - ownerTop;
18161
-
18162
- Object.assign(this.style, { left: `${l}px`, top: `${t}px`, display: "flex" });
18163
- this.classList.add("loading");
18164
-
18165
- this.shadowRoot.querySelector("input[type=text]").value = "";
18166
-
18167
- setTimeout(() => {
18168
- const data = this.#owner.data.getValidDataNF();
18169
- const col = cell.dataset.col;
18170
- this.col = col;
18171
- this.#button = filterButton;
18172
-
18173
- const ds = filterButton.filterOptions.map((opt, i) => {
18174
- const groupLabel = `<span class="group">${cell.textContent}${filterButton.filterOptions.length > 1 ? ` #${i + 1} (${opt.colnm})` : ""}</span>`;
18175
-
18176
- const cellEl = this.#owner.activeTmpl.querySelector(`[data-col="${col}"][data-bind="${opt.colnm}"]`);
18177
- const expr = cellEl?.getAttribute("data-expr");
18178
- const exprFunc = expr ? this.#owner.exprFunction(expr) : null;
18179
-
18180
- const data2 = data.map(rowData => {
18181
- const idx = this.#owner.fields.indexOf(opt.colnm);
18182
- return expr ? { v: rowData.v[idx], v2: exprFunc(this.#owner.data.conv(rowData), rowData.__ng.rowidx, this.#owner.data) } : { v: rowData.v[idx], v2: rowData.v[idx] };
18183
- });
18184
-
18185
- return [
18186
- { LVL: 1, CHK: "N", DATA2: groupLabel },
18187
- ...[...new Set(data2.map(JSON.stringify))]
18188
- .map(JSON.parse)
18189
- .sort((a, b) => (a.v2 || "") > (b.v2 || "") ? 1 : (a.v2 || "") < (b.v2 || "") ? -1 : 0)
18190
- .map(o => ({
18191
- LVL: 2,
18192
- DATA: o.v,
18193
- DATA2: o.v2 || ` <span class="empty">(empty)</span> ${o.v}`,
18194
- COLNM: opt.colnm,
18195
- CHK: opt.data.length === 0 || opt.data.nineBinarySearch(o.v || "") >= 0 ? "Y" : "N",
18196
- }))
18197
- ];
18198
- }).flat();
18199
-
18200
- const grd = this.shadowRoot.querySelector("nine-grid");
18201
- grd.fields.add(["DATA","DATA2","COLNM"]);
18202
- grd.data.set(ds);
18203
-
18204
- // ✅ 데이터 필터링 및 체크 상태 결정
18205
- const checkbox = this.shadowRoot.querySelector("input[type=checkbox]");
18206
- checkbox.checked = grd.data.getValidData().every(item => !(item.LVL === 2 && item.CHK !== "Y"));
18207
-
18208
- this.shadowRoot.querySelector("input").focus();
18209
- this.classList.remove("loading");
18210
- });
18211
- };
18212
- }
18213
-
18214
-
18215
- customElements.define("ng-filter-button", ngFilterButton);
18216
- customElements.define("ng-filter-panel", ngFilterPanel);
18217
-
18218
- class ngFoot extends HTMLElement
18219
- {
18220
- #owner;
18221
-
18222
- constructor () {
18223
- super();
18224
-
18225
- this.attachShadow({ mode: 'open' });
18226
- }
18227
-
18228
- connectedCallback() {
18229
-
18230
- this.#owner = this.getRootNode().host;//this.closest("nine-grid");
18231
-
18232
- this.shadowRoot.innerHTML = `
18233
- <style>
18234
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngFoot.css";
18235
- ${ninegrid.getCustomPath(this,"ngFoot.css")}
18236
- </style>
18237
-
18238
- <ng-layout></ng-layout>
18239
- <ng-paging></ng-paging>
18240
- `;
18241
-
18242
- };
18243
- }
18244
-
18245
- class ngLayout extends HTMLElement
18246
- {
18247
- #owner;
18248
- #tables;
18249
- #wrap;
18250
- #at;
18251
-
18252
- constructor () {
18253
- super();
18254
-
18255
- this.#tables = [];
18256
- this.#at = 0;
18257
- }
18258
-
18259
- connectedCallback() {
18260
- this.#owner = this.getRootNode().host.getRootNode().host;//this.closest("nine-grid");
18261
-
18262
- this.innerHTML =`<div class="ng-wrap"></div>`;
18263
- this.#wrap = this.querySelector(".ng-wrap");
18264
- };
18265
-
18266
- add = (arr) => {
18267
-
18268
- this.#tables.push(...arr);
18269
-
18270
- var i = 0;
18271
- for (const tbl of this.#tables) {
18272
- i++;
18273
- const caption = tbl.querySelector("caption");
18274
- const sheetName = caption ? caption.innerHTML : `Sheet${i}`;
18275
-
18276
- $(this.#wrap).append(`<button class="ng-button" value="${i-1}" title="${sheetName.replaceAll('"', "'")}">${sheetName}</button>`);
18277
- }
18278
-
18279
- if (this.#tables.length <= 1) $(this.#wrap).hide();
18280
-
18281
- //$("button", this.#wrap).eq(0).addClass("ng-active");
18282
- ninegrid.j.querySelectorAll(this.#wrap.querySelector("button")).addClass("ng-active");
18283
-
18284
- $("button", this.#wrap).on("click", this.#onClick);
18285
- };
18286
-
18287
- get tables() {
18288
- return this.#tables;
18289
- };
18290
- get currentTable() {
18291
- return this.#tables[this.#at];
18292
- };
18293
-
18294
- resize = () => {
18295
- const overflow = $(this.#wrap).width() > $(this).width();
18296
-
18297
- $(this.#wrap).css({
18298
- "width": overflow ? "100%" : "unset",
18299
- "justify-content": overflow ? "space-evenly" : "unset",
18300
- });
18301
- };
18302
-
18303
- #onClick = e => {
18304
-
18305
- if (ninegrid.j.querySelectorAll(e.currentTarget).hasClass("ng-active")) return;
18306
-
18307
- const oldIndex = this.#at;
18308
-
18309
- this.#at = parseInt(e.currentTarget.value);
18310
-
18311
- this.#owner.changeRayout(this.#at);
18312
-
18313
- ninegrid.j.querySelectorAll("button", this.#wrap).removeClass("ng-active");
18314
- ninegrid.j.querySelectorAll(e.currentTarget).addClass("ng-active");
18315
-
18316
- var customEvent = new CustomEvent(ninegrid.EVENT.LAYOUT_CHANGED, { bubbles: true, detail: {} });
18317
- customEvent.oldIndex = oldIndex;
18318
- customEvent.newIndex = this.#at;
18319
-
18320
- this.#owner.dispatchEvent(customEvent);
18321
- };
18322
- }
18323
-
18324
- customElements.define("ng-layout", ngLayout);
18325
- customElements.define("ng-foot", ngFoot);
18326
-
18327
- class ngHead extends HTMLElement
18394
+ class ngInfo extends HTMLElement
18328
18395
  {
18329
18396
  #owner;
18397
+ #cell;
18330
18398
 
18331
18399
  constructor () {
18332
18400
  super();
@@ -18335,617 +18403,170 @@ class ngHead extends HTMLElement
18335
18403
  }
18336
18404
 
18337
18405
  connectedCallback() {
18338
-
18339
- this.#owner = this.getRootNode().host;
18340
18406
 
18341
- if (ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) return;
18407
+ const contents = this.innerHTML;
18342
18408
 
18343
18409
  this.shadowRoot.innerHTML = `
18344
18410
  <style>
18345
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngHead.css";
18346
- ${ninegrid.getCustomPath(this,"ngHead.css")}
18411
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngInfo.css";
18412
+ ${ninegrid.getCustomPath(this,"ngInfo.css")}
18347
18413
  </style>
18414
+
18415
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16">
18416
+ <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
18417
+ <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/>
18418
+ </svg>
18348
18419
 
18349
- <ng-menu></ng-menu>
18350
- <ng-title></ng-title>
18351
- <ng-custom></ng-custom>
18420
+ <div class="panel">
18421
+ ${contents}
18422
+ </div>
18352
18423
  `;
18353
- };
18354
- }
18355
-
18356
- class ngTitle extends HTMLElement
18357
- {
18358
- #owner;
18359
-
18360
- constructor () {
18361
- super();
18362
- }
18363
-
18364
- connectedCallback() {
18365
- if (!this.getRootNode().host) return;
18366
18424
 
18367
- this.#owner = this.getRootNode().host.getRootNode().host;
18368
-
18369
- this.innerHTML = `<span>${this.#owner.getAttribute("caption")}</span>`;
18425
+ this.#owner = this.getRootNode().host;//this.closest("nine-grid");
18426
+ this.#cell = this.closest("th,td");
18370
18427
 
18371
- $("span", this).on("dblclick", e => {
18372
- const link = '<font color="green">https://www.ninegrid.net</font>';
18373
- e.currentTarget.innerHTML = e.currentTarget.innerHTML == link ? `<span>${this.#owner.getAttribute("caption")}</span>` : link;
18374
- });
18375
- };
18376
- }
18428
+ $("svg.icon", this.shadowRoot).on("click", e => {
18429
+ e.preventDefault();
18430
+ e.stopPropagation();
18377
18431
 
18378
- class ngCustom extends HTMLElement
18379
- {
18380
- #owner;
18381
-
18382
- constructor () {
18383
- super();
18384
- }
18385
-
18386
- connectedCallback() {
18387
- if (!this.getRootNode().host) return;
18388
-
18389
- this.#owner = this.getRootNode().host.getRootNode().host;
18432
+ if ($("div.panel", this.shadowRoot).is(":visible")) {
18433
+ this.#closeAll();
18434
+ }
18435
+ else {
18436
+ this.#closeAll();
18437
+ this.#open();
18438
+ }
18439
+ });
18390
18440
 
18391
- this.innerHTML = `<div></div>`;
18441
+ $(this).on("click", e => { e.stopPropagation(); });
18392
18442
  };
18393
18443
 
18394
- add = (element) => {
18395
- this.querySelector("div:first-child").append(element);
18444
+ #open = () => {
18445
+ $("div.panel", this.shadowRoot).show();
18446
+ $(this.#cell).css({overflow: "visible"});
18447
+ };
18448
+ close = () => {
18449
+ $("div.panel", this.shadowRoot).hide();
18450
+ $(this.#cell).css({overflow: "unset"});
18396
18451
  };
18397
- }
18398
-
18399
- customElements.define("ng-title", ngTitle);
18400
- customElements.define("ng-custom", ngCustom);
18401
- customElements.define("ng-head", ngHead);
18402
-
18403
- class ngIcon extends HTMLElement
18404
- {
18405
- constructor() {
18406
- super();
18407
-
18408
- this.attachShadow({ mode: 'open' });
18409
- }
18410
18452
 
18411
- connectedCallback() {
18412
-
18413
- new MutationObserver((entries, observer) => {
18414
- for (var entry of entries) {
18415
- if (entry.type != "attributes") continue;
18416
- switch (entry.attributeName) {
18417
- case "stroke":
18418
- this.shadowRoot.querySelector("polyline").setAttribute(entry.attributeName, this.getAttribute(entry.attributeName));
18419
- break;
18420
-
18421
- case "fill":
18422
- this.shadowRoot.querySelector("circle,rect,svg").setAttribute(entry.attributeName, this.getAttribute(entry.attributeName));
18423
- break;
18424
-
18425
- case "start-fill":
18426
- case "end-fill":
18427
- var startFill = this.getAttribute("start-fill") || "rgba(255, 255, 255, 0.85)";
18428
- var endFill = this.getAttribute("end-fill") || "rgba(30, 30, 40, 0.85)";
18429
- $(entry.target).css({
18430
- //background: `radial-gradient(circle farthest-corner at 33% 33%, ${startFill} 0%, ${endFill} 80%), radial-gradient(circle farthest-corner at 45% 45%, rgba(0, 0, 0, 0) 50%, #000000 80%)`
18431
- background: `radial-gradient(circle farthest-corner at 33% 33%, ${startFill} 0%, ${endFill} 80%), radial-gradient(circle farthest-corner at 45% 45%, rgba(0, 0, 0, 0) 50%, #000000 80%)`
18432
- });
18433
- break;
18434
- }
18453
+ #closeAll = () => {
18454
+ this.#owner.body.querySelectorAll("ng-info").forEach(el => {
18455
+ var p = el.shadowRoot.querySelector("div.panel");
18456
+ if ($(p).is(":visible")) {
18457
+ $(p).hide();
18458
+ $(el.closest("th,td")).css({overflow: "unset"});
18435
18459
  }
18436
- }).observe(this, {
18437
- childList: false,
18438
- subtree: false,
18439
- attributeOldValue: true,
18440
18460
  });
18441
18461
  };
18442
18462
  }
18443
18463
 
18444
- class ngCheck extends ngIcon
18464
+ customElements.define("ng-info", ngInfo);
18465
+
18466
+ class ngInputColor extends ngEditableEx
18445
18467
  {
18468
+ #target;
18469
+
18446
18470
  constructor() {
18447
18471
  super();
18448
18472
  }
18449
18473
 
18450
18474
  connectedCallback() {
18451
- var size = this.getAttribute("size") || "12";
18452
- this.getAttribute("stroke") || "black";
18453
-
18475
+
18454
18476
  this.shadowRoot.innerHTML = `
18455
18477
  <style>
18456
- :host {
18457
- width: ${size}px;
18458
- --border: 1px solid
18459
- }
18460
-
18461
- polyline {
18462
- fill: none;
18463
- stroke-width: 2px;
18464
- }
18478
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngInputColor.css";
18479
+ ${ninegrid.getCustomPath(this,"ngInputColor.css")}
18465
18480
  </style>
18466
-
18467
- <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="currentColor" viewBox="0 0 16 16">
18468
- <path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425z"/>
18469
- </svg>
18481
+
18482
+ <input type="color" required />
18483
+ <button tabindex="-1"></button>
18470
18484
  `;
18471
18485
 
18472
18486
  super.connectedCallback();
18487
+
18488
+ this.#target = this.shadowRoot.querySelector("input");
18489
+
18490
+ $("button", this.shadowRoot).on("click", e => { $("input", this.shadowRoot).trigger("click"); });
18491
+
18492
+ $(this.cell).on("keydown", e => {
18493
+ switch (e.target.tagName) {
18494
+ case "TH": case "TD":
18495
+ if (["Enter"].includes(e.code)) {
18496
+ $(this.#target).trigger("click");
18497
+ }
18498
+ break;
18499
+ }
18500
+ });
18501
+
18502
+ this.#target.addEventListener("keydown", e => {
18503
+ if (["Enter"].includes(e.code)) {
18504
+ $(this.#target).trigger("click");
18505
+ }
18506
+ else if (["Escape"].includes(e.code)) {
18507
+ this.owner.cell.currentCell = this.cell;
18508
+ }
18509
+ else if (["Tab","PageUp","PageDown","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(e.code)) {
18510
+ this.owner.cell.currentCell = this.cell;
18511
+ this.owner.cell.moveCell(e);
18512
+ }
18513
+ });
18473
18514
  };
18474
- }
18475
18515
 
18476
- class ngRect extends ngIcon
18477
- {
18478
- constructor() {
18479
- super();
18480
- }
18481
-
18482
- connectedCallback() {
18483
- var size = this.getAttribute("size") || "12";
18484
- this.getAttribute("fill") || "black";
18516
+ dataRefresh = (v) => {
18485
18517
 
18486
- this.shadowRoot.innerHTML = `
18487
- <style>
18488
- :host {
18489
- }
18490
- </style>
18491
-
18492
- <svg viewBox="0 0 ${size} ${size}" width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
18493
- <rect width="${size}" height="${size}" x="0" y="0" rx="0" ry="0" fill="#ccc" />
18494
- </svg>
18495
- `;
18518
+ if (this.#isValidColor(this.value)) {
18519
+ this.#target.value = ninegrid.nvl(this.value, "#ffffff");
18520
+ ninegrid.j.querySelectorAll(this).removeClass("invalid");
18521
+ }
18522
+ else {
18523
+ this.#target.value = "#ffffff";
18524
+ ninegrid.j.querySelectorAll(this).addClass("invalid");
18525
+ }
18496
18526
 
18497
- super.connectedCallback();
18498
- };
18499
- }
18527
+ this.#target.setAttribute("title", this.value);
18500
18528
 
18501
- class ngCircle extends ngIcon
18502
- {
18503
- constructor() {
18504
- super();
18505
- }
18529
+ if (!v) this.reset();
18530
+ };
18506
18531
 
18507
- connectedCallback() {
18508
- var size = Number(this.getAttribute("size") || 12);
18509
- var round = size / 2;
18510
- var fill = this.getAttribute("fill") || "black";
18511
- var stroke = this.getAttribute("stroke") || "#666";
18512
- var strokeWidth = Number(this.getAttribute("stroke-width") || 0);
18513
- var cx = round + strokeWidth;
18514
- var cy = round + strokeWidth;
18532
+ #isValidColor = (v) => {
18533
+
18534
+ if (ninegrid.isNull(v)) return true;
18515
18535
 
18516
- this.shadowRoot.innerHTML = `
18517
- <style>
18518
- :host {
18519
- }
18520
- </style>
18521
-
18522
- <svg viewBox="0 0 ${size} ${size}" width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
18523
- <circle fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" cx="${cx}" cy="${cy}" r="${round}"/>
18524
- </svg>
18525
- `;
18536
+ if (typeof v !== "string" || v.length != 7 || !v.startsWith("#")) return false;
18526
18537
 
18527
- super.connectedCallback();
18528
- };
18529
- }
18530
-
18531
- class ngSphere extends ngIcon
18532
- {
18533
- constructor() {
18534
- super();
18535
- }
18536
-
18537
- connectedCallback() {
18538
- var size = this.getAttribute("size") || "12";
18539
- var startFill = this.getAttribute("start-fill") || "rgba(255, 255, 255, 0.85)";
18540
- var endFill = this.getAttribute("end-fill") || "rgba(0, 0, 0, 0.85)";
18538
+ var r = parseInt(v.substr(1,2), 16);
18539
+ var g = parseInt(v.substr(3,2), 16);
18540
+ var b = parseInt(v.substr(5,2), 16);
18541
18541
 
18542
- this.shadowRoot.innerHTML = `
18543
- <style>
18544
- :host {
18545
- position: absolute;
18546
- width: ${size}px;
18547
- height: ${size}px;
18548
- border-radius: 50%;
18549
- background: radial-gradient(circle farthest-corner at 33% 33%, ${startFill} 0%, ${endFill} 80%), radial-gradient(circle farthest-corner at 45% 45%, rgba(0, 0, 0, 0) 50%, #000000 80%);
18550
- }
18551
- </style>
18552
- `;
18542
+ if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) return false;
18553
18543
 
18554
- super.connectedCallback();
18555
- };
18544
+ return true;
18545
+ }
18556
18546
  }
18557
18547
 
18558
- customElements.define("ng-check", ngCheck);
18559
- customElements.define("ng-rect", ngRect);
18560
- customElements.define("ng-circle", ngCircle);
18561
- customElements.define("ng-sphere", ngSphere);
18562
-
18563
- class ngImg extends ngCellEx
18564
- {
18565
- #target;
18566
- //#src;
18567
-
18568
- constructor() {
18569
- super();
18570
-
18571
- //this.#src = this.getAttribute("src");
18572
- }
18573
-
18574
- connectedCallback() {
18575
- const border = this.getAttribute("border") || "unset";
18576
- const borderRadius = this.getAttribute("border-radius") || "unset";
18577
- const link = this.getAttribute("link");
18578
-
18579
- var attr = [];
18580
- for (var i = 0; i < this.attributes.length; i++) {
18581
- if (["alt","width","height"].includes(this.attributes[i].name)) attr.push(`${this.attributes[i].name}="${this.attributes[i].value}"`);
18582
- }
18583
-
18584
- this.shadowRoot.innerHTML = `
18585
- <style>
18586
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngImg.css";
18587
- ${ninegrid.getCustomPath(this,"ngImg.css")}
18588
-
18589
- img {
18590
- border: ${border};
18591
- border-radius: ${borderRadius};
18592
- cursor: ${link == "true"? "pointer" : "unset"};
18593
- }
18594
- img:hover {
18595
- filter: ${link == "true"? "brightness(80%)" : "unset"};
18596
- }
18597
- </style>
18598
-
18599
- <div class="ng-wrap">
18600
- <img ${attr.join(" ")} class="renderer" />
18601
- </div>
18602
- `;
18603
-
18604
- /** img,div class="renderer" 일때 ngCell에서 link로 연결 */
18605
-
18606
- super.connectedCallback();
18607
-
18608
- this.#target = this.shadowRoot.querySelector("img");
18609
- };
18610
-
18611
- dataRefresh = (v) => {
18612
-
18613
- var src = this.getAttribute("src");// || this.cell.getAttribute("src");
18614
-
18615
- var exprSrc = (src) ? this.getExprValue(src, this.cell.closest("tr").data, this.cell.dataset.row) : "";
18616
-
18617
- src = exprSrc || this.getDisplayText();
18618
- this.#target.src = src;
18619
-
18620
- src ? $(this.#target).show() : $(this.#target).hide();
18621
-
18622
- if (!v) this.reset();
18623
- };
18624
-
18625
- r = (owner, cell) => {
18626
-
18627
- const src = this.getAttribute("icon-src") || cell.getAttribute("icon-src");
18628
- var exprSrc = (src) ? this.getExprValue(src, cell.closest("tr").data, cell.dataset.row) : "";
18629
-
18630
- this.#target.src = exprSrc || this.value;
18631
- };
18632
- }
18633
-
18634
- class ngSvg extends ngCellEx
18635
- {
18636
- #target;
18637
- #src;
18638
-
18639
- constructor() {
18640
- super();
18641
-
18642
- this.#src = this.getAttribute("src");
18643
- }
18644
-
18645
- connectedCallback() {
18646
- const border = this.getAttribute("border") || "unset";
18647
- const borderRadius = this.getAttribute("border-radius") || "unset";
18648
- const link = this.getAttribute("link");
18649
-
18650
- this.shadowRoot.innerHTML = `
18651
- <style>
18652
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngSvg.css";
18653
- ${ninegrid.getCustomPath(this,"ngSvg.css")}
18654
-
18655
- div {
18656
- border: ${border};
18657
- border-radius: ${borderRadius};
18658
- cursor: ${link == "true"? "pointer" : "unset"};
18659
- }
18660
- div:hover {
18661
- filter: ${link == "true"? "brightness(80%)" : "unset"};
18662
- }
18663
- </style>
18664
-
18665
- <div class="ng-wrap"></div>
18666
- `;
18667
-
18668
- /** img,div class="renderer" 일때 ngCell에서 link로 연결 */
18669
-
18670
- super.connectedCallback();
18671
-
18672
- this.#target = this.shadowRoot.querySelector("div");
18673
- };
18674
-
18675
- dataRefresh = (v) => {
18676
-
18677
- const src = this.getAttribute("src");// || this.cell.getAttribute("src");
18678
-
18679
- var exprSrc = (src) ? this.getExprValue(src, this.cell.closest("tr").data, this.cell.dataset.row) : "";
18680
-
18681
- this.#target.innerHTML = exprSrc || this.getDisplayText();
18682
-
18683
- if (!v) this.reset();
18684
- };
18685
-
18686
-
18687
- r = (owner, cell) => {
18688
-
18689
- const src = this.getAttribute("icon-src") || cell.getAttribute("icon-src");
18690
- var exprSrc = (src) ? this.getExprValue(src, cell.closest("tr").data, cell.dataset.row) : "";
18691
-
18692
- this.#target.innerHTML = exprSrc || this.value;
18693
- };
18694
- }
18695
-
18696
- customElements.define("ng-img", ngImg);
18697
- customElements.define("ng-svg", ngSvg);
18548
+ customElements.define("ng-input-color", ngInputColor);
18698
18549
 
18699
- class ngImport
18700
- {
18701
- #owner;
18702
-
18703
- constructor (owner) {
18704
- //super();
18705
-
18706
- this.#owner = owner;
18707
- };
18708
-
18709
- importExcel = () => {
18710
- this.#openFileDialog();
18711
- };
18712
-
18713
- #openFileDialog = () => {
18714
- if (!this.input) {
18715
- this.input = document.createElement('input');
18716
- this.input.type = 'file';
18717
- this.input.onchange = this.#fileSelectedHandler;
18718
- }
18719
-
18720
- this.input.click();
18721
- };
18722
-
18723
- #fileSelectedHandler = (e) => {
18724
- var files = e.target.files;
18725
- if (files.length < 1) {
18726
- alert('select a file...');
18727
- return;
18728
- }
18729
- var file = files[0];
18730
- var reader = new FileReader();
18731
-
18732
- reader.onload = this.#fileLoadedHandler;
18733
- reader.readAsDataURL(file);
18734
- reader.onloadend = this.#fileLoadEndHandler;
18735
- }
18736
-
18737
- #fileLoadedHandler = e => {
18738
-
18739
- var match = /^data:(.*);base64,(.*)$/.exec(e.target.result);
18740
- if (match == null) {
18741
- throw 'Could not parse result'; // should not happen
18742
- }
18743
-
18744
- match[1];
18745
- match[2];
18746
- //alert(mimeType);
18747
- //alert(content);
18748
-
18749
-
18750
- //var fileReader = new FileReader();
18751
-
18752
- }
18753
-
18754
- #fileLoadEndHandler = (e) => {
18755
-
18756
- //var arrayBuffer = fileReader.result;
18757
- var arrayBuffer = e.target.result;
18758
-
18759
- const workbook = new ExcelJS.Workbook();
18760
-
18761
- workbook.xlsx.load(arrayBuffer).then(() => {
18762
-
18763
- workbook.worksheets.forEach((sheet) => {
18764
- sheet.eachRow((row,rowNumber) => {
18765
-
18766
- if (rowNumber % 10 == 0) console.log(rowNumber);
18767
- });
18768
- });
18769
- });
18770
- }
18771
- }
18772
-
18773
- class ngInfo extends HTMLElement
18774
- {
18775
- #owner;
18776
- #cell;
18777
-
18778
- constructor () {
18779
- super();
18780
-
18781
- this.attachShadow({ mode: 'open' });
18782
- }
18783
-
18784
- connectedCallback() {
18785
-
18786
- const contents = this.innerHTML;
18787
-
18788
- this.shadowRoot.innerHTML = `
18789
- <style>
18790
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngInfo.css";
18791
- ${ninegrid.getCustomPath(this,"ngInfo.css")}
18792
- </style>
18793
-
18794
- <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16">
18795
- <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
18796
- <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/>
18797
- </svg>
18798
-
18799
- <div class="panel">
18800
- ${contents}
18801
- </div>
18802
- `;
18803
-
18804
- this.#owner = this.getRootNode().host;//this.closest("nine-grid");
18805
- this.#cell = this.closest("th,td");
18806
-
18807
- $("svg.icon", this.shadowRoot).on("click", e => {
18808
- e.preventDefault();
18809
- e.stopPropagation();
18810
-
18811
- if ($("div.panel", this.shadowRoot).is(":visible")) {
18812
- this.#closeAll();
18813
- }
18814
- else {
18815
- this.#closeAll();
18816
- this.#open();
18817
- }
18818
- });
18819
-
18820
- $(this).on("click", e => { e.stopPropagation(); });
18821
- };
18822
-
18823
- #open = () => {
18824
- $("div.panel", this.shadowRoot).show();
18825
- $(this.#cell).css({overflow: "visible"});
18826
- };
18827
- close = () => {
18828
- $("div.panel", this.shadowRoot).hide();
18829
- $(this.#cell).css({overflow: "unset"});
18830
- };
18831
-
18832
- #closeAll = () => {
18833
- this.#owner.body.querySelectorAll("ng-info").forEach(el => {
18834
- var p = el.shadowRoot.querySelector("div.panel");
18835
- if ($(p).is(":visible")) {
18836
- $(p).hide();
18837
- $(el.closest("th,td")).css({overflow: "unset"});
18838
- }
18839
- });
18840
- };
18841
- }
18842
-
18843
- customElements.define("ng-info", ngInfo);
18844
-
18845
- class ngInputColor extends ngEditableEx
18846
- {
18847
- #target;
18848
-
18849
- constructor() {
18850
- super();
18851
- }
18852
-
18853
- connectedCallback() {
18854
-
18855
- this.shadowRoot.innerHTML = `
18856
- <style>
18857
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngInputColor.css";
18858
- ${ninegrid.getCustomPath(this,"ngInputColor.css")}
18859
- </style>
18860
-
18861
- <input type="color" required />
18862
- <button tabindex="-1"></button>
18863
- `;
18864
-
18865
- super.connectedCallback();
18866
-
18867
- this.#target = this.shadowRoot.querySelector("input");
18868
-
18869
- $("button", this.shadowRoot).on("click", e => { $("input", this.shadowRoot).trigger("click"); });
18870
-
18871
- $(this.cell).on("keydown", e => {
18872
- switch (e.target.tagName) {
18873
- case "TH": case "TD":
18874
- if (["Enter"].includes(e.code)) {
18875
- $(this.#target).trigger("click");
18876
- }
18877
- break;
18878
- }
18879
- });
18880
-
18881
- this.#target.addEventListener("keydown", e => {
18882
- if (["Enter"].includes(e.code)) {
18883
- $(this.#target).trigger("click");
18884
- }
18885
- else if (["Escape"].includes(e.code)) {
18886
- this.owner.cell.currentCell = this.cell;
18887
- }
18888
- else if (["Tab","PageUp","PageDown","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(e.code)) {
18889
- this.owner.cell.currentCell = this.cell;
18890
- this.owner.cell.moveCell(e);
18891
- }
18892
- });
18893
- };
18894
-
18895
- dataRefresh = (v) => {
18896
-
18897
- if (this.#isValidColor(this.value)) {
18898
- this.#target.value = ninegrid.nvl(this.value, "#ffffff");
18899
- ninegrid.j.querySelectorAll(this).removeClass("invalid");
18900
- }
18901
- else {
18902
- this.#target.value = "#ffffff";
18903
- ninegrid.j.querySelectorAll(this).addClass("invalid");
18904
- }
18905
-
18906
- this.#target.setAttribute("title", this.value);
18907
-
18908
- if (!v) this.reset();
18909
- };
18910
-
18911
- #isValidColor = (v) => {
18912
-
18913
- if (ninegrid.isNull(v)) return true;
18914
-
18915
- if (typeof v !== "string" || v.length != 7 || !v.startsWith("#")) return false;
18916
-
18917
- var r = parseInt(v.substr(1,2), 16);
18918
- var g = parseInt(v.substr(3,2), 16);
18919
- var b = parseInt(v.substr(5,2), 16);
18920
-
18921
- if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) return false;
18922
-
18923
- return true;
18924
- }
18925
- }
18926
-
18927
- customElements.define("ng-input-color", ngInputColor);
18928
-
18929
- class ngInputDate extends ngEditableEx
18930
- {
18931
- #target;
18932
- #type;
18933
-
18934
- constructor () {
18935
- super();
18936
-
18937
- this.#type = this.getAttribute("type") || "date";
18938
- if (!["date","datetime-local","time","week","month"].includes(this.#type)) throw `invalid type : ${this.#type}`;
18939
- }
18940
-
18941
- connectedCallback() {
18942
-
18943
- const height = parseInt(this.closest("tr").style.height, 10) - 1;
18944
-
18945
- var attr = [];
18946
- for (var i = 0; i < this.attributes.length; i++) {
18947
- if (["value","max","min","step"].includes(this.attributes[i].name)) attr.push(`${this.attributes[i].name}="${this.attributes[i].value}"`);
18948
- }
18550
+ class ngInputDate extends ngEditableEx
18551
+ {
18552
+ #target;
18553
+ #type;
18554
+
18555
+ constructor () {
18556
+ super();
18557
+
18558
+ this.#type = this.getAttribute("type") || "date";
18559
+ if (!["date","datetime-local","time","week","month"].includes(this.#type)) throw `invalid type : ${this.#type}`;
18560
+ }
18561
+
18562
+ connectedCallback() {
18563
+
18564
+ const height = parseInt(this.closest("tr").style.height, 10) - 1;
18565
+
18566
+ var attr = [];
18567
+ for (var i = 0; i < this.attributes.length; i++) {
18568
+ if (["value","max","min","step"].includes(this.attributes[i].name)) attr.push(`${this.attributes[i].name}="${this.attributes[i].value}"`);
18569
+ }
18949
18570
 
18950
18571
  this.shadowRoot.innerHTML = `
18951
18572
  <style>
@@ -23466,594 +23087,985 @@ customElements.define("ng-tree-item", ngTreeItem);
23466
23087
 
23467
23088
  //import { ngData } from "./ngData.js";
23468
23089
 
23469
- class ngView
23470
- {
23471
- #owner;
23472
- #rawIndex;
23473
- #height;
23474
- #ing;
23475
- #timers = [];
23476
-
23477
- constructor(owner) {
23478
- this.#owner = owner;
23479
-
23480
- this.#ing = false;
23090
+ class ngView
23091
+ {
23092
+ #owner;
23093
+ #rawIndex;
23094
+ #height;
23095
+ #ing;
23096
+ #timers = [];
23097
+
23098
+ constructor(owner) {
23099
+ this.#owner = owner;
23100
+
23101
+ this.#ing = false;
23102
+
23103
+ this.init();
23104
+ }
23105
+
23106
+ /**
23107
+ * changelayout
23108
+ * resize
23109
+ */
23110
+ init = () => {
23111
+
23112
+ //if (this.#ing) return;
23113
+
23114
+ this.#height = {
23115
+ body : $(".ng-container-body", this.#owner.body).height() || 0,
23116
+ thead : $(".ng-container-body thead", this.#owner.body).height() || 0,
23117
+ tfoot : $(".ng-container-body tfoot", this.#owner.body).height() || 0,
23118
+ };
23119
+
23120
+ if (this.#ing) return;
23121
+
23122
+ $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(this.#height.body);
23123
+
23124
+ this.#ing = true;
23125
+ };
23126
+
23127
+ redraw = () => {
23128
+
23129
+ this.#owner.touchEvent.scrollTop();
23130
+
23131
+ this.init();
23132
+
23133
+ if (!this.#owner.dataManager) return;
23134
+
23135
+ this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23136
+
23137
+ this.#resetTR3();
23138
+
23139
+ this.#r();
23140
+
23141
+ this.#owner.body.querySelector("ng-vscrollbar").refresh();
23142
+ };
23143
+
23144
+ redrawV2 = () => {
23145
+
23146
+ this.#owner.touchEvent.scrollTop();
23147
+
23148
+ this.init();
23149
+
23150
+ this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23151
+
23152
+ this.#resetTR3V2();
23153
+
23154
+ this.#r();
23155
+ this.#owner.body.querySelector("ng-vscrollbar").refreshV2();
23156
+ };
23157
+
23158
+ redrawV3 = () => {
23159
+
23160
+ this.init();
23161
+
23162
+ this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23163
+
23164
+ //this.#resetTR3V2();
23165
+
23166
+ this.#r();
23167
+ //this.#owner.body.querySelector("ng-vscrollbar").refreshV2();
23168
+ };
23169
+
23170
+
23171
+ #getPinHeight = () => {
23172
+ var h = 0;
23173
+
23174
+ $(".ng-container-body tbody.fixed tr", this.#owner.body).each((i, tr) => {
23175
+ if (tr.data && tr.data.__ng) {
23176
+ h += this.#owner.matrix.getHeight(tr.data.__ng.height[parseInt(i % this.#owner.template.length)]);
23177
+ }
23178
+ });
23179
+
23180
+ return h;
23181
+ }
23182
+
23183
+ #getTrHeight = () => {
23184
+ var h = this.#getPinHeight();
23185
+
23186
+ const startRow = parseInt(this.#rawIndex * this.#owner.template.length);
23187
+
23188
+ $(".ng-container-body tbody.bindable tr:not(.nodata)", this.#owner.body).each((i, tr) => {
23189
+ h += this.#owner.matrix.getHeight(startRow + i);
23190
+ });
23191
+
23192
+ return h;
23193
+ }
23194
+
23195
+ #getRowCount = () => {
23196
+
23197
+ const displayRowCount = parseInt(this.#owner.getAttribute("display-row-count"));
23198
+
23199
+ if (this.#owner.closest("dialog") && ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) return 1;
23200
+ if ([ninegrid.PAGINGTYPE.CLIENT,ninegrid.PAGINGTYPE.SERVER].includes(this.#owner.paging.type)) return this.#owner.paging.linesPerPage;
23201
+ if (!isNaN(displayRowCount)) {
23202
+
23203
+ /**
23204
+ * 데이타가 적을 경우 rawIndex = 0;
23205
+ * 마지막 페이지 인경우 rawIndex 계산
23206
+ */
23207
+ if (this.#rawIndex + displayRowCount > this.#owner.data.count()) {
23208
+ this.#rawIndex = (displayRowCount >= this.#owner.data.count()) ? 0 : this.#owner.data.count() - displayRowCount;
23209
+ this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23210
+
23211
+ }
23212
+
23213
+
23214
+ return displayRowCount;
23215
+ }
23216
+
23217
+ const totalHeight = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getPinHeight();
23218
+
23219
+ var rowCount = 0;
23220
+ var h = 0;
23221
+ while (h < totalHeight) {
23222
+ h += this.getRowHeight(this.#rawIndex + rowCount++);
23223
+ }
23224
+
23225
+ if (this.#rawIndex + rowCount > this.#owner.data.count()) {
23226
+
23227
+ rowCount = 0;
23228
+ h = 0;
23229
+ for (var i = this.#owner.data.count() - 1; i >= 0 && h < totalHeight; i--) {
23230
+ h += this.getRowHeight(i);
23231
+ rowCount++;
23232
+ }
23233
+
23234
+ if (h > totalHeight) rowCount --;
23235
+
23236
+ this.#rawIndex = this.#owner.data.count() - rowCount;
23237
+
23238
+ this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23239
+ }
23240
+
23241
+
23242
+ return rowCount;
23243
+ };
23244
+
23245
+ #createTR = () => {
23246
+
23247
+ // 1. 스크린에 보여질 TR 갯수 계산
23248
+ var rowCount = this.#getRowCount();
23249
+
23250
+ // 0. TR 생성여부 판단
23251
+ //if (!this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:not(.nodata)") || this.isLastPage) {
23252
+ // 2. TR 생성
23253
+ var startRow = $(".ng-container-body tbody.bindable tr", this.#owner.body).not('tr.nodata').length / (this.#owner.template.length || 1);
23254
+
23255
+
23256
+ const leftTmpl = this.#owner.template.clone();
23257
+ const rightTmpl = leftTmpl.clone();//this.#owner.template.clone();
23258
+ const bodyTmpl = leftTmpl.clone();//this.#owner.template.clone();
23259
+
23260
+ leftTmpl .find("th,td").not("[fixed=left]").remove();
23261
+ rightTmpl .find("th,td").not("[fixed=right]").remove();
23262
+ bodyTmpl .find("th[fixed=left],td[fixed=left],th[fixed=right],td[fixed=right]").remove();
23263
+
23264
+ const leftNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-left"));
23265
+ const rightNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-right"));
23266
+ const bodyNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-body"));
23267
+
23268
+ for (var i = startRow; i < rowCount; i++) {
23269
+
23270
+
23271
+ leftTmpl .each((i,tr) => { leftNoData .before(tr.outerHTML); });
23272
+ rightTmpl .each((i,tr) => { rightNoData .before(tr.outerHTML); });
23273
+ bodyTmpl .each((i,tr) => { bodyNoData .before(tr.outerHTML); });
23274
+ }
23275
+ //}
23276
+
23277
+
23278
+
23279
+ // 3. 불필요 TR 제거
23280
+ /**
23281
+ rowCount = Math.min(rowCount, this.#owner.data.count());// + ($(".ng-container-body tbody.fixed tr", this.#owner.body).length / this.#owner.template.length);
23282
+ $(".ng-container tbody.bindable tr", this.#owner.body).not(".nodata").each((i,tr) => {
23283
+ if (parseInt(tr.sectionRowIndex / this.#owner.template.length) >= rowCount) {
23284
+ $(tr).remove();
23285
+ }
23286
+ }); */
23287
+
23288
+ return;
23289
+
23290
+ // 4. row resize
23291
+ var trsBody = $(".ng-container-body tbody.bindable tr:not(.nodata)", this.#owner.body);
23292
+
23293
+ if (trsBody.length > 0) {
23294
+ var startRow = parseInt(this.#rawIndex * this.#owner.template.length);
23295
+ }
23296
+ };
23297
+
23298
+ #removeGarbageTR = () => {
23299
+
23300
+ var rowCount = Math.min(this.#getRowCount(), this.#owner.data.count());// + this.pin.count();
23301
+
23302
+
23303
+ $(".ng-container tbody.bindable tr:not(.nodata)", this.#owner.body).each((i,tr) => {
23304
+ if (parseInt(tr.sectionRowIndex / this.#owner.template.length) >= rowCount) {
23305
+ $(tr).remove();
23306
+ }
23307
+ });
23308
+
23309
+ this.#owner.body.querySelectorAll(".ng-container tbody.bindable tr:not(.nodata)").forEach(tr => {
23310
+ const row = this.#rawIndex + parseInt(tr.sectionRowIndex / this.#owner.template.length);
23311
+ if (row >= this.#owner.data.count()) {
23312
+ $(tr).remove();
23313
+ }
23314
+ });
23315
+ };
23316
+
23317
+ #resetTR3 = () => {
23318
+
23319
+ // 1. TR 생성
23320
+ this.#createTR();
23321
+
23322
+ this.#removeGarbageTR();
23323
+
23324
+ // 5. nodata resize
23325
+
23326
+ var h = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getTrHeight();
23327
+ $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23328
+
23329
+ /**
23330
+ this.#owner.body.querySelector(".ng-container-body .ng-table").style.top = 0;
23331
+ this.#owner.body.querySelector(".ng-container-left .ng-table").style.top = 0;
23332
+ this.#owner.body.querySelector(".ng-container-right .ng-table").style.top = 0;
23333
+ */
23334
+
23335
+ //$('.ng-container .ng-table', this.#owner.body).offset({top:$('.ng-container .ng-table', this.#owner.body).offset().top});
23336
+ //$('.ng-container .ng-table', this.#owner.body).offset({top:$('.ng-container .ng-table', this.#owner.body).offset().top});
23337
+ //this.isLastPage = h > 0 ? true : false;
23338
+
23339
+
23340
+ if (this.#owner.closest("dialog") && ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) {
23341
+ $("tr.nodata", this.#owner.body).height(0);
23342
+
23343
+ var h = 47;//this.#getTrHeight();
23344
+ $('.ng-container-body tbody.bindable tr', this.#owner.body).not('.nodata').each((i,tr) => {
23345
+ h += $(tr).height();
23346
+ });
23347
+
23348
+ $(this.#owner.closest("dialog")).height(h);
23349
+ }
23350
+ };
23351
+
23352
+ #resetTR3V2 = () => {
23353
+
23354
+ // 1. TR 생성
23355
+ //this.#owner.isLastPage = rows[rows.length-1] == this.#owner.data.count() - 1 ? true : false;//this.#isLastPage();
23356
+
23357
+ //if (this.#owner.isLastPage) {
23358
+ //if (this.#isLastPage) {
23359
+
23360
+ //if (this.#owner.isLastPage) {
23361
+ this.#createTR();
23362
+ //}
23363
+ /**
23364
+ if ($(".ng-container-body tbody.bindable tr.nodata", this.#owner.body).height() > 0) {
23365
+ this.#createTR();
23366
+ } */
23367
+
23368
+ this.#removeGarbageTR();
23369
+
23370
+
23371
+ // 5. nodata resize
23372
+ /**
23373
+ if (this.#owner.isFirstPage || this.#owner.isLastPage) {
23374
+ var h = this.#owner.body.querySelector("ng-vscrollbar").bodyHeight - $(".ng-container-body .ng-table", this.#owner.body).height() + $('.ng-container-body .ng-table tr.nodata', this.#owner.body).height();
23375
+ $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23376
+ }*/
23377
+ //$(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(700);
23378
+
23379
+ if (this.#owner.isLastPage) ;
23380
+
23381
+
23382
+ //this.isLastPage = h > 0 ? true : false;
23383
+
23384
+
23385
+ //this.nodata = h > 0 ? true : false;
23386
+
23387
+ // 6. 마지막 TR tabindex
23388
+ };
23389
+
23390
+ #refreshData = () => {
23391
+
23392
+ for (const v of this.#timers) {
23393
+ clearTimeout(v);
23394
+ }
23395
+ this.#timers = [];
23396
+
23397
+ this.#timers.push(setTimeout(() => {
23398
+
23399
+ this.#ing = false;
23400
+
23401
+ // 4. row resize
23402
+ var trsBody = $(".ng-container-body tbody.bindable tr", this.#owner.body).not(".nodata");
23403
+ var trsLeft = $(".ng-container-left tbody.bindable tr", this.#owner.body).not(".nodata");
23404
+ var trsRight= $(".ng-container-right tbody.bindable tr", this.#owner.body).not(".nodata");
23405
+
23406
+ if (trsBody.length > 0) {
23407
+ var startRow = parseInt(trsBody[0].dataset.row * this.#owner.template.length);
23408
+
23409
+ trsBody.each((i, tr) => {
23410
+ var h = this.#owner.matrix.getHeight(startRow + i);// || 32;
23411
+
23412
+ $("canvas.chart", tr).css({ "height" : h-4 });
23413
+ $("canvas.chart", trsLeft.eq(i)).css({ "height" : h-4 });
23414
+ $("canvas.chart", trsRight.eq(i)).css({ "height" : h-4 });
23415
+
23416
+ $(tr).css({ "height" : h }).height(h);
23417
+ trsLeft.eq(i).css({ "height" : h }).height(h);
23418
+ trsRight.eq(i).css({ "height" : h }).height(h);
23419
+ });
23420
+ }
23421
+
23422
+ this.#owner.cell.refresh();
23423
+
23424
+ //row resize
23425
+ $('ng-row-indicator', this.#owner.body).each((i,el) => {
23426
+ el.refresh();
23427
+ });
23428
+
23429
+ //5. nodata resize
23430
+ var h = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getTrHeight();
23431
+ $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23432
+
23433
+ //this.isLastPage = h > 0 ? true : false;
23434
+
23435
+
23436
+ this.#owner.refreshBindRows();
23437
+
23438
+ this.#owner.refreshData();
23439
+
23440
+ this.#owner.paging.totalCount = this.#owner.data.countNF();
23441
+
23442
+ this.#owner.body.querySelector("ng-vscrollbar").refresh();
23443
+
23444
+ ninegrid.j.querySelectorAll(".ng-table tbody tr", this.#owner.body).removeClass("hover");
23445
+ ninegrid.j.querySelectorAll(this.#owner).removeClass("loading");
23446
+ ninegrid.j.querySelectorAll("ng-filter-panel").removeClass("loading");
23447
+
23448
+ /**
23449
+ this.#owner.body.querySelector(".ng-container-left").style.position = "sticky";
23450
+ this.#owner.body.querySelector(".ng-container-right").style.position = "sticky";
23451
+ this.#owner.body.querySelector(".ng-container-body").style.position = "sticky";
23452
+ this.#owner.body.querySelector(".ng-container-left").style.top = "0px";
23453
+ this.#owner.body.querySelector(".ng-container-right").style.top = "0px";
23454
+ this.#owner.body.querySelector(".ng-container-body").style.top = "0px";
23455
+
23456
+ setTimeout(() => {
23457
+ this.#owner.body.querySelector(".ng-container-left").style.position = "relative";
23458
+ this.#owner.body.querySelector(".ng-container-right").style.position = "relative";
23459
+ this.#owner.body.querySelector(".ng-container-body").style.position = "relative";
23460
+ this.#owner.body.querySelector(".ng-container-left").style.top = "0px";
23461
+ this.#owner.body.querySelector(".ng-container-right").style.top = "0px";
23462
+ this.#owner.body.querySelector(".ng-container-body").style.top = "0px";
23463
+ }, 1000); */
23464
+
23465
+ }, 300));
23481
23466
 
23482
- this.init();
23467
+ //if (!this.timers) this.timers = [];
23468
+ this.#timers.push(setTimeout(() => {
23469
+ this.#owner.refreshDataV2();
23470
+ }));
23483
23471
  }
23484
23472
 
23485
- /**
23486
- * changelayout
23487
- * resize
23488
- */
23489
- init = () => {
23490
-
23491
- //if (this.#ing) return;
23492
-
23493
- this.#height = {
23494
- body : $(".ng-container-body", this.#owner.body).height() || 0,
23495
- thead : $(".ng-container-body thead", this.#owner.body).height() || 0,
23496
- tfoot : $(".ng-container-body tfoot", this.#owner.body).height() || 0,
23497
- };
23498
-
23499
- if (this.#ing) return;
23473
+ #r = () => {
23474
+
23475
+ var rows = [];
23476
+ this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)").forEach(tr => {
23477
+ rows.push(this.#rawIndex + parseInt(tr.sectionRowIndex / this.#owner.template.length));
23478
+ });
23500
23479
 
23501
- $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(this.#height.body);
23480
+ rows = [...new Set(rows)];
23502
23481
 
23503
- this.#ing = true;
23504
- };
23505
-
23506
- redraw = () => {
23482
+ this.#owner.body.querySelectorAll(".ng-container tbody.bindable tr:not(.nodata)").forEach(tr => {
23483
+ const index = parseInt(tr.sectionRowIndex / this.#owner.template.length);
23507
23484
 
23508
- this.#owner.touchEvent.scrollTop();
23509
-
23510
- this.init();
23485
+ tr.dataset.row = rows[index];
23486
+ tr.dataset.matrixRow = parseInt(rows[index] * this.#owner.template.length) + parseInt(tr.sectionRowIndex % this.#owner.template.length);
23487
+
23488
+ for (const cell of tr.cells) {
23489
+ cell.dataset.row = tr.dataset.row;
23490
+ cell.dataset.matrixRow = tr.dataset.matrixRow;
23491
+ }
23492
+ });
23511
23493
 
23512
- if (!this.#owner.dataManager) return;
23513
23494
 
23514
- this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23495
+ rows.forEach(row => {
23496
+ const data = this.#owner.data.get(row);
23515
23497
 
23516
- this.#resetTR3();
23498
+ this.#owner.body.querySelectorAll(`.ng-container tbody.bindable tr[data-row="${row}"]`).forEach(tr => {
23499
+ tr.data = data;
23500
+ });
23501
+ });
23517
23502
 
23518
- this.#r();
23503
+ this.#rawIndex = parseInt(this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:first-of-type").getAttribute("data-row"));
23504
+
23505
+ this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23519
23506
 
23520
- this.#owner.body.querySelector("ng-vscrollbar").refresh();
23521
- };
23522
-
23523
- redrawV2 = () => {
23524
23507
 
23525
- this.#owner.touchEvent.scrollTop();
23526
-
23527
- this.init();
23508
+ this.#refreshData();
23528
23509
 
23529
- this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23510
+ //this.#owner.isFirstPage = this.#rawIndex == this.#owner.cell.firstRow() ? true : false;
23511
+ this.#owner.isFirstPage = this.#rawIndex == 0 ? true : false;
23512
+ //this.#owner.isLastPage = this.isLastPage;
23530
23513
 
23531
- this.#resetTR3V2();
23532
23514
 
23533
- this.#r();
23534
- this.#owner.body.querySelector("ng-vscrollbar").refreshV2();
23515
+ this.#owner.isLastPage = rows[rows.length-1] == this.#owner.data.count() - 1 ? true : false;//this.#isLastPage();
23516
+ //this.#owner.lastHidden = (parseInt($(".ng-container-body", this.#owner.body).height() - ($(".ng-container-body tfoot", this.#owner.body).height() || 0) + 2) >= t + h) ? false : true;
23517
+
23518
+ //this.#owner.body.querySelector("ng-vscrollbar").refresh();
23535
23519
  };
23536
23520
 
23537
- redrawV3 = () => {
23538
-
23539
- this.init();
23540
-
23541
- this.#rawIndex = this.#owner.dataManager.viewRecords.rawIndex || 0;
23542
-
23543
- //this.#resetTR3V2();
23521
+ getVisibleFirstRow = () => {
23522
+ var tr = this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:first-child");
23523
+ return parseInt(tr.dataset.row);
23524
+ };
23544
23525
 
23545
- this.#r();
23546
- //this.#owner.body.querySelector("ng-vscrollbar").refreshV2();
23526
+ getVisibleLastRow = () => {
23527
+ var trs = this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)");
23528
+ return (trs.length > 0) ? parseInt(trs[trs.length - 1].dataset.row) : -1;
23547
23529
  };
23548
-
23549
23530
 
23550
- #getPinHeight = () => {
23551
- var h = 0;
23531
+ getRowHeight = (rowIndex) => {
23532
+ let h = 0;
23552
23533
 
23553
- $(".ng-container-body tbody.fixed tr", this.#owner.body).each((i, tr) => {
23554
- if (tr.data && tr.data.__ng) {
23555
- h += this.#owner.matrix.getHeight(tr.data.__ng.height[parseInt(i % this.#owner.template.length)]);
23556
- }
23557
- });
23534
+ for (var i = 0; i < this.#owner.template.length; i++) {
23535
+ h += this.#owner.matrix.getHeight(parseInt(rowIndex * this.#owner.template.length + i));
23536
+ }
23558
23537
 
23559
23538
  return h;
23560
- }
23539
+ };
23561
23540
 
23562
- #getTrHeight = () => {
23563
- var h = this.#getPinHeight();
23564
-
23565
- const startRow = parseInt(this.#rawIndex * this.#owner.template.length);
23566
-
23567
- $(".ng-container-body tbody.bindable tr:not(.nodata)", this.#owner.body).each((i, tr) => {
23568
- h += this.#owner.matrix.getHeight(startRow + i);
23569
- });
23541
+ getTranslateY = () => {
23542
+ const tbody = this.#owner.body.querySelector(".ng-container-body tbody.bindable");
23543
+ return (!tbody || !tbody.style || !tbody.style.transform) ? 0 : parseFloat(tbody.style.transform.replace("translateY(", "").replace(")", ""));
23544
+ };
23545
+
23546
+ getTotalSpace = () => {
23570
23547
 
23571
- return h;
23572
- }
23548
+ return $(this.#owner.body.querySelector(".ng-container-body")).height()
23549
+ - ($(this.#owner.body.querySelector(".ng-container-body thead")).height() || 0)
23550
+ - ($(this.#owner.body.querySelector(".ng-container-body tfoot")).height() || 0)
23551
+ - ($(this.#owner.body.querySelector(".ng-container-body tbody.fixed")).height() || 0);
23552
+ //- this.getTranslateY();
23553
+ };
23573
23554
 
23574
- #getRowCount = () => {
23555
+ fitPage = () => {
23556
+ const lastRow = this.getVisibleLastRow();
23557
+ const totalSpace = this.getTotalSpace();
23575
23558
 
23576
- const displayRowCount = parseInt(this.#owner.getAttribute("display-row-count"));
23577
-
23578
- if (this.#owner.closest("dialog") && ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) return 1;
23579
- if ([ninegrid.PAGINGTYPE.CLIENT,ninegrid.PAGINGTYPE.SERVER].includes(this.#owner.paging.type)) return this.#owner.paging.linesPerPage;
23580
- if (!isNaN(displayRowCount)) {
23581
-
23582
- /**
23583
- * 데이타가 적을 경우 rawIndex = 0;
23584
- * 마지막 페이지 인경우 rawIndex 계산
23585
- */
23586
- if (this.#rawIndex + displayRowCount > this.#owner.data.count()) {
23587
- this.#rawIndex = (displayRowCount >= this.#owner.data.count()) ? 0 : this.#owner.data.count() - displayRowCount;
23588
- this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23589
-
23590
- }
23591
-
23592
-
23593
- return displayRowCount;
23594
- }
23595
-
23596
- const totalHeight = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getPinHeight();
23597
-
23598
- var rowCount = 0;
23599
- var h = 0;
23600
- while (h < totalHeight) {
23601
- h += this.getRowHeight(this.#rawIndex + rowCount++);
23602
- }
23603
-
23604
- if (this.#rawIndex + rowCount > this.#owner.data.count()) {
23605
-
23606
- rowCount = 0;
23607
- h = 0;
23608
- for (var i = this.#owner.data.count() - 1; i >= 0 && h < totalHeight; i--) {
23559
+ /**
23560
+ * 마지막 페이지 정리
23561
+ */
23562
+ if (lastRow == this.#owner.data.count() - 1) {
23563
+ var h = 0;
23564
+ for (var i = lastRow; i >= 0; i--) {
23609
23565
  h += this.getRowHeight(i);
23610
- rowCount++;
23566
+
23567
+ if (h > totalSpace) {
23568
+ this.#owner.scrollTo(i + 1);
23569
+ break;
23570
+ }
23611
23571
  }
23612
-
23613
- if (h > totalHeight) rowCount --;
23614
-
23615
- this.#rawIndex = this.#owner.data.count() - rowCount;
23616
-
23617
- this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23618
23572
  }
23619
-
23573
+ }
23574
+ /**
23575
+ #isLastPage = () => {
23576
+ const arr = this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)");
23577
+ if (arr.length <= 0) return null;
23620
23578
 
23621
- return rowCount;
23622
- };
23579
+ return arr[arr.length - 1].dataset.row == this.#owner.data.count() - 1 ? true : false;
23580
+ }*/
23623
23581
 
23624
- #createTR = () => {
23625
-
23626
- // 1. 스크린에 보여질 TR 갯수 계산
23627
- var rowCount = this.#getRowCount();
23628
-
23629
- // 0. TR 생성여부 판단
23630
- //if (!this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:not(.nodata)") || this.isLastPage) {
23631
- // 2. TR 생성
23632
- var startRow = $(".ng-container-body tbody.bindable tr", this.#owner.body).not('tr.nodata').length / (this.#owner.template.length || 1);
23633
-
23634
-
23635
- const leftTmpl = this.#owner.template.clone();
23636
- const rightTmpl = leftTmpl.clone();//this.#owner.template.clone();
23637
- const bodyTmpl = leftTmpl.clone();//this.#owner.template.clone();
23638
-
23639
- leftTmpl .find("th,td").not("[fixed=left]").remove();
23640
- rightTmpl .find("th,td").not("[fixed=right]").remove();
23641
- bodyTmpl .find("th[fixed=left],td[fixed=left],th[fixed=right],td[fixed=right]").remove();
23642
-
23643
- const leftNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-left"));
23644
- const rightNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-right"));
23645
- const bodyNoData = $("tr.nodata", this.#owner.body.querySelector(".ng-container-body"));
23646
-
23647
- for (var i = startRow; i < rowCount; i++) {
23648
-
23649
-
23650
- leftTmpl .each((i,tr) => { leftNoData .before(tr.outerHTML); });
23651
- rightTmpl .each((i,tr) => { rightNoData .before(tr.outerHTML); });
23652
- bodyTmpl .each((i,tr) => { bodyNoData .before(tr.outerHTML); });
23653
- }
23654
- //}
23655
-
23582
+
23583
+ /**
23584
+ * grid.selectCell(row, colnm, subrow) => scrollTo
23585
+ * grid.selectCol(fromColNm, toColNm)
23586
+ * grid.selectRow(row) => scrollTo
23587
+ * grid.selectArea(row1, col1, row2, col2) => scrollTo
23588
+ *
23589
+ * grid.clearSelect()
23590
+ *
23591
+ * grid.selection.currentCell;
23592
+ * grid.selection.currentRow;
23593
+ * grid.selection.currentSubRow;
23594
+ * grid.selection.currentCol;
23595
+ * grid.selection.firstRow
23596
+ * grid.selection.firstCol
23597
+ * grid.selection.lastRow
23598
+ * grid.selection.lastCol
23599
+ */
23600
+
23601
+ selectArea = (oRow1, oCol1, oRow2, oCol2) => {
23656
23602
 
23603
+ const row1 = this.#getRowIndex(oRow1);
23604
+ const row2 = this.#getRowIndex(oRow2);
23605
+ const col1 = this.#getColIndex(oCol1);
23606
+ const col2 = this.#getColIndex(oCol2);
23607
+ const matrixRow1= parseInt(row1 * this.#owner.template.length);
23608
+ const matrixRow2= parseInt(row2 * this.#owner.template.length) + this.#owner.template.length - 1;
23657
23609
 
23658
- // 3. 불필요 TR 제거
23659
- /**
23660
- rowCount = Math.min(rowCount, this.#owner.data.count());// + ($(".ng-container-body tbody.fixed tr", this.#owner.body).length / this.#owner.template.length);
23661
- $(".ng-container tbody.bindable tr", this.#owner.body).not(".nodata").each((i,tr) => {
23662
- if (parseInt(tr.sectionRowIndex / this.#owner.template.length) >= rowCount) {
23663
- $(tr).remove();
23664
- }
23665
- }); */
23666
-
23667
- return;
23668
-
23669
- // 4. row resize
23670
- var trsBody = $(".ng-container-body tbody.bindable tr:not(.nodata)", this.#owner.body);
23610
+ this.#owner.row.at = row1;
23671
23611
 
23672
- if (trsBody.length > 0) {
23673
- var startRow = parseInt(this.#rawIndex * this.#owner.template.length);
23674
- }
23612
+ (row1 >= 0 && row2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, col1, matrixRow2, col2) : this.clearSelection();
23675
23613
  };
23676
23614
 
23677
- #removeGarbageTR = () => {
23615
+ selectRow = (oRow1, oRow2) => {
23616
+
23617
+ const row1 = this.#getRowIndex(oRow1);
23618
+ const row2 = ninegrid.isNull(oRow2) ? row1 : this.#getRowIndex(oRow2);
23619
+ const matrixRow1= parseInt(row1 * this.#owner.template.length);
23620
+ const matrixRow2= parseInt(row2 * this.#owner.template.length) + this.#owner.template.length - 1;
23678
23621
 
23679
- var rowCount = Math.min(this.#getRowCount(), this.#owner.data.count());// + this.pin.count();
23680
-
23622
+ this.#owner.row.at = row1;
23681
23623
 
23682
- $(".ng-container tbody.bindable tr:not(.nodata)", this.#owner.body).each((i,tr) => {
23683
- if (parseInt(tr.sectionRowIndex / this.#owner.template.length) >= rowCount) {
23684
- $(tr).remove();
23685
- }
23686
- });
23687
-
23688
- this.#owner.body.querySelectorAll(".ng-container tbody.bindable tr:not(.nodata)").forEach(tr => {
23689
- const row = this.#rawIndex + parseInt(tr.sectionRowIndex / this.#owner.template.length);
23690
- if (row >= this.#owner.data.count()) {
23691
- $(tr).remove();
23692
- }
23693
- });
23624
+ (row1 >= 0 && row2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, this.#owner.firstCol, matrixRow2, this.#owner.lastCol) : this.clearSelection();
23694
23625
  };
23695
23626
 
23696
- #resetTR3 = () => {
23697
-
23698
- // 1. TR 생성
23699
- this.#createTR();
23700
-
23701
- this.#removeGarbageTR();
23702
-
23703
- // 5. nodata resize
23627
+ selectCol = (oCol1, oCol2) => {
23628
+ const col1 = this.#getColIndex(oCol1);
23629
+ const col2 = ninegrid.isNull(oCol2) ? col1 : this.#getColIndex(oCol2);
23630
+ const matrixRow1= 0;
23631
+ const matrixRow2= parseInt(this.#owner.data.count() * this.#owner.template.length - 1);
23704
23632
 
23705
- var h = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getTrHeight();
23706
- $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23633
+ (col1 >= 0 && col2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, col1, matrixRow2, col2) : this.clearSelection();
23634
+ };
23635
+
23636
+ selectCell = (oRow, oCol, subrow) => {
23707
23637
 
23708
- /**
23709
- this.#owner.body.querySelector(".ng-container-body .ng-table").style.top = 0;
23710
- this.#owner.body.querySelector(".ng-container-left .ng-table").style.top = 0;
23711
- this.#owner.body.querySelector(".ng-container-right .ng-table").style.top = 0;
23712
- */
23638
+ const row = this.#getRowIndex(oRow);
23639
+ const col = this.#getColIndex(oCol);
23640
+ const matrixRow = parseInt(row * this.#owner.template.length) + ninegrid.nvl(subrow, 0);
23713
23641
 
23714
- //$('.ng-container .ng-table', this.#owner.body).offset({top:$('.ng-container .ng-table', this.#owner.body).offset().top});
23715
- //$('.ng-container .ng-table', this.#owner.body).offset({top:$('.ng-container .ng-table', this.#owner.body).offset().top});
23716
- //this.isLastPage = h > 0 ? true : false;
23717
-
23642
+ this.#owner.row.at = row;
23718
23643
 
23719
- if (this.#owner.closest("dialog") && ninegrid.j.querySelectorAll(this.#owner).hasClass("simple")) {
23720
- $("tr.nodata", this.#owner.body).height(0);
23721
-
23722
- var h = 47;//this.#getTrHeight();
23723
- $('.ng-container-body tbody.bindable tr', this.#owner.body).not('.nodata').each((i,tr) => {
23724
- h += $(tr).height();
23725
- });
23726
-
23727
- $(this.#owner.closest("dialog")).height(h);
23644
+ (row >= 0) ? this.#owner.selection.selectArea(matrixRow, col, matrixRow, col) : this.clearSelection();
23645
+ };
23646
+
23647
+ #getRowIndex = (oRow) => {
23648
+ if (typeof oRow === "number") {
23649
+ return oRow;
23650
+ }
23651
+ else if (typeof oRow === "function") {
23652
+ return this.#owner.data.findIndex(oRow);
23653
+ }
23654
+ else {
23655
+ throw `invalid ${oRow}`;
23728
23656
  }
23729
23657
  };
23658
+
23659
+ #getColIndex = (oCol) => {
23660
+ if (typeof oCol === "number") {
23661
+ return oCol;
23662
+ }
23663
+ else if (typeof oCol === "string" && this.#owner.fields.includes(oCol)) {
23664
+ return parseInt($(`[data-bind="${oCol}"]`, this.#owner.template)[0].dataset.col);
23665
+ }
23666
+ else {
23667
+ throw `invalid ${oCol}`;
23668
+ }
23669
+ };
23670
+
23671
+
23672
+
23673
+ moveTo = (at) => {
23674
+ this.#owner.scrollTo(at);
23675
+ }
23676
+ }
23730
23677
 
23731
- #resetTR3V2 = () => {
23732
-
23733
- // 1. TR 생성
23734
- //this.#owner.isLastPage = rows[rows.length-1] == this.#owner.data.count() - 1 ? true : false;//this.#isLastPage();
23735
-
23736
- //if (this.#owner.isLastPage) {
23737
- //if (this.#isLastPage) {
23738
-
23739
- //if (this.#owner.isLastPage) {
23740
- this.#createTR();
23741
- //}
23742
- /**
23743
- if ($(".ng-container-body tbody.bindable tr.nodata", this.#owner.body).height() > 0) {
23744
- this.#createTR();
23745
- } */
23746
-
23747
- this.#removeGarbageTR();
23678
+ /**
23679
+ * button.filterOptions = [{
23680
+ * colnm: "userId",
23681
+ * data: [],
23682
+ * }]
23683
+ * button.
23684
+ */
23685
+ class ngFiltering
23686
+ {
23687
+ #owner;
23688
+ #isFiltering;
23689
+
23690
+ constructor(owner) {
23691
+ this.#owner = owner;
23692
+ this.#isFiltering = false;
23748
23693
 
23749
-
23750
- // 5. nodata resize
23694
+ const filterPanel = document.createElement("ng-filter-panel"); // ✅ 필터 패널 생성
23695
+ filterPanel.style.display = "none"; // 숨김 처리
23696
+ this.#owner.shadowRoot.appendChild(filterPanel); // ✅ Shadow DOM 내부에 추가
23697
+ }
23698
+
23699
+ initialize = () => {
23751
23700
  /**
23752
- if (this.#owner.isFirstPage || this.#owner.isLastPage) {
23753
- var h = this.#owner.body.querySelector("ng-vscrollbar").bodyHeight - $(".ng-container-body .ng-table", this.#owner.body).height() + $('.ng-container-body .ng-table tr.nodata', this.#owner.body).height();
23754
- $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23755
- }*/
23756
- //$(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(700);
23757
-
23758
- if (this.#owner.isLastPage) ;
23759
-
23760
-
23761
- //this.isLastPage = h > 0 ? true : false;
23762
-
23763
-
23764
- //this.nodata = h > 0 ? true : false;
23765
-
23766
- // 6. 마지막 TR tabindex
23701
+ * 1. grid attr == filter : on();
23702
+ */
23703
+ this.#isFiltering = false;
23704
+ this.#owner.data.clearFilter();
23767
23705
  };
23768
23706
 
23769
- #refreshData = () => {
23707
+ isFiltering = () => {
23708
+ return this.#isFiltering;
23709
+ };
23710
+
23711
+ on = (v) => {
23770
23712
 
23771
- for (const v of this.#timers) {
23772
- clearTimeout(v);
23773
- }
23774
- this.#timers = [];
23713
+ this.#isFiltering = true;
23714
+ const lastRowIndex = this.#getLastRowIndex();
23775
23715
 
23776
- this.#timers.push(setTimeout(() => {
23777
-
23778
- this.#ing = false;
23779
-
23780
- // 4. row resize
23781
- var trsBody = $(".ng-container-body tbody.bindable tr", this.#owner.body).not(".nodata");
23782
- var trsLeft = $(".ng-container-left tbody.bindable tr", this.#owner.body).not(".nodata");
23783
- var trsRight= $(".ng-container-right tbody.bindable tr", this.#owner.body).not(".nodata");
23784
-
23785
- if (trsBody.length > 0) {
23786
- var startRow = parseInt(trsBody[0].dataset.row * this.#owner.template.length);
23787
-
23788
- trsBody.each((i, tr) => {
23789
- var h = this.#owner.matrix.getHeight(startRow + i);// || 32;
23716
+ this.#owner.body.querySelectorAll(".ng-table thead th, .ng-table thead td").forEach((td) => {
23717
+ const rowIndex = td.closest("tr")?.sectionRowIndex;
23718
+ if (rowIndex !== undefined && rowIndex + td.rowSpan - 1 === lastRowIndex) {
23719
+ const options = Array.from(this.#owner.activeTmpl.querySelectorAll(`[data-col="${td.dataset.col}"]`))
23720
+ .map(el => el.dataset.bind ? { colnm: el.dataset.bind, data: [] } : null)
23721
+ .filter(Boolean); // `null` 제거
23790
23722
 
23791
- $("canvas.chart", tr).css({ "height" : h-4 });
23792
- $("canvas.chart", trsLeft.eq(i)).css({ "height" : h-4 });
23793
- $("canvas.chart", trsRight.eq(i)).css({ "height" : h-4 });
23723
+ if (options.length > 0) {
23724
+ td.querySelector("ng-filter-button")?.remove();
23794
23725
 
23795
- $(tr).css({ "height" : h }).height(h);
23796
- trsLeft.eq(i).css({ "height" : h }).height(h);
23797
- trsRight.eq(i).css({ "height" : h }).height(h);
23798
- });
23726
+ const filterButton = document.createElement("ng-filter-button");
23727
+ td.appendChild(filterButton);
23728
+ filterButton.filterOptions = options;
23729
+ }
23799
23730
  }
23800
-
23801
- this.#owner.cell.refresh();
23802
-
23803
- //row resize
23804
- $('ng-row-indicator', this.#owner.body).each((i,el) => {
23805
- el.refresh();
23806
- });
23807
-
23808
- //5. nodata resize
23809
- var h = this.#height.body - this.#height.thead - this.#height.tfoot - this.#getTrHeight();
23810
- $(".ng-container tbody.bindable tr.nodata", this.#owner.body).height(h);
23811
-
23812
- //this.isLastPage = h > 0 ? true : false;
23731
+ });
23732
+ };
23733
+
23734
+ off = () => {
23735
+ this.#isFiltering = false;
23813
23736
 
23814
-
23815
- this.#owner.refreshBindRows();
23737
+ this.#owner.body.querySelectorAll(".ng-table ng-filter-button").forEach((el) => {
23738
+ el.remove(); // ✅ 요소 삭제
23739
+ });
23740
+ };
23816
23741
 
23817
- this.#owner.refreshData();
23818
-
23819
- this.#owner.paging.totalCount = this.#owner.data.countNF();
23820
-
23821
- this.#owner.body.querySelector("ng-vscrollbar").refresh();
23742
+ /**
23743
+ clear = () => {
23744
+ console.log(this.#owner.dataManager.rawRecords);
23745
+ this.#owner.dataManager.rawRecords.map(item => { item.__ng.filtered = false; });
23822
23746
 
23823
- ninegrid.j.querySelectorAll(".ng-table tbody tr", this.#owner.body).removeClass("hover");
23824
- ninegrid.j.querySelectorAll(this.#owner).removeClass("loading");
23825
- ninegrid.j.querySelectorAll("ng-filter-panel").removeClass("loading");
23826
-
23827
- /**
23828
- this.#owner.body.querySelector(".ng-container-left").style.position = "sticky";
23829
- this.#owner.body.querySelector(".ng-container-right").style.position = "sticky";
23830
- this.#owner.body.querySelector(".ng-container-body").style.position = "sticky";
23831
- this.#owner.body.querySelector(".ng-container-left").style.top = "0px";
23832
- this.#owner.body.querySelector(".ng-container-right").style.top = "0px";
23833
- this.#owner.body.querySelector(".ng-container-body").style.top = "0px";
23834
-
23835
- setTimeout(() => {
23836
- this.#owner.body.querySelector(".ng-container-left").style.position = "relative";
23837
- this.#owner.body.querySelector(".ng-container-right").style.position = "relative";
23838
- this.#owner.body.querySelector(".ng-container-body").style.position = "relative";
23839
- this.#owner.body.querySelector(".ng-container-left").style.top = "0px";
23840
- this.#owner.body.querySelector(".ng-container-right").style.top = "0px";
23841
- this.#owner.body.querySelector(".ng-container-body").style.top = "0px";
23842
- }, 1000); */
23843
23747
 
23844
- }, 300));
23845
-
23846
- //if (!this.timers) this.timers = [];
23847
- this.#timers.push(setTimeout(() => {
23848
- this.#owner.refreshDataV2();
23849
- }));
23748
+ this.#records = this.#owner.dataManager.rawRecords.filter(item => { return !item.__ng.deleted && !item.__ng.filtered; } );
23749
+ return this.#records;
23850
23750
  }
23851
23751
 
23852
- #r = () => {
23752
+ refresh = () => {
23753
+ this.#records = this.#owner.dataManager.rawRecords.filter(item => { return !item.__ng.deleted && !item.__ng.filtered; } );
23754
+ //this.#records.forEach((o,i) => { o.__ng.rowidx = i; });
23755
+ //this.getValidData().forEach((o,i) => { o.__ng._[ninegrid.ROW.ORDER] = i + 1; });
23756
+ this.#resetOrder();
23757
+ return this.#records;
23758
+ } */
23759
+
23760
+
23761
+ /**
23762
+ * oFilter : {
23763
+ * col1 : [],
23764
+ * col2 : []
23765
+ * }
23766
+ */
23767
+ set = (oFilter) => {
23853
23768
 
23854
- var rows = [];
23855
- this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)").forEach(tr => {
23856
- rows.push(this.#rawIndex + parseInt(tr.sectionRowIndex / this.#owner.template.length));
23857
- });
23769
+ this.on();
23770
+
23771
+ // JSON 변환 (배열 객체)
23772
+ let jsonFilter = Array.isArray(oFilter)
23773
+ ? Object.fromEntries(Object.keys(oFilter[0]).map(key => [key, [...new Set(oFilter.map(item => item[key]))]]))
23774
+ : oFilter;
23858
23775
 
23859
- rows = [...new Set(rows)];
23776
+ this.#owner.data.clearFilter();
23860
23777
 
23861
- this.#owner.body.querySelectorAll(".ng-container tbody.bindable tr:not(.nodata)").forEach(tr => {
23862
- const index = parseInt(tr.sectionRowIndex / this.#owner.template.length);
23778
+ Object.entries(jsonFilter).forEach(([key, arr]) => {
23779
+ const idx = this.#owner.fields.indexOf(key);
23863
23780
 
23864
- tr.dataset.row = rows[index];
23865
- tr.dataset.matrixRow = parseInt(rows[index] * this.#owner.template.length) + parseInt(tr.sectionRowIndex % this.#owner.template.length);
23866
-
23867
- for (const cell of tr.cells) {
23868
- cell.dataset.row = tr.dataset.row;
23869
- cell.dataset.matrixRow = tr.dataset.matrixRow;
23781
+ // ✅ 숫자 판별 및 변환
23782
+ if (this.#owner.data.getValidData().some(o => typeof o.v[idx] === "number")) {
23783
+ arr = arr.map(Number);
23870
23784
  }
23785
+
23786
+ // ✅ 필터 적용
23787
+ this.#owner.data.getValidData()
23788
+ .filter(m => arr.nineBinarySearch(m.v[idx] || '') < 0)
23789
+ .forEach(m => { m.__ng.filtered = true; });
23790
+ });
23791
+
23792
+
23793
+ this.#owner.data.refreshFilter();
23794
+
23795
+ // ✅ 필터 버튼 초기화
23796
+ this.#owner.shadowRoot.querySelectorAll("ng-filter-button").forEach(el => {
23797
+ el.filterOptions.forEach(opt => { opt.data = []; });
23871
23798
  });
23872
23799
 
23800
+ Object.entries(jsonFilter).forEach(([key, arr]) => {
23801
+ this.#owner.shadowRoot.querySelectorAll("ng-filter-button").forEach(el => {
23873
23802
 
23874
- rows.forEach(row => {
23875
- const data = this.#owner.data.get(row);
23803
+ let options = el.filterOptions;
23876
23804
 
23877
- this.#owner.body.querySelectorAll(`.ng-container tbody.bindable tr[data-row="${row}"]`).forEach(tr => {
23878
- tr.data = data;
23805
+ options.forEach(opt => {
23806
+ if (opt.colnm === key) opt.data = arr;
23807
+ });
23808
+
23809
+ el.filterOptions = options;
23879
23810
  });
23880
23811
  });
23881
23812
 
23882
- this.#rawIndex = parseInt(this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:first-of-type").getAttribute("data-row"));
23883
-
23884
- this.#owner.dataManager.viewRecords.rawIndex = this.#rawIndex;
23885
-
23886
-
23887
- this.#refreshData();
23888
23813
 
23889
- //this.#owner.isFirstPage = this.#rawIndex == this.#owner.cell.firstRow() ? true : false;
23890
- this.#owner.isFirstPage = this.#rawIndex == 0 ? true : false;
23891
- //this.#owner.isLastPage = this.isLastPage;
23814
+ this.#owner.scrollTo_V1(0);
23892
23815
 
23816
+ this.#owner.paging.reset();
23817
+ };
23818
+
23819
+ #getLastRowIndex = (v = "thead") =>
23820
+ [...this.#owner.body.querySelectorAll(`.ng-table ${v}`)]
23821
+ .reduce((maxIndex, el) => Math.max(el.rows.length - 1, maxIndex), 0);
23893
23822
 
23894
- this.#owner.isLastPage = rows[rows.length-1] == this.#owner.data.count() - 1 ? true : false;//this.#isLastPage();
23895
- //this.#owner.lastHidden = (parseInt($(".ng-container-body", this.#owner.body).height() - ($(".ng-container-body tfoot", this.#owner.body).height() || 0) + 2) >= t + h) ? false : true;
23823
+ }
23896
23824
 
23897
- //this.#owner.body.querySelector("ng-vscrollbar").refresh();
23825
+ class ngFilterButton extends HTMLElement
23826
+ {
23827
+ #owner;
23828
+ #filterOptions;
23829
+
23830
+ constructor () {
23831
+ super();
23832
+ }
23833
+
23834
+ connectedCallback() {
23835
+ this.#owner = this.getRootNode().host;//this.closest("nine-grid");
23836
+
23837
+ this.removeEventListener("click", this.#onClick);
23838
+ this.addEventListener("click", this.#onClick);
23898
23839
  };
23899
23840
 
23900
- getVisibleFirstRow = () => {
23901
- var tr = this.#owner.body.querySelector(".ng-container-body tbody.bindable tr:first-child");
23902
- return parseInt(tr.dataset.row);
23841
+ get filterOptions() {
23842
+ return this.#filterOptions;
23903
23843
  };
23844
+ set filterOptions(v) {
23845
+ this.#filterOptions = v;
23846
+ this.classList.toggle('filtered', this.#filterOptions.some(item => item.data.length > 0));
23847
+ }
23904
23848
 
23905
- getVisibleLastRow = () => {
23906
- var trs = this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)");
23907
- return (trs.length > 0) ? parseInt(trs[trs.length - 1].dataset.row) : -1;
23849
+ #onClick = (e) => {
23850
+ e.preventDefault();
23851
+ e.stopPropagation();
23852
+
23853
+ const panel = this.#owner.shadowRoot.querySelector("ng-filter-panel");
23854
+ const col = this.closest("th,td")?.dataset.col; // ✅ `?.` 활용하여 안전한 접근
23855
+
23856
+ panel.col === col ? panel.close() : panel.open(this);
23857
+ };
23858
+ }
23859
+
23860
+ class ngFilterPanel extends HTMLElement
23861
+ {
23862
+ #owner;
23863
+ #button;
23864
+ #timer;
23865
+
23866
+ constructor () {
23867
+ super();
23868
+ this.attachShadow({ mode: 'open' });
23869
+ }
23870
+
23871
+ connectedCallback() {
23872
+
23873
+ this.#owner = this.getRootNode().host;
23874
+
23875
+ const cssPath = this.getRootNode().host.closest("nine-grid").getAttribute("css-path") || "";
23876
+
23877
+ this.shadowRoot.innerHTML = `
23878
+ <style>
23879
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ngFilterPanel.css";
23880
+ ${ninegrid.getCustomPath(this,"ngFilterPanel.css")}
23881
+ </style>
23882
+
23883
+ <div class="head">
23884
+ <input type="text">
23885
+ </div>
23886
+ <nine-grid class="simple filter" css-path="${cssPath}" select-type="row" auto-fit-col="true" col-indicator-type="collapse">
23887
+ <table style="display: none;">
23888
+ <colgroup>
23889
+ <col width="180" />
23890
+ <col width="30" />
23891
+ </colgroup>
23892
+ <tbody>
23893
+ <tr style="height: 24px;">
23894
+ <td data-bind="LVL" data-expr="data.DATA2">
23895
+ <ng-tree-item />
23896
+ </td>
23897
+ <td data-bind="CHK">
23898
+ <ng-checkbox update-row-state="false" visible="data.LVL!=1" selected-border-color="white" />
23899
+ </td>
23900
+ </tr>
23901
+ </tbody>
23902
+ </table>
23903
+ </nine-grid>
23904
+
23905
+ <div>
23906
+ <label><input type="checkbox" checked>Select All</label>
23907
+ <button id="btnOk">OK</button>
23908
+ <button id="btnCancel">Cancel</button>
23909
+ </div>
23910
+ `;
23911
+
23912
+ this.shadowRoot.querySelector("input[type=text]").addEventListener("input", this.#onInput);
23913
+ this.shadowRoot.querySelector("input[type=checkbox]").addEventListener("change", this.#onSelectAll);
23914
+ this.shadowRoot.querySelector("#btnOk").addEventListener("click", this.#onOk);
23915
+ this.shadowRoot.querySelector("#btnCancel").addEventListener("click", this.#onCancel);
23916
+ };
23917
+
23918
+ #onOk = (e) => {
23919
+
23920
+ const grd = this.shadowRoot.querySelector("nine-grid");
23921
+
23922
+ this.classList.add("loading");
23923
+
23924
+ setTimeout(() => {
23925
+ const [LVL_IDX, CHK_IDX, COLNM_IDX, DATA_IDX] = ["LVL", "CHK", "COLNM", "DATA"].map(k => grd.fields.indexOf(k));
23926
+ const checked = grd.data.getValidDataNF().filter(m => m.v[LVL_IDX] === 2 && m.v[CHK_IDX] === "Y");
23927
+ const filterOptions = checked.length > 0 ? [...new Set(checked.map(m => m.v[COLNM_IDX]))].map(v => ({
23928
+ colnm: v,
23929
+ data: [...new Set(grd.data.getValidData().filter(m => m.v[CHK_IDX] === "Y" && m.v[COLNM_IDX] === v).map(m => m.v[DATA_IDX] || ''))].sort()
23930
+ })) : [];
23931
+
23932
+
23933
+ this.#button.filterOptions = filterOptions;
23934
+
23935
+ const oParam = Object.fromEntries(
23936
+ [...this.#owner.body.querySelectorAll("ng-filter-button")]
23937
+ .flatMap(el => el.filterOptions)
23938
+ .filter(opt => opt.data.length > 0)
23939
+ .map(opt => [opt.colnm, opt.data])
23940
+ );
23941
+
23942
+ this.#owner.filtering.set(oParam);
23943
+
23944
+ this.classList.remove("loading");
23945
+ this.style.display = 'none';
23946
+ });
23908
23947
  };
23909
23948
 
23910
- getRowHeight = (rowIndex) => {
23911
- let h = 0;
23912
-
23913
- for (var i = 0; i < this.#owner.template.length; i++) {
23914
- h += this.#owner.matrix.getHeight(parseInt(rowIndex * this.#owner.template.length + i));
23915
- }
23916
-
23917
- return h;
23918
- };
23919
23949
 
23920
- getTranslateY = () => {
23921
- const tbody = this.#owner.body.querySelector(".ng-container-body tbody.bindable");
23922
- return (!tbody || !tbody.style || !tbody.style.transform) ? 0 : parseFloat(tbody.style.transform.replace("translateY(", "").replace(")", ""));
23923
- };
23924
23950
 
23925
- getTotalSpace = () => {
23926
-
23927
- return $(this.#owner.body.querySelector(".ng-container-body")).height()
23928
- - ($(this.#owner.body.querySelector(".ng-container-body thead")).height() || 0)
23929
- - ($(this.#owner.body.querySelector(".ng-container-body tfoot")).height() || 0)
23930
- - ($(this.#owner.body.querySelector(".ng-container-body tbody.fixed")).height() || 0);
23931
- //- this.getTranslateY();
23951
+ #onCancel = (e) => {
23952
+ this.style.display = 'none';
23953
+ };
23954
+
23955
+ #onSelectAll = (e) => {
23956
+ const grd = this.shadowRoot.querySelector("nine-grid");
23957
+ const idx = grd.fields.indexOf("CHK");
23958
+ const isChecked = e.target.checked; // ✅ jQuery 없이 `checked` 값 가져오기
23959
+
23960
+ grd.data.getValidData().forEach(m => {
23961
+ m.v[idx] = isChecked ? "Y" : "N"; // ✅ `Y` 또는 `N` 값 설정
23962
+ });
23963
+
23964
+ grd.refreshData();
23932
23965
  };
23933
-
23934
- fitPage = () => {
23935
- const lastRow = this.getVisibleLastRow();
23936
- const totalSpace = this.getTotalSpace();
23937
-
23938
- /**
23939
- * 마지막 페이지 정리
23940
- */
23941
- if (lastRow == this.#owner.data.count() - 1) {
23942
- var h = 0;
23943
- for (var i = lastRow; i >= 0; i--) {
23944
- h += this.getRowHeight(i);
23945
-
23946
- if (h > totalSpace) {
23947
- this.#owner.scrollTo(i + 1);
23948
- break;
23949
- }
23950
- }
23951
- }
23952
- }
23953
- /**
23954
- #isLastPage = () => {
23955
- const arr = this.#owner.body.querySelectorAll(".ng-container-body tbody.bindable tr:not(.nodata)");
23956
- if (arr.length <= 0) return null;
23957
23966
 
23958
- return arr[arr.length - 1].dataset.row == this.#owner.data.count() - 1 ? true : false;
23959
- }*/
23960
-
23961
23967
 
23962
- /**
23963
- * grid.selectCell(row, colnm, subrow) => scrollTo
23964
- * grid.selectCol(fromColNm, toColNm)
23965
- * grid.selectRow(row) => scrollTo
23966
- * grid.selectArea(row1, col1, row2, col2) => scrollTo
23967
- *
23968
- * grid.clearSelect()
23969
- *
23970
- * grid.selection.currentCell;
23971
- * grid.selection.currentRow;
23972
- * grid.selection.currentSubRow;
23973
- * grid.selection.currentCol;
23974
- * grid.selection.firstRow
23975
- * grid.selection.firstCol
23976
- * grid.selection.lastRow
23977
- * grid.selection.lastCol
23978
- */
23979
23968
 
23980
- selectArea = (oRow1, oCol1, oRow2, oCol2) => {
23981
-
23982
- const row1 = this.#getRowIndex(oRow1);
23983
- const row2 = this.#getRowIndex(oRow2);
23984
- const col1 = this.#getColIndex(oCol1);
23985
- const col2 = this.#getColIndex(oCol2);
23986
- const matrixRow1= parseInt(row1 * this.#owner.template.length);
23987
- const matrixRow2= parseInt(row2 * this.#owner.template.length) + this.#owner.template.length - 1;
23988
-
23989
- this.#owner.row.at = row1;
23969
+ #onInput = (e) => {
23970
+ const grd = this.shadowRoot.querySelector("nine-grid");
23990
23971
 
23991
- (row1 >= 0 && row2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, col1, matrixRow2, col2) : this.clearSelection();
23992
- };
23993
-
23994
- selectRow = (oRow1, oRow2) => {
23995
-
23996
- const row1 = this.#getRowIndex(oRow1);
23997
- const row2 = ninegrid.isNull(oRow2) ? row1 : this.#getRowIndex(oRow2);
23998
- const matrixRow1= parseInt(row1 * this.#owner.template.length);
23999
- const matrixRow2= parseInt(row2 * this.#owner.template.length) + this.#owner.template.length - 1;
23972
+ grd.classList.add("loading");
24000
23973
 
24001
- this.#owner.row.at = row1;
23974
+ const data = grd.dataManager.rawRecords;
23975
+ data.forEach(m => { m.__ng.filtered = false; });
24002
23976
 
24003
- (row1 >= 0 && row2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, this.#owner.firstCol, matrixRow2, this.#owner.lastCol) : this.clearSelection();
24004
- };
24005
-
24006
- selectCol = (oCol1, oCol2) => {
24007
- const col1 = this.#getColIndex(oCol1);
24008
- const col2 = ninegrid.isNull(oCol2) ? col1 : this.#getColIndex(oCol2);
24009
- const matrixRow1= 0;
24010
- const matrixRow2= parseInt(this.#owner.data.count() * this.#owner.template.length - 1);
23977
+ const v = e.target.value.toLowerCase(); // jQuery 없이 값 가져오기
23978
+ const [LVL_IDX, DATA_IDX] = ["LVL", "DATA"].map(field => grd.fields.indexOf(field));
24011
23979
 
24012
- (col1 >= 0 && col2 >= 0) ? this.#owner.selection.selectArea(matrixRow1, col1, matrixRow2, col2) : this.clearSelection();
24013
- };
24014
-
24015
- selectCell = (oRow, oCol, subrow) => {
24016
-
24017
- const row = this.#getRowIndex(oRow);
24018
- const col = this.#getColIndex(oCol);
24019
- const matrixRow = parseInt(row * this.#owner.template.length) + ninegrid.nvl(subrow, 0);
24020
-
24021
- this.#owner.row.at = row;
23980
+ data.forEach(m => {
23981
+ m.__ng.filtered = m.v[LVL_IDX] === 2 && !String(m.v[DATA_IDX] || "").toLowerCase().includes(v);
23982
+ });
23983
+
23984
+ grd.data.resetRecords();
24022
23985
 
24023
- (row >= 0) ? this.#owner.selection.selectArea(matrixRow, col, matrixRow, col) : this.clearSelection();
23986
+ clearTimeout(this.#timer);
23987
+ this.#timer = setTimeout(() => {
23988
+ grd.dataManager.viewRecords.reset();
23989
+ grd.dataManager.viewRecords.touch();
23990
+ }, 200);
24024
23991
  };
24025
23992
 
24026
- #getRowIndex = (oRow) => {
24027
- if (typeof oRow === "number") {
24028
- return oRow;
24029
- }
24030
- else if (typeof oRow === "function") {
24031
- return this.#owner.data.findIndex(oRow);
24032
- }
24033
- else {
24034
- throw `invalid ${oRow}`;
24035
- }
23993
+ close = () => {
23994
+ this.col = null;
23995
+ this.style.display = 'none';
24036
23996
  };
24037
-
24038
- #getColIndex = (oCol) => {
24039
- if (typeof oCol === "number") {
24040
- return oCol;
24041
- }
24042
- else if (typeof oCol === "string" && this.#owner.fields.includes(oCol)) {
24043
- return parseInt($(`[data-bind="${oCol}"]`, this.#owner.template)[0].dataset.col);
24044
- }
24045
- else {
24046
- throw `invalid ${oCol}`;
24047
- }
23997
+
23998
+ open = (filterButton) => {
23999
+
24000
+ /** 위치 */
24001
+ const cell = filterButton.closest("th,td");
24002
+
24003
+ const { left: btnLeft } = filterButton.getBoundingClientRect();
24004
+ const { left: ownerLeft, width: ownerWidth, top: ownerTop } = this.#owner.getBoundingClientRect();
24005
+ const { top: cellTop, height: cellHeight } = cell.getBoundingClientRect();
24006
+ const { width: targetWidth } = this.getBoundingClientRect();
24007
+
24008
+ let l = Math.max(0, btnLeft - ownerLeft);
24009
+ l = Math.min(l, ownerWidth - targetWidth - 5);
24010
+
24011
+ const t = cellTop + cellHeight - ownerTop;
24012
+
24013
+ Object.assign(this.style, { left: `${l}px`, top: `${t}px`, display: "flex" });
24014
+ this.classList.add("loading");
24015
+
24016
+ this.shadowRoot.querySelector("input[type=text]").value = "";
24017
+
24018
+ setTimeout(() => {
24019
+ const data = this.#owner.data.getValidDataNF();
24020
+ const col = cell.dataset.col;
24021
+ this.col = col;
24022
+ this.#button = filterButton;
24023
+
24024
+ const ds = filterButton.filterOptions.map((opt, i) => {
24025
+ const groupLabel = `<span class="group">${cell.textContent}${filterButton.filterOptions.length > 1 ? ` #${i + 1} (${opt.colnm})` : ""}</span>`;
24026
+
24027
+ const cellEl = this.#owner.activeTmpl.querySelector(`[data-col="${col}"][data-bind="${opt.colnm}"]`);
24028
+ const expr = cellEl?.getAttribute("data-expr");
24029
+ const exprFunc = expr ? this.#owner.exprFunction(expr) : null;
24030
+
24031
+ const data2 = data.map(rowData => {
24032
+ const idx = this.#owner.fields.indexOf(opt.colnm);
24033
+ return expr ? { v: rowData.v[idx], v2: exprFunc(this.#owner.data.conv(rowData), rowData.__ng.rowidx, this.#owner.data) } : { v: rowData.v[idx], v2: rowData.v[idx] };
24034
+ });
24035
+
24036
+ return [
24037
+ { LVL: 1, CHK: "N", DATA2: groupLabel },
24038
+ ...[...new Set(data2.map(JSON.stringify))]
24039
+ .map(JSON.parse)
24040
+ .sort((a, b) => (a.v2 || "") > (b.v2 || "") ? 1 : (a.v2 || "") < (b.v2 || "") ? -1 : 0)
24041
+ .map(o => ({
24042
+ LVL: 2,
24043
+ DATA: o.v,
24044
+ DATA2: o.v2 || ` <span class="empty">(empty)</span> ${o.v}`,
24045
+ COLNM: opt.colnm,
24046
+ CHK: opt.data.length === 0 || opt.data.nineBinarySearch(o.v || "") >= 0 ? "Y" : "N",
24047
+ }))
24048
+ ];
24049
+ }).flat();
24050
+
24051
+ const grd = this.shadowRoot.querySelector("nine-grid");
24052
+ grd.fields.add(["DATA","DATA2","COLNM"]);
24053
+ grd.data.set(ds);
24054
+
24055
+ // ✅ 데이터 필터링 및 체크 상태 결정
24056
+ const checkbox = this.shadowRoot.querySelector("input[type=checkbox]");
24057
+ checkbox.checked = grd.data.getValidData().every(item => !(item.LVL === 2 && item.CHK !== "Y"));
24058
+
24059
+ this.shadowRoot.querySelector("input").focus();
24060
+ this.classList.remove("loading");
24061
+ });
24048
24062
  };
24049
-
24050
-
24051
-
24052
- moveTo = (at) => {
24053
- this.#owner.scrollTo(at);
24054
- }
24055
24063
  }
24056
24064
 
24065
+
24066
+ customElements.define("ng-filter-button", ngFilterButton);
24067
+ customElements.define("ng-filter-panel", ngFilterPanel);
24068
+
24057
24069
  class ninegridContainer extends HTMLElement
24058
24070
  {
24059
24071
  #colResizer;
@@ -27330,18 +27342,7 @@ class aiMessage extends HTMLElement
27330
27342
  set data(v) {
27331
27343
  console.log(v); }
27332
27344
 
27333
- #isEllipsisActive = () => {
27334
-
27335
- const element = this.shadowRoot.querySelector(".message"); const clone = element.cloneNode(true);
27336
- clone.style.width = "auto";
27337
- clone.style.visibility = "hidden";
27338
- document.body.appendChild(clone);
27339
-
27340
- const isTruncated = clone.scrollWidth > element.clientWidth;
27341
- document.body.removeChild(clone);
27342
-
27343
- return isTruncated;
27344
- }
27345
+
27345
27346
 
27346
27347
  #init = () => {
27347
27348
 
@@ -27379,29 +27380,7 @@ class aiMessage extends HTMLElement
27379
27380
  alert(this.#message);
27380
27381
  });
27381
27382
 
27382
-
27383
-
27384
- setTimeout(() => {
27385
- //const elMessage = this.shadowRoot.querySelector(".message");
27386
- const elMore = this.shadowRoot.querySelector(".more");
27387
-
27388
- console.log(this.#isEllipsisActive());
27389
-
27390
- //elMore.style.display = "flex";
27391
- //elMore.style.position = "absolute";
27392
-
27393
- //console.log(elMessage.scrollWidth, elMessage.clientWidth);
27394
-
27395
- //elMore.style.display = (elMessage.scrollWidth > elMessage.clientWidth) ? "flex" : "none";
27396
- if (this.#isEllipsisActive()) {
27397
- elMore.style.display = "flex";
27398
- elMore.style.position = "absolute";
27399
- }
27400
- else {
27401
- elMore.style.display = "none";
27402
- }
27403
- }, 100);
27404
-
27383
+ this.shadowRoot.querySelector(".more").style.display = (ninegrid.isEllipsis(this.shadowRoot.querySelector(".message"))) ? "flex" : "none";
27405
27384
  };
27406
27385
  }
27407
27386