reflex-frp / reflex-dom-semui

A reflex-dom API for Semantic UI components
https://reflex-frp.org/
BSD 3-Clause "New" or "Revised" License
22 stars 10 forks source link

Try to implement Sidebar #18

Closed kaizhang closed 7 years ago

kaizhang commented 7 years ago

This may not directly relate with this package. I am implementing the "sidebar" widget from Semantic UI. I'm new to both semui and reflex. And I had hard time to get the sidebar initialization process worked. I want to call $(.sidebar).sidebar({ dimPage: false}) after the sidebar is loaded (I found that it will lead to an error if this function is called before the elements are loaded). So I used the "Load" event. However, for some reasons the event never fired. Other than not being able to change the settings, my sidebar implementation works as expected. Here is the codes. Any suggestions? Thanks!

uiSidebar :: MonadWidget t m
          => UiSidebar    -- ^ Sidebar configuration
          -> m a  -- ^ Sidebar content
          -> m b  -- ^ Sidebar pusher
          -> (b -> Event t SidebarBehavior)   -- ^ Assign sidebar events
          -> m (a, b)
uiSidebar config content pusher getBeh = do
    (e1, res1) <- elClass' "div" "ui sidebar" content
    (e2, res2) <- elClass' "div" "pusher" pusher
    performEvent (DOM.liftJSM . uiTriggerSidebarAction (_element_raw e1) <$> getBeh res2)
    performEvent (DOM.liftJSM . configSidebar (_element_raw e1) .
        (const config) <$> domEvent Load e2)
    return (res1, res2)

uiTriggerSidebarAction :: DOM.Element -> SidebarBehavior -> DOM.JSM ()
uiTriggerSidebarAction e beh = js_sidebarAction e
                      (DOM.toJSString $ sidebarBehaviorString beh)

foreign import javascript unsafe "$($1).sidebar($2);"
    js_sidebarAction :: DOM.Element -> DOM.JSString -> DOM.JSM ()

configSidebar :: DOM.Element -> UiSidebar -> DOM.JSM ()
configSidebar e UiSidebar{..} = js_configSidebar e _uiSidebar_dimPage

foreign import javascript unsafe
    "$($1).sidebar({  \
        dimPage: $2   \
    });alert($2);"
    js_configSidebar :: DOM.Element -> Bool -> DOM.JSM ()

data SidebarBehavior = ShowSidebar
                     | HideSidebar
                     | ToggleSidebar
                     deriving (Eq, Ord, Enum)

sidebarBehaviorString :: SidebarBehavior -> T.Text
sidebarBehaviorString beh = case beh of
    ShowSidebar -> "show"
    HideSidebar -> "hide"
    ToggleSidebar -> "toggle"

data UiSidebar = UiSidebar
    { _uiSidebar_context :: T.Text    -- ^ Context which sidebar will appear inside
    , _uiSidebar_exclusive :: Bool    -- ^ Whether multiple sidebars can be open at once
    , _uiSidebar_closable :: Bool     -- ^ Whether sidebar can be closed by clicking on page
    , _uiSidebar_dimPage :: Bool      -- ^ Whether to dim page contents when sidebar is visible
    , _uiSidebar_scrollLock :: Bool   -- ^ Whether to lock page scroll when sidebar is visible
    , _uiSidebar_returnScroll :: Bool -- ^ Whether to return to original scroll position when sidebar is hidden, automatically occurs with transition: scale
    , _uiSidebar_delaySetup :: Bool   -- ^ When sidebar is initialized without the proper HTML, using this option will defer creation of DOM to use requestAnimationFrame.
    }

instance Default UiSidebar where
    def = UiSidebar
        { _uiSidebar_context = "body"
        , _uiSidebar_exclusive = False
        , _uiSidebar_closable = True
        , _uiSidebar_dimPage = False
        , _uiSidebar_scrollLock = False
        , _uiSidebar_returnScroll = False
        , _uiSidebar_delaySetup = False
        }
tomsmalley commented 7 years ago

Try to instead use the event from getPostBuild:

pb <- getPostBuild
performEvent_ $ DOM.liftJSM . configSidebar (_element_raw e1) .
        (const config) <$> pb
kaizhang commented 7 years ago

getPostBuild does return a usable event. But the event seems to occur before the "pusher" is loaded, because I still got the same error from semui: Sidebar: – "Had to add pusher element. For optimal performance make sure body content is inside a pusher element" Where is the documentation of the getPostBuild function and why is the Load event never happened?

kaizhang commented 7 years ago

domEvent Load does not work because onload only works for following tags: <body>, <frame>, <iframe>, <img>, <input type="image">, <link>, <script>, <style>. I don't know why pb <- getPostBuild does not work. But I found pb <- delay 0 =<< getPostbuild works. Can anyone explain this?

ryantrinkle commented 7 years ago

@kaizhang That's probably because the event from getPostBuild fires immediately after the widget is built - but usually before it's placed in the document. When you add the delay 0, you will generally delay until after the element has been placed in the document. If it's possible to write your code so that it works regardless of whether the widget is in the document or not, that's the most robust approach. Otherwise, there is a PR being worked on that will give you a nice way of doing things when the widget is mounted.

kaizhang commented 7 years ago

@ryantrinkle That makes sense. To do what you suggested, I probably need to rewrite the js part of semantic UI in Haskell. I do not want to do that for now. I hope the PR can be merged soon. Right now I will just use a safe value for delay.