exsdk-ui5 0.1.0 → 0.1.2
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.
- package/package.json +3 -1
- package/src/controls/ExButton.js +210 -0
- package/src/controls/ExTable.js +209 -0
- package/src/controls/Exfileuploader.js +303 -0
- package/src/controls/Exform.js +367 -0
- package/src/controls/Exkanban.js +507 -0
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exsdk-ui5",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "Modern UI component SDK for SAP Fiori & UI5 — beautiful, fast, framework-independent",
|
|
5
6
|
"author": "ExSDK",
|
|
6
7
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"dist",
|
|
21
|
+
"src/controls",
|
|
20
22
|
"ui5-wrapper",
|
|
21
23
|
"README.md",
|
|
22
24
|
"LICENSE.md"
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
sap.ui.define([
|
|
2
|
+
"sap/ui/core/Control"
|
|
3
|
+
], function (Control) {
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
return Control.extend("project1.control.ExButton", {
|
|
7
|
+
|
|
8
|
+
metadata: {
|
|
9
|
+
properties: {
|
|
10
|
+
text: { type: "string", defaultValue: "Button" },
|
|
11
|
+
// primary | secondary | ghost | danger | pill | gradient | glass | text-btn
|
|
12
|
+
type: { type: "string", defaultValue: "primary" },
|
|
13
|
+
// sm | md | lg
|
|
14
|
+
size: { type: "string", defaultValue: "md" },
|
|
15
|
+
icon: { type: "string", defaultValue: "" },
|
|
16
|
+
iconPos: { type: "string", defaultValue: "left" }, // left | right
|
|
17
|
+
iconOnly: { type: "boolean", defaultValue: false },
|
|
18
|
+
loading: { type: "boolean", defaultValue: false },
|
|
19
|
+
enabled: { type: "boolean", defaultValue: true },
|
|
20
|
+
// success | error | idle
|
|
21
|
+
state: { type: "string", defaultValue: "idle" },
|
|
22
|
+
width: { type: "string", defaultValue: "" } // "full" veya px değeri
|
|
23
|
+
},
|
|
24
|
+
events: {
|
|
25
|
+
press: {}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
renderer: function (oRM, oControl) {
|
|
30
|
+
oRM.openStart("div", oControl);
|
|
31
|
+
oRM.class("ex-btn-wrapper");
|
|
32
|
+
oRM.openEnd();
|
|
33
|
+
oRM.close("div");
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
onAfterRendering: function () {
|
|
37
|
+
this._render();
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// =============================
|
|
41
|
+
// RENDER
|
|
42
|
+
// =============================
|
|
43
|
+
_render: function () {
|
|
44
|
+
const oWrapper = this.getDomRef();
|
|
45
|
+
if (!oWrapper) return;
|
|
46
|
+
|
|
47
|
+
const type = this.getType();
|
|
48
|
+
const size = this.getSize();
|
|
49
|
+
const text = this.getText();
|
|
50
|
+
const icon = this.getIcon();
|
|
51
|
+
const iconPos = this.getIconPos();
|
|
52
|
+
const iconOnly = this.getIconOnly();
|
|
53
|
+
const loading = this.getLoading();
|
|
54
|
+
const enabled = this.getEnabled();
|
|
55
|
+
const state = this.getState();
|
|
56
|
+
const width = this.getWidth();
|
|
57
|
+
|
|
58
|
+
const isDisabled = !enabled || loading;
|
|
59
|
+
|
|
60
|
+
// State'e göre icon & metin
|
|
61
|
+
let displayText = text;
|
|
62
|
+
let displayIcon = icon;
|
|
63
|
+
|
|
64
|
+
if (state === "success") {
|
|
65
|
+
displayText = "Başarılı";
|
|
66
|
+
displayIcon = "✓";
|
|
67
|
+
} else if (state === "error") {
|
|
68
|
+
displayText = "Hata";
|
|
69
|
+
displayIcon = "✕";
|
|
70
|
+
} else if (loading) {
|
|
71
|
+
displayText = "Yükleniyor...";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const spinnerHTML = `
|
|
75
|
+
<span class="ex-btn-icon ex-btn-spinner-icon">
|
|
76
|
+
<svg viewBox="0 0 20 20" fill="none">
|
|
77
|
+
<circle cx="10" cy="10" r="7"
|
|
78
|
+
stroke="currentColor"
|
|
79
|
+
stroke-width="2.5"
|
|
80
|
+
stroke-linecap="round"
|
|
81
|
+
stroke-dasharray="38"
|
|
82
|
+
stroke-dashoffset="28"/>
|
|
83
|
+
</svg>
|
|
84
|
+
</span>`;
|
|
85
|
+
|
|
86
|
+
const iconHTML = displayIcon
|
|
87
|
+
? `<span class="ex-btn-icon">${displayIcon}</span>`
|
|
88
|
+
: "";
|
|
89
|
+
|
|
90
|
+
const textHTML = (!iconOnly || loading || state !== "idle")
|
|
91
|
+
? `<span class="ex-btn-text">${displayText}</span>`
|
|
92
|
+
: "";
|
|
93
|
+
|
|
94
|
+
const leftContent = (iconPos === "left" && !loading) ? iconHTML : "";
|
|
95
|
+
const rightContent = (iconPos === "right" && !loading) ? iconHTML : "";
|
|
96
|
+
const loaderHTML = loading ? spinnerHTML : "";
|
|
97
|
+
|
|
98
|
+
const widthStyle = width === "full"
|
|
99
|
+
? "width:100%;"
|
|
100
|
+
: width ? `width:${width};` : "";
|
|
101
|
+
|
|
102
|
+
const stateClass = state !== "idle" ? `ex-btn--${state}` : "";
|
|
103
|
+
|
|
104
|
+
oWrapper.innerHTML = `
|
|
105
|
+
<button
|
|
106
|
+
class="ex-btn ex-btn--${type} ex-btn--${size} ${stateClass} ${iconOnly && state === "idle" && !loading ? "ex-btn--icon-only" : ""} ${loading ? "ex-btn--loading" : ""}"
|
|
107
|
+
style="${widthStyle}"
|
|
108
|
+
${isDisabled ? "disabled" : ""}
|
|
109
|
+
aria-busy="${loading}"
|
|
110
|
+
aria-label="${text}">
|
|
111
|
+
<span class="ex-btn-ripple-container"></span>
|
|
112
|
+
${loaderHTML}
|
|
113
|
+
${leftContent}
|
|
114
|
+
${textHTML}
|
|
115
|
+
${rightContent}
|
|
116
|
+
</button>
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
this._attachEvents(oWrapper);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// =============================
|
|
123
|
+
// EVENTS
|
|
124
|
+
// =============================
|
|
125
|
+
_attachEvents: function (oWrapper) {
|
|
126
|
+
const btn = oWrapper.querySelector("button");
|
|
127
|
+
if (!btn) return;
|
|
128
|
+
|
|
129
|
+
btn.addEventListener("click", (e) => {
|
|
130
|
+
if (btn.disabled) return;
|
|
131
|
+
this._ripple(btn, e);
|
|
132
|
+
this.firePress();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
btn.addEventListener("keydown", (e) => {
|
|
136
|
+
if ((e.key === "Enter" || e.key === " ") && !btn.disabled) {
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
this._ripple(btn, null);
|
|
139
|
+
this.firePress();
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
// =============================
|
|
145
|
+
// RIPPLE
|
|
146
|
+
// =============================
|
|
147
|
+
_ripple: function (btn, e) {
|
|
148
|
+
const container = btn.querySelector(".ex-btn-ripple-container");
|
|
149
|
+
if (!container) return;
|
|
150
|
+
|
|
151
|
+
const circle = document.createElement("span");
|
|
152
|
+
circle.className = "ex-btn-ripple";
|
|
153
|
+
|
|
154
|
+
const rect = btn.getBoundingClientRect();
|
|
155
|
+
const size = Math.max(rect.width, rect.height) * 2;
|
|
156
|
+
const x = e ? e.clientX - rect.left - size / 2 : rect.width / 2 - size / 2;
|
|
157
|
+
const y = e ? e.clientY - rect.top - size / 2 : rect.height / 2 - size / 2;
|
|
158
|
+
|
|
159
|
+
circle.style.cssText = `width:${size}px; height:${size}px; left:${x}px; top:${y}px;`;
|
|
160
|
+
container.appendChild(circle);
|
|
161
|
+
circle.addEventListener("animationend", () => circle.remove());
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// =============================
|
|
165
|
+
// PUBLIC SETTERS
|
|
166
|
+
// =============================
|
|
167
|
+
setText: function (s) {
|
|
168
|
+
this.setProperty("text", s, true);
|
|
169
|
+
const el = this._getEl(".ex-btn-text");
|
|
170
|
+
if (el) el.textContent = s;
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
setLoading: function (b) {
|
|
174
|
+
this.setProperty("loading", b, true);
|
|
175
|
+
this._render();
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
setEnabled: function (b) {
|
|
179
|
+
this.setProperty("enabled", b, true);
|
|
180
|
+
const btn = this._getEl("button");
|
|
181
|
+
if (btn) btn.disabled = !b || this.getLoading();
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
setType: function (s) {
|
|
185
|
+
this.setProperty("type", s, true);
|
|
186
|
+
this._render();
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
setState: function (s) {
|
|
190
|
+
this.setProperty("state", s, true);
|
|
191
|
+
this._render();
|
|
192
|
+
|
|
193
|
+
// success / error sonrası otomatik idle'a dön
|
|
194
|
+
if (s === "success" || s === "error") {
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
this.setProperty("state", "idle", true);
|
|
197
|
+
this._render();
|
|
198
|
+
}, 2000);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
// =============================
|
|
203
|
+
// UTILS
|
|
204
|
+
// =============================
|
|
205
|
+
_getEl: function (sel) {
|
|
206
|
+
const w = this.getDomRef();
|
|
207
|
+
return w ? w.querySelector(sel) : null;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
sap.ui.define([
|
|
2
|
+
"sap/ui/core/Control"
|
|
3
|
+
], function (Control) {
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
return Control.extend("project1.control.ExTable", {
|
|
7
|
+
|
|
8
|
+
metadata: {
|
|
9
|
+
properties: {
|
|
10
|
+
columns: { type: "object", defaultValue: [] },
|
|
11
|
+
rows: { type: "object", defaultValue: [] },
|
|
12
|
+
title: { type: "string", defaultValue: "Tablo" },
|
|
13
|
+
total: { type: "int", defaultValue: 0 }
|
|
14
|
+
},
|
|
15
|
+
events: {
|
|
16
|
+
loadMore: {}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
renderer: function (oRM, oControl) {
|
|
21
|
+
oRM.openStart("div", oControl);
|
|
22
|
+
oRM.class("ex-table-wrapper");
|
|
23
|
+
oRM.openEnd();
|
|
24
|
+
oRM.close("div");
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
onAfterRendering: function () {
|
|
28
|
+
const oWrapper = this.getDomRef();
|
|
29
|
+
oWrapper.innerHTML = this._buildBaseHTML();
|
|
30
|
+
|
|
31
|
+
this._body = oWrapper.querySelector(".ex-table-body");
|
|
32
|
+
this._tbody = oWrapper.querySelector(".ex-table-tbody");
|
|
33
|
+
this._thead = oWrapper.querySelector(".ex-table-thead");
|
|
34
|
+
this._colgroup = oWrapper.querySelector(".ex-table-colgroup");
|
|
35
|
+
this._totalEl = oWrapper.querySelector(".ex-table-total");
|
|
36
|
+
|
|
37
|
+
this._rowHeight = 44;
|
|
38
|
+
this._visibleCount = 20;
|
|
39
|
+
this._buffer = 5;
|
|
40
|
+
|
|
41
|
+
this._fullData = this.getRows() || [];
|
|
42
|
+
this._columns = this.getColumns();
|
|
43
|
+
this._sortState = {};
|
|
44
|
+
|
|
45
|
+
this._attachEvents();
|
|
46
|
+
this._renderColgroup();
|
|
47
|
+
this._renderHeader();
|
|
48
|
+
this._updateVisibleRows(0);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
_buildBaseHTML: function () {
|
|
52
|
+
return `
|
|
53
|
+
<div class="ex-table">
|
|
54
|
+
<div class="ex-table-header">
|
|
55
|
+
<span class="ex-table-title">${this.getTitle()}</span>
|
|
56
|
+
<span class="ex-table-total">(${this.getTotal()} Ürün)</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="ex-table-body">
|
|
59
|
+
<table class="ex-table-inner">
|
|
60
|
+
<colgroup class="ex-table-colgroup"></colgroup>
|
|
61
|
+
<thead class="ex-table-thead"></thead>
|
|
62
|
+
<tbody class="ex-table-tbody"></tbody>
|
|
63
|
+
</table>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
`;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// colgroup — her kolon için eşit genişlik tanımla
|
|
70
|
+
// th ve td artık aynı col'u paylaşır, hizalama garantili
|
|
71
|
+
_renderColgroup: function () {
|
|
72
|
+
const colWidth = Math.floor(100 / this._columns.length);
|
|
73
|
+
this._colgroup.innerHTML = this._columns.map(() =>
|
|
74
|
+
`<col style="width:${colWidth}%;">`
|
|
75
|
+
).join("");
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
_attachEvents: function () {
|
|
79
|
+
this._body.addEventListener("scroll", this._onScroll.bind(this));
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
_onScroll: function () {
|
|
83
|
+
const scrollTop = this._body.scrollTop;
|
|
84
|
+
const startIndex = Math.floor(scrollTop / this._rowHeight);
|
|
85
|
+
this._updateVisibleRows(startIndex);
|
|
86
|
+
|
|
87
|
+
if (scrollTop + this._body.clientHeight >= this._body.scrollHeight - 100) {
|
|
88
|
+
this.fireLoadMore();
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
_renderHeader: function () {
|
|
93
|
+
this._thead.innerHTML = `<tr>
|
|
94
|
+
${this._columns.map(col => `
|
|
95
|
+
<th data-key="${col.key}">
|
|
96
|
+
<div class="ex-th-inner">
|
|
97
|
+
<span class="ex-th-label">${col.label}</span>
|
|
98
|
+
<span class="ex-th-sort" data-key="${col.key}">↕</span>
|
|
99
|
+
<span class="resize-handle"></span>
|
|
100
|
+
</div>
|
|
101
|
+
</th>
|
|
102
|
+
`).join("")}
|
|
103
|
+
</tr>`;
|
|
104
|
+
|
|
105
|
+
this._attachHeaderEvents();
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
_attachHeaderEvents: function () {
|
|
109
|
+
this._thead.querySelectorAll("th").forEach(th => {
|
|
110
|
+
th.addEventListener("click", (e) => {
|
|
111
|
+
if (e.target.classList.contains("resize-handle")) return;
|
|
112
|
+
this._sort(th.dataset.key);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const handle = th.querySelector(".resize-handle");
|
|
116
|
+
let startX, startWidth, colIndex;
|
|
117
|
+
|
|
118
|
+
handle.addEventListener("mousedown", (e) => {
|
|
119
|
+
e.stopPropagation();
|
|
120
|
+
startX = e.pageX;
|
|
121
|
+
startWidth = th.offsetWidth;
|
|
122
|
+
colIndex = Array.from(th.parentNode.children).indexOf(th);
|
|
123
|
+
|
|
124
|
+
const onMove = (e) => {
|
|
125
|
+
const newWidth = Math.max(60, startWidth + (e.pageX - startX));
|
|
126
|
+
// colgroup'taki ilgili col'u güncelle — hem th hem td hizalanır
|
|
127
|
+
const cols = this._colgroup.querySelectorAll("col");
|
|
128
|
+
if (cols[colIndex]) {
|
|
129
|
+
cols[colIndex].style.width = newWidth + "px";
|
|
130
|
+
cols[colIndex].style.minWidth = newWidth + "px";
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const onUp = () => {
|
|
134
|
+
document.removeEventListener("mousemove", onMove);
|
|
135
|
+
document.removeEventListener("mouseup", onUp);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
document.addEventListener("mousemove", onMove);
|
|
139
|
+
document.addEventListener("mouseup", onUp);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
_sort: function (key) {
|
|
145
|
+
const next = (this._sortState[key] || "asc") === "asc" ? "desc" : "asc";
|
|
146
|
+
this._sortState = { [key]: next };
|
|
147
|
+
|
|
148
|
+
this._thead.querySelectorAll(".ex-th-sort").forEach(el => {
|
|
149
|
+
el.textContent = el.dataset.key === key
|
|
150
|
+
? (next === "asc" ? "↑" : "↓")
|
|
151
|
+
: "↕";
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
this._fullData.sort((a, b) => {
|
|
155
|
+
if (a[key] < b[key]) return next === "asc" ? -1 : 1;
|
|
156
|
+
if (a[key] > b[key]) return next === "asc" ? 1 : -1;
|
|
157
|
+
return 0;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
this._body.scrollTop = 0;
|
|
161
|
+
this._updateVisibleRows(0);
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
_updateVisibleRows: function (startIndex) {
|
|
165
|
+
const total = this._fullData.length;
|
|
166
|
+
const start = Math.max(0, startIndex - this._buffer);
|
|
167
|
+
const end = Math.min(total, startIndex + this._visibleCount + this._buffer);
|
|
168
|
+
|
|
169
|
+
const paddingTop = start * this._rowHeight;
|
|
170
|
+
const paddingBottom = Math.max(0, (total - end) * this._rowHeight);
|
|
171
|
+
|
|
172
|
+
this._renderRows(this._fullData.slice(start, end), paddingTop, paddingBottom);
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
_renderRows: function (rows, paddingTop, paddingBottom) {
|
|
176
|
+
const colSpan = this._columns.length;
|
|
177
|
+
|
|
178
|
+
const topRow = paddingTop > 0 ? `<tr style="height:${paddingTop}px;"> <td colspan="${colSpan}"></td></tr>` : "";
|
|
179
|
+
const bottomRow = paddingBottom > 0 ? `<tr style="height:${paddingBottom}px;"><td colspan="${colSpan}"></td></tr>` : "";
|
|
180
|
+
|
|
181
|
+
const dataRows = rows.length > 0
|
|
182
|
+
? rows.map(row => `
|
|
183
|
+
<tr class="ex-table-row">
|
|
184
|
+
${this._columns.map(col =>
|
|
185
|
+
`<td class="ex-td">${row[col.key] ?? ""}</td>`
|
|
186
|
+
).join("")}
|
|
187
|
+
</tr>`).join("")
|
|
188
|
+
: `<tr><td colspan="${colSpan}" class="ex-td-empty">Veri yok</td></tr>`;
|
|
189
|
+
|
|
190
|
+
this._tbody.innerHTML = topRow + dataRows + bottomRow;
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
setRows: function (aRows) {
|
|
194
|
+
this.setProperty("rows", aRows, true);
|
|
195
|
+
this._fullData = aRows;
|
|
196
|
+
if (this._body) {
|
|
197
|
+
this._body.scrollTop = 0;
|
|
198
|
+
this._updateVisibleRows(0);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
setTotal: function (iTotal) {
|
|
203
|
+
this.setProperty("total", iTotal, true);
|
|
204
|
+
if (this._totalEl) {
|
|
205
|
+
this._totalEl.textContent = "(" + iTotal + " Ürün)";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|