datavis-glide 4.0.2 → 4.1.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.
- package/dist/wcdatavis.css +84 -0
- package/dist/wcdatavis.min.js +1 -1
- package/package.json +2 -2
- package/src/grid.js +142 -1
- package/src/lang/en-US.js +7 -0
- package/src/lang/es-MX.js +7 -0
- package/src/lang/fr-FR.js +7 -0
- package/src/lang/id-ID.js +7 -0
- package/src/lang/nl-NL.js +7 -0
- package/src/lang/pt-BR.js +7 -0
- package/src/lang/ru-RU.js +7 -0
- package/src/lang/th-TH.js +7 -0
- package/src/lang/vi-VN.js +7 -0
- package/src/lang/zh-Hans-CN.js +7 -0
- package/src/renderers/grid/table/plain.js +272 -5
- package/src/renderers/grid/table.js +383 -31
- package/src/ui/popup_menu.js +43 -1
- package/src/ui/toast.js +64 -0
- package/src/util/clipboard.js +32 -0
- package/wcdatavis.css +54 -0
|
@@ -185,6 +185,7 @@ var GridTable = makeSubclass('GridTable', GridRenderer, function () {
|
|
|
185
185
|
self.super['GridRenderer'].ctor.apply(self, arguments);
|
|
186
186
|
|
|
187
187
|
self.selection = [];
|
|
188
|
+
self.cellSelection = null;
|
|
188
189
|
self.needsRedraw = false;
|
|
189
190
|
self.popupMenus = [];
|
|
190
191
|
self.csvLock = new Lock('GridTable/csv');
|
|
@@ -275,6 +276,7 @@ mixinEventHandling(GridTable, [
|
|
|
275
276
|
, 'renderBegin'
|
|
276
277
|
, 'renderEnd'
|
|
277
278
|
, 'selectionChange'
|
|
279
|
+
, 'cellSelectionChange'
|
|
278
280
|
]);
|
|
279
281
|
|
|
280
282
|
// #_validateFeatures {{{2
|
|
@@ -818,9 +820,13 @@ GridTable.prototype._addSortingToHeader = function (data, orientation, spec, con
|
|
|
818
820
|
*
|
|
819
821
|
* @param {string} [dir]
|
|
820
822
|
* What direction we're sorting by, ascending or descending.
|
|
823
|
+
*
|
|
824
|
+
* @param {number} [priority]
|
|
825
|
+
* When this column is part of a multi-column sort, its 1-based position in the sort chain. When
|
|
826
|
+
* given, a numbered badge is shown next to the direction arrow.
|
|
821
827
|
*/
|
|
822
828
|
|
|
823
|
-
var replaceSortIndicator = function (span, dir) {
|
|
829
|
+
var replaceSortIndicator = function (span, dir, priority) {
|
|
824
830
|
if (!(span instanceof Element)) {
|
|
825
831
|
throw new Error('Call Error: `span` must be an Element');
|
|
826
832
|
}
|
|
@@ -832,6 +838,9 @@ GridTable.prototype._addSortingToHeader = function (data, orientation, spec, con
|
|
|
832
838
|
throw new Error('Call Error: `dir` must be either "ASC" or "DESC"');
|
|
833
839
|
}
|
|
834
840
|
}
|
|
841
|
+
if (priority != null && !_.isNumber(priority)) {
|
|
842
|
+
throw new Error('Call Error: `priority` must be null or a number');
|
|
843
|
+
}
|
|
835
844
|
|
|
836
845
|
var th = container.closest('th');
|
|
837
846
|
|
|
@@ -846,15 +855,72 @@ GridTable.prototype._addSortingToHeader = function (data, orientation, spec, con
|
|
|
846
855
|
iconName = dir.toUpperCase() === 'ASC' ? 'arrow-up' : 'arrow-down';
|
|
847
856
|
}
|
|
848
857
|
|
|
858
|
+
// Clear any prior content (icon and/or priority badge) before rebuilding.
|
|
859
|
+
while (span.firstChild) {
|
|
860
|
+
span.removeChild(span.firstChild);
|
|
861
|
+
}
|
|
862
|
+
|
|
849
863
|
var newIcon = createLucideSvg(iconName);
|
|
850
864
|
if (newIcon) {
|
|
851
865
|
newIcon.classList.add('wcdv_icon');
|
|
852
866
|
newIcon.setAttribute('data-icon', iconName);
|
|
853
|
-
while (span.firstChild) {
|
|
854
|
-
span.removeChild(span.firstChild);
|
|
855
|
-
}
|
|
856
867
|
span.appendChild(newIcon);
|
|
857
868
|
}
|
|
869
|
+
|
|
870
|
+
// Show a numbered badge when this column is one of several in a multi-column sort.
|
|
871
|
+
if (dir != null && priority != null) {
|
|
872
|
+
var badge = document.createElement('span');
|
|
873
|
+
badge.className = 'wcdv_sort_priority_badge';
|
|
874
|
+
badge.textContent = String(priority);
|
|
875
|
+
badge.setAttribute('aria-label', trans('GRID.TABLE.SORT_MENU.PRIORITY_BADGE', priority));
|
|
876
|
+
span.appendChild(badge);
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Compare two sort specs to see if they refer to the same column or aggregate, ignoring sort
|
|
882
|
+
* direction. Used to avoid adding duplicate entries to a sort chain.
|
|
883
|
+
*
|
|
884
|
+
* @param {object} a
|
|
885
|
+
* @param {object} b
|
|
886
|
+
* @returns {boolean}
|
|
887
|
+
*/
|
|
888
|
+
|
|
889
|
+
var sortEntriesMatch = function (a, b) {
|
|
890
|
+
return a.field === b.field
|
|
891
|
+
&& a.groupFieldIndex === b.groupFieldIndex
|
|
892
|
+
&& a.pivotFieldIndex === b.pivotFieldIndex
|
|
893
|
+
&& a.aggType === b.aggType
|
|
894
|
+
&& a.aggNum === b.aggNum
|
|
895
|
+
;
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Determine whether a sort entry applies to the current output mode (plain, grouped, or
|
|
900
|
+
* pivotted). The view keeps a single sort chain per orientation, but each output mode produces
|
|
901
|
+
* its own kinds of sortable headers, so an entry created in one mode must not count towards the
|
|
902
|
+
* sort in another. This keeps each mode's sort independent.
|
|
903
|
+
*
|
|
904
|
+
* @param {object} entry
|
|
905
|
+
* A sort chain entry.
|
|
906
|
+
*
|
|
907
|
+
* @returns {boolean}
|
|
908
|
+
*/
|
|
909
|
+
|
|
910
|
+
var sortEntryAppliesTo = function (entry) {
|
|
911
|
+
if (entry.pivotFieldIndex != null || entry.aggType === 'pivot' || entry.colVal != null || entry.rowVal != null) {
|
|
912
|
+
return !!data.isPivot;
|
|
913
|
+
}
|
|
914
|
+
if (entry.aggType === 'group') {
|
|
915
|
+
return !!data.isGroup;
|
|
916
|
+
}
|
|
917
|
+
if (entry.groupFieldIndex != null) {
|
|
918
|
+
return !!(data.isGroup || data.isPivot);
|
|
919
|
+
}
|
|
920
|
+
if (entry.field != null) {
|
|
921
|
+
return !!data.isPlain;
|
|
922
|
+
}
|
|
923
|
+
return true;
|
|
858
924
|
};
|
|
859
925
|
|
|
860
926
|
/**
|
|
@@ -866,9 +932,13 @@ GridTable.prototype._addSortingToHeader = function (data, orientation, spec, con
|
|
|
866
932
|
* @param {number} [aggNum]
|
|
867
933
|
* If missing, no aggregate number is added to the sort spec. Used when sorting directly by the
|
|
868
934
|
* field (e.g. in plain output) or by the group field index (e.g. in group detail output).
|
|
935
|
+
*
|
|
936
|
+
* @param {boolean} [additive]
|
|
937
|
+
* When true, this column is appended to the existing sort chain (or its direction updated if it's
|
|
938
|
+
* already in the chain) rather than replacing it. Used by the per-row "add to sort" button.
|
|
869
939
|
*/
|
|
870
940
|
|
|
871
|
-
var setSort = function (dir, aggNum) {
|
|
941
|
+
var setSort = function (dir, aggNum, additive) {
|
|
872
942
|
if (!_.isString(dir)) {
|
|
873
943
|
throw new Error('Call Error: `dir` must be a string');
|
|
874
944
|
}
|
|
@@ -880,19 +950,64 @@ GridTable.prototype._addSortingToHeader = function (data, orientation, spec, con
|
|
|
880
950
|
throw new Error('Call Error: `aggNum` must be a number');
|
|
881
951
|
}
|
|
882
952
|
|
|
883
|
-
jQuery('button.wcdv_icon_button' + sortIcon_orientationClass + '.wcdv_sort_icon').each(function (i, elt) {
|
|
884
|
-
replaceSortIndicator(elt);
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
jQuery('button.wcdv_icon_button.' + sortIcon_class).each(function (i, elt) {
|
|
888
|
-
replaceSortIndicator(elt, dir);
|
|
889
|
-
});
|
|
890
|
-
|
|
891
953
|
spec.aggNum = aggNum;
|
|
892
954
|
spec.dir = dir;
|
|
893
955
|
|
|
894
|
-
|
|
895
|
-
|
|
956
|
+
// Deep-copy the view's current sort so we build a brand-new spec object. `getSort()` returns
|
|
957
|
+
// the view's live internal object; mutating it in place would defeat the view's change
|
|
958
|
+
// detection (it compares the new spec to its current one), which would silently skip saving
|
|
959
|
+
// the updated sort to the perspective.
|
|
960
|
+
var sortSpec = deepCopy(self.view.getSort()) || {};
|
|
961
|
+
|
|
962
|
+
// The view stores each orientation's sort as a chain (array) of specs. Older specs may be a
|
|
963
|
+
// bare object, so normalize to an array before working with it.
|
|
964
|
+
var chain = _.isArray(sortSpec[orientation])
|
|
965
|
+
? deepCopy(sortSpec[orientation])
|
|
966
|
+
: sortSpec[orientation] != null
|
|
967
|
+
? [deepCopy(sortSpec[orientation])]
|
|
968
|
+
: []
|
|
969
|
+
;
|
|
970
|
+
|
|
971
|
+
if (additive) {
|
|
972
|
+
|
|
973
|
+
// Add this column to the existing chain. If it's already present, just update its
|
|
974
|
+
// direction so we never create duplicate entries for the same column.
|
|
975
|
+
|
|
976
|
+
var matchIndex = -1;
|
|
977
|
+
for (var ci = 0; ci < chain.length; ci++) {
|
|
978
|
+
if (sortEntriesMatch(chain[ci], spec)) {
|
|
979
|
+
matchIndex = ci;
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (matchIndex < 0) {
|
|
984
|
+
chain.push(deepCopy(spec));
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
chain[matchIndex].dir = dir;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
|
|
992
|
+
// Replace this output mode's sort with just this column, but keep any sort entries that
|
|
993
|
+
// belong to the other output modes so each mode keeps its own independent sort.
|
|
994
|
+
|
|
995
|
+
var preserved = _.filter(chain, function (entry) {
|
|
996
|
+
return !sortEntryAppliesTo(entry);
|
|
997
|
+
});
|
|
998
|
+
chain = preserved.concat([deepCopy(spec)]);
|
|
999
|
+
|
|
1000
|
+
// Give immediate visual feedback before the view redraws.
|
|
1001
|
+
jQuery('button.wcdv_icon_button' + sortIcon_orientationClass + '.wcdv_sort_icon').each(function (i, elt) {
|
|
1002
|
+
replaceSortIndicator(elt);
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
jQuery('button.wcdv_icon_button.' + sortIcon_class).each(function (i, elt) {
|
|
1006
|
+
replaceSortIndicator(elt, dir);
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
sortSpec[orientation] = chain;
|
|
896
1011
|
self.view.setSort(sortSpec, self.makeProgress('Sort'));
|
|
897
1012
|
};
|
|
898
1013
|
|
|
@@ -936,9 +1051,17 @@ GridTable.prototype._addSortingToHeader = function (data, orientation, spec, con
|
|
|
936
1051
|
|
|
937
1052
|
sortIcon_menu.addItem(trans('GRID.TABLE.SORT_MENU.ASCENDING', name), 'arrow-up-narrow-wide', function () {
|
|
938
1053
|
setSort('asc');
|
|
1054
|
+
}, null, {
|
|
1055
|
+
iconName: 'plus',
|
|
1056
|
+
label: trans('GRID.TABLE.SORT_MENU.ADD_TO_SORT'),
|
|
1057
|
+
callback: function () { setSort('asc', null, true); }
|
|
939
1058
|
});
|
|
940
1059
|
sortIcon_menu.addItem(trans('GRID.TABLE.SORT_MENU.DESCENDING', name), 'arrow-down-wide-narrow', function () {
|
|
941
1060
|
setSort('desc');
|
|
1061
|
+
}, null, {
|
|
1062
|
+
iconName: 'plus',
|
|
1063
|
+
label: trans('GRID.TABLE.SORT_MENU.ADD_TO_SORT'),
|
|
1064
|
+
callback: function () { setSort('desc', null, true); }
|
|
942
1065
|
});
|
|
943
1066
|
sortIcon_menu.addSeparator();
|
|
944
1067
|
}
|
|
@@ -954,9 +1077,21 @@ GridTable.prototype._addSortingToHeader = function (data, orientation, spec, con
|
|
|
954
1077
|
//var aggType = aggInfo.instance.getType();
|
|
955
1078
|
sortIcon_menu.addItem(trans('GRID.TABLE.SORT_MENU.ASCENDING', aggInfo.instance.getFullName()), 'arrow-up-narrow-wide', (function (n) {
|
|
956
1079
|
return function () { setSort('asc', n); };
|
|
1080
|
+
})(aggNum), null, (function (n) {
|
|
1081
|
+
return {
|
|
1082
|
+
iconName: 'plus',
|
|
1083
|
+
label: trans('GRID.TABLE.SORT_MENU.ADD_TO_SORT'),
|
|
1084
|
+
callback: function () { setSort('asc', n, true); }
|
|
1085
|
+
};
|
|
957
1086
|
})(aggNum));
|
|
958
1087
|
sortIcon_menu.addItem(trans('GRID.TABLE.SORT_MENU.DESCENDING', aggInfo.instance.getFullName()), 'arrow-down-wide-narrow', (function (n) {
|
|
959
1088
|
return function () { setSort('desc', n); };
|
|
1089
|
+
})(aggNum), null, (function (n) {
|
|
1090
|
+
return {
|
|
1091
|
+
iconName: 'plus',
|
|
1092
|
+
label: trans('GRID.TABLE.SORT_MENU.ADD_TO_SORT'),
|
|
1093
|
+
callback: function () { setSort('desc', n, true); }
|
|
1094
|
+
};
|
|
960
1095
|
})(aggNum));
|
|
961
1096
|
sortIcon_menu.addSeparator();
|
|
962
1097
|
});
|
|
@@ -986,31 +1121,52 @@ GridTable.prototype._addSortingToHeader = function (data, orientation, spec, con
|
|
|
986
1121
|
var sortSpec_copy = deepCopy(self.view.getSort());
|
|
987
1122
|
var spec_copy = deepCopy(spec);
|
|
988
1123
|
|
|
989
|
-
if (sortSpec_copy[orientation]) {
|
|
990
|
-
|
|
1124
|
+
if (sortSpec_copy && sortSpec_copy[orientation]) {
|
|
1125
|
+
|
|
1126
|
+
// The view stores each orientation's sort as a chain (array) of specs. Normalize to an array
|
|
1127
|
+
// so we can light up every column that participates in the sort, each with its 1-based
|
|
1128
|
+
// priority. Crucially, for grid tables that redraw when the view is updated, this is the only
|
|
1129
|
+
// way you're ever going to see what the sort is.
|
|
1130
|
+
|
|
1131
|
+
var chain = _.isArray(sortSpec_copy[orientation])
|
|
1132
|
+
? sortSpec_copy[orientation]
|
|
1133
|
+
: [sortSpec_copy[orientation]]
|
|
1134
|
+
;
|
|
991
1135
|
|
|
992
|
-
//
|
|
993
|
-
// the
|
|
994
|
-
//
|
|
995
|
-
// sort that is already set in the view. Crucially, for grid tables that redraw when the view
|
|
996
|
-
// is updated, this is the only way you're ever going to see what the sort is.
|
|
1136
|
+
// Restrict the chain to the entries that apply to the current output mode (plain, grouped, or
|
|
1137
|
+
// pivotted), so the priority numbers reflect only this mode's sort and aren't inflated by
|
|
1138
|
+
// sorts configured in the other modes.
|
|
997
1139
|
|
|
998
|
-
|
|
1140
|
+
var applicable = _.filter(chain, sortEntryAppliesTo);
|
|
999
1141
|
|
|
1000
|
-
//
|
|
1001
|
-
//
|
|
1002
|
-
//
|
|
1142
|
+
// `aggNum` is an important part of the spec when sorting group or pivot aggregates (i.e. total
|
|
1143
|
+
// rows/columns) because they have their own row/column, and aren't thrown together like cell
|
|
1144
|
+
// aggregates are. When it's not relevant, ignore it on both sides of the comparison.
|
|
1003
1145
|
|
|
1004
1146
|
if (spec.aggType == null) {
|
|
1005
|
-
delete sortSpec_copy[orientation].aggNum;
|
|
1006
1147
|
delete spec_copy.aggNum;
|
|
1007
1148
|
}
|
|
1008
1149
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1150
|
+
for (var pi = 0; pi < applicable.length; pi++) {
|
|
1151
|
+
|
|
1152
|
+
// Compare each chain entry against the spec we were provided, ignoring direction (which is
|
|
1153
|
+
// independent of the user interface reflecting the sort).
|
|
1154
|
+
|
|
1155
|
+
var entry = deepCopy(applicable[pi]);
|
|
1156
|
+
var entryDir = entry.dir;
|
|
1157
|
+
delete entry.dir;
|
|
1158
|
+
if (spec.aggType == null) {
|
|
1159
|
+
delete entry.aggNum;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (_.isEqual(entry, spec_copy)) {
|
|
1011
1163
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1164
|
+
// Only show a numbered priority badge when the sort spans more than one column.
|
|
1165
|
+
|
|
1166
|
+
var priority = applicable.length > 1 ? pi + 1 : null;
|
|
1167
|
+
replaceSortIndicator(sortIcon_btn, entryDir, priority);
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1014
1170
|
}
|
|
1015
1171
|
}
|
|
1016
1172
|
};
|
|
@@ -1600,6 +1756,15 @@ GridTable.prototype.draw = function (root, opts, cont) {
|
|
|
1600
1756
|
}
|
|
1601
1757
|
}
|
|
1602
1758
|
|
|
1759
|
+
if (self.features.cellSelect && data.isPlain) {
|
|
1760
|
+
if (typeof self._addCellSelectHandler !== 'function') {
|
|
1761
|
+
self.logWarning(self.makeLogTag() + ' Requested feature "cellSelect" is not available: `_addCellSelectHandler` method does not exist');
|
|
1762
|
+
}
|
|
1763
|
+
else {
|
|
1764
|
+
self._addCellSelectHandler();
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1603
1768
|
if (self.features.rowReorder) {
|
|
1604
1769
|
self._addRowReorderHandler();
|
|
1605
1770
|
}
|
|
@@ -2161,6 +2326,130 @@ GridTable.prototype.getCsv = function () {
|
|
|
2161
2326
|
return self.csv.toString();
|
|
2162
2327
|
};
|
|
2163
2328
|
|
|
2329
|
+
// #_getPlainVisibleColumns {{{2
|
|
2330
|
+
|
|
2331
|
+
GridTable.prototype._getPlainVisibleColumns = function () {
|
|
2332
|
+
var self = this;
|
|
2333
|
+
|
|
2334
|
+
return _.filter(determineColumns(self.colConfig, self.data, self.typeInfo), function (field) {
|
|
2335
|
+
var fcc = self.colConfig.get(field) || {};
|
|
2336
|
+
return !fcc.isHidden;
|
|
2337
|
+
});
|
|
2338
|
+
};
|
|
2339
|
+
|
|
2340
|
+
// #_getPlainCellDisplayValue {{{2
|
|
2341
|
+
|
|
2342
|
+
GridTable.prototype._getPlainCellDisplayValue = function (field, cell) {
|
|
2343
|
+
var self = this;
|
|
2344
|
+
var fcc = self.colConfig.get(field) || {};
|
|
2345
|
+
var value = format(fcc, self.typeInfo.get(field), cell);
|
|
2346
|
+
|
|
2347
|
+
if (value instanceof Element) {
|
|
2348
|
+
return jQuery(value).text();
|
|
2349
|
+
}
|
|
2350
|
+
if (value instanceof jQuery) {
|
|
2351
|
+
return value.text();
|
|
2352
|
+
}
|
|
2353
|
+
if (value == null) {
|
|
2354
|
+
return '';
|
|
2355
|
+
}
|
|
2356
|
+
if (fcc.allowHtml && self.typeInfo.get(field).type === 'string' && typeof value === 'string' && value.charAt(0) === '<') {
|
|
2357
|
+
return jQuery(value).text();
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
return value;
|
|
2361
|
+
};
|
|
2362
|
+
|
|
2363
|
+
// #_getPlainCellCopyValue {{{2
|
|
2364
|
+
|
|
2365
|
+
GridTable.prototype._getPlainCellCopyValue = function (rowData, field) {
|
|
2366
|
+
var self = this;
|
|
2367
|
+
var fcc = self.colConfig.get(field) || {};
|
|
2368
|
+
var cell = rowData[field] || {};
|
|
2369
|
+
var value = cell.cachedRender;
|
|
2370
|
+
|
|
2371
|
+
if (value == null) {
|
|
2372
|
+
value = cell.value;
|
|
2373
|
+
}
|
|
2374
|
+
if (value == null) {
|
|
2375
|
+
value = cell.orig;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
if (value instanceof Element) {
|
|
2379
|
+
return jQuery(value).text();
|
|
2380
|
+
}
|
|
2381
|
+
if (value instanceof jQuery) {
|
|
2382
|
+
return value.text();
|
|
2383
|
+
}
|
|
2384
|
+
if (value == null) {
|
|
2385
|
+
return '';
|
|
2386
|
+
}
|
|
2387
|
+
if (fcc.allowHtml && self.typeInfo.get(field).type === 'string' && typeof value === 'string' && value.charAt(0) === '<') {
|
|
2388
|
+
return jQuery(value).text();
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
return value;
|
|
2392
|
+
};
|
|
2393
|
+
|
|
2394
|
+
// #getSelectedDataAsTsv {{{2
|
|
2395
|
+
|
|
2396
|
+
GridTable.prototype.getSelectedDataAsTsv = function () {
|
|
2397
|
+
var self = this;
|
|
2398
|
+
var selectedRows = self.getSelection().rows;
|
|
2399
|
+
var fields = self._getPlainVisibleColumns();
|
|
2400
|
+
var tsv = new Csv({
|
|
2401
|
+
separator: '\t'
|
|
2402
|
+
});
|
|
2403
|
+
|
|
2404
|
+
if (selectedRows.length === 0 || fields.length === 0) {
|
|
2405
|
+
return '';
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
tsv.addRow();
|
|
2409
|
+
_.each(fields, function (field) {
|
|
2410
|
+
var fcc = self.colConfig.get(field) || {};
|
|
2411
|
+
tsv.addCol(fcc.displayText || field);
|
|
2412
|
+
});
|
|
2413
|
+
|
|
2414
|
+
_.each(selectedRows, function (rowData) {
|
|
2415
|
+
tsv.addRow();
|
|
2416
|
+
_.each(fields, function (field) {
|
|
2417
|
+
tsv.addCol(self._getPlainCellDisplayValue(field, rowData[field]));
|
|
2418
|
+
});
|
|
2419
|
+
});
|
|
2420
|
+
|
|
2421
|
+
return tsv.toString();
|
|
2422
|
+
};
|
|
2423
|
+
|
|
2424
|
+
// #getSelectedCellsAsTsv {{{2
|
|
2425
|
+
|
|
2426
|
+
GridTable.prototype.getSelectedCellsAsTsv = function () {
|
|
2427
|
+
var self = this;
|
|
2428
|
+
var selection = self.getCellSelection();
|
|
2429
|
+
var tsv = new Csv({
|
|
2430
|
+
separator: '\t'
|
|
2431
|
+
});
|
|
2432
|
+
|
|
2433
|
+
if (selection.rowNums.length === 0 || selection.fields.length === 0) {
|
|
2434
|
+
return '';
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
_.each(selection.rowNums, function (rowNum) {
|
|
2438
|
+
var rowData = self.data.dataByRowId[rowNum];
|
|
2439
|
+
|
|
2440
|
+
if (rowData == null) {
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
tsv.addRow();
|
|
2445
|
+
_.each(selection.fields, function (field) {
|
|
2446
|
+
tsv.addCol(self._getPlainCellCopyValue(rowData, field));
|
|
2447
|
+
});
|
|
2448
|
+
});
|
|
2449
|
+
|
|
2450
|
+
return tsv.toString();
|
|
2451
|
+
};
|
|
2452
|
+
|
|
2164
2453
|
// #getSelection {{{2
|
|
2165
2454
|
|
|
2166
2455
|
/**
|
|
@@ -2191,6 +2480,63 @@ GridTable.prototype.getSelection = function () {
|
|
|
2191
2480
|
};
|
|
2192
2481
|
};
|
|
2193
2482
|
|
|
2483
|
+
// #getCellSelection {{{2
|
|
2484
|
+
|
|
2485
|
+
GridTable.prototype.getCellSelection = function () {
|
|
2486
|
+
var self = this;
|
|
2487
|
+
var selection = self.cellSelection;
|
|
2488
|
+
var rowNums = [];
|
|
2489
|
+
var fields = [];
|
|
2490
|
+
var cells = [];
|
|
2491
|
+
|
|
2492
|
+
if (selection == null || !_.isArray(selection.rowNums) || !_.isArray(selection.fields)) {
|
|
2493
|
+
return {
|
|
2494
|
+
rowNums: rowNums,
|
|
2495
|
+
fields: fields,
|
|
2496
|
+
cells: cells
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
rowNums = selection.rowNums.slice();
|
|
2501
|
+
fields = selection.fields.slice();
|
|
2502
|
+
|
|
2503
|
+
_.each(rowNums, function (rowNum) {
|
|
2504
|
+
_.each(fields, function (field) {
|
|
2505
|
+
cells.push({
|
|
2506
|
+
rowNum: rowNum,
|
|
2507
|
+
field: field
|
|
2508
|
+
});
|
|
2509
|
+
});
|
|
2510
|
+
});
|
|
2511
|
+
|
|
2512
|
+
return {
|
|
2513
|
+
rowNums: rowNums,
|
|
2514
|
+
fields: fields,
|
|
2515
|
+
cells: cells,
|
|
2516
|
+
anchor: selection.anchor,
|
|
2517
|
+
focus: selection.focus
|
|
2518
|
+
};
|
|
2519
|
+
};
|
|
2520
|
+
|
|
2521
|
+
// #clearCellSelection {{{2
|
|
2522
|
+
|
|
2523
|
+
GridTable.prototype.clearCellSelection = function () {
|
|
2524
|
+
var self = this;
|
|
2525
|
+
var prevSelection = self.getCellSelection();
|
|
2526
|
+
|
|
2527
|
+
if (prevSelection.cells.length === 0) {
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
self.cellSelection = null;
|
|
2532
|
+
|
|
2533
|
+
if (typeof self._clearCellSelectionGui === 'function') {
|
|
2534
|
+
self._clearCellSelectionGui();
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
self.fire('cellSelectionChange', null, self.getCellSelection());
|
|
2538
|
+
};
|
|
2539
|
+
|
|
2194
2540
|
// #setSelection {{{2
|
|
2195
2541
|
|
|
2196
2542
|
/**
|
|
@@ -2206,6 +2552,8 @@ GridTable.prototype.setSelection = function (what) {
|
|
|
2206
2552
|
var self = this;
|
|
2207
2553
|
var data = self.data.data;
|
|
2208
2554
|
|
|
2555
|
+
self.clearCellSelection();
|
|
2556
|
+
|
|
2209
2557
|
if (!self.features.rowSelect) {
|
|
2210
2558
|
return;
|
|
2211
2559
|
}
|
|
@@ -2261,6 +2609,8 @@ GridTable.prototype.select = function (what) {
|
|
|
2261
2609
|
var self = this;
|
|
2262
2610
|
var data = self.data.data;
|
|
2263
2611
|
|
|
2612
|
+
self.clearCellSelection();
|
|
2613
|
+
|
|
2264
2614
|
if (self.data.isGroup) {
|
|
2265
2615
|
data = _.flatten(data);
|
|
2266
2616
|
}
|
|
@@ -2321,6 +2671,8 @@ GridTable.prototype.unselect = function (what) {
|
|
|
2321
2671
|
var self = this;
|
|
2322
2672
|
var data = self.data.data;
|
|
2323
2673
|
|
|
2674
|
+
self.clearCellSelection();
|
|
2675
|
+
|
|
2324
2676
|
if (self.data.isGroup) {
|
|
2325
2677
|
data = _.flatten(data);
|
|
2326
2678
|
}
|
package/src/ui/popup_menu.js
CHANGED
|
@@ -42,9 +42,22 @@ var PopupMenu = makeSubclass('PopupMenu', Object, function () {
|
|
|
42
42
|
*
|
|
43
43
|
* @param {*} [userdata]
|
|
44
44
|
* Arbitrary data passed to the callback when this item is clicked.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} [action]
|
|
47
|
+
* An optional secondary action rendered as a trailing button on the right of the item. Clicking it
|
|
48
|
+
* does not trigger the item's primary callback.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} action.iconName
|
|
51
|
+
* A Lucide icon name for the action button.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} action.label
|
|
54
|
+
* Accessible label and tooltip for the action button.
|
|
55
|
+
*
|
|
56
|
+
* @param {function} action.callback
|
|
57
|
+
* Called when the action button is clicked. Receives `userdata` as its argument.
|
|
45
58
|
*/
|
|
46
59
|
|
|
47
|
-
PopupMenu.prototype.addItem = function (label, iconName, callback, userdata) {
|
|
60
|
+
PopupMenu.prototype.addItem = function (label, iconName, callback, userdata, action) {
|
|
48
61
|
var self = this;
|
|
49
62
|
|
|
50
63
|
self.items.push({
|
|
@@ -52,6 +65,7 @@ PopupMenu.prototype.addItem = function (label, iconName, callback, userdata) {
|
|
|
52
65
|
iconName: iconName,
|
|
53
66
|
callback: callback,
|
|
54
67
|
userdata: userdata,
|
|
68
|
+
action: action,
|
|
55
69
|
separator: false
|
|
56
70
|
});
|
|
57
71
|
};
|
|
@@ -128,6 +142,34 @@ PopupMenu.prototype.open = function (anchorElement) {
|
|
|
128
142
|
});
|
|
129
143
|
})(entry);
|
|
130
144
|
|
|
145
|
+
// Optionally render a trailing action button that performs a secondary action without
|
|
146
|
+
// triggering the item's primary callback.
|
|
147
|
+
if (entry.action) {
|
|
148
|
+
var actionBtn = document.createElement('button');
|
|
149
|
+
actionBtn.className = 'wcdv-popup-menu-item-action wcdv_icon_button';
|
|
150
|
+
actionBtn.setAttribute('type', 'button');
|
|
151
|
+
if (entry.action.label) {
|
|
152
|
+
actionBtn.setAttribute('aria-label', entry.action.label);
|
|
153
|
+
actionBtn.setAttribute('title', entry.action.label);
|
|
154
|
+
}
|
|
155
|
+
if (entry.action.iconName) {
|
|
156
|
+
var actionIconElt = icon(entry.action.iconName).get(0);
|
|
157
|
+
if (actionIconElt) {
|
|
158
|
+
actionBtn.appendChild(actionIconElt);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
(function (e) {
|
|
162
|
+
actionBtn.addEventListener('click', function (evt) {
|
|
163
|
+
evt.stopPropagation();
|
|
164
|
+
self.close();
|
|
165
|
+
if (typeof e.action.callback === 'function') {
|
|
166
|
+
e.action.callback(e.userdata);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
})(entry);
|
|
170
|
+
item.appendChild(actionBtn);
|
|
171
|
+
}
|
|
172
|
+
|
|
131
173
|
root.appendChild(item);
|
|
132
174
|
}
|
|
133
175
|
|
package/src/ui/toast.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import jQuery from 'jquery';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
makeSubclass,
|
|
5
|
+
} from '../util/misc.js';
|
|
6
|
+
|
|
7
|
+
var Toast = makeSubclass('Toast', Object, function () {
|
|
8
|
+
var self = this;
|
|
9
|
+
|
|
10
|
+
self._hideTimer = null;
|
|
11
|
+
|
|
12
|
+
self.ui = {
|
|
13
|
+
root: jQuery('<div>', {
|
|
14
|
+
'class': 'wcdv_toast',
|
|
15
|
+
'role': 'status',
|
|
16
|
+
'aria-live': 'polite',
|
|
17
|
+
'aria-atomic': 'true'
|
|
18
|
+
})
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
jQuery(document.body).append(self.ui.root);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
Toast.prototype.show = function (text) {
|
|
25
|
+
var self = this;
|
|
26
|
+
|
|
27
|
+
if (self._hideTimer != null) {
|
|
28
|
+
clearTimeout(self._hideTimer);
|
|
29
|
+
self._hideTimer = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
self.ui.root.text(text);
|
|
33
|
+
self.ui.root.addClass('wcdv_toast_visible');
|
|
34
|
+
|
|
35
|
+
self._hideTimer = setTimeout(function () {
|
|
36
|
+
self.hide();
|
|
37
|
+
}, 2000);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
Toast.prototype.hide = function () {
|
|
41
|
+
var self = this;
|
|
42
|
+
|
|
43
|
+
if (self._hideTimer != null) {
|
|
44
|
+
clearTimeout(self._hideTimer);
|
|
45
|
+
self._hideTimer = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
self.ui.root.removeClass('wcdv_toast_visible');
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
Toast.prototype.destroy = function () {
|
|
52
|
+
var self = this;
|
|
53
|
+
|
|
54
|
+
if (self._hideTimer != null) {
|
|
55
|
+
clearTimeout(self._hideTimer);
|
|
56
|
+
self._hideTimer = null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (self.ui != null && self.ui.root != null) {
|
|
60
|
+
self.ui.root.remove();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default Toast;
|