Fossil SCM

Minor code style improvements, fixed a couple jquery-isms in the confirmer port, added the ability to select tabs by number and pre-select a specific tab, removed a superfluous C function.

stephan 2020-05-06 04:52 fileedit-ajaxify
Commit 9085ec2351d42966153cb6b13ed15f0c4a20611e4fd901694898857b8c696496
--- src/default_css.txt
+++ src/default_css.txt
@@ -1004,30 +1004,30 @@
10041004
// applicability:
10051005
.flex-container {
10061006
display: flex;
10071007
}
10081008
.flex-container.row {
1009
- flex-direction: row;
1010
- flex-wrap: wrap;
1011
- justify-content: center;
1012
- align-items: center;
1009
+ flex-direction: row;
1010
+ flex-wrap: wrap;
1011
+ justify-content: center;
1012
+ align-items: center;
10131013
}
10141014
.flex-container.row.stretch {
1015
- flex-direction: row;
1016
- flex-wrap: wrap;
1017
- align-items: stretch;
1018
- margin: 0;
1015
+ flex-direction: row;
1016
+ flex-wrap: wrap;
1017
+ align-items: stretch;
1018
+ margin: 0;
10191019
}
10201020
.flex-container.column {
1021
- flex-direction: column;
1022
- flex-wrap: wrap;
1023
- justify-content: center;
1024
- align-items: center;
1021
+ flex-direction: column;
1022
+ flex-wrap: wrap;
1023
+ justify-content: center;
1024
+ align-items: center;
10251025
}
10261026
.flex-container.column.stretch {
1027
- align-items: stretch;
1028
- margin: 0;
1027
+ align-items: stretch;
1028
+ margin: 0;
10291029
}
10301030
.font-size-100 {
10311031
font-size: 100%;
10321032
}
10331033
.font-size-125 {
@@ -1055,11 +1055,11 @@
10551055
}
10561056
.input-with-label > * {
10571057
vertical-align: middle;
10581058
}
10591059
.input-with-label > input {
1060
- margin: 0;
1060
+ margin: 0;
10611061
}
10621062
.input-with-label > select {
10631063
margin: 0;
10641064
}
10651065
.input-with-label > input[type=text] {
@@ -1067,14 +1067,14 @@
10671067
}
10681068
.input-with-label > textarea {
10691069
margin: 0;
10701070
}
10711071
.input-with-label > input[type=checkbox] {
1072
- vertical-align: sub;
1072
+ vertical-align: sub;
10731073
}
10741074
.input-with-label > input[type=radio] {
1075
- vertical-align: sub;
1075
+ vertical-align: sub;
10761076
}
10771077
.input-with-label > span {
1078
- margin: 0 0.25em 0 0.25em;
1079
- vertical-align: middle;
1078
+ margin: 0 0.25em 0 0.25em;
1079
+ vertical-align: middle;
10801080
}
10811081
--- src/default_css.txt
+++ src/default_css.txt
@@ -1004,30 +1004,30 @@
1004 // applicability:
1005 .flex-container {
1006 display: flex;
1007 }
1008 .flex-container.row {
1009 flex-direction: row;
1010 flex-wrap: wrap;
1011 justify-content: center;
1012 align-items: center;
1013 }
1014 .flex-container.row.stretch {
1015 flex-direction: row;
1016 flex-wrap: wrap;
1017 align-items: stretch;
1018 margin: 0;
1019 }
1020 .flex-container.column {
1021 flex-direction: column;
1022 flex-wrap: wrap;
1023 justify-content: center;
1024 align-items: center;
1025 }
1026 .flex-container.column.stretch {
1027 align-items: stretch;
1028 margin: 0;
1029 }
1030 .font-size-100 {
1031 font-size: 100%;
1032 }
1033 .font-size-125 {
@@ -1055,11 +1055,11 @@
1055 }
1056 .input-with-label > * {
1057 vertical-align: middle;
1058 }
1059 .input-with-label > input {
1060 margin: 0;
1061 }
1062 .input-with-label > select {
1063 margin: 0;
1064 }
1065 .input-with-label > input[type=text] {
@@ -1067,14 +1067,14 @@
1067 }
1068 .input-with-label > textarea {
1069 margin: 0;
1070 }
1071 .input-with-label > input[type=checkbox] {
1072 vertical-align: sub;
1073 }
1074 .input-with-label > input[type=radio] {
1075 vertical-align: sub;
1076 }
1077 .input-with-label > span {
1078 margin: 0 0.25em 0 0.25em;
1079 vertical-align: middle;
1080 }
1081
--- src/default_css.txt
+++ src/default_css.txt
@@ -1004,30 +1004,30 @@
1004 // applicability:
1005 .flex-container {
1006 display: flex;
1007 }
1008 .flex-container.row {
1009 flex-direction: row;
1010 flex-wrap: wrap;
1011 justify-content: center;
1012 align-items: center;
1013 }
1014 .flex-container.row.stretch {
1015 flex-direction: row;
1016 flex-wrap: wrap;
1017 align-items: stretch;
1018 margin: 0;
1019 }
1020 .flex-container.column {
1021 flex-direction: column;
1022 flex-wrap: wrap;
1023 justify-content: center;
1024 align-items: center;
1025 }
1026 .flex-container.column.stretch {
1027 align-items: stretch;
1028 margin: 0;
1029 }
1030 .font-size-100 {
1031 font-size: 100%;
1032 }
1033 .font-size-125 {
@@ -1055,11 +1055,11 @@
1055 }
1056 .input-with-label > * {
1057 vertical-align: middle;
1058 }
1059 .input-with-label > input {
1060 margin: 0;
1061 }
1062 .input-with-label > select {
1063 margin: 0;
1064 }
1065 .input-with-label > input[type=text] {
@@ -1067,14 +1067,14 @@
1067 }
1068 .input-with-label > textarea {
1069 margin: 0;
1070 }
1071 .input-with-label > input[type=checkbox] {
1072 vertical-align: sub;
1073 }
1074 .input-with-label > input[type=radio] {
1075 vertical-align: sub;
1076 }
1077 .input-with-label > span {
1078 margin: 0 0.25em 0 0.25em;
1079 vertical-align: middle;
1080 }
1081
+16 -12
--- src/fileedit.c
+++ src/fileedit.c
@@ -28,12 +28,13 @@
2828
** db, e.g. for use via an HTTP request.
2929
**
3030
** Use CheckinMiniInfo_init() to cleanly initialize one to a known
3131
** valid/empty default state.
3232
**
33
-** Memory for all non-const (char *) members is owned by the
33
+** Memory for all non-const pointer members is owned by the
3434
** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup().
35
+** Similarly, each instance owns any memory for its Blob members.
3536
*/
3637
struct CheckinMiniInfo {
3738
Manifest * pParent; /* parent checkin. Memory is owned by this
3839
object. */
3940
char *zParentUuid; /* Full UUID of pParent */
@@ -168,11 +169,12 @@
168169
CheckinMiniInfo_init(p);
169170
}
170171
171172
/*
172173
** Internal helper which returns an F-card perms string suitable for
173
-** writing into a manifest.
174
+** writing as-is into a manifest. If it's not empty, it includes a
175
+** leading space to separate it from the F-card's hash field.
174176
*/
175177
static const char * mfile_permint_mstring(int perm){
176178
switch(perm){
177179
case PERM_EXE: return " x";
178180
case PERM_LNK: return " l";
@@ -189,23 +191,21 @@
189191
else if(strstr(zPerm,"x")) return PERM_EXE;
190192
else if(strstr(zPerm,"l")) return PERM_LNK;
191193
else return PERM_REG/*???*/;
192194
}
193195
194
-static const char * mfile_perm_mstring(const ManifestFile * p){
195
- return mfile_permint_mstring(manifest_file_mperm(p));
196
-}
197
-
198196
/*
199197
** Internal helper for checkin_mini() and friends. Appends an F-card
200198
** for p to pOut.
201199
*/
202
-static void checkin_mini_append_fcard(Blob *pOut, const ManifestFile *p){
200
+static void checkin_mini_append_fcard(Blob *pOut,
201
+ const ManifestFile *p){
203202
if(p->zUuid){
204203
assert(*p->zUuid);
205204
blob_appendf(pOut, "F %F %s%s", p->zName,
206
- p->zUuid, mfile_perm_mstring(p));
205
+ p->zUuid,
206
+ mfile_permint_mstring(manifest_file_mperm(p)));
207207
if(p->zPrior){
208208
assert(*p->zPrior);
209209
blob_appendf(pOut, " %F\n", p->zPrior);
210210
}else{
211211
blob_append(pOut, "\n", 1);
@@ -1676,19 +1676,19 @@
16761676
"Diffs will be shown here."
16771677
"</div>");
16781678
CX("</div>"/*#fileedit-tab-diff*/);
16791679
}
16801680
1681
-
16821681
/****** Commit ******/
16831682
CX("<div id='fileedit-tab-commit' "
16841683
"data-tab-parent='fileedit-tabs' "
1684
+ "data-tab-select='1' "
16851685
"data-tab-label='Commit'"
16861686
">");
16871687
16881688
{
1689
- /******* Flags/options *******/
1689
+ /******* Commit flags/options *******/
16901690
CX("<div class='fileedit-options flex-container row'>");
16911691
style_labeled_checkbox("cb-dry-run",
16921692
"dry_run", "Dry-run?", "1",
16931693
"In dry-run mode, the Save button performs "
16941694
"all work needed for saving but then rolls "
@@ -1735,11 +1735,11 @@
17351735
"Windows", 2,
17361736
NULL);
17371737
CX("</div>"/*checkboxes*/);
17381738
}
17391739
1740
- { /******* Comment *******/
1740
+ { /******* Commit comment, button, and result manifest *******/
17411741
CX("<fieldset class='fileedit-options'>"
17421742
"<legend>Message (required)</legend><div>");
17431743
CX("<input type='text' name='comment' "
17441744
"id='fileedit-comment'>");
17451745
/* ^^^ adding the 'required' attribute means we cannot even
@@ -1782,13 +1782,17 @@
17821782
style_emit_script_builtin("fossil.page.fileedit.js",0);
17831783
style_emit_script_confirmer(0);
17841784
if(blob_size(&endScript)>0){
17851785
style_emit_script_tag(0,0);
17861786
CX("(function(){\n");
1787
- CX("try{\n%b\n}catch(e){console.error('Exception:',e)}\n",
1787
+ CX("try{\n%b\n}"
1788
+ "catch(e){"
1789
+ "fossil.error(e);\n"
1790
+ "console.error('Exception:',e);\n"
1791
+ "}\n",
17881792
&endScript);
17891793
CX("})();");
17901794
style_emit_script_tag(1,0);
17911795
}
17921796
db_end_transaction(0);
17931797
style_footer();
17941798
}
17951799
--- src/fileedit.c
+++ src/fileedit.c
@@ -28,12 +28,13 @@
28 ** db, e.g. for use via an HTTP request.
29 **
30 ** Use CheckinMiniInfo_init() to cleanly initialize one to a known
31 ** valid/empty default state.
32 **
33 ** Memory for all non-const (char *) members is owned by the
34 ** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup().
 
35 */
36 struct CheckinMiniInfo {
37 Manifest * pParent; /* parent checkin. Memory is owned by this
38 object. */
39 char *zParentUuid; /* Full UUID of pParent */
@@ -168,11 +169,12 @@
168 CheckinMiniInfo_init(p);
169 }
170
171 /*
172 ** Internal helper which returns an F-card perms string suitable for
173 ** writing into a manifest.
 
174 */
175 static const char * mfile_permint_mstring(int perm){
176 switch(perm){
177 case PERM_EXE: return " x";
178 case PERM_LNK: return " l";
@@ -189,23 +191,21 @@
189 else if(strstr(zPerm,"x")) return PERM_EXE;
190 else if(strstr(zPerm,"l")) return PERM_LNK;
191 else return PERM_REG/*???*/;
192 }
193
194 static const char * mfile_perm_mstring(const ManifestFile * p){
195 return mfile_permint_mstring(manifest_file_mperm(p));
196 }
197
198 /*
199 ** Internal helper for checkin_mini() and friends. Appends an F-card
200 ** for p to pOut.
201 */
202 static void checkin_mini_append_fcard(Blob *pOut, const ManifestFile *p){
 
203 if(p->zUuid){
204 assert(*p->zUuid);
205 blob_appendf(pOut, "F %F %s%s", p->zName,
206 p->zUuid, mfile_perm_mstring(p));
 
207 if(p->zPrior){
208 assert(*p->zPrior);
209 blob_appendf(pOut, " %F\n", p->zPrior);
210 }else{
211 blob_append(pOut, "\n", 1);
@@ -1676,19 +1676,19 @@
1676 "Diffs will be shown here."
1677 "</div>");
1678 CX("</div>"/*#fileedit-tab-diff*/);
1679 }
1680
1681
1682 /****** Commit ******/
1683 CX("<div id='fileedit-tab-commit' "
1684 "data-tab-parent='fileedit-tabs' "
 
1685 "data-tab-label='Commit'"
1686 ">");
1687
1688 {
1689 /******* Flags/options *******/
1690 CX("<div class='fileedit-options flex-container row'>");
1691 style_labeled_checkbox("cb-dry-run",
1692 "dry_run", "Dry-run?", "1",
1693 "In dry-run mode, the Save button performs "
1694 "all work needed for saving but then rolls "
@@ -1735,11 +1735,11 @@
1735 "Windows", 2,
1736 NULL);
1737 CX("</div>"/*checkboxes*/);
1738 }
1739
1740 { /******* Comment *******/
1741 CX("<fieldset class='fileedit-options'>"
1742 "<legend>Message (required)</legend><div>");
1743 CX("<input type='text' name='comment' "
1744 "id='fileedit-comment'>");
1745 /* ^^^ adding the 'required' attribute means we cannot even
@@ -1782,13 +1782,17 @@
1782 style_emit_script_builtin("fossil.page.fileedit.js",0);
1783 style_emit_script_confirmer(0);
1784 if(blob_size(&endScript)>0){
1785 style_emit_script_tag(0,0);
1786 CX("(function(){\n");
1787 CX("try{\n%b\n}catch(e){console.error('Exception:',e)}\n",
 
 
 
 
1788 &endScript);
1789 CX("})();");
1790 style_emit_script_tag(1,0);
1791 }
1792 db_end_transaction(0);
1793 style_footer();
1794 }
1795
--- src/fileedit.c
+++ src/fileedit.c
@@ -28,12 +28,13 @@
28 ** db, e.g. for use via an HTTP request.
29 **
30 ** Use CheckinMiniInfo_init() to cleanly initialize one to a known
31 ** valid/empty default state.
32 **
33 ** Memory for all non-const pointer members is owned by the
34 ** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup().
35 ** Similarly, each instance owns any memory for its Blob members.
36 */
37 struct CheckinMiniInfo {
38 Manifest * pParent; /* parent checkin. Memory is owned by this
39 object. */
40 char *zParentUuid; /* Full UUID of pParent */
@@ -168,11 +169,12 @@
169 CheckinMiniInfo_init(p);
170 }
171
172 /*
173 ** Internal helper which returns an F-card perms string suitable for
174 ** writing as-is into a manifest. If it's not empty, it includes a
175 ** leading space to separate it from the F-card's hash field.
176 */
177 static const char * mfile_permint_mstring(int perm){
178 switch(perm){
179 case PERM_EXE: return " x";
180 case PERM_LNK: return " l";
@@ -189,23 +191,21 @@
191 else if(strstr(zPerm,"x")) return PERM_EXE;
192 else if(strstr(zPerm,"l")) return PERM_LNK;
193 else return PERM_REG/*???*/;
194 }
195
 
 
 
 
196 /*
197 ** Internal helper for checkin_mini() and friends. Appends an F-card
198 ** for p to pOut.
199 */
200 static void checkin_mini_append_fcard(Blob *pOut,
201 const ManifestFile *p){
202 if(p->zUuid){
203 assert(*p->zUuid);
204 blob_appendf(pOut, "F %F %s%s", p->zName,
205 p->zUuid,
206 mfile_permint_mstring(manifest_file_mperm(p)));
207 if(p->zPrior){
208 assert(*p->zPrior);
209 blob_appendf(pOut, " %F\n", p->zPrior);
210 }else{
211 blob_append(pOut, "\n", 1);
@@ -1676,19 +1676,19 @@
1676 "Diffs will be shown here."
1677 "</div>");
1678 CX("</div>"/*#fileedit-tab-diff*/);
1679 }
1680
 
1681 /****** Commit ******/
1682 CX("<div id='fileedit-tab-commit' "
1683 "data-tab-parent='fileedit-tabs' "
1684 "data-tab-select='1' "
1685 "data-tab-label='Commit'"
1686 ">");
1687
1688 {
1689 /******* Commit flags/options *******/
1690 CX("<div class='fileedit-options flex-container row'>");
1691 style_labeled_checkbox("cb-dry-run",
1692 "dry_run", "Dry-run?", "1",
1693 "In dry-run mode, the Save button performs "
1694 "all work needed for saving but then rolls "
@@ -1735,11 +1735,11 @@
1735 "Windows", 2,
1736 NULL);
1737 CX("</div>"/*checkboxes*/);
1738 }
1739
1740 { /******* Commit comment, button, and result manifest *******/
1741 CX("<fieldset class='fileedit-options'>"
1742 "<legend>Message (required)</legend><div>");
1743 CX("<input type='text' name='comment' "
1744 "id='fileedit-comment'>");
1745 /* ^^^ adding the 'required' attribute means we cannot even
@@ -1782,13 +1782,17 @@
1782 style_emit_script_builtin("fossil.page.fileedit.js",0);
1783 style_emit_script_confirmer(0);
1784 if(blob_size(&endScript)>0){
1785 style_emit_script_tag(0,0);
1786 CX("(function(){\n");
1787 CX("try{\n%b\n}"
1788 "catch(e){"
1789 "fossil.error(e);\n"
1790 "console.error('Exception:',e);\n"
1791 "}\n",
1792 &endScript);
1793 CX("})();");
1794 style_emit_script_tag(1,0);
1795 }
1796 db_end_transaction(0);
1797 style_footer();
1798 }
1799
--- src/fossil.confirmer.js
+++ src/fossil.confirmer.js
@@ -1,5 +1,6 @@
1
+"use strict";
12
/**************************************************************
23
Confirmer is a utility which provides an alternative to confirmation
34
dialog boxes and "check this checkbox to confirm action" widgets. It
45
acts by modifying a button to require two clicks within a certain
56
time, with the second click acting as a confirmation of the first. If
@@ -51,11 +52,11 @@
5152
when it is not waiting on a timeout). When the target is activated
5253
(waiting on a timeout) this class is removed. In the case of a
5354
timeout, this class is added *before* the .ontimeout handler is
5455
called.
5556
56
- .classActivated = optional CSS class string (default='') which is
57
+ .classWaiting = optional CSS class string (default='') which is
5758
added to the target when it is waiting on a timeout. When the target
5859
leaves timeout-wait mode, this class is removed. When timeout-wait
5960
mode is entered, this class is added *before* the .onactivate
6061
handler is called.
6162
@@ -70,24 +71,21 @@
7071
To change the default option values, modify the
7172
fossil.confirmer.defaultOpts object.
7273
7374
Terse Change history:
7475
75
-20200506:
76
+- 20200506:
7677
- Ported from jQuery to plain JS.
7778
7879
- 20181112:
7980
- extended to support certain INPUT elements.
8081
- made default opts configurable.
8182
8283
- 20070717: initial jQuery-based impl.
8384
*/
8485
(function(F/*the fossil object*/){
85
- "use strict";
86
-
8786
F.confirmer = function f(elem,opt){
88
-
8987
const dbg = opt.debug
9088
? function(){console.debug.apply(console,arguments)}
9189
: function(){};
9290
dbg("confirmer opt =",opt);
9391
if(!f.Holder){
@@ -121,11 +119,14 @@
121119
};
122120
target.addEventListener(
123121
'click', function(){
124122
switch( self.state ) {
125123
case( self.states.waiting ):
126
- if( undefined !== self.timerID ) clearTimeout( self.timerID );
124
+ if( undefined !== self.timerID ){
125
+ clearTimeout( self.timerID );
126
+ delete self.timerID;
127
+ }
127128
self.state = self.states.initial;
128129
self.setClasses( false );
129130
dbg("Confirmed");
130131
updateText(self.opt.initialText);
131132
if( self.opt.onconfirm ) self.opt.onconfirm.call(self.target);
@@ -143,31 +144,28 @@
143144
}
144145
}, false
145146
);
146147
};
147148
f.Holder.prototype = {
148
- states:{
149
- initial: 0, waiting: 1
150
- },
151
- setClasses: function(activated) {
152
- if( activated ) {
153
- if( this.opt.classActivated ) {
154
- this.target.addClass( this.opt.classActivated );
155
- }
156
- if( this.opt.classInitial ) {
157
- this.target.removeClass( this.opt.classInitial );
158
- }
159
- } else {
160
- if( this.opt.classInitial ) {
161
- this.target.addClass( this.opt.classInitial );
162
- }
163
- if( this.opt.classActivated ) {
164
- this.target.removeClass( this.opt.classActivated );
149
+ states:{initial: 0, waiting: 1},
150
+ setClasses: function(activated) {
151
+ if(activated) {
152
+ if( this.opt.classWaiting ) {
153
+ this.target.classList.add( this.opt.classWaiting );
154
+ }
155
+ if( this.opt.classInitial ) {
156
+ this.target.classList.remove( this.opt.classInitial );
157
+ }
158
+ }else{
159
+ if( this.opt.classInitial ) {
160
+ this.target.classList.add( this.opt.classInitial );
161
+ }
162
+ if( this.opt.classWaiting ) {
163
+ this.target.classList.remove( this.opt.classWaiting );
165164
}
166165
}
167166
}
168
-
169167
};
170168
}/*static init*/
171169
opt = F.mergeLastWins(f.defaultOpts,{
172170
initialText: (
173171
f.isInput(elem) ? elem.value : elem.innerHTML
@@ -185,14 +183,14 @@
185183
dynamically-generated, and can't reasonably be set in the
186184
defaults.
187185
*/
188186
F.confirmer.defaultOpts = {
189187
timeout:3000,
190
- onconfirm:undefined,
191
- ontimeout:undefined,
192
- onactivate:undefined,
193
- classInitial:'',
194
- classActivated:'',
195
- debug:true
188
+ onconfirm: undefined,
189
+ ontimeout: undefined,
190
+ onactivate: undefined,
191
+ classInitial: '',
192
+ classWaiting: '',
193
+ debug: false
196194
};
197195
198196
})(window.fossil);
199197
--- src/fossil.confirmer.js
+++ src/fossil.confirmer.js
@@ -1,5 +1,6 @@
 
1 /**************************************************************
2 Confirmer is a utility which provides an alternative to confirmation
3 dialog boxes and "check this checkbox to confirm action" widgets. It
4 acts by modifying a button to require two clicks within a certain
5 time, with the second click acting as a confirmation of the first. If
@@ -51,11 +52,11 @@
51 when it is not waiting on a timeout). When the target is activated
52 (waiting on a timeout) this class is removed. In the case of a
53 timeout, this class is added *before* the .ontimeout handler is
54 called.
55
56 .classActivated = optional CSS class string (default='') which is
57 added to the target when it is waiting on a timeout. When the target
58 leaves timeout-wait mode, this class is removed. When timeout-wait
59 mode is entered, this class is added *before* the .onactivate
60 handler is called.
61
@@ -70,24 +71,21 @@
70 To change the default option values, modify the
71 fossil.confirmer.defaultOpts object.
72
73 Terse Change history:
74
75 20200506:
76 - Ported from jQuery to plain JS.
77
78 - 20181112:
79 - extended to support certain INPUT elements.
80 - made default opts configurable.
81
82 - 20070717: initial jQuery-based impl.
83 */
84 (function(F/*the fossil object*/){
85 "use strict";
86
87 F.confirmer = function f(elem,opt){
88
89 const dbg = opt.debug
90 ? function(){console.debug.apply(console,arguments)}
91 : function(){};
92 dbg("confirmer opt =",opt);
93 if(!f.Holder){
@@ -121,11 +119,14 @@
121 };
122 target.addEventListener(
123 'click', function(){
124 switch( self.state ) {
125 case( self.states.waiting ):
126 if( undefined !== self.timerID ) clearTimeout( self.timerID );
 
 
 
127 self.state = self.states.initial;
128 self.setClasses( false );
129 dbg("Confirmed");
130 updateText(self.opt.initialText);
131 if( self.opt.onconfirm ) self.opt.onconfirm.call(self.target);
@@ -143,31 +144,28 @@
143 }
144 }, false
145 );
146 };
147 f.Holder.prototype = {
148 states:{
149 initial: 0, waiting: 1
150 },
151 setClasses: function(activated) {
152 if( activated ) {
153 if( this.opt.classActivated ) {
154 this.target.addClass( this.opt.classActivated );
155 }
156 if( this.opt.classInitial ) {
157 this.target.removeClass( this.opt.classInitial );
158 }
159 } else {
160 if( this.opt.classInitial ) {
161 this.target.addClass( this.opt.classInitial );
162 }
163 if( this.opt.classActivated ) {
164 this.target.removeClass( this.opt.classActivated );
165 }
166 }
167 }
168
169 };
170 }/*static init*/
171 opt = F.mergeLastWins(f.defaultOpts,{
172 initialText: (
173 f.isInput(elem) ? elem.value : elem.innerHTML
@@ -185,14 +183,14 @@
185 dynamically-generated, and can't reasonably be set in the
186 defaults.
187 */
188 F.confirmer.defaultOpts = {
189 timeout:3000,
190 onconfirm:undefined,
191 ontimeout:undefined,
192 onactivate:undefined,
193 classInitial:'',
194 classActivated:'',
195 debug:true
196 };
197
198 })(window.fossil);
199
--- src/fossil.confirmer.js
+++ src/fossil.confirmer.js
@@ -1,5 +1,6 @@
1 "use strict";
2 /**************************************************************
3 Confirmer is a utility which provides an alternative to confirmation
4 dialog boxes and "check this checkbox to confirm action" widgets. It
5 acts by modifying a button to require two clicks within a certain
6 time, with the second click acting as a confirmation of the first. If
@@ -51,11 +52,11 @@
52 when it is not waiting on a timeout). When the target is activated
53 (waiting on a timeout) this class is removed. In the case of a
54 timeout, this class is added *before* the .ontimeout handler is
55 called.
56
57 .classWaiting = optional CSS class string (default='') which is
58 added to the target when it is waiting on a timeout. When the target
59 leaves timeout-wait mode, this class is removed. When timeout-wait
60 mode is entered, this class is added *before* the .onactivate
61 handler is called.
62
@@ -70,24 +71,21 @@
71 To change the default option values, modify the
72 fossil.confirmer.defaultOpts object.
73
74 Terse Change history:
75
76 - 20200506:
77 - Ported from jQuery to plain JS.
78
79 - 20181112:
80 - extended to support certain INPUT elements.
81 - made default opts configurable.
82
83 - 20070717: initial jQuery-based impl.
84 */
85 (function(F/*the fossil object*/){
 
 
86 F.confirmer = function f(elem,opt){
 
87 const dbg = opt.debug
88 ? function(){console.debug.apply(console,arguments)}
89 : function(){};
90 dbg("confirmer opt =",opt);
91 if(!f.Holder){
@@ -121,11 +119,14 @@
119 };
120 target.addEventListener(
121 'click', function(){
122 switch( self.state ) {
123 case( self.states.waiting ):
124 if( undefined !== self.timerID ){
125 clearTimeout( self.timerID );
126 delete self.timerID;
127 }
128 self.state = self.states.initial;
129 self.setClasses( false );
130 dbg("Confirmed");
131 updateText(self.opt.initialText);
132 if( self.opt.onconfirm ) self.opt.onconfirm.call(self.target);
@@ -143,31 +144,28 @@
144 }
145 }, false
146 );
147 };
148 f.Holder.prototype = {
149 states:{initial: 0, waiting: 1},
150 setClasses: function(activated) {
151 if(activated) {
152 if( this.opt.classWaiting ) {
153 this.target.classList.add( this.opt.classWaiting );
154 }
155 if( this.opt.classInitial ) {
156 this.target.classList.remove( this.opt.classInitial );
157 }
158 }else{
159 if( this.opt.classInitial ) {
160 this.target.classList.add( this.opt.classInitial );
161 }
162 if( this.opt.classWaiting ) {
163 this.target.classList.remove( this.opt.classWaiting );
 
 
164 }
165 }
166 }
 
167 };
168 }/*static init*/
169 opt = F.mergeLastWins(f.defaultOpts,{
170 initialText: (
171 f.isInput(elem) ? elem.value : elem.innerHTML
@@ -185,14 +183,14 @@
183 dynamically-generated, and can't reasonably be set in the
184 defaults.
185 */
186 F.confirmer.defaultOpts = {
187 timeout:3000,
188 onconfirm: undefined,
189 ontimeout: undefined,
190 onactivate: undefined,
191 classInitial: '',
192 classWaiting: '',
193 debug: false
194 };
195
196 })(window.fossil);
197
--- src/fossil.fetch.js
+++ src/fossil.fetch.js
@@ -23,11 +23,11 @@
2323
the console).
2424
2525
- onerror: callback(XHR onload event | exception)
2626
(default = event or exception to the console).
2727
28
- - method: 'POST' | 'GET' (default = 'GET')
28
+ - method: 'POST' | 'GET' (default = 'GET'). CASE SENSITIVE!
2929
3030
- payload: anything acceptable by XHR2.send(ARG) (DOMString,
3131
Document, FormData, Blob, File, ArrayBuffer), or a plain object or
3232
array, either of which gets JSON.stringify()'d. If payload is set
3333
then the method is automatically set to 'POST'. If an object/array
@@ -60,15 +60,16 @@
6060
6161
Returns this object, noting that the XHR request is asynchronous,
6262
and still in transit (or has yet to be sent) when that happens.
6363
*/
6464
window.fossil.fetch = function f(uri,opt){
65
+ const F = fossil;
6566
if(!f.onerror){
6667
f.onerror = function(e/*event or exception*/){
6768
console.error("Ajax error:",e);
6869
if(e instanceof Error){
69
- fossil.error('Exception:',e);
70
+ F.error('Exception:',e);
7071
}
7172
else if(e.originalTarget && e.originalTarget.responseType==='text'){
7273
const txt = e.originalTarget.responseText;
7374
try{
7475
/* The convention from the /filepage_xyz routes is to
@@ -75,13 +76,13 @@
7576
return error responses in JSON form if possible:
7677
{error: "..."}
7778
*/
7879
const j = JSON.parse(txt);
7980
console.error("Error JSON:",j);
80
- if(j.error){ fossil.error(j.error) };
81
+ if(j.error){ F.error(j.error) };
8182
}catch(e){/* Try harder */
82
- fossil.error(txt)
83
+ F.error(txt)
8384
}
8485
}
8586
};
8687
f.onload = (r)=>console.debug('ajax response:',r);
8788
}
@@ -89,11 +90,11 @@
8990
if(!opt) opt = {};
9091
else if('function'===typeof opt) opt={onload:opt};
9192
if(!opt.onload) opt.onload = f.onload;
9293
if(!opt.onerror) opt.onerror = f.onerror;
9394
let payload = opt.payload, jsonResponse = false;
94
- if(payload){
95
+ if(undefined!==payload){
9596
opt.method = 'POST';
9697
if(!(payload instanceof FormData)
9798
&& !(payload instanceof Document)
9899
&& !(payload instanceof Blob)
99100
&& !(payload instanceof File)
@@ -102,11 +103,11 @@
102103
|| payload instanceof Array)){
103104
payload = JSON.stringify(payload);
104105
opt.contentType = 'application/json';
105106
}
106107
}
107
- const url=[window.fossil.repoUrl(uri,opt.urlParams)],
108
+ const url=[F.repoUrl(uri,opt.urlParams)],
108109
x=new XMLHttpRequest();
109110
if('POST'===opt.method && 'string'===typeof opt.contentType){
110111
x.setRequestHeader('Content-Type',opt.contentType);
111112
}
112113
x.open(opt.method||'GET', url.join(''), true);
@@ -131,9 +132,9 @@
131132
}catch(e){
132133
if(opt.onerror) opt.onerror(e);
133134
}
134135
}
135136
}
136
- if(payload) x.send(payload);
137
+ if(undefined!==payload) x.send(payload);
137138
else x.send();
138139
return this;
139140
};
140141
--- src/fossil.fetch.js
+++ src/fossil.fetch.js
@@ -23,11 +23,11 @@
23 the console).
24
25 - onerror: callback(XHR onload event | exception)
26 (default = event or exception to the console).
27
28 - method: 'POST' | 'GET' (default = 'GET')
29
30 - payload: anything acceptable by XHR2.send(ARG) (DOMString,
31 Document, FormData, Blob, File, ArrayBuffer), or a plain object or
32 array, either of which gets JSON.stringify()'d. If payload is set
33 then the method is automatically set to 'POST'. If an object/array
@@ -60,15 +60,16 @@
60
61 Returns this object, noting that the XHR request is asynchronous,
62 and still in transit (or has yet to be sent) when that happens.
63 */
64 window.fossil.fetch = function f(uri,opt){
 
65 if(!f.onerror){
66 f.onerror = function(e/*event or exception*/){
67 console.error("Ajax error:",e);
68 if(e instanceof Error){
69 fossil.error('Exception:',e);
70 }
71 else if(e.originalTarget && e.originalTarget.responseType==='text'){
72 const txt = e.originalTarget.responseText;
73 try{
74 /* The convention from the /filepage_xyz routes is to
@@ -75,13 +76,13 @@
75 return error responses in JSON form if possible:
76 {error: "..."}
77 */
78 const j = JSON.parse(txt);
79 console.error("Error JSON:",j);
80 if(j.error){ fossil.error(j.error) };
81 }catch(e){/* Try harder */
82 fossil.error(txt)
83 }
84 }
85 };
86 f.onload = (r)=>console.debug('ajax response:',r);
87 }
@@ -89,11 +90,11 @@
89 if(!opt) opt = {};
90 else if('function'===typeof opt) opt={onload:opt};
91 if(!opt.onload) opt.onload = f.onload;
92 if(!opt.onerror) opt.onerror = f.onerror;
93 let payload = opt.payload, jsonResponse = false;
94 if(payload){
95 opt.method = 'POST';
96 if(!(payload instanceof FormData)
97 && !(payload instanceof Document)
98 && !(payload instanceof Blob)
99 && !(payload instanceof File)
@@ -102,11 +103,11 @@
102 || payload instanceof Array)){
103 payload = JSON.stringify(payload);
104 opt.contentType = 'application/json';
105 }
106 }
107 const url=[window.fossil.repoUrl(uri,opt.urlParams)],
108 x=new XMLHttpRequest();
109 if('POST'===opt.method && 'string'===typeof opt.contentType){
110 x.setRequestHeader('Content-Type',opt.contentType);
111 }
112 x.open(opt.method||'GET', url.join(''), true);
@@ -131,9 +132,9 @@
131 }catch(e){
132 if(opt.onerror) opt.onerror(e);
133 }
134 }
135 }
136 if(payload) x.send(payload);
137 else x.send();
138 return this;
139 };
140
--- src/fossil.fetch.js
+++ src/fossil.fetch.js
@@ -23,11 +23,11 @@
23 the console).
24
25 - onerror: callback(XHR onload event | exception)
26 (default = event or exception to the console).
27
28 - method: 'POST' | 'GET' (default = 'GET'). CASE SENSITIVE!
29
30 - payload: anything acceptable by XHR2.send(ARG) (DOMString,
31 Document, FormData, Blob, File, ArrayBuffer), or a plain object or
32 array, either of which gets JSON.stringify()'d. If payload is set
33 then the method is automatically set to 'POST'. If an object/array
@@ -60,15 +60,16 @@
60
61 Returns this object, noting that the XHR request is asynchronous,
62 and still in transit (or has yet to be sent) when that happens.
63 */
64 window.fossil.fetch = function f(uri,opt){
65 const F = fossil;
66 if(!f.onerror){
67 f.onerror = function(e/*event or exception*/){
68 console.error("Ajax error:",e);
69 if(e instanceof Error){
70 F.error('Exception:',e);
71 }
72 else if(e.originalTarget && e.originalTarget.responseType==='text'){
73 const txt = e.originalTarget.responseText;
74 try{
75 /* The convention from the /filepage_xyz routes is to
@@ -75,13 +76,13 @@
76 return error responses in JSON form if possible:
77 {error: "..."}
78 */
79 const j = JSON.parse(txt);
80 console.error("Error JSON:",j);
81 if(j.error){ F.error(j.error) };
82 }catch(e){/* Try harder */
83 F.error(txt)
84 }
85 }
86 };
87 f.onload = (r)=>console.debug('ajax response:',r);
88 }
@@ -89,11 +90,11 @@
90 if(!opt) opt = {};
91 else if('function'===typeof opt) opt={onload:opt};
92 if(!opt.onload) opt.onload = f.onload;
93 if(!opt.onerror) opt.onerror = f.onerror;
94 let payload = opt.payload, jsonResponse = false;
95 if(undefined!==payload){
96 opt.method = 'POST';
97 if(!(payload instanceof FormData)
98 && !(payload instanceof Document)
99 && !(payload instanceof Blob)
100 && !(payload instanceof File)
@@ -102,11 +103,11 @@
103 || payload instanceof Array)){
104 payload = JSON.stringify(payload);
105 opt.contentType = 'application/json';
106 }
107 }
108 const url=[F.repoUrl(uri,opt.urlParams)],
109 x=new XMLHttpRequest();
110 if('POST'===opt.method && 'string'===typeof opt.contentType){
111 x.setRequestHeader('Content-Type',opt.contentType);
112 }
113 x.open(opt.method||'GET', url.join(''), true);
@@ -131,9 +132,9 @@
132 }catch(e){
133 if(opt.onerror) opt.onerror(e);
134 }
135 }
136 }
137 if(undefined!==payload) x.send(payload);
138 else x.send();
139 return this;
140 };
141
+60 -27
--- src/fossil.tabs.js
+++ src/fossil.tabs.js
@@ -1,27 +1,29 @@
11
"use strict";
2
-(function(F){
2
+(function(F/*fossil object*/){
33
const E = (s)=>document.querySelector(s),
44
EA = (s)=>document.querySelectorAll(s),
55
D = F.dom;
66
7
- const stopEvent = function(e){
8
- e.preventDefault();
9
- e.stopPropagation();
10
- };
11
-
127
/**
138
Creates a TabManager. If passed an argument, it is
149
passed to init().
1510
*/
1611
const TabManager = function(domElem){
1712
this.e = {};
1813
if(domElem) this.init(domElem);
1914
};
2015
21
- const tabArg = function(arg){
16
+ /**
17
+ Internal helper to normalize a method argument
18
+ to a tab element.
19
+ */
20
+ const tabArg = function(arg,tabMgr){
2221
if('string'===typeof arg) arg = E(arg);
22
+ else if(tabMgr && 'number'===typeof arg && arg>=0){
23
+ arg = tabMgr.e.tabs.childNodes[arg];
24
+ }
2325
return arg;
2426
};
2527
2628
const setVisible = function(e,yes){
2729
D[yes ? 'removeClass' : 'addClass'](e, 'hidden');
@@ -35,16 +37,40 @@
3537
The tab container must have an 'id' attribute. This function
3638
looks through the DOM for all elements which have
3739
data-tab-parent=thatId. For each one it creates a button to
3840
switch to that tab and moves the element into this.e.tabs.
3941
40
- When it's done, it auto-selects the first tab.
42
+ The label for each tab is set by the data-tab-label attribute
43
+ of each element, defaulting to something not terribly useful.
44
+
45
+ When it's done, it auto-selects the first tab unless a tab has
46
+ a truthy numeric value in its data-tab-select attribute, in
47
+ which case the last tab to have such a property is selected.
4148
4249
This method must only be called once per instance. TabManagers
43
- may be nested but may not share any tabs.
50
+ may be nested but may not share any tabs instances.
4451
4552
Returns this object.
53
+
54
+ DOM elements of potential interest to users:
55
+
56
+ this.e.container = the outermost container element.
57
+
58
+ this.e.tabBar = the button bar
59
+
60
+ this.e.tabs = the parent for all of the tab elements.
61
+
62
+ It is legal, within reason, to manipulate these a bit, in
63
+ particular this.e.container, e.g. by adding more children to
64
+ it. Do not remove elements from the tabs or tabBar, however, or
65
+ the tab state may get sorely out of sync.
66
+
67
+ CSS classes: the container element has whatever class(es) the
68
+ client sets on. this.e.tabBar gets the 'tab-bar' class and
69
+ this.e.tabs gets the 'tabs' class. It's hypothetically possible
70
+ to move the tabs to either side or the bottom using only CSS,
71
+ but it's never been tested.
4672
*/
4773
init: function(container){
4874
container = tabArg(container);
4975
const cID = container.getAttribute('id');
5076
if(!cID){
@@ -52,21 +78,25 @@
5278
}
5379
const c = this.e.container = container;
5480
this.e.tabBar = D.addClass(D.div(),'tab-bar');
5581
this.e.tabs = D.addClass(D.div(),'tabs');
5682
D.append(c, this.e.tabBar, this.e.tabs);
57
- const childs = EA('[data-tab-parent='+cID+']');
58
- childs.forEach((c)=>this.addTab(c));
59
- return this.switchToTab(this.e.tabs.firstChild);
83
+ let selectIndex = 0;
84
+ EA('[data-tab-parent='+cID+']').forEach((c,n)=>{
85
+ if(+c.dataset.tabSelect) selectIndex=n;
86
+ this.addTab(c);
87
+ });
88
+ return this.switchToTab(selectIndex);
6089
},
6190
/**
62
- For the given tab element or unique selector string, returns
63
- the button associated with that tab, or undefined if the
64
- argument does not match any current tab.
91
+ For the given tab element, unique selector string, or integer
92
+ (0-based tab number), returns the button associated with that
93
+ tab, or undefined if the argument does not match any current
94
+ tab.
6595
*/
6696
getButtonForTab: function(tab){
67
- tab = tabArg(tab);
97
+ tab = tabArg(tab,this);
6898
var i = -1;
6999
this.e.tabs.childNodes.forEach(function(e,n){
70100
if(e===tab) i = n;
71101
});
72102
return i>=0 ? this.e.tabBar.childNodes[i] : undefined;
@@ -74,31 +104,34 @@
74104
/**
75105
Adds the given DOM element or unique selector as the next
76106
tab in the tab container, adding a button to switch to
77107
the tab. Returns this object.
78108
*/
79
- addTab: function(tab){
109
+ addTab: function f(tab){
110
+ if(!f.click){
111
+ f.click = function(e){
112
+ e.target.$manager.switchToTab(e.target.$tab);
113
+ };
114
+ }
80115
tab = tabArg(tab);
81116
tab.remove();
82117
D.append(this.e.tabs, D.addClass(tab,'tab-panel'));
83
- const lbl = tab.dataset.tabLabel || 'Tab #'+this.e.tabs.childNodes.length;
118
+ const lbl = tab.dataset.tabLabel || 'Tab #'+(this.e.tabs.childNodes.length-1);
84119
const btn = D.button(lbl);
85120
D.append(this.e.tabBar,btn);
86
- const self = this;
87
- btn.addEventListener('click',function(e){
88
- //stopEvent(e);
89
- self.switchToTab(tab);
90
- }, false);
121
+ btn.$manager = this;
122
+ btn.$tab = tab;
123
+ btn.addEventListener('click', f.click, false);
91124
return this;
92125
},
93126
/**
94
- If the given DOM element or unique selector is one of this
95
- object's tabs, the UI makes that tab the currently-visible
96
- one. Returns this object.
127
+ If the given DOM element, unique selector, or integer (0-based
128
+ tab number) is one of this object's tabs, the UI makes that tab
129
+ the currently-visible one. Returns this object.
97130
*/
98131
switchToTab: function(tab){
99
- tab = tabArg(tab);
132
+ tab = tabArg(tab,this);
100133
const self = this;
101134
this.e.tabs.childNodes.forEach((e,ndx)=>{
102135
const btn = this.e.tabBar.childNodes[ndx];
103136
if(e===tab){
104137
setVisible(e, true);
105138
--- src/fossil.tabs.js
+++ src/fossil.tabs.js
@@ -1,27 +1,29 @@
1 "use strict";
2 (function(F){
3 const E = (s)=>document.querySelector(s),
4 EA = (s)=>document.querySelectorAll(s),
5 D = F.dom;
6
7 const stopEvent = function(e){
8 e.preventDefault();
9 e.stopPropagation();
10 };
11
12 /**
13 Creates a TabManager. If passed an argument, it is
14 passed to init().
15 */
16 const TabManager = function(domElem){
17 this.e = {};
18 if(domElem) this.init(domElem);
19 };
20
21 const tabArg = function(arg){
 
 
 
 
22 if('string'===typeof arg) arg = E(arg);
 
 
 
23 return arg;
24 };
25
26 const setVisible = function(e,yes){
27 D[yes ? 'removeClass' : 'addClass'](e, 'hidden');
@@ -35,16 +37,40 @@
35 The tab container must have an 'id' attribute. This function
36 looks through the DOM for all elements which have
37 data-tab-parent=thatId. For each one it creates a button to
38 switch to that tab and moves the element into this.e.tabs.
39
40 When it's done, it auto-selects the first tab.
 
 
 
 
 
41
42 This method must only be called once per instance. TabManagers
43 may be nested but may not share any tabs.
44
45 Returns this object.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46 */
47 init: function(container){
48 container = tabArg(container);
49 const cID = container.getAttribute('id');
50 if(!cID){
@@ -52,21 +78,25 @@
52 }
53 const c = this.e.container = container;
54 this.e.tabBar = D.addClass(D.div(),'tab-bar');
55 this.e.tabs = D.addClass(D.div(),'tabs');
56 D.append(c, this.e.tabBar, this.e.tabs);
57 const childs = EA('[data-tab-parent='+cID+']');
58 childs.forEach((c)=>this.addTab(c));
59 return this.switchToTab(this.e.tabs.firstChild);
 
 
 
60 },
61 /**
62 For the given tab element or unique selector string, returns
63 the button associated with that tab, or undefined if the
64 argument does not match any current tab.
 
65 */
66 getButtonForTab: function(tab){
67 tab = tabArg(tab);
68 var i = -1;
69 this.e.tabs.childNodes.forEach(function(e,n){
70 if(e===tab) i = n;
71 });
72 return i>=0 ? this.e.tabBar.childNodes[i] : undefined;
@@ -74,31 +104,34 @@
74 /**
75 Adds the given DOM element or unique selector as the next
76 tab in the tab container, adding a button to switch to
77 the tab. Returns this object.
78 */
79 addTab: function(tab){
 
 
 
 
 
80 tab = tabArg(tab);
81 tab.remove();
82 D.append(this.e.tabs, D.addClass(tab,'tab-panel'));
83 const lbl = tab.dataset.tabLabel || 'Tab #'+this.e.tabs.childNodes.length;
84 const btn = D.button(lbl);
85 D.append(this.e.tabBar,btn);
86 const self = this;
87 btn.addEventListener('click',function(e){
88 //stopEvent(e);
89 self.switchToTab(tab);
90 }, false);
91 return this;
92 },
93 /**
94 If the given DOM element or unique selector is one of this
95 object's tabs, the UI makes that tab the currently-visible
96 one. Returns this object.
97 */
98 switchToTab: function(tab){
99 tab = tabArg(tab);
100 const self = this;
101 this.e.tabs.childNodes.forEach((e,ndx)=>{
102 const btn = this.e.tabBar.childNodes[ndx];
103 if(e===tab){
104 setVisible(e, true);
105
--- src/fossil.tabs.js
+++ src/fossil.tabs.js
@@ -1,27 +1,29 @@
1 "use strict";
2 (function(F/*fossil object*/){
3 const E = (s)=>document.querySelector(s),
4 EA = (s)=>document.querySelectorAll(s),
5 D = F.dom;
6
 
 
 
 
 
7 /**
8 Creates a TabManager. If passed an argument, it is
9 passed to init().
10 */
11 const TabManager = function(domElem){
12 this.e = {};
13 if(domElem) this.init(domElem);
14 };
15
16 /**
17 Internal helper to normalize a method argument
18 to a tab element.
19 */
20 const tabArg = function(arg,tabMgr){
21 if('string'===typeof arg) arg = E(arg);
22 else if(tabMgr && 'number'===typeof arg && arg>=0){
23 arg = tabMgr.e.tabs.childNodes[arg];
24 }
25 return arg;
26 };
27
28 const setVisible = function(e,yes){
29 D[yes ? 'removeClass' : 'addClass'](e, 'hidden');
@@ -35,16 +37,40 @@
37 The tab container must have an 'id' attribute. This function
38 looks through the DOM for all elements which have
39 data-tab-parent=thatId. For each one it creates a button to
40 switch to that tab and moves the element into this.e.tabs.
41
42 The label for each tab is set by the data-tab-label attribute
43 of each element, defaulting to something not terribly useful.
44
45 When it's done, it auto-selects the first tab unless a tab has
46 a truthy numeric value in its data-tab-select attribute, in
47 which case the last tab to have such a property is selected.
48
49 This method must only be called once per instance. TabManagers
50 may be nested but may not share any tabs instances.
51
52 Returns this object.
53
54 DOM elements of potential interest to users:
55
56 this.e.container = the outermost container element.
57
58 this.e.tabBar = the button bar
59
60 this.e.tabs = the parent for all of the tab elements.
61
62 It is legal, within reason, to manipulate these a bit, in
63 particular this.e.container, e.g. by adding more children to
64 it. Do not remove elements from the tabs or tabBar, however, or
65 the tab state may get sorely out of sync.
66
67 CSS classes: the container element has whatever class(es) the
68 client sets on. this.e.tabBar gets the 'tab-bar' class and
69 this.e.tabs gets the 'tabs' class. It's hypothetically possible
70 to move the tabs to either side or the bottom using only CSS,
71 but it's never been tested.
72 */
73 init: function(container){
74 container = tabArg(container);
75 const cID = container.getAttribute('id');
76 if(!cID){
@@ -52,21 +78,25 @@
78 }
79 const c = this.e.container = container;
80 this.e.tabBar = D.addClass(D.div(),'tab-bar');
81 this.e.tabs = D.addClass(D.div(),'tabs');
82 D.append(c, this.e.tabBar, this.e.tabs);
83 let selectIndex = 0;
84 EA('[data-tab-parent='+cID+']').forEach((c,n)=>{
85 if(+c.dataset.tabSelect) selectIndex=n;
86 this.addTab(c);
87 });
88 return this.switchToTab(selectIndex);
89 },
90 /**
91 For the given tab element, unique selector string, or integer
92 (0-based tab number), returns the button associated with that
93 tab, or undefined if the argument does not match any current
94 tab.
95 */
96 getButtonForTab: function(tab){
97 tab = tabArg(tab,this);
98 var i = -1;
99 this.e.tabs.childNodes.forEach(function(e,n){
100 if(e===tab) i = n;
101 });
102 return i>=0 ? this.e.tabBar.childNodes[i] : undefined;
@@ -74,31 +104,34 @@
104 /**
105 Adds the given DOM element or unique selector as the next
106 tab in the tab container, adding a button to switch to
107 the tab. Returns this object.
108 */
109 addTab: function f(tab){
110 if(!f.click){
111 f.click = function(e){
112 e.target.$manager.switchToTab(e.target.$tab);
113 };
114 }
115 tab = tabArg(tab);
116 tab.remove();
117 D.append(this.e.tabs, D.addClass(tab,'tab-panel'));
118 const lbl = tab.dataset.tabLabel || 'Tab #'+(this.e.tabs.childNodes.length-1);
119 const btn = D.button(lbl);
120 D.append(this.e.tabBar,btn);
121 btn.$manager = this;
122 btn.$tab = tab;
123 btn.addEventListener('click', f.click, false);
 
 
124 return this;
125 },
126 /**
127 If the given DOM element, unique selector, or integer (0-based
128 tab number) is one of this object's tabs, the UI makes that tab
129 the currently-visible one. Returns this object.
130 */
131 switchToTab: function(tab){
132 tab = tabArg(tab,this);
133 const self = this;
134 this.e.tabs.childNodes.forEach((e,ndx)=>{
135 const btn = this.e.tabBar.childNodes[ndx];
136 if(e===tab){
137 setVisible(e, true);
138

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button