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