Merge branch 'js-utils-cleanup'
This commit is contained in:
commit
55c1385e3e
@ -7,7 +7,7 @@ import Import.NoFoundation
|
||||
import Database.Persist.Sql (SqlBackendCanRead)
|
||||
|
||||
import Utils.Form
|
||||
|
||||
|
||||
import Data.CaseInsensitive (CI)
|
||||
import qualified Data.CaseInsensitive as CI
|
||||
|
||||
@ -54,4 +54,4 @@ dummyLogin = AuthPlugin{..}
|
||||
apDispatch _ _ = notFound
|
||||
apLogin toMaster = do
|
||||
(login, loginEnctype) <- handlerToWidget . generateFormPost $ renderAForm FormStandard dummyForm
|
||||
$(widgetFile "widgets/dummy-login-form")
|
||||
$(widgetFile "widgets/dummy-login-form/dummy-login-form")
|
||||
|
||||
@ -36,7 +36,7 @@ data CampusMessage = MsgCampusIdentNote
|
||||
| MsgCampusSubmit
|
||||
| MsgCampusInvalidCredentials
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
||||
|
||||
|
||||
|
||||
findUser :: LdapConf -> Ldap -> Text -> [Ldap.Attr] -> IO [Ldap.SearchEntry]
|
||||
findUser LdapConf{..} ldap campusIdent = Ldap.search ldap ldapBase userSearchSettings userFilter
|
||||
@ -48,7 +48,7 @@ findUser LdapConf{..} ldap campusIdent = Ldap.search ldap ldapBase userSearchSet
|
||||
, Ldap.time ldapSearchTimeout
|
||||
, Ldap.derefAliases Ldap.DerefAlways
|
||||
]
|
||||
|
||||
|
||||
userPrincipalName :: Ldap.Attr
|
||||
userPrincipalName = Ldap.Attr "userPrincipalName"
|
||||
|
||||
@ -105,7 +105,7 @@ campusLogin conf@LdapConf{..} pool = AuthPlugin{..}
|
||||
apDispatch _ _ = notFound
|
||||
apLogin toMaster = do
|
||||
(login, loginEnctype) <- handlerToWidget . generateFormPost $ renderAForm FormStandard campusForm
|
||||
$(widgetFile "widgets/campus-login-form")
|
||||
$(widgetFile "widgets/campus-login/campus-login-form")
|
||||
|
||||
data CampusUserException = CampusUserLdapError LdapPoolError
|
||||
| CampusUserHostNotResolved String
|
||||
|
||||
@ -35,7 +35,7 @@ hashForm = HashLogin
|
||||
<*> areq passwordField (fslpI MsgPWHashPassword "Passwort") Nothing
|
||||
<* submitButton
|
||||
|
||||
|
||||
|
||||
hashLogin :: ( YesodAuth site
|
||||
, YesodPersist site
|
||||
, SqlBackendCanRead (YesodPersistBackend site)
|
||||
@ -90,5 +90,5 @@ hashLogin pwHashAlgo = AuthPlugin{..}
|
||||
apDispatch _ _ = notFound
|
||||
apLogin toMaster = do
|
||||
(login, loginEnctype) <- handlerToWidget . generateFormPost $ renderAForm FormStandard hashForm
|
||||
$(widgetFile "widgets/hash-login-form")
|
||||
$(widgetFile "widgets/hash-login-form/hash-login-form")
|
||||
|
||||
|
||||
@ -982,19 +982,19 @@ siteLayout' headingOverride widget = do
|
||||
-- you to use normal widget features in default-layout.
|
||||
|
||||
navbar :: Widget
|
||||
navbar = $(widgetFile "widgets/navbar")
|
||||
navbar = $(widgetFile "widgets/navbar/navbar")
|
||||
asidenav :: Widget
|
||||
asidenav = $(widgetFile "widgets/asidenav")
|
||||
asidenav = $(widgetFile "widgets/asidenav/asidenav")
|
||||
footer :: Widget
|
||||
footer = $(widgetFile "widgets/footer")
|
||||
footer = $(widgetFile "widgets/footer/footer")
|
||||
alerts :: Widget
|
||||
alerts = $(widgetFile "widgets/alerts/alerts")
|
||||
contentHeadline :: Maybe Widget
|
||||
contentHeadline = headingOverride <|> (pageHeading =<< mcurrentRoute)
|
||||
breadcrumbsWgt :: Widget
|
||||
breadcrumbsWgt = $(widgetFile "widgets/breadcrumbs")
|
||||
breadcrumbsWgt = $(widgetFile "widgets/breadcrumbs/breadcrumbs")
|
||||
pageaction :: Widget
|
||||
pageaction = $(widgetFile "widgets/pageaction")
|
||||
pageaction = $(widgetFile "widgets/pageaction/pageaction")
|
||||
-- functions to determine if there are page-actions (primary or secondary)
|
||||
hasPageActions, hasSecondaryPageActions, hasPrimaryPageActions :: Bool
|
||||
hasPageActions = hasPrimaryPageActions || hasSecondaryPageActions
|
||||
@ -1002,25 +1002,35 @@ siteLayout' headingOverride widget = do
|
||||
hasPrimaryPageActions = any (is _PageActionPrime) $ toListOf (traverse . _1 . _menuItemType) menuTypes
|
||||
|
||||
pc <- widgetToPageContent $ do
|
||||
addScript $ StaticR js_vendor_zepto_js
|
||||
-- 3rd party
|
||||
addScript $ StaticR js_vendor_flatpickr_js
|
||||
addScript $ StaticR js_polyfills_fetchPolyfill_js
|
||||
addScript $ StaticR js_polyfills_urlPolyfill_js
|
||||
addScript $ StaticR js_utils_featureChecker_js
|
||||
addScript $ StaticR js_utils_tabber_js
|
||||
addScript $ StaticR js_utils_alerts_js
|
||||
addScript $ StaticR js_vendor_zepto_js
|
||||
addStylesheet $ StaticR css_vendor_flatpickr_css
|
||||
addStylesheet $ StaticR css_vendor_fontawesome_css
|
||||
-- fonts
|
||||
addStylesheet $ StaticR css_fonts_css
|
||||
addStylesheet $ StaticR css_utils_tabber_css
|
||||
-- polyfills
|
||||
addScript $ StaticR js_polyfills_fetchPolyfill_js
|
||||
addScript $ StaticR js_polyfills_urlPolyfill_js
|
||||
-- JavaScript utils
|
||||
addScript $ StaticR js_utils_alerts_js
|
||||
addScript $ StaticR js_utils_asidenav_js
|
||||
addScript $ StaticR js_utils_asyncTable_js
|
||||
addScript $ StaticR js_utils_form_js
|
||||
addScript $ StaticR js_utils_inputs_js
|
||||
addScript $ StaticR js_utils_setup_js
|
||||
addScript $ StaticR js_utils_showHide_js
|
||||
addScript $ StaticR js_utils_tabber_js
|
||||
addStylesheet $ StaticR css_utils_alerts_scss
|
||||
addStylesheet $ StaticR css_utils_asidenav_scss
|
||||
addStylesheet $ StaticR css_utils_form_scss
|
||||
addStylesheet $ StaticR css_utils_inputs_scss
|
||||
addStylesheet $ StaticR css_utils_showHide_scss
|
||||
addStylesheet $ StaticR css_utils_tabber_scss
|
||||
addStylesheet $ StaticR css_utils_tooltip_scss
|
||||
-- widgets
|
||||
$(widgetFile "default-layout")
|
||||
$(widgetFile "standalone/modal")
|
||||
$(widgetFile "standalone/showHide")
|
||||
$(widgetFile "standalone/inputs")
|
||||
$(widgetFile "standalone/tooltip")
|
||||
$(widgetFile "standalone/tabber")
|
||||
$(widgetFile "standalone/datepicker")
|
||||
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
|
||||
|
||||
|
||||
|
||||
@ -162,7 +162,7 @@ colRating = sortable (Just "rating") (i18nCell MsgRating) $ \DBRow{ dbrOutput=(E
|
||||
cid <- encrypt subId
|
||||
return $ CSubmissionR tid ssh csh sheetName cid CorrectionR
|
||||
in mconcat
|
||||
[ anchorCellM mkRoute $(widgetFile "widgets/rating")
|
||||
[ anchorCellM mkRoute $(widgetFile "widgets/rating/rating")
|
||||
, writerCell $ do
|
||||
let
|
||||
summary :: SheetTypeSummary
|
||||
|
||||
@ -297,7 +297,7 @@ registerForm registered msecret extra = do
|
||||
(Just _) | not registered -> bimap Just Just <$> mreq textField (fslpI MsgCourseSecret "Code") Nothing
|
||||
_ -> return (Nothing,Nothing)
|
||||
(btnRes, btnView) <- mreq (buttonField $ bool BtnRegister BtnDeregister registered) "buttonField ignores settings anyway" Nothing
|
||||
let widget = $(widgetFile "widgets/registerForm")
|
||||
let widget = $(widgetFile "widgets/register-form/register-form")
|
||||
let msecretRes | Just res <- msecretRes' = Just <$> res
|
||||
| otherwise = FormSuccess Nothing
|
||||
return (btnRes *> ((==msecret) <$> msecretRes), widget) -- checks that correct button was pressed, and ignores result of btnRes
|
||||
|
||||
@ -221,7 +221,7 @@ getProfileDataR = do
|
||||
-- Delete Button
|
||||
(btnWdgt, btnEnctype) <- generateFormPost (buttonForm :: Form ButtonDelete)
|
||||
defaultLayout $ do
|
||||
let delWdgt = $(widgetFile "widgets/data-delete")
|
||||
let delWdgt = $(widgetFile "widgets/data-delete/data-delete")
|
||||
$(widgetFile "profileData")
|
||||
$(widgetFile "dsgvDisclaimer")
|
||||
|
||||
|
||||
@ -205,7 +205,7 @@ getSheetListR tid ssh csh = do
|
||||
mkRoute = do
|
||||
cid' <- mkCid
|
||||
return $ CSubmissionR tid ssh csh sheetName cid' CorrectionR
|
||||
acell = anchorCellM mkRoute $(widgetFile "widgets/rating")
|
||||
acell = anchorCellM mkRoute $(widgetFile "widgets/rating/rating")
|
||||
in cellTell acell $ stats submissionRatingPoints
|
||||
|
||||
, sortable Nothing -- (Just "percent")
|
||||
|
||||
@ -182,7 +182,7 @@ postAdminUserR uuid = do
|
||||
return (school, cbAdmin, cbLecturer)
|
||||
let result = forM boxRights $ \(Entity sid _, (resAdmin,_), (resLecturer, _)) ->
|
||||
(,,) <$> pure sid <*> resAdmin <*> resLecturer
|
||||
return (result,$(widgetFile "widgets/user-rights-form"))
|
||||
return (result,$(widgetFile "widgets/user-rights-form/user-rights-form"))
|
||||
let userRightsAction changes = do
|
||||
void . runDB $
|
||||
forM changes $ \(sid, userAdmin, userLecturer) ->
|
||||
|
||||
@ -90,9 +90,9 @@ getDeleteR DeleteRoute{..} = do
|
||||
(deleteFormWdgt, deleteFormEnctype) <- generateFormPost $ confirmForm' drRecords confirmString
|
||||
|
||||
Just targetRoute <- getCurrentRoute
|
||||
|
||||
|
||||
sendResponse =<<
|
||||
defaultLayout $(widgetFile "widgets/delete-confirmation")
|
||||
defaultLayout $(widgetFile "widgets/delete-confirmation/delete-confirmation")
|
||||
|
||||
|
||||
|
||||
|
||||
@ -591,9 +591,9 @@ multiAction acts defAction = do
|
||||
widgets <- mapM mToWidget results
|
||||
let actionWidgets = Map.foldrWithKey accWidget [] widgets
|
||||
accWidget _act Nothing = id
|
||||
accWidget act (Just w) = cons $(widgetFile "widgets/multiAction")
|
||||
accWidget act (Just w) = cons $(widgetFile "widgets/multi-action/multi-action")
|
||||
actionResults = Map.map fst results
|
||||
return ((actionResults Map.!) =<< actionRes, $(widgetFile "widgets/multiActionCollect"))
|
||||
return ((actionResults Map.!) =<< actionRes, $(widgetFile "widgets/multi-action/multi-action-collect"))
|
||||
|
||||
multiActionA :: (RenderMessage UniWorX action, PathPiece action, Ord action, Eq action)
|
||||
=> FieldSettings UniWorX
|
||||
|
||||
@ -25,7 +25,7 @@ gradeSummaryWidget title sts =
|
||||
hasMarkedPasses = positiveSum $ numMarkedPasses sumSummaries
|
||||
hasPoints = positiveSum $ numSheetsPoints sumSummaries
|
||||
hasMarkedPoints = positiveSum $ numMarkedPoints sumSummaries
|
||||
rowWdgts = [ $(widgetFile "widgets/gradingSummaryRow")
|
||||
rowWdgts = [ $(widgetFile "widgets/grading-summary/grading-summary-row")
|
||||
| (sumHeader,summary) <-
|
||||
[ (MsgSheetTypeNormal' ,normalSummary)
|
||||
, (MsgSheetTypeBonus' ,bonusSummary)
|
||||
@ -33,4 +33,4 @@ gradeSummaryWidget title sts =
|
||||
] ]
|
||||
in if 0 == numSheets sumSummaries
|
||||
then mempty
|
||||
else $(widgetFile "widgets/gradingSummary")
|
||||
else $(widgetFile "widgets/grading-summary/grading-summary")
|
||||
|
||||
@ -576,7 +576,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
||||
, let t' = toPathPiece $ SortingSetting t d
|
||||
]
|
||||
wIdent :: Text -> Text
|
||||
wIdent = toPathPiece . WithIdent dbtIdent
|
||||
wIdent = toPathPiece . WithIdent dbtIdent
|
||||
dbsAttrs'
|
||||
| not $ null dbtIdent = ("id", dbtIdent) : dbsAttrs
|
||||
| otherwise = dbsAttrs
|
||||
@ -802,7 +802,7 @@ cellTooltip :: (RenderMessage UniWorX msg, IsDBTable m a) => msg -> DBCell m a -
|
||||
cellTooltip msg = cellContents.mapped %~ (<> tipWdgt)
|
||||
where
|
||||
tipWdgt = [whamlet|
|
||||
<div .js-tooltip>
|
||||
<div .tooltip>
|
||||
<div .tooltip__handle>
|
||||
<div .tooltip__content>_{msg}
|
||||
|]
|
||||
|
||||
@ -9,7 +9,7 @@ modal modalTrigger modalContent = do
|
||||
let modalDynamic = isLeft modalContent
|
||||
modalId <- newIdent
|
||||
triggerId <- newIdent
|
||||
$(widgetFile "widgets/modal")
|
||||
$(widgetFile "widgets/modal/modal")
|
||||
case modalContent of
|
||||
Left route -> do
|
||||
route' <- toTextUrl route
|
||||
|
||||
@ -41,7 +41,7 @@ data FormLayout = FormStandard | FormDBTableFilter | FormDBTablePagesize
|
||||
renderAForm :: Monad m => FormLayout -> FormRender m a
|
||||
renderAForm formLayout aform fragment = do
|
||||
(res, ($ []) -> fieldViews) <- aFormToForm aform
|
||||
let widget = $(widgetFile "widgets/form")
|
||||
let widget = $(widgetFile "widgets/form/form")
|
||||
return (res, widget)
|
||||
|
||||
--------------------
|
||||
@ -367,7 +367,7 @@ reorderField optList = Field{..}
|
||||
isSel n = (==) (either (const $ map optionInternalValue olOptions) id val !! pred n) . optionInternalValue
|
||||
nums = map (id &&& withNum theId) [1..length olOptions]
|
||||
withNum t n = tshow n <> "." <> t
|
||||
$(widgetFile "widgets/permutation")
|
||||
$(widgetFile "widgets/permutation/permutation")
|
||||
|
||||
optionsFinite :: ( MonadHandler m
|
||||
, Finite a
|
||||
|
||||
@ -4,12 +4,11 @@
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
flex: 0 0 0;
|
||||
flex-basis: var(--asidenav-width-lg, 20%);
|
||||
min-height: calc(100% - var(--header-height));
|
||||
transition: all .2s ease-out;
|
||||
width: var(--asidenav-width-lg, 20%);
|
||||
height: 100%;
|
||||
flex: 0 0 0;
|
||||
flex-basis: var(--asidenav-width-lg, 20%);
|
||||
transition: all .2s ease-out;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
@ -72,6 +71,14 @@
|
||||
|
||||
.asidenav {
|
||||
color: var(--color-font);
|
||||
min-height: calc(100% - var(--header-height));
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__box {
|
||||
@ -183,9 +190,7 @@
|
||||
/* LIST-ITEM */
|
||||
|
||||
.asidenav__list-item {
|
||||
position: relative;
|
||||
color: var(--color-font);
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
@ -207,10 +212,8 @@
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.asidenav__nested-list {
|
||||
transform: translateX(100%);
|
||||
opacity: 1;
|
||||
width: 200px;
|
||||
.asidenav__nested-list-wrapper {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -242,7 +245,7 @@
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
padding: 7px 10px;
|
||||
padding: 8px 3px;
|
||||
justify-content: flex-start;
|
||||
color: var(--color-font);
|
||||
width: 100%;
|
||||
@ -258,17 +261,17 @@
|
||||
}
|
||||
|
||||
/* hover sub-menus */
|
||||
.asidenav__nested-list {
|
||||
.asidenav__nested-list-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
display: none;
|
||||
color: var(--color-font);
|
||||
transform: translateX(0);
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
z-index: -1;
|
||||
box-shadow: 0 0 13px rgba(0, 0, 0, 0.4);
|
||||
background-color: var(--color-grey-light);
|
||||
box-shadow: 1px 1px 1px 0px var(--color-grey);
|
||||
}
|
||||
|
||||
.asidenav__nested-list {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
@media (max-width: 425px) {
|
||||
@ -280,19 +283,16 @@
|
||||
|
||||
.asidenav__nested-list-item {
|
||||
position: relative;
|
||||
color: var(--color-lightwhite);
|
||||
background-color: var(--color-dark);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-darker);
|
||||
background-color: var(--color-lightwhite);
|
||||
}
|
||||
|
||||
.asidenav__link-wrapper {
|
||||
padding-left: 13px;
|
||||
padding-right: 13px;
|
||||
border-left: 20px solid white;
|
||||
transition: all .2s ease;
|
||||
color: var(--color-lightwhite);
|
||||
color: var(--color-font);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
.js-show-hide {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.js-show-hide__toggle {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
@ -17,7 +17,7 @@
|
||||
text-align: center;
|
||||
padding: 0 13px;
|
||||
margin: 0 2px;
|
||||
background-color: #b3b7c1;
|
||||
background-color: var(--color-dark);
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
@ -35,5 +35,5 @@
|
||||
.tab-opener.tab-visible {
|
||||
background-color: transparent;
|
||||
color: rgb(52, 48, 58);
|
||||
border-bottom-color: #5F98C2;
|
||||
border-bottom-color: var(--color-primary);
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
.js-tooltip {
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 1.5rem;
|
||||
@ -67,7 +67,7 @@
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.js-tooltip {
|
||||
.tooltip {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
var ALERTS_CLASS = 'alerts';
|
||||
var ALERTS_TOGGLER_CLASS = 'alerts__toggler';
|
||||
var ALERTS_TOGGLER_VISIBLE_CLASS = 'alerts__toggler--visible';
|
||||
var ALERTS_TOGGLER_APPEAR_DELAY = 120;
|
||||
@ -18,7 +19,11 @@
|
||||
window.utils.alerts = function(alertsEl) {
|
||||
|
||||
if (alertsEl.classList.contains(JS_INITIALIZED_CLASS)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!alertsEl || !alertsEl.classList.contains(ALERTS_CLASS)) {
|
||||
throw new Error('utils.alerts has to be called with alerts element');
|
||||
}
|
||||
|
||||
var togglerCheckRequested = false;
|
||||
|
||||
59
static/js/utils/asidenav.js
Normal file
59
static/js/utils/asidenav.js
Normal file
@ -0,0 +1,59 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
var FAVORITES_BTN_CLASS = 'navbar__list-item--favorite';
|
||||
var FAVORITES_BTN_ACTIVE_CLASS = 'navbar__list-item--active';
|
||||
var ASIDENAV_EXPANDED_CLASS = 'main__aside--expanded';
|
||||
var ASIDENAV_LIST_ITEM_CLASS = 'asidenav__list-item';
|
||||
var ASIDENAV_SUBMENU_CLASS = 'asidenav__nested-list-wrapper';
|
||||
|
||||
window.utils.aside = function(asideEl) {
|
||||
|
||||
if (!asideEl) {
|
||||
throw new Error('asideEl not defined');
|
||||
}
|
||||
|
||||
function initFavoritesButton() {
|
||||
var favoritesBtn = document.querySelector('.' + FAVORITES_BTN_CLASS);
|
||||
favoritesBtn.addEventListener('click', function(event) {
|
||||
favoritesBtn.classList.toggle(FAVORITES_BTN_ACTIVE_CLASS);
|
||||
asideEl.classList.toggle(ASIDENAV_EXPANDED_CLASS);
|
||||
event.preventDefault();
|
||||
}, true);
|
||||
}
|
||||
|
||||
function initAsidenavSubmenus() {
|
||||
var asidenavLinksWithSubmenus = Array.from(asideEl.querySelectorAll('.' + ASIDENAV_LIST_ITEM_CLASS))
|
||||
.map(function(listItem) {
|
||||
var submenu = listItem.querySelector('.' + ASIDENAV_SUBMENU_CLASS);
|
||||
return { listItem, submenu };
|
||||
}).filter(function(union) {
|
||||
return union.submenu !== null;
|
||||
});
|
||||
|
||||
asidenavLinksWithSubmenus.forEach(function(union) {
|
||||
union.listItem.addEventListener('mouseover', createMouseoverHandler(union));
|
||||
});
|
||||
}
|
||||
|
||||
function createMouseoverHandler(union) {
|
||||
return function mouseoverHanlder(event) {
|
||||
var rectListItem = union.listItem.getBoundingClientRect();
|
||||
var rectSubMenu = union.submenu.getBoundingClientRect();
|
||||
|
||||
union.submenu.style.left = (rectListItem.left + rectListItem.width) + 'px';
|
||||
if (window.innerHeight - rectListItem.top < rectSubMenu.height) {
|
||||
union.submenu.style.top = (rectListItem.top + rectListItem.height - rectSubMenu.height) + 'px';
|
||||
} else {
|
||||
union.submenu.style.top = rectListItem.top + 'px';
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
initFavoritesButton();
|
||||
initAsidenavSubmenus();
|
||||
};
|
||||
})();
|
||||
202
static/js/utils/asyncTable.js
Normal file
202
static/js/utils/asyncTable.js
Normal file
@ -0,0 +1,202 @@
|
||||
(function collonadeClosure() {
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
var HEADER_HEIGHT = 80;
|
||||
var RESET_OPTIONS = [ 'scrollTo' ];
|
||||
|
||||
window.utils.asyncTable = function(wrapper, options) {
|
||||
|
||||
options = options || {};
|
||||
var tableIdent = options.dbtIdent;
|
||||
var shortCircuitHeader = options ? options.headerDBTableShortcircuit : null;
|
||||
|
||||
var ths = [];
|
||||
var pageLinks = [];
|
||||
var pagesizeForm;
|
||||
var scrollTable;
|
||||
|
||||
function init() {
|
||||
var table = wrapper.querySelector('#' + tableIdent);
|
||||
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollTable = wrapper.querySelector('.scrolltable');
|
||||
|
||||
// sortable table headers
|
||||
ths = Array.from(table.querySelectorAll('th.sortable')).map(function(th) {
|
||||
return { element: th };
|
||||
});
|
||||
|
||||
// pagination links
|
||||
var pagination = wrapper.querySelector('#' + tableIdent + '-pagination');
|
||||
if (pagination) {
|
||||
pageLinks = Array.from(pagination.querySelectorAll('.page-link')).map(function(link) {
|
||||
return { element: link };
|
||||
});
|
||||
}
|
||||
|
||||
// pagesize form
|
||||
pagesizeForm = wrapper.querySelector('#' + tableIdent + '-pagesize-form');
|
||||
|
||||
// take options into account
|
||||
if (options && options.scrollTo) {
|
||||
window.scrollTo(options.scrollTo);
|
||||
}
|
||||
|
||||
if (options && options.horizPos && scrollTable) {
|
||||
scrollTable.scrollLeft = options.horizPos;
|
||||
}
|
||||
|
||||
setupListeners();
|
||||
wrapper.classList.add('js-initialized');
|
||||
}
|
||||
|
||||
function setupListeners() {
|
||||
ths.forEach(function(th) {
|
||||
th.clickHandler = function(event) {
|
||||
var boundClickHandler = clickHandler.bind(this);
|
||||
var horizPos = (scrollTable || {}).scrollLeft;
|
||||
boundClickHandler(event, { horizPos });
|
||||
};
|
||||
th.element.addEventListener('click', th.clickHandler);
|
||||
});
|
||||
|
||||
pageLinks.forEach(function(link) {
|
||||
link.clickHandler = function(event) {
|
||||
var boundClickHandler = clickHandler.bind(this);
|
||||
var tableBoundingRect = scrollTable.getBoundingClientRect();
|
||||
var tableOptions = {};
|
||||
if (tableBoundingRect.top < HEADER_HEIGHT) {
|
||||
tableOptions.scrollTo = {
|
||||
top: (scrollTable.offsetTop || 0) - HEADER_HEIGHT,
|
||||
left: scrollTable.offsetLeft || 0,
|
||||
behavior: 'smooth',
|
||||
};
|
||||
}
|
||||
boundClickHandler(event, tableOptions);
|
||||
}
|
||||
link.element.addEventListener('click', link.clickHandler);
|
||||
});
|
||||
|
||||
if (pagesizeForm) {
|
||||
var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]');
|
||||
pagesizeSelect.addEventListener('change', changePagesizeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function removeListeners() {
|
||||
ths.forEach(function(th) {
|
||||
th.element.removeEventListener('click', th.clickHandler);
|
||||
});
|
||||
|
||||
pageLinks.forEach(function(link) {
|
||||
link.element.removeEventListener('click', link.clickHandler);
|
||||
});
|
||||
|
||||
if (pagesizeForm) {
|
||||
var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]')
|
||||
pagesizeSelect.removeEventListener('change', changePagesizeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function clickHandler(event, tableOptions) {
|
||||
event.preventDefault();
|
||||
var url = new URL(window.location.origin + window.location.pathname + getClickDestination(this));
|
||||
updateTableFrom(url, tableOptions);
|
||||
}
|
||||
|
||||
function getClickDestination(el) {
|
||||
if (!el.querySelector('a')) {
|
||||
return '';
|
||||
}
|
||||
return el.querySelector('a').getAttribute('href');
|
||||
}
|
||||
|
||||
function changePagesizeHandler(event) {
|
||||
var currentTableUrl = options.currentUrl || window.location.href;
|
||||
var url = getUrlWithUpdatedPagesize(currentTableUrl, event.target.value);
|
||||
url = new URL(getUrlWithResetPagenumber(url));
|
||||
updateTableFrom(url);
|
||||
}
|
||||
|
||||
function getUrlWithUpdatedPagesize(url, pagesize) {
|
||||
if (url.indexOf('pagesize') >= 0) {
|
||||
return url.replace(/pagesize=(\d+|all)/, 'pagesize=' + pagesize);
|
||||
} else if (url.indexOf('?') >= 0) {
|
||||
return url += '&' + tableIdent + '-pagesize=' + pagesize;
|
||||
}
|
||||
|
||||
return url += '?' + tableIdent + '-pagesize=' + pagesize;
|
||||
}
|
||||
|
||||
function getUrlWithResetPagenumber(url) {
|
||||
return url.replace(/-page=\d+/, '-page=0');
|
||||
}
|
||||
|
||||
// fetches new sorted table from url with params and replaces contents of current table
|
||||
function updateTableFrom(url, tableOptions) {
|
||||
tableOptions = tableOptions || {};
|
||||
fetch(url, {
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Accept': 'text/html',
|
||||
[shortCircuitHeader]: tableIdent
|
||||
}
|
||||
}).then(function(response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Looks like there was a problem fetching ' + url.href + '. Status Code: ' + response.status);
|
||||
}
|
||||
return response.text();
|
||||
}).then(function(data) {
|
||||
tableOptions.currentUrl = url.href;
|
||||
removeListeners();
|
||||
updateWrapperContents(data, tableOptions);
|
||||
}).catch(function(err) {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
function updateWrapperContents(newHtml, tableOptions) {
|
||||
tableOptions = tableOptions || {};
|
||||
wrapper.innerHTML = newHtml;
|
||||
wrapper.classList.remove("js-initialized");
|
||||
|
||||
// setup the wrapper and its components to behave async again
|
||||
window.utils.teardown('asyncTable');
|
||||
window.utils.teardown('form');
|
||||
// merge global options and table specific options
|
||||
var resetOptions = {};
|
||||
Object.keys(options)
|
||||
.filter(function(key) {
|
||||
return !RESET_OPTIONS.includes(key);
|
||||
})
|
||||
.forEach(function(key) {
|
||||
resetOptions[key] = options[key];
|
||||
});
|
||||
var combinedOptions = {};
|
||||
combinedOptions = Object.keys(tableOptions)
|
||||
.filter(function(key) {
|
||||
return tableOptions.hasOwnProperty(key);
|
||||
})
|
||||
.map(function(key) {
|
||||
return { key, value: tableOptions[key] }
|
||||
})
|
||||
.reduce(function(cumulatedOpts, opt) {
|
||||
cumulatedOpts[opt.key] = opt.value;
|
||||
return cumulatedOpts;
|
||||
}, resetOptions);
|
||||
|
||||
window.utils.setup('asyncTable', wrapper, combinedOptions);
|
||||
|
||||
Array.from(wrapper.querySelectorAll('form')).forEach(function(form) {
|
||||
window.utils.setup('form', form);
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
};
|
||||
})();
|
||||
@ -1,4 +0,0 @@
|
||||
window.addEventListener('touchstart', function onFirstTouch() {
|
||||
document.body.classList.add('touch-supported');
|
||||
window.removeEventListener('touchstart', onFirstTouch, false);
|
||||
}, false);
|
||||
@ -3,13 +3,57 @@
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
var JS_INITIALIZED = 'js-initialized';
|
||||
var SUBMIT_BUTTON_SELECTOR = '[type="submit"]:not([formnovalidate])';
|
||||
var AUTOSUBMIT_BUTTON_SELECTOR = '[type="submit"][data-autosubmit]';
|
||||
|
||||
function formValidator(inputs) {
|
||||
var done = true;
|
||||
inputs.forEach(function(inp) {
|
||||
var len = inp.value.trim().length;
|
||||
if (done && len === 0) {
|
||||
done = false;
|
||||
}
|
||||
});
|
||||
return done;
|
||||
}
|
||||
|
||||
window.utils.form = function(form, options) {
|
||||
|
||||
if (form.classList.contains(JS_INITIALIZED)) {
|
||||
return false;
|
||||
}
|
||||
form.classList.add(JS_INITIALIZED);
|
||||
|
||||
// reactive buttons
|
||||
var submitBtn = form.querySelector(SUBMIT_BUTTON_SELECTOR);
|
||||
if (submitBtn) {
|
||||
window.utils.setup('reactiveButton', form, { button: submitBtn });
|
||||
}
|
||||
|
||||
// conditonal fieldsets
|
||||
var fieldSets = Array.from(form.querySelectorAll('fieldset[data-conditional-id][data-conditional-value]'));
|
||||
window.utils.setup('interactiveFieldset', form, { fieldSets });
|
||||
|
||||
// hide autoSubmit submit button
|
||||
window.utils.setup('autoSubmit', form, options);
|
||||
};
|
||||
|
||||
// registers input-listener for each element in <inputs> (array) and
|
||||
// enables <button> if <validation> for these inputs returns true
|
||||
window.utils.reactiveButton = function(form, button, validation) {
|
||||
// enables <button> if <formValidator> for these inputs returns true
|
||||
window.utils.reactiveButton = function(form, options) {
|
||||
options = options || {};
|
||||
var button = options.button;
|
||||
var requireds = Array.from(form.querySelectorAll('[required]'));
|
||||
|
||||
if (!button) {
|
||||
throw new Error('Please provide both a button to reactiveButton');
|
||||
}
|
||||
|
||||
if (requireds.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof button.dataset.formnorequired !== 'undefined' && button.dataset.formnorequired !== null) {
|
||||
button.addEventListener('click', function() {
|
||||
form.submit();
|
||||
@ -27,7 +71,7 @@
|
||||
});
|
||||
|
||||
function updateButtonState() {
|
||||
if (validation.call(null, requireds) === true) {
|
||||
if (formValidator(requireds) === true) {
|
||||
button.removeAttribute('disabled');
|
||||
} else {
|
||||
button.setAttribute('disabled', 'true');
|
||||
@ -35,7 +79,14 @@
|
||||
}
|
||||
};
|
||||
|
||||
window.utils.interactiveFieldset = function(form, fieldSets) {
|
||||
window.utils.interactiveFieldset = function(form, options) {
|
||||
options = options || {};
|
||||
var fieldSets = options.fieldSets;
|
||||
|
||||
if (!fieldSets) {
|
||||
throw new Error('interactiveFieldset must be passed fieldSets via options');
|
||||
}
|
||||
|
||||
var fields = fieldSets.map(function(fs) {
|
||||
return {
|
||||
fieldSet: fs,
|
||||
@ -64,58 +115,11 @@
|
||||
updateFields();
|
||||
}
|
||||
};
|
||||
|
||||
window.utils.autoSubmit = function(form, options) {
|
||||
var button = form.querySelector(AUTOSUBMIT_BUTTON_SELECTOR);
|
||||
if (button) {
|
||||
button.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
if (e.detail.module && e.detail.module !== 'showHide')
|
||||
return;
|
||||
|
||||
var forms = e.detail.scope.querySelectorAll('form');
|
||||
Array.from(forms).forEach(function(form) {
|
||||
// auto reactiveButton submit-buttons with required fields
|
||||
var submitBtns = Array.from(form.querySelectorAll('[type=submit]:not([formnovalidate])'));
|
||||
submitBtns.forEach(function(submitBtn) {
|
||||
window.utils.reactiveButton(form, submitBtn, validateForm);
|
||||
});
|
||||
|
||||
// auto conditonal fieldsets
|
||||
var fieldSets = Array.from(form.querySelectorAll('fieldset[data-conditional-id][data-conditional-value]'));
|
||||
window.utils.interactiveFieldset(form, fieldSets);
|
||||
});
|
||||
|
||||
function validateForm(inputs) {
|
||||
var done = true;
|
||||
inputs.forEach(function(inp) {
|
||||
var len = inp.value.trim().length;
|
||||
if (done && len === 0) {
|
||||
done = false;
|
||||
}
|
||||
});
|
||||
return done;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'showHide' }, bubbles: true, cancelable: true }))
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
if (e.detail.module && e.detail.module !== 'autoSubmit')
|
||||
return;
|
||||
|
||||
Array.from(e.detail.scope.querySelectorAll('[data-autosubmit]:not(.js-initialized)')).forEach(function(elem) {
|
||||
if ((elem instanceof HTMLInputElement && elem.type == 'submit') || (elem instanceof HTMLButtonElement && elem.type == 'submit')) {
|
||||
var ancestor = elem.closest('.form-group');
|
||||
var target = ancestor || elem;
|
||||
|
||||
target.classList.add('hidden');
|
||||
}
|
||||
|
||||
elem.classList.add('js-initialized');
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'autoSubmit' }, bubbles: true, cancelable: true }))
|
||||
});
|
||||
171
static/js/utils/inputs.js
Normal file
171
static/js/utils/inputs.js
Normal file
@ -0,0 +1,171 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
var JS_INITIALIZED_CLASS = 'js-initialized';
|
||||
|
||||
function isNotInitialized(element) {
|
||||
return !element.classList.contains(JS_INITIALIZED_CLASS);
|
||||
}
|
||||
|
||||
window.utils.inputs = function(wrapper, options) {
|
||||
// checkboxes / radios
|
||||
var checkboxes = Array.from(wrapper.querySelectorAll('input[type="checkbox"], input[type="radio"]'));
|
||||
checkboxes.filter(isNotInitialized).forEach(window.utils.checkboxRadio);
|
||||
|
||||
// file-uploads
|
||||
var fileUploads = Array.from(wrapper.querySelectorAll('input[type="file"]'));
|
||||
fileUploads.filter(isNotInitialized).forEach(function(input) {
|
||||
window.utils.fileUpload(input, options);
|
||||
});
|
||||
|
||||
// file-checkboxes
|
||||
var fileCheckboxes = Array.from(wrapper.querySelectorAll('.file-checkbox'));
|
||||
fileCheckboxes.filter(isNotInitialized).forEach(function(inp) {
|
||||
window.utils.fileCheckbox(inp);
|
||||
inp.classList.add(JS_INITIALIZED_CLASS);
|
||||
});
|
||||
};
|
||||
|
||||
// (multiple) dynamic file uploads
|
||||
// expects i18n object with following strings:
|
||||
// »filesSelected«: label of multi-upload button after selection
|
||||
// example: "Dateien ausgewählt" (will be prepended by number of selected files)
|
||||
// »selectFile«: label of single-upload button before selection
|
||||
// example: "Datei auswählen"
|
||||
// »selectFiles«: label of multi-upload button before selection
|
||||
// example: "Datei(en) auswählen"
|
||||
|
||||
var FILE_UPLOAD_INPUT_LIST_CLASS = 'file-input__list';
|
||||
var FILE_UPLOAD_INPUT_UNPACK_CHECKBOX_CLASS = 'file-input__unpack';
|
||||
var FILE_UPLOAD_INPUT_LABEL_CLASS = 'file-input__label';
|
||||
var FILE_UPLOAD_INPUT_HIDDEN_CLASS = 'file-input__input--hidden';
|
||||
|
||||
window.utils.fileUpload = function(input, options) {
|
||||
var isMulti = input.hasAttribute('multiple');
|
||||
var fileList = isMulti ? addFileList() : null;
|
||||
var label = addFileLabel();
|
||||
var i18n = options.i18n;
|
||||
|
||||
if (!i18n) {
|
||||
throw new Error('window.utils.fileUpload(input, options) needs to be passed i18n object via options');
|
||||
}
|
||||
input.classList.add(JS_INITIALIZED_CLASS);
|
||||
|
||||
function renderFileList(files) {
|
||||
fileList.innerHTML = '';
|
||||
Array.from(files).forEach(function(file, index) {
|
||||
var fileDisplayEl = document.createElement('li');
|
||||
fileDisplayEl.innerHTML = file.name;
|
||||
fileList.appendChild(fileDisplayEl);
|
||||
});
|
||||
}
|
||||
|
||||
function updateLabel(files) {
|
||||
if (files.length) {
|
||||
if (isMulti) {
|
||||
label.innerText = files.length + ' ' + i18n.filesSelected;
|
||||
} else {
|
||||
label.innerHTML = files[0].name;
|
||||
}
|
||||
} else {
|
||||
resetFileLabel();
|
||||
}
|
||||
}
|
||||
|
||||
function addFileList() {
|
||||
var list = document.createElement('ol');
|
||||
list.classList.add(FILE_UPLOAD_INPUT_LIST_CLASS);
|
||||
var unpackEl = input.parentElement.querySelector('.' + FILE_UPLOAD_INPUT_UNPACK_CHECKBOX_CLASS);
|
||||
if (unpackEl) {
|
||||
input.parentElement.insertBefore(list, unpackEl);
|
||||
} else {
|
||||
input.parentElement.appendChild(list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
function addFileLabel() {
|
||||
var label = document.createElement('label');
|
||||
label.classList.add(FILE_UPLOAD_INPUT_LABEL_CLASS);
|
||||
label.setAttribute('for', input.id);
|
||||
input.parentElement.insertBefore(label, input);
|
||||
return label;
|
||||
}
|
||||
|
||||
function resetFileLabel() {
|
||||
if (isMulti) {
|
||||
label.innerText = i18n.selectFiles;
|
||||
} else {
|
||||
label.innerText = i18n.selectFile;
|
||||
}
|
||||
}
|
||||
|
||||
// initial setup
|
||||
resetFileLabel();
|
||||
input.classList.add(FILE_UPLOAD_INPUT_HIDDEN_CLASS);
|
||||
input.addEventListener('change', function() {
|
||||
input.dispatchEvent(new Event('input'));
|
||||
if (isMulti) {
|
||||
renderFileList(input.files);
|
||||
}
|
||||
|
||||
updateLabel(input.files);
|
||||
});
|
||||
}
|
||||
|
||||
// to remove previously uploaded files
|
||||
|
||||
var FILE_UPLOAD_CONTAINER_CLASS = 'file-container';
|
||||
var FILE_UPLOAD_CONTAINER_CHECKED_CLASS = 'file-container--checked';
|
||||
|
||||
window.utils.fileCheckbox = function(input) {
|
||||
// adds eventlistener(s)
|
||||
function addListener(container) {
|
||||
input.addEventListener('change', function(event) {
|
||||
container.classList.toggle(FILE_UPLOAD_CONTAINER_CHECKED_CLASS, this.checked);
|
||||
});
|
||||
}
|
||||
|
||||
// initial setup
|
||||
function setup() {
|
||||
var cont = input.parentNode;
|
||||
while (cont !== document.body) {
|
||||
if (cont.matches('.' + FILE_UPLOAD_CONTAINER_CLASS)) {
|
||||
break;
|
||||
}
|
||||
cont = cont.parentNode;
|
||||
}
|
||||
addListener(cont);
|
||||
input.classList.add(JS_INITIALIZED_CLASS);
|
||||
cont.classList.add(JS_INITIALIZED_CLASS);
|
||||
}
|
||||
setup();
|
||||
}
|
||||
|
||||
// turns native checkboxes and radio buttons into custom ones
|
||||
window.utils.checkboxRadio = function(input) {
|
||||
|
||||
var type = input.getAttribute('type');
|
||||
|
||||
if (!input.parentElement.classList.contains(type)) {
|
||||
var parentEl = input.parentElement;
|
||||
var siblingEl = input.nextElementSibling;
|
||||
var wrapperEl = document.createElement('div');
|
||||
var labelEl = document.createElement('label');
|
||||
wrapperEl.classList.add(type);
|
||||
labelEl.setAttribute('for', input.id);
|
||||
wrapperEl.appendChild(input);
|
||||
wrapperEl.appendChild(labelEl);
|
||||
input.classList.add(JS_INITIALIZED_CLASS);
|
||||
|
||||
if (siblingEl) {
|
||||
parentEl.insertBefore(wrapperEl, siblingEl);
|
||||
} else {
|
||||
parentEl.appendChild(wrapperEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
58
static/js/utils/setup.js
Normal file
58
static/js/utils/setup.js
Normal file
@ -0,0 +1,58 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
var registeredSetupListeners = {};
|
||||
|
||||
window.utils.setup = function(utilType, scope, options) {
|
||||
|
||||
if (!utilType || !scope) {
|
||||
return;
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
|
||||
var listener = function(event) {
|
||||
|
||||
if (event.detail.targetUtil !== utilType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.setupFunction) {
|
||||
options.setupFunction(scope, options);
|
||||
} else {
|
||||
var util = window.utils[utilType];
|
||||
if (!util) {
|
||||
throw new Error('"' + utilType + '" is not a known js util');
|
||||
}
|
||||
|
||||
util(scope, options);
|
||||
}
|
||||
};
|
||||
|
||||
if (registeredSetupListeners[utilType] && !options.singleton) {
|
||||
registeredSetupListeners[utilType].push(listener);
|
||||
} else {
|
||||
window.utils.teardown(utilType);
|
||||
registeredSetupListeners[utilType] = [ listener ];
|
||||
}
|
||||
|
||||
document.addEventListener('setup', listener);
|
||||
|
||||
document.dispatchEvent(new CustomEvent('setup', {
|
||||
detail: { targetUtil: utilType, module: 'none' },
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}));
|
||||
};
|
||||
|
||||
window.utils.teardown = function(utilType) {
|
||||
if (registeredSetupListeners[utilType]) {
|
||||
registeredSetupListeners[utilType].forEach(function(listener) {
|
||||
document.removeEventListener('setup', listener);
|
||||
});
|
||||
delete registeredSetupListeners[utilType];
|
||||
}
|
||||
}
|
||||
})();
|
||||
55
static/js/utils/showHide.js
Normal file
55
static/js/utils/showHide.js
Normal file
@ -0,0 +1,55 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
var LOCAL_STORAGE_SHOW_HIDE = 'SHOW_HIDE';
|
||||
/**
|
||||
* div
|
||||
* div.js-show-hide__toggle
|
||||
* toggle here
|
||||
* div
|
||||
* content here
|
||||
*/
|
||||
window.utils.showHide = function(wrapper, options) {
|
||||
function addEventHandler(el) {
|
||||
el.addEventListener('click', function elClickListener() {
|
||||
var newState = el.parentElement.classList.toggle('js-show-hide--collapsed');
|
||||
updateLSState(el.dataset.shIndex || null, newState);
|
||||
});
|
||||
}
|
||||
|
||||
function updateLSState(index, state) {
|
||||
if (!index) {
|
||||
return false;
|
||||
}
|
||||
var lsData = fromLocalStorage();
|
||||
lsData[index] = state;
|
||||
window.localStorage.setItem(LOCAL_STORAGE_SHOW_HIDE, JSON.stringify(lsData));
|
||||
}
|
||||
|
||||
function collapsedStateInLocalStorage(index) {
|
||||
return fromLocalStorage()[index] || null;
|
||||
}
|
||||
|
||||
function fromLocalStorage() {
|
||||
return JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_SHOW_HIDE)) || {};
|
||||
}
|
||||
|
||||
Array
|
||||
.from(wrapper.querySelectorAll('.js-show-hide__toggle'))
|
||||
.forEach(function(el) {
|
||||
var index = el.dataset.shIndex || null;
|
||||
el.parentElement.classList.toggle(
|
||||
'js-show-hide--collapsed',
|
||||
collapsedStateInLocalStorage(index) || el.dataset.collapsed === 'true'
|
||||
);
|
||||
Array.from(el.parentElement.children).forEach(function(el) {
|
||||
if (!el.classList.contains('js-show-hide__toggle')) {
|
||||
el.classList.add('js-show-hide__target');
|
||||
}
|
||||
});
|
||||
addEventHandler(el);
|
||||
});
|
||||
};
|
||||
})();
|
||||
48
templates/default-layout.julius
Normal file
48
templates/default-layout.julius
Normal file
@ -0,0 +1,48 @@
|
||||
function setupDatepicker(wrapper) {
|
||||
"use strict";
|
||||
|
||||
var config = {
|
||||
dtLocal: {
|
||||
enableTime: true,
|
||||
altInput: true,
|
||||
altFormat: "j. F Y, H:i", // maybe interpolate these formats for locale
|
||||
dateFormat: "Y-m-dTH:i",
|
||||
time_24hr: true
|
||||
},
|
||||
d: {
|
||||
altFormat: "j. F Y",
|
||||
dateFormat: "Y-m-d",
|
||||
altInput: true
|
||||
},
|
||||
t: {
|
||||
enableTime: true,
|
||||
noCalendar: true,
|
||||
altFormat: "H:i",
|
||||
dateFormat: "H:i",
|
||||
altInput: true,
|
||||
time_24hr: true
|
||||
}
|
||||
};
|
||||
|
||||
Array.from(wrapper.querySelectorAll('input[type="date"]')).forEach(function(el) {
|
||||
flatpickr(el, config.d);
|
||||
});
|
||||
Array.from(wrapper.querySelectorAll('input[type="time"]')).forEach(function(el) {
|
||||
flatpickr(el, config.t);
|
||||
});
|
||||
Array.from(wrapper.querySelectorAll('input[type="datetime-local"]')).forEach(function(el) {
|
||||
flatpickr(el, config.dtLocal);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var I18N = {
|
||||
filesSelected: 'Dateien ausgewählt', // TODO: interpolate these to be translated
|
||||
selectFile: 'Datei auswählen',
|
||||
selectFiles: 'Datei(en) auswählen',
|
||||
};
|
||||
|
||||
window.utils.setup('flatpickr', document.body, { setupFunction: setupDatepicker });
|
||||
window.utils.setup('showHide', document.body);
|
||||
window.utils.setup('inputs', document.body, { i18n: I18N });
|
||||
});
|
||||
21
templates/formPageI18n.julius
Normal file
21
templates/formPageI18n.julius
Normal file
@ -0,0 +1,21 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
var themeSelector = document.querySelector('#theme-select');
|
||||
|
||||
if (themeSelector) {
|
||||
themeSelector.addEventListener('change', function() {
|
||||
// get rid of old themes on body
|
||||
var options = Array.from(themeSelector.options)
|
||||
.forEach(function (option) {
|
||||
document.body.classList.remove(optionToTheme(option));
|
||||
});
|
||||
// add newly selected theme
|
||||
document.body.classList.add(optionToTheme(themeSelector.selectedOptions[0]));
|
||||
});
|
||||
}
|
||||
|
||||
function optionToTheme(option) {
|
||||
return optionValue = 'theme--' + option.value;
|
||||
}
|
||||
|
||||
});
|
||||
@ -3,7 +3,7 @@ $forall FileUploadInfo{..} <- fileInfos
|
||||
<div .file-container :fuiChecked:.file-container--checked>
|
||||
<label .file-container__label.btn for=#{fuiHtmlId}>#{fuiTitle}
|
||||
<div .checkbox>
|
||||
<input .file-container__checkbox.js-file-checkbox id=#{fuiHtmlId} name=#{fieldName} :fuiChecked:checked value=#{toPathPiece fuiId} type="checkbox">
|
||||
<input .file-container__checkbox.file-checkbox id=#{fuiHtmlId} name=#{fieldName} :fuiChecked:checked value=#{toPathPiece fuiId} type="checkbox">
|
||||
<label for=#{fuiHtmlId}>
|
||||
|
||||
$# new files
|
||||
@ -15,6 +15,6 @@ $# new files
|
||||
<div .file-input__unpack>
|
||||
<label for=#{fieldId}_zip>ZIPs automatisch entpacken
|
||||
<input type=checkbox id=#{fieldId}_zip name=#{fieldName} value=#{unpackZips}>
|
||||
<div class="js-tooltip">
|
||||
<div class="tooltip">
|
||||
<div class="tooltip__handle">
|
||||
<div class="tooltip__content">Entpackt hochgeladene Zip-Dateien (*.zip) automatisch und fügt den Inhalt dem Stamm-Verzeichnis der Abgabe hinzu.
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
document.addEventListener('setup', function (e) {
|
||||
|
||||
var themeSelector = e.detail.scope.querySelector('#theme-select');
|
||||
themeSelector.addEventListener('change', function() {
|
||||
// get rid of old themes on body
|
||||
var options = Array.from(themeSelector.options)
|
||||
.forEach(function (option) {
|
||||
document.body.classList.remove(optionToTheme(option));
|
||||
});
|
||||
// add newly selected theme
|
||||
document.body.classList.add(optionToTheme(themeSelector.selectedOptions[0]));
|
||||
});
|
||||
|
||||
function optionToTheme(option) {
|
||||
return optionValue = 'theme--' + option.value;
|
||||
}
|
||||
});
|
||||
@ -21,7 +21,7 @@ $maybe descr <- sheetDescription sheet
|
||||
<dd .deflist__dd>_{sheetSubmissionMode sheet}
|
||||
$case sheetSubmissionMode sheet
|
||||
$of CorrectorSubmissions
|
||||
<div .js-tooltip>
|
||||
<div .tooltip>
|
||||
<div .tooltip__handle>
|
||||
<div .tooltip__content>_{MsgSheetCorrectorSubmissionsTip}
|
||||
$of _
|
||||
|
||||
@ -1 +0,0 @@
|
||||
<!-- only here to be able to include datepicker using `toWidget` -->
|
||||
@ -1,43 +0,0 @@
|
||||
document.addEventListener('setup', function(e) {
|
||||
"use strict";
|
||||
|
||||
if (e.detail.module && e.detail.module !== 'datepicker')
|
||||
return;
|
||||
|
||||
var config = {
|
||||
dtLocal: {
|
||||
enableTime: true,
|
||||
altInput: true,
|
||||
altFormat: "j. F Y, H:i",
|
||||
dateFormat: "Y-m-dTH:i",
|
||||
time_24hr: true
|
||||
},
|
||||
d: {
|
||||
altFormat: "j. F Y",
|
||||
dateFormat: "Y-m-d",
|
||||
altInput: true
|
||||
},
|
||||
t: {
|
||||
enableTime: true,
|
||||
noCalendar: true,
|
||||
altFormat: "H:i",
|
||||
dateFormat: "H:i",
|
||||
altInput: true,
|
||||
time_24hr: true
|
||||
}
|
||||
};
|
||||
|
||||
Array.from(e.detail.scope.querySelectorAll('input[type="date"]')).forEach(function(el) {
|
||||
flatpickr(el, config.d);
|
||||
});
|
||||
Array.from(e.detail.scope.querySelectorAll('input[type="time"]')).forEach(function(el) {
|
||||
flatpickr(el, config.t);
|
||||
});
|
||||
Array.from(e.detail.scope.querySelectorAll('input[type="datetime-local"]')).forEach(function(el) {
|
||||
flatpickr(el, config.dtLocal);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'datepicker' }, bubbles: true, cancelable: true }));
|
||||
});
|
||||
@ -1 +0,0 @@
|
||||
<!-- only here to be able to include inputs using `toWidget` -->
|
||||
@ -1,147 +0,0 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
// allows for multiple file uploads with separate inputs
|
||||
window.utils.initializeFileUpload = function(input) {
|
||||
var isMulti = input.hasAttribute('multiple');
|
||||
var fileList = isMulti ? addFileList() : null;
|
||||
var label = addFileLabel();
|
||||
|
||||
function renderFileList(files) {
|
||||
fileList.innerHTML = '';
|
||||
Array.from(files).forEach(function(file, index) {
|
||||
var fileDisplayEl = document.createElement('li');
|
||||
fileDisplayEl.innerHTML = file.name;
|
||||
fileList.appendChild(fileDisplayEl);
|
||||
});
|
||||
}
|
||||
|
||||
function updateLabel(files) {
|
||||
if (files.length) {
|
||||
if (isMulti) {
|
||||
label.innerText = files.length + ' Dateien ausgwählt';
|
||||
} else {
|
||||
label.innerHTML = files[0].name;
|
||||
}
|
||||
} else {
|
||||
resetFileLabel();
|
||||
}
|
||||
}
|
||||
|
||||
function addFileList() {
|
||||
var list = document.createElement('ol');
|
||||
list.classList.add('file-input__list');
|
||||
var unpackEl = input.parentElement.querySelector('.file-input__unpack');
|
||||
if (unpackEl) {
|
||||
input.parentElement.insertBefore(list, unpackEl);
|
||||
} else {
|
||||
input.parentElement.appendChild(list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
function addFileLabel() {
|
||||
var label = document.createElement('label');
|
||||
label.classList.add('file-input__label');
|
||||
label.setAttribute('for', input.id);
|
||||
input.parentElement.insertBefore(label, input);
|
||||
return label;
|
||||
}
|
||||
|
||||
function resetFileLabel() {
|
||||
// interpolate translated String here
|
||||
label.innerText = 'Datei' + (isMulti ? 'en' : '') + ' auswählen';
|
||||
}
|
||||
|
||||
// initial setup
|
||||
resetFileLabel();
|
||||
input.classList.add('file-input__input--hidden');
|
||||
input.addEventListener('change', function() {
|
||||
input.dispatchEvent(new Event('input'));
|
||||
if (isMulti) {
|
||||
renderFileList(input.files);
|
||||
}
|
||||
|
||||
updateLabel(input.files);
|
||||
});
|
||||
}
|
||||
|
||||
// to remove previously uploaded files
|
||||
window.utils.reactiveFileCheckbox = function(input) {
|
||||
// adds eventlistener(s)
|
||||
function addListener(container) {
|
||||
input.addEventListener('change', function(event) {
|
||||
container.classList.toggle('file-container--checked', this.checked);
|
||||
});
|
||||
}
|
||||
|
||||
// initial setup
|
||||
function setup() {
|
||||
var cont = input.parentNode;
|
||||
while (cont !== document.body) {
|
||||
if (cont.matches('.file-container')) {
|
||||
break;
|
||||
}
|
||||
cont = cont.parentNode;
|
||||
}
|
||||
addListener(cont);
|
||||
}
|
||||
setup();
|
||||
}
|
||||
|
||||
window.utils.initializeCheckboxRadio = function(input, type) {
|
||||
|
||||
if (!input.parentElement.classList.contains(type)) {
|
||||
var parentEl = input.parentElement;
|
||||
var siblingEl = input.nextElementSibling;
|
||||
var wrapperEl = document.createElement('div');
|
||||
var labelEl = document.createElement('label');
|
||||
wrapperEl.classList.add(type);
|
||||
labelEl.setAttribute('for', input.id);
|
||||
wrapperEl.appendChild(input);
|
||||
wrapperEl.appendChild(labelEl);
|
||||
|
||||
if (siblingEl) {
|
||||
parentEl.insertBefore(wrapperEl, siblingEl);
|
||||
} else {
|
||||
parentEl.appendChild(wrapperEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
if (e.detail.module && e.detail.module !== 'inputs')
|
||||
return;
|
||||
|
||||
// initialize checkboxes
|
||||
Array.from(e.detail.scope.querySelectorAll('input[type="checkbox"]:not(.js-initialized)')).forEach(function(inp) {
|
||||
window.utils.initializeCheckboxRadio(inp, 'checkbox');
|
||||
inp.classList.add("js-initialized");
|
||||
});
|
||||
|
||||
// initialize radios
|
||||
Array.from(e.detail.scope.querySelectorAll('input[type="radio"]:not(.js-initialized)')).forEach(function(inp) {
|
||||
window.utils.initializeCheckboxRadio(inp, 'radio');
|
||||
inp.classList.add("js-initialized");
|
||||
});
|
||||
|
||||
// initialize file-upload-fields
|
||||
Array.from(e.detail.scope.querySelectorAll('input[type="file"]:not(.js-initialized)')).forEach(function(inp) {
|
||||
window.utils.initializeFileUpload(inp);
|
||||
inp.classList.add("js-initialized");
|
||||
});
|
||||
|
||||
// initialize file-checkbox-fields
|
||||
Array.from(e.detail.scope.querySelectorAll('.js-file-checkbox:not(.js-initialized)')).forEach(function(inp) {
|
||||
window.utils.reactiveFileCheckbox(inp);
|
||||
inp.classList.add("js-initialized");
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'inputs' }, bubbles: true, cancelable: true }));
|
||||
});
|
||||
@ -1 +0,0 @@
|
||||
<!-- only here to be able to include showHide using `toWidget` -->
|
||||
@ -1,58 +0,0 @@
|
||||
/**
|
||||
* div
|
||||
* div.js-show-hide__toggle
|
||||
* toggle here
|
||||
* div
|
||||
* content here
|
||||
*/
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
if (e.detail.module && e.detail.module !== 'showHide')
|
||||
return;
|
||||
|
||||
var LSNAME = 'SHOW_HIDE';
|
||||
|
||||
function addEventHandler(el) {
|
||||
el.addEventListener('click', function elClickListener() {
|
||||
var newState = el.parentElement.classList.toggle('js-show-hide--collapsed');
|
||||
updateLSState(el.dataset.shIndex || null, newState);
|
||||
});
|
||||
}
|
||||
|
||||
function updateLSState(index, state) {
|
||||
if (!index) {
|
||||
return false;
|
||||
}
|
||||
var lsData = fromLocalStorage();
|
||||
lsData[index] = state;
|
||||
window.localStorage.setItem(LSNAME, JSON.stringify(lsData));
|
||||
}
|
||||
|
||||
function collapsedStateInLocalStorage(index) {
|
||||
return fromLocalStorage()[index] || null;
|
||||
}
|
||||
|
||||
function fromLocalStorage() {
|
||||
return JSON.parse(window.localStorage.getItem(LSNAME)) || {};
|
||||
}
|
||||
|
||||
Array
|
||||
.from(e.detail.scope.querySelectorAll('.js-show-hide__toggle'))
|
||||
.forEach(function(el) {
|
||||
var index = el.dataset.shIndex || null;
|
||||
el.parentElement.classList.toggle(
|
||||
'js-show-hide--collapsed',
|
||||
collapsedStateInLocalStorage(index) || el.dataset.collapsed === 'true'
|
||||
);
|
||||
Array.from(el.parentElement.children).forEach(function(el) {
|
||||
if (!el.classList.contains('js-show-hide__toggle')) {
|
||||
el.classList.add('js-show-hide__target');
|
||||
}
|
||||
});
|
||||
addEventHandler(el);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'showHide' }, bubbles: true, cancelable: true }))
|
||||
});
|
||||
@ -1 +0,0 @@
|
||||
<!-- only here to be able to include tabber using `toWidget` -->
|
||||
@ -1,7 +0,0 @@
|
||||
.tab-opener {
|
||||
background-color: var(--color-dark);
|
||||
|
||||
&.tab-visible {
|
||||
border-bottom-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
<!-- only here to be able to include tooltips using `toWidget` -->
|
||||
@ -1,67 +0,0 @@
|
||||
(function() {;
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
window.utils.tooltip = function(tt) {
|
||||
var handle = tt.querySelector('.tooltip__handle');
|
||||
var content = tt.querySelector('.tooltip__content');
|
||||
|
||||
var left = false;
|
||||
|
||||
// initially set content to hidden
|
||||
content.classList.add('hidden');
|
||||
|
||||
handle.addEventListener('mouseenter', function() {
|
||||
left = false;
|
||||
content.classList.toggle('to-left', handle.getBoundingClientRect().left + 300 > window.innerWidth);
|
||||
content.classList.remove('hidden');
|
||||
});
|
||||
|
||||
handle.addEventListener('mouseleave', function() {
|
||||
left = true;
|
||||
window.setTimeout(function() {
|
||||
if (left) {
|
||||
content.classList.add('hidden');
|
||||
}
|
||||
}, 250);
|
||||
});
|
||||
};
|
||||
|
||||
window.utils.tooltipFromAttribute = function(el) {
|
||||
var tt = document.createElement('div');
|
||||
var handle = document.createElement('div');
|
||||
var content = document.createElement('div');
|
||||
|
||||
tt.classList.add('js-tooltip');
|
||||
handle.classList.add('tooltip__handle');
|
||||
content.classList.add('tooltip__content', 'hidden');
|
||||
|
||||
handle.innerText = '?';
|
||||
content.innerHTML = el.getAttribute('data-tooltip');
|
||||
|
||||
tt.appendChild(handle);
|
||||
tt.appendChild(content);
|
||||
|
||||
if (el.nextSiblingElement) {
|
||||
el.parentElement.insertBefore(tt, el.nextSiblingElement);
|
||||
} else {
|
||||
el.parentElement.appendChild(tt);
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
// JS-TOOLTIPS NOT USED CURRENTLY.
|
||||
|
||||
// initialize tooltips set via `data-tooltip`
|
||||
// Array.from(e.detail.scope.querySelectorAll('[data-tooltip]')).forEach(function(el) {
|
||||
// window.utils.tooltipFromAttribute(el)
|
||||
// });
|
||||
|
||||
// initialize tooltips
|
||||
// Array.from(e.detail.scope.querySelectorAll('.js-tooltip')).forEach(function(tt) {
|
||||
// window.utils.tooltip(tt);
|
||||
// });
|
||||
});
|
||||
@ -1,186 +1,10 @@
|
||||
(function collonadeClosure() {
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
window.utils.asyncTable = function(wrapper, options) {
|
||||
|
||||
var tableIdent = wrapper.dataset.dbtIdent;
|
||||
var shortCircuitHeader = #{String (toPathPiece HeaderDBTableShortcircuit)};
|
||||
|
||||
var ths = [];
|
||||
var pageLinks = [];
|
||||
var pagesizeForm;
|
||||
var scrollTable;
|
||||
|
||||
function init() {
|
||||
var table = wrapper.querySelector('#' + tableIdent);
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollTable = wrapper.querySelector('.scrolltable');
|
||||
|
||||
// sortable table headers
|
||||
ths = Array.from(table.querySelectorAll('th.sortable')).map(function(th) {
|
||||
return { element: th };
|
||||
});
|
||||
|
||||
// pagination links
|
||||
var pagination = wrapper.querySelector('#' + tableIdent + '-pagination');
|
||||
if (pagination) {
|
||||
pageLinks = Array.from(pagination.querySelectorAll('.page-link')).map(function(link) {
|
||||
return { element: link };
|
||||
});
|
||||
}
|
||||
|
||||
// pagesize form
|
||||
pagesizeForm = wrapper.querySelector('#' + tableIdent + '-pagesize-form');
|
||||
|
||||
// take options into account
|
||||
if (options && options.scrollTo) {
|
||||
window.scrollTo(options.scrollTo);
|
||||
}
|
||||
|
||||
if (options && options.horizPos && scrollTable) {
|
||||
scrollTable.scrollLeft = options.horizPos;
|
||||
}
|
||||
|
||||
setupListeners();
|
||||
wrapper.classList.add('js-initialized');
|
||||
}
|
||||
|
||||
function setupListeners() {
|
||||
ths.forEach(function(th) {
|
||||
th.clickHandler = function(event) {
|
||||
var boundClickHandler = clickHandler.bind(this);
|
||||
var horizPos = (scrollTable || {}).scrollLeft;
|
||||
boundClickHandler(event, { horizPos });
|
||||
};
|
||||
th.element.addEventListener('click', th.clickHandler);
|
||||
});
|
||||
|
||||
pageLinks.forEach(function(link) {
|
||||
link.clickHandler = function(event) {
|
||||
var boundClickHandler = clickHandler.bind(this);
|
||||
var wrapperBoundingRect = wrapper.getBoundingClientRect();
|
||||
var options = {};
|
||||
if (wrapperBoundingRect.top < 160) {
|
||||
options.scrollTo = {
|
||||
top: (wrapper.offsetTop || 0) - 60,
|
||||
left: wrapper.offsetLeft || 0,
|
||||
behavior: 'smooth',
|
||||
};
|
||||
}
|
||||
boundClickHandler(event, options);
|
||||
}
|
||||
link.element.addEventListener('click', link.clickHandler);
|
||||
});
|
||||
|
||||
if (pagesizeForm) {
|
||||
var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]');
|
||||
pagesizeSelect.addEventListener('change', changePagesizeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function removeListeners() {
|
||||
ths.forEach(function(th) {
|
||||
th.element.removeEventListener('click', th.clickHandler);
|
||||
});
|
||||
|
||||
pageLinks.forEach(function(link) {
|
||||
link.element.removeEventListener('click', link.clickHandler);
|
||||
});
|
||||
|
||||
if (pagesizeForm) {
|
||||
var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]')
|
||||
pagesizeSelect.removeEventListener('change', changePagesizeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function clickHandler(event, options) {
|
||||
event.preventDefault();
|
||||
var url = new URL(window.location.origin + window.location.pathname + getClickDestination(this));
|
||||
updateTableFrom(url, options);
|
||||
}
|
||||
|
||||
function getClickDestination(el) {
|
||||
if (!el.querySelector('a')) {
|
||||
return '';
|
||||
}
|
||||
return el.querySelector('a').getAttribute('href');
|
||||
}
|
||||
|
||||
function changePagesizeHandler(event) {
|
||||
var currentTableUrl = wrapper.dataset.currentUrl || window.location.href;
|
||||
var url = getUrlWithUpdatedPagesize(currentTableUrl, event.target.value);
|
||||
url = getUrlWithResetPagenumber(url);
|
||||
updateTableFrom(url);
|
||||
}
|
||||
|
||||
function getUrlWithUpdatedPagesize(url, pagesize) {
|
||||
if (url.indexOf('pagesize') >= 0) {
|
||||
return url.replace(/pagesize=(\d+|all)/, 'pagesize=' + pagesize);
|
||||
} else if (url.indexOf('?') >= 0) {
|
||||
return url += '&' + tableIdent + '-pagesize=' + pagesize;
|
||||
}
|
||||
|
||||
return url += '?' + tableIdent + '-pagesize=' + pagesize;
|
||||
}
|
||||
|
||||
function getUrlWithResetPagenumber(url) {
|
||||
return url.replace(/-page=\d+/, '-page=0');
|
||||
}
|
||||
|
||||
function updateWrapperContents(newHtml, options) {
|
||||
wrapper.innerHTML = newHtml;
|
||||
wrapper.classList.remove("js-initialized");
|
||||
|
||||
// setup the wrapper and its components to behave async again
|
||||
window.utils.asyncTable(wrapper, options);
|
||||
|
||||
// make sure to hide any new submit buttons
|
||||
document.dispatchEvent(new CustomEvent('setup', {
|
||||
detail: {
|
||||
scope: wrapper,
|
||||
module: 'autoSubmit'
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// fetches new sorted table from url with params and replaces contents of current table
|
||||
function updateTableFrom(url, options) {
|
||||
|
||||
fetch(url, {
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Accept': 'text/html',
|
||||
[shortCircuitHeader]: tableIdent
|
||||
}
|
||||
}).then(function(response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Looks like there was a problem fetching ' + url + '. Status Code: ' + response.status);
|
||||
}
|
||||
return response.text();
|
||||
}).then(function(data) {
|
||||
wrapper.dataset.currentUrl = url;
|
||||
removeListeners();
|
||||
updateWrapperContents(data, options);
|
||||
}).catch(function(err) {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
};
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var dbtIdent = #{String $ dbtIdent};
|
||||
var headerDBTableShortcircuit = #{String (toPathPiece HeaderDBTableShortcircuit)};
|
||||
var selector = '#' + dbtIdent + '-table-wrapper:not(.js-initialized)';
|
||||
var wrapper = document.querySelector(selector);
|
||||
|
||||
if (wrapper) {
|
||||
wrapper.dataset.dbtIdent = dbtIdent;
|
||||
window.utils.asyncTable(wrapper);
|
||||
window.utils.setup('asyncTable', wrapper, { headerDBTableShortcircuit, dbtIdent });
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<div #alerts .alerts>
|
||||
<div #alerts-1 .alerts> <!-- make wIdent work here instead of '#alerts-1' -->
|
||||
<div .alerts__toggler>
|
||||
$forall (status, msg) <- mmsgs
|
||||
$with status2 <- bool status "info" (status == "")
|
||||
<div class="alert alert-#{status2}">
|
||||
<div .alert.alert-#{status2}>
|
||||
<div .alert__closer>
|
||||
<div .alert__icon>
|
||||
<div .alert__content>
|
||||
|
||||
@ -1,18 +1,4 @@
|
||||
document.addEventListener('setup', function(e) {
|
||||
if (!e.detail.module || e.detail.module !== 'alerts') {
|
||||
return;
|
||||
}
|
||||
|
||||
// setup alerts
|
||||
if (e.detail.scope.classList.contains('alerts')) {
|
||||
window.utils.alerts(e.detail.scope);
|
||||
} else {
|
||||
var alertsEl = e.detail.scope.querySelector('.alerts');
|
||||
if (alertsEl)
|
||||
window.utils.alerts(alertsEl);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'alerts' }, bubbles: true, cancelable: true }))
|
||||
var alertsElement = document.querySelector('#' + 'alerts-1');
|
||||
window.utils.setup('alerts', alertsElement);
|
||||
});
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
window.utils.aside = function(asideEl) {
|
||||
|
||||
function init() {
|
||||
var favoritesBtn = document.querySelector('.navbar__list-item--favorite');
|
||||
favoritesBtn.addEventListener('click', function(event) {
|
||||
favoritesBtn.classList.toggle('navbar__list-item--active');
|
||||
asideEl.classList.toggle('main__aside--expanded');
|
||||
event.preventDefault();
|
||||
}, true);
|
||||
}
|
||||
init();
|
||||
};
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var asidenavEl = document.querySelector('.main__aside');
|
||||
window.utils.aside(asidenavEl);
|
||||
});
|
||||
@ -16,10 +16,11 @@ $newline never
|
||||
<a .asidenav__link-wrapper href=@{courseRoute}>
|
||||
<div .asidenav__link-shorthand>#{courseShorthand}
|
||||
<div .asidenav__link-label>#{courseName}
|
||||
<ul .asidenav__nested-list.list--iconless>
|
||||
$forall (MenuItem{menuItemType, menuItemLabel}, route) <- pageActions
|
||||
$case menuItemType
|
||||
$of PageActionPrime
|
||||
<li .asidenav__nested-list-item>
|
||||
<a .asidenav__link-wrapper href=#{route}>_{menuItemLabel}
|
||||
$of _
|
||||
<div .asidenav__nested-list-wrapper>
|
||||
<ul .asidenav__nested-list.list--iconless>
|
||||
$forall (MenuItem{menuItemType, menuItemLabel}, route) <- pageActions
|
||||
$case menuItemType
|
||||
$of PageActionPrime
|
||||
<li .asidenav__nested-list-item>
|
||||
<a .asidenav__link-wrapper href=#{route}>_{menuItemLabel}
|
||||
$of _
|
||||
4
templates/widgets/asidenav/asidenav.julius
Normal file
4
templates/widgets/asidenav/asidenav.julius
Normal file
@ -0,0 +1,4 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var asidenavEl = document.querySelector('.main__aside');
|
||||
window.utils.setup('aside', asidenavEl);
|
||||
});
|
||||
@ -13,7 +13,7 @@ $case formLayout
|
||||
<label .form-group__label for=#{fvId view}>
|
||||
#{fvLabel view}
|
||||
$maybe tooltip <- fvTooltip view
|
||||
<div .js-tooltip>
|
||||
<div .tooltip>
|
||||
<div .tooltip__handle>
|
||||
<div .tooltip__content>^{tooltip}
|
||||
<div .form-group__input>
|
||||
5
templates/widgets/form/form.julius
Normal file
5
templates/widgets/form/form.julius
Normal file
@ -0,0 +1,5 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
Array.from(document.querySelectorAll('form')).forEach(function(form) {
|
||||
window.utils.setup('form', form);
|
||||
});
|
||||
});
|
||||
@ -51,4 +51,4 @@ $#
|
||||
\ (_{title $ getSum $ summary ^. _numSheetsPoints})
|
||||
$# Kurze Alternative mit Hashtag-Symbol für "Anzahl"
|
||||
$# \ (##{display $ summary ^. _numSheetsPoints})
|
||||
<td .table__td>#{display $ summary ^. _numSheets}
|
||||
<td .table__td>#{display $ summary ^. _numSheets}
|
||||
Loading…
Reference in New Issue
Block a user