diff --git a/src/Auth/Dummy.hs b/src/Auth/Dummy.hs
index e7033f3d8..cdb8db1e8 100644
--- a/src/Auth/Dummy.hs
+++ b/src/Auth/Dummy.hs
@@ -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")
diff --git a/src/Auth/LDAP.hs b/src/Auth/LDAP.hs
index cd2a9a037..861c03620 100644
--- a/src/Auth/LDAP.hs
+++ b/src/Auth/LDAP.hs
@@ -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
diff --git a/src/Auth/PWHash.hs b/src/Auth/PWHash.hs
index 68df34703..74c4e67a3 100644
--- a/src/Auth/PWHash.hs
+++ b/src/Auth/PWHash.hs
@@ -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")
diff --git a/src/Foundation.hs b/src/Foundation.hs
index 9653564e0..a5a8e1f56 100644
--- a/src/Foundation.hs
+++ b/src/Foundation.hs
@@ -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")
diff --git a/src/Handler/Corrections.hs b/src/Handler/Corrections.hs
index 1fece3778..29f31b107 100644
--- a/src/Handler/Corrections.hs
+++ b/src/Handler/Corrections.hs
@@ -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
diff --git a/src/Handler/Course.hs b/src/Handler/Course.hs
index 080cb2d22..18d507e0f 100644
--- a/src/Handler/Course.hs
+++ b/src/Handler/Course.hs
@@ -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
diff --git a/src/Handler/Profile.hs b/src/Handler/Profile.hs
index 1e2547db9..33736976e 100644
--- a/src/Handler/Profile.hs
+++ b/src/Handler/Profile.hs
@@ -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")
diff --git a/src/Handler/Sheet.hs b/src/Handler/Sheet.hs
index 615fa91e1..ebd365521 100644
--- a/src/Handler/Sheet.hs
+++ b/src/Handler/Sheet.hs
@@ -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")
diff --git a/src/Handler/Users.hs b/src/Handler/Users.hs
index 7196055a9..bd0feefe1 100644
--- a/src/Handler/Users.hs
+++ b/src/Handler/Users.hs
@@ -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) ->
diff --git a/src/Handler/Utils/Delete.hs b/src/Handler/Utils/Delete.hs
index c35d2a14f..400ef2d72 100644
--- a/src/Handler/Utils/Delete.hs
+++ b/src/Handler/Utils/Delete.hs
@@ -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")
diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs
index f7a3579cd..0e50c8820 100644
--- a/src/Handler/Utils/Form.hs
+++ b/src/Handler/Utils/Form.hs
@@ -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
diff --git a/src/Handler/Utils/SheetType.hs b/src/Handler/Utils/SheetType.hs
index 43b98a92f..9e69815c9 100644
--- a/src/Handler/Utils/SheetType.hs
+++ b/src/Handler/Utils/SheetType.hs
@@ -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")
diff --git a/src/Handler/Utils/Table/Pagination.hs b/src/Handler/Utils/Table/Pagination.hs
index 368758bea..b5b36fdcf 100644
--- a/src/Handler/Utils/Table/Pagination.hs
+++ b/src/Handler/Utils/Table/Pagination.hs
@@ -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|
-
+
_{msg}
|]
diff --git a/src/Handler/Utils/Templates.hs b/src/Handler/Utils/Templates.hs
index ce0d70bc0..ae8673af1 100644
--- a/src/Handler/Utils/Templates.hs
+++ b/src/Handler/Utils/Templates.hs
@@ -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
diff --git a/src/Utils/Form.hs b/src/Utils/Form.hs
index c4fe43f80..787a2e4b5 100644
--- a/src/Utils/Form.hs
+++ b/src/Utils/Form.hs
@@ -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
diff --git a/templates/widgets/asidenav.lucius b/static/css/utils/asidenav.scss
similarity index 92%
rename from templates/widgets/asidenav.lucius
rename to static/css/utils/asidenav.scss
index 9123c407d..51fe73163 100644
--- a/templates/widgets/asidenav.lucius
+++ b/static/css/utils/asidenav.scss
@@ -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);
}
}
diff --git a/templates/widgets/form.lucius b/static/css/utils/form.scss
similarity index 100%
rename from templates/widgets/form.lucius
rename to static/css/utils/form.scss
diff --git a/templates/standalone/inputs.lucius b/static/css/utils/inputs.scss
similarity index 100%
rename from templates/standalone/inputs.lucius
rename to static/css/utils/inputs.scss
diff --git a/templates/standalone/showHide.lucius b/static/css/utils/showHide.scss
similarity index 94%
rename from templates/standalone/showHide.lucius
rename to static/css/utils/showHide.scss
index d1413c0fb..64dfe367c 100644
--- a/templates/standalone/showHide.lucius
+++ b/static/css/utils/showHide.scss
@@ -1,7 +1,3 @@
-.js-show-hide {
- position: relative;
-}
-
.js-show-hide__toggle {
position: relative;
cursor: pointer;
diff --git a/static/css/utils/tabber.css b/static/css/utils/tabber.scss
similarity index 89%
rename from static/css/utils/tabber.css
rename to static/css/utils/tabber.scss
index 6f823b410..d135a5f27 100644
--- a/static/css/utils/tabber.css
+++ b/static/css/utils/tabber.scss
@@ -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);
}
diff --git a/templates/standalone/tooltip.lucius b/static/css/utils/tooltip.scss
similarity index 97%
rename from templates/standalone/tooltip.lucius
rename to static/css/utils/tooltip.scss
index 0a2154768..7e538e46a 100644
--- a/templates/standalone/tooltip.lucius
+++ b/static/css/utils/tooltip.scss
@@ -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;
diff --git a/static/js/utils/alerts.js b/static/js/utils/alerts.js
index eec2fb73a..d52a47376 100644
--- a/static/js/utils/alerts.js
+++ b/static/js/utils/alerts.js
@@ -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;
diff --git a/static/js/utils/asidenav.js b/static/js/utils/asidenav.js
new file mode 100644
index 000000000..154232109
--- /dev/null
+++ b/static/js/utils/asidenav.js
@@ -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();
+ };
+})();
diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js
new file mode 100644
index 000000000..fe164d3b2
--- /dev/null
+++ b/static/js/utils/asyncTable.js
@@ -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();
+ };
+})();
diff --git a/static/js/utils/featureChecker.js b/static/js/utils/featureChecker.js
deleted file mode 100644
index ad8e26303..000000000
--- a/static/js/utils/featureChecker.js
+++ /dev/null
@@ -1,4 +0,0 @@
-window.addEventListener('touchstart', function onFirstTouch() {
- document.body.classList.add('touch-supported');
- window.removeEventListener('touchstart', onFirstTouch, false);
-}, false);
diff --git a/templates/widgets/form.julius b/static/js/utils/form.js
similarity index 51%
rename from templates/widgets/form.julius
rename to static/js/utils/form.js
index e318ca3f3..30ba76c96 100644
--- a/templates/widgets/form.julius
+++ b/static/js/utils/form.js
@@ -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
(array) and
- // enables