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.
Commit
9085ec2351d42966153cb6b13ed15f0c4a20611e4fd901694898857b8c696496
Parent
2abc1ff42a4d3e1…
5 files changed
+19
-19
+16
-12
+28
-30
+8
-7
+60
-27
+19
-19
| --- src/default_css.txt | ||
| +++ src/default_css.txt | ||
| @@ -1004,30 +1004,30 @@ | ||
| 1004 | 1004 | // applicability: |
| 1005 | 1005 | .flex-container { |
| 1006 | 1006 | display: flex; |
| 1007 | 1007 | } |
| 1008 | 1008 | .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; | |
| 1013 | 1013 | } |
| 1014 | 1014 | .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; | |
| 1019 | 1019 | } |
| 1020 | 1020 | .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; | |
| 1025 | 1025 | } |
| 1026 | 1026 | .flex-container.column.stretch { |
| 1027 | - align-items: stretch; | |
| 1028 | - margin: 0; | |
| 1027 | + align-items: stretch; | |
| 1028 | + margin: 0; | |
| 1029 | 1029 | } |
| 1030 | 1030 | .font-size-100 { |
| 1031 | 1031 | font-size: 100%; |
| 1032 | 1032 | } |
| 1033 | 1033 | .font-size-125 { |
| @@ -1055,11 +1055,11 @@ | ||
| 1055 | 1055 | } |
| 1056 | 1056 | .input-with-label > * { |
| 1057 | 1057 | vertical-align: middle; |
| 1058 | 1058 | } |
| 1059 | 1059 | .input-with-label > input { |
| 1060 | - margin: 0; | |
| 1060 | + margin: 0; | |
| 1061 | 1061 | } |
| 1062 | 1062 | .input-with-label > select { |
| 1063 | 1063 | margin: 0; |
| 1064 | 1064 | } |
| 1065 | 1065 | .input-with-label > input[type=text] { |
| @@ -1067,14 +1067,14 @@ | ||
| 1067 | 1067 | } |
| 1068 | 1068 | .input-with-label > textarea { |
| 1069 | 1069 | margin: 0; |
| 1070 | 1070 | } |
| 1071 | 1071 | .input-with-label > input[type=checkbox] { |
| 1072 | - vertical-align: sub; | |
| 1072 | + vertical-align: sub; | |
| 1073 | 1073 | } |
| 1074 | 1074 | .input-with-label > input[type=radio] { |
| 1075 | - vertical-align: sub; | |
| 1075 | + vertical-align: sub; | |
| 1076 | 1076 | } |
| 1077 | 1077 | .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; | |
| 1080 | 1080 | } |
| 1081 | 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 |
| --- 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 @@ | ||
| 28 | 28 | ** db, e.g. for use via an HTTP request. |
| 29 | 29 | ** |
| 30 | 30 | ** Use CheckinMiniInfo_init() to cleanly initialize one to a known |
| 31 | 31 | ** valid/empty default state. |
| 32 | 32 | ** |
| 33 | -** Memory for all non-const (char *) members is owned by the | |
| 33 | +** Memory for all non-const pointer members is owned by the | |
| 34 | 34 | ** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup(). |
| 35 | +** Similarly, each instance owns any memory for its Blob members. | |
| 35 | 36 | */ |
| 36 | 37 | struct CheckinMiniInfo { |
| 37 | 38 | Manifest * pParent; /* parent checkin. Memory is owned by this |
| 38 | 39 | object. */ |
| 39 | 40 | char *zParentUuid; /* Full UUID of pParent */ |
| @@ -168,11 +169,12 @@ | ||
| 168 | 169 | CheckinMiniInfo_init(p); |
| 169 | 170 | } |
| 170 | 171 | |
| 171 | 172 | /* |
| 172 | 173 | ** 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. | |
| 174 | 176 | */ |
| 175 | 177 | static const char * mfile_permint_mstring(int perm){ |
| 176 | 178 | switch(perm){ |
| 177 | 179 | case PERM_EXE: return " x"; |
| 178 | 180 | case PERM_LNK: return " l"; |
| @@ -189,23 +191,21 @@ | ||
| 189 | 191 | else if(strstr(zPerm,"x")) return PERM_EXE; |
| 190 | 192 | else if(strstr(zPerm,"l")) return PERM_LNK; |
| 191 | 193 | else return PERM_REG/*???*/; |
| 192 | 194 | } |
| 193 | 195 | |
| 194 | -static const char * mfile_perm_mstring(const ManifestFile * p){ | |
| 195 | - return mfile_permint_mstring(manifest_file_mperm(p)); | |
| 196 | -} | |
| 197 | - | |
| 198 | 196 | /* |
| 199 | 197 | ** Internal helper for checkin_mini() and friends. Appends an F-card |
| 200 | 198 | ** for p to pOut. |
| 201 | 199 | */ |
| 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){ | |
| 203 | 202 | if(p->zUuid){ |
| 204 | 203 | assert(*p->zUuid); |
| 205 | 204 | 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))); | |
| 207 | 207 | if(p->zPrior){ |
| 208 | 208 | assert(*p->zPrior); |
| 209 | 209 | blob_appendf(pOut, " %F\n", p->zPrior); |
| 210 | 210 | }else{ |
| 211 | 211 | blob_append(pOut, "\n", 1); |
| @@ -1676,19 +1676,19 @@ | ||
| 1676 | 1676 | "Diffs will be shown here." |
| 1677 | 1677 | "</div>"); |
| 1678 | 1678 | CX("</div>"/*#fileedit-tab-diff*/); |
| 1679 | 1679 | } |
| 1680 | 1680 | |
| 1681 | - | |
| 1682 | 1681 | /****** Commit ******/ |
| 1683 | 1682 | CX("<div id='fileedit-tab-commit' " |
| 1684 | 1683 | "data-tab-parent='fileedit-tabs' " |
| 1684 | + "data-tab-select='1' " | |
| 1685 | 1685 | "data-tab-label='Commit'" |
| 1686 | 1686 | ">"); |
| 1687 | 1687 | |
| 1688 | 1688 | { |
| 1689 | - /******* Flags/options *******/ | |
| 1689 | + /******* Commit flags/options *******/ | |
| 1690 | 1690 | CX("<div class='fileedit-options flex-container row'>"); |
| 1691 | 1691 | style_labeled_checkbox("cb-dry-run", |
| 1692 | 1692 | "dry_run", "Dry-run?", "1", |
| 1693 | 1693 | "In dry-run mode, the Save button performs " |
| 1694 | 1694 | "all work needed for saving but then rolls " |
| @@ -1735,11 +1735,11 @@ | ||
| 1735 | 1735 | "Windows", 2, |
| 1736 | 1736 | NULL); |
| 1737 | 1737 | CX("</div>"/*checkboxes*/); |
| 1738 | 1738 | } |
| 1739 | 1739 | |
| 1740 | - { /******* Comment *******/ | |
| 1740 | + { /******* Commit comment, button, and result manifest *******/ | |
| 1741 | 1741 | CX("<fieldset class='fileedit-options'>" |
| 1742 | 1742 | "<legend>Message (required)</legend><div>"); |
| 1743 | 1743 | CX("<input type='text' name='comment' " |
| 1744 | 1744 | "id='fileedit-comment'>"); |
| 1745 | 1745 | /* ^^^ adding the 'required' attribute means we cannot even |
| @@ -1782,13 +1782,17 @@ | ||
| 1782 | 1782 | style_emit_script_builtin("fossil.page.fileedit.js",0); |
| 1783 | 1783 | style_emit_script_confirmer(0); |
| 1784 | 1784 | if(blob_size(&endScript)>0){ |
| 1785 | 1785 | style_emit_script_tag(0,0); |
| 1786 | 1786 | 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", | |
| 1788 | 1792 | &endScript); |
| 1789 | 1793 | CX("})();"); |
| 1790 | 1794 | style_emit_script_tag(1,0); |
| 1791 | 1795 | } |
| 1792 | 1796 | db_end_transaction(0); |
| 1793 | 1797 | style_footer(); |
| 1794 | 1798 | } |
| 1795 | 1799 |
| --- 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 |
+28
-30
| --- src/fossil.confirmer.js | ||
| +++ src/fossil.confirmer.js | ||
| @@ -1,5 +1,6 @@ | ||
| 1 | +"use strict"; | |
| 1 | 2 | /************************************************************** |
| 2 | 3 | Confirmer is a utility which provides an alternative to confirmation |
| 3 | 4 | dialog boxes and "check this checkbox to confirm action" widgets. It |
| 4 | 5 | acts by modifying a button to require two clicks within a certain |
| 5 | 6 | time, with the second click acting as a confirmation of the first. If |
| @@ -51,11 +52,11 @@ | ||
| 51 | 52 | when it is not waiting on a timeout). When the target is activated |
| 52 | 53 | (waiting on a timeout) this class is removed. In the case of a |
| 53 | 54 | timeout, this class is added *before* the .ontimeout handler is |
| 54 | 55 | called. |
| 55 | 56 | |
| 56 | - .classActivated = optional CSS class string (default='') which is | |
| 57 | + .classWaiting = optional CSS class string (default='') which is | |
| 57 | 58 | added to the target when it is waiting on a timeout. When the target |
| 58 | 59 | leaves timeout-wait mode, this class is removed. When timeout-wait |
| 59 | 60 | mode is entered, this class is added *before* the .onactivate |
| 60 | 61 | handler is called. |
| 61 | 62 | |
| @@ -70,24 +71,21 @@ | ||
| 70 | 71 | To change the default option values, modify the |
| 71 | 72 | fossil.confirmer.defaultOpts object. |
| 72 | 73 | |
| 73 | 74 | Terse Change history: |
| 74 | 75 | |
| 75 | -20200506: | |
| 76 | +- 20200506: | |
| 76 | 77 | - Ported from jQuery to plain JS. |
| 77 | 78 | |
| 78 | 79 | - 20181112: |
| 79 | 80 | - extended to support certain INPUT elements. |
| 80 | 81 | - made default opts configurable. |
| 81 | 82 | |
| 82 | 83 | - 20070717: initial jQuery-based impl. |
| 83 | 84 | */ |
| 84 | 85 | (function(F/*the fossil object*/){ |
| 85 | - "use strict"; | |
| 86 | - | |
| 87 | 86 | F.confirmer = function f(elem,opt){ |
| 88 | - | |
| 89 | 87 | const dbg = opt.debug |
| 90 | 88 | ? function(){console.debug.apply(console,arguments)} |
| 91 | 89 | : function(){}; |
| 92 | 90 | dbg("confirmer opt =",opt); |
| 93 | 91 | if(!f.Holder){ |
| @@ -121,11 +119,14 @@ | ||
| 121 | 119 | }; |
| 122 | 120 | target.addEventListener( |
| 123 | 121 | 'click', function(){ |
| 124 | 122 | switch( self.state ) { |
| 125 | 123 | 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 | + } | |
| 127 | 128 | self.state = self.states.initial; |
| 128 | 129 | self.setClasses( false ); |
| 129 | 130 | dbg("Confirmed"); |
| 130 | 131 | updateText(self.opt.initialText); |
| 131 | 132 | if( self.opt.onconfirm ) self.opt.onconfirm.call(self.target); |
| @@ -143,31 +144,28 @@ | ||
| 143 | 144 | } |
| 144 | 145 | }, false |
| 145 | 146 | ); |
| 146 | 147 | }; |
| 147 | 148 | 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 ); | |
| 165 | 164 | } |
| 166 | 165 | } |
| 167 | 166 | } |
| 168 | - | |
| 169 | 167 | }; |
| 170 | 168 | }/*static init*/ |
| 171 | 169 | opt = F.mergeLastWins(f.defaultOpts,{ |
| 172 | 170 | initialText: ( |
| 173 | 171 | f.isInput(elem) ? elem.value : elem.innerHTML |
| @@ -185,14 +183,14 @@ | ||
| 185 | 183 | dynamically-generated, and can't reasonably be set in the |
| 186 | 184 | defaults. |
| 187 | 185 | */ |
| 188 | 186 | F.confirmer.defaultOpts = { |
| 189 | 187 | 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 | |
| 196 | 194 | }; |
| 197 | 195 | |
| 198 | 196 | })(window.fossil); |
| 199 | 197 |
| --- 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 |
+8
-7
| --- src/fossil.fetch.js | ||
| +++ src/fossil.fetch.js | ||
| @@ -23,11 +23,11 @@ | ||
| 23 | 23 | the console). |
| 24 | 24 | |
| 25 | 25 | - onerror: callback(XHR onload event | exception) |
| 26 | 26 | (default = event or exception to the console). |
| 27 | 27 | |
| 28 | - - method: 'POST' | 'GET' (default = 'GET') | |
| 28 | + - method: 'POST' | 'GET' (default = 'GET'). CASE SENSITIVE! | |
| 29 | 29 | |
| 30 | 30 | - payload: anything acceptable by XHR2.send(ARG) (DOMString, |
| 31 | 31 | Document, FormData, Blob, File, ArrayBuffer), or a plain object or |
| 32 | 32 | array, either of which gets JSON.stringify()'d. If payload is set |
| 33 | 33 | then the method is automatically set to 'POST'. If an object/array |
| @@ -60,15 +60,16 @@ | ||
| 60 | 60 | |
| 61 | 61 | Returns this object, noting that the XHR request is asynchronous, |
| 62 | 62 | and still in transit (or has yet to be sent) when that happens. |
| 63 | 63 | */ |
| 64 | 64 | window.fossil.fetch = function f(uri,opt){ |
| 65 | + const F = fossil; | |
| 65 | 66 | if(!f.onerror){ |
| 66 | 67 | f.onerror = function(e/*event or exception*/){ |
| 67 | 68 | console.error("Ajax error:",e); |
| 68 | 69 | if(e instanceof Error){ |
| 69 | - fossil.error('Exception:',e); | |
| 70 | + F.error('Exception:',e); | |
| 70 | 71 | } |
| 71 | 72 | else if(e.originalTarget && e.originalTarget.responseType==='text'){ |
| 72 | 73 | const txt = e.originalTarget.responseText; |
| 73 | 74 | try{ |
| 74 | 75 | /* The convention from the /filepage_xyz routes is to |
| @@ -75,13 +76,13 @@ | ||
| 75 | 76 | return error responses in JSON form if possible: |
| 76 | 77 | {error: "..."} |
| 77 | 78 | */ |
| 78 | 79 | const j = JSON.parse(txt); |
| 79 | 80 | console.error("Error JSON:",j); |
| 80 | - if(j.error){ fossil.error(j.error) }; | |
| 81 | + if(j.error){ F.error(j.error) }; | |
| 81 | 82 | }catch(e){/* Try harder */ |
| 82 | - fossil.error(txt) | |
| 83 | + F.error(txt) | |
| 83 | 84 | } |
| 84 | 85 | } |
| 85 | 86 | }; |
| 86 | 87 | f.onload = (r)=>console.debug('ajax response:',r); |
| 87 | 88 | } |
| @@ -89,11 +90,11 @@ | ||
| 89 | 90 | if(!opt) opt = {}; |
| 90 | 91 | else if('function'===typeof opt) opt={onload:opt}; |
| 91 | 92 | if(!opt.onload) opt.onload = f.onload; |
| 92 | 93 | if(!opt.onerror) opt.onerror = f.onerror; |
| 93 | 94 | let payload = opt.payload, jsonResponse = false; |
| 94 | - if(payload){ | |
| 95 | + if(undefined!==payload){ | |
| 95 | 96 | opt.method = 'POST'; |
| 96 | 97 | if(!(payload instanceof FormData) |
| 97 | 98 | && !(payload instanceof Document) |
| 98 | 99 | && !(payload instanceof Blob) |
| 99 | 100 | && !(payload instanceof File) |
| @@ -102,11 +103,11 @@ | ||
| 102 | 103 | || payload instanceof Array)){ |
| 103 | 104 | payload = JSON.stringify(payload); |
| 104 | 105 | opt.contentType = 'application/json'; |
| 105 | 106 | } |
| 106 | 107 | } |
| 107 | - const url=[window.fossil.repoUrl(uri,opt.urlParams)], | |
| 108 | + const url=[F.repoUrl(uri,opt.urlParams)], | |
| 108 | 109 | x=new XMLHttpRequest(); |
| 109 | 110 | if('POST'===opt.method && 'string'===typeof opt.contentType){ |
| 110 | 111 | x.setRequestHeader('Content-Type',opt.contentType); |
| 111 | 112 | } |
| 112 | 113 | x.open(opt.method||'GET', url.join(''), true); |
| @@ -131,9 +132,9 @@ | ||
| 131 | 132 | }catch(e){ |
| 132 | 133 | if(opt.onerror) opt.onerror(e); |
| 133 | 134 | } |
| 134 | 135 | } |
| 135 | 136 | } |
| 136 | - if(payload) x.send(payload); | |
| 137 | + if(undefined!==payload) x.send(payload); | |
| 137 | 138 | else x.send(); |
| 138 | 139 | return this; |
| 139 | 140 | }; |
| 140 | 141 |
| --- 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 @@ | ||
| 1 | 1 | "use strict"; |
| 2 | -(function(F){ | |
| 2 | +(function(F/*fossil object*/){ | |
| 3 | 3 | const E = (s)=>document.querySelector(s), |
| 4 | 4 | EA = (s)=>document.querySelectorAll(s), |
| 5 | 5 | D = F.dom; |
| 6 | 6 | |
| 7 | - const stopEvent = function(e){ | |
| 8 | - e.preventDefault(); | |
| 9 | - e.stopPropagation(); | |
| 10 | - }; | |
| 11 | - | |
| 12 | 7 | /** |
| 13 | 8 | Creates a TabManager. If passed an argument, it is |
| 14 | 9 | passed to init(). |
| 15 | 10 | */ |
| 16 | 11 | const TabManager = function(domElem){ |
| 17 | 12 | this.e = {}; |
| 18 | 13 | if(domElem) this.init(domElem); |
| 19 | 14 | }; |
| 20 | 15 | |
| 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){ | |
| 22 | 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 | + } | |
| 23 | 25 | return arg; |
| 24 | 26 | }; |
| 25 | 27 | |
| 26 | 28 | const setVisible = function(e,yes){ |
| 27 | 29 | D[yes ? 'removeClass' : 'addClass'](e, 'hidden'); |
| @@ -35,16 +37,40 @@ | ||
| 35 | 37 | The tab container must have an 'id' attribute. This function |
| 36 | 38 | looks through the DOM for all elements which have |
| 37 | 39 | data-tab-parent=thatId. For each one it creates a button to |
| 38 | 40 | switch to that tab and moves the element into this.e.tabs. |
| 39 | 41 | |
| 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. | |
| 41 | 48 | |
| 42 | 49 | 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. | |
| 44 | 51 | |
| 45 | 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. | |
| 46 | 72 | */ |
| 47 | 73 | init: function(container){ |
| 48 | 74 | container = tabArg(container); |
| 49 | 75 | const cID = container.getAttribute('id'); |
| 50 | 76 | if(!cID){ |
| @@ -52,21 +78,25 @@ | ||
| 52 | 78 | } |
| 53 | 79 | const c = this.e.container = container; |
| 54 | 80 | this.e.tabBar = D.addClass(D.div(),'tab-bar'); |
| 55 | 81 | this.e.tabs = D.addClass(D.div(),'tabs'); |
| 56 | 82 | 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); | |
| 60 | 89 | }, |
| 61 | 90 | /** |
| 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. | |
| 65 | 95 | */ |
| 66 | 96 | getButtonForTab: function(tab){ |
| 67 | - tab = tabArg(tab); | |
| 97 | + tab = tabArg(tab,this); | |
| 68 | 98 | var i = -1; |
| 69 | 99 | this.e.tabs.childNodes.forEach(function(e,n){ |
| 70 | 100 | if(e===tab) i = n; |
| 71 | 101 | }); |
| 72 | 102 | return i>=0 ? this.e.tabBar.childNodes[i] : undefined; |
| @@ -74,31 +104,34 @@ | ||
| 74 | 104 | /** |
| 75 | 105 | Adds the given DOM element or unique selector as the next |
| 76 | 106 | tab in the tab container, adding a button to switch to |
| 77 | 107 | the tab. Returns this object. |
| 78 | 108 | */ |
| 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 | + } | |
| 80 | 115 | tab = tabArg(tab); |
| 81 | 116 | tab.remove(); |
| 82 | 117 | 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); | |
| 84 | 119 | const btn = D.button(lbl); |
| 85 | 120 | 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); | |
| 91 | 124 | return this; |
| 92 | 125 | }, |
| 93 | 126 | /** |
| 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. | |
| 97 | 130 | */ |
| 98 | 131 | switchToTab: function(tab){ |
| 99 | - tab = tabArg(tab); | |
| 132 | + tab = tabArg(tab,this); | |
| 100 | 133 | const self = this; |
| 101 | 134 | this.e.tabs.childNodes.forEach((e,ndx)=>{ |
| 102 | 135 | const btn = this.e.tabBar.childNodes[ndx]; |
| 103 | 136 | if(e===tab){ |
| 104 | 137 | setVisible(e, true); |
| 105 | 138 |
| --- 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 |