Notice: The Monaca & Onsen UI Community Forum is shutting down.
For Onsen UI bug reports, feature requests and questions, please use the Onsen UI GitHub issues page. For help with Monaca, please contact Monaca Support Team.
Thank you to all our community for your contributions to the forum. We look forward to hearing from you in the new communication channels.
Using a splitter menu with a navigator - example inside!
-
This has been asked several times in multiple locations. @IliaSky has written a codepen previously, but I took the liberty to write a more advanced example to demonstrate several ways to implement this oft requested tidbit. A functioning codepen, is located here: https://codepen.io/anon/pen/NALzWZ
The HTML:
<ons-page> <ons-splitter> <ons-splitter-side id="menu" side="right" width="220px" swipe-target-width="150px" collapse swipeable> <ons-page> <ons-list> <ons-list-item tappable onclick="fn.load('page2.html', {data: {title: 'Page 2'}})"> Page 2 </ons-list-item> <ons-list-item tappable onclick="fn.load('page3.html')"> Page 3 </ons-list-item> </ons-list> </ons-page> </ons-splitter-side> <ons-splitter-content id="content" page="homePage.html"></ons-splitter-content> </ons-splitter> </ons-page> <ons-template id="homePage.html"> <ons-page id="homePage"> <ons-navigator id="myNavigator" page="page1.html"></ons-navigator> </ons-page> </ons-template> <ons-template id="page1.html"> <ons-page id="page1"> <ons-toolbar> <div class="left"> <ons-toolbar-button onclick="fn.open()"> <ons-icon icon="md-menu"></ons-icon> </ons-toolbar-button> </div> <div class="center">Home Page</div> </ons-toolbar> <p>This is the home page in the navigator.</p> <ons-button id="push-button">Push Page 3</ons-button> </ons-page> </ons-template> <ons-template id="page2.html"> <ons-page id="page2"> <ons-toolbar> <div class="left"><ons-toolbar-button onclick="fn.open()"> <ons-icon icon="md-menu"></ons-icon> </ons-toolbar-button> </div> <div class="center"></div> </ons-toolbar> <p>This is page 2 in the navigator still using the splitter.</p> <ons-button onclick="fn.pop()">Pop Page</ons-button> </ons-page> </ons-template> <ons-template id="page3.html"> <ons-page id="page3"> <ons-toolbar> <div class="left"><ons-back-button>Back</ons-back-button> </div> <div class="center"></div> </ons-toolbar> <p>This is page 3 in the navigator still using the splitter.</p> <ons-button onclick="fn.open()">Open Menu</ons-button> </ons-page> </ons-template>
The JS:
window.fn = {}; window.fn.open = function() { var menu = document.getElementById('menu'); menu.open(); }; window.fn.load = function(page, data) { var content = document.getElementById('myNavigator'); var menu = document.getElementById('menu'); content.pushPage(page, data) .then(menu.close.bind(menu)); }; window.fn.pop = function() { var content = document.getElementById('myNavigator'); content.popPage(); }; document.addEventListener('init', function(event) { var page = event.target; if (page.id === 'page1') { page.querySelector('#push-button').onclick = function() { document.querySelector('#myNavigator').pushPage('page3.html', {data: {title: 'Page 3'}}); }; } else if (page.id === 'page2' || page.id === 'page3') { page.querySelector('ons-toolbar .center').innerHTML = page.data.title; } });
Feedback is always welcome and encouraged, enjoy!
-
@munsterlander Thanks! I also wrote a tutorial about this here. It uses
resetToPage
but of course it can be changed to any other method.Could you please add some tags to the post?
-
@Fran-Diox Tags have been added. I knew it existed in the tutorial and you may recognize some of your code in mine, ha! :stuck_out_tongue:
-
Awesome guys! Is this able to be reproduced in the React version?
-
@lifz You can find a basic react demo of the same functionality as the one in the tutorial here.
However do note that I am not a React developer, so I may be showing bad practices. Probably in an actual app you would want to separate your app into multiple components rather than just having one giant app component as shown in the demo.
Perhaps @argelius or @patrick would be able to make a better demo, as they are more into react.
Also do note that react’s
Ons.Navigator
may undergo through some changes soon to make it feel more natural to react developers.
-
@IliaSky Fantastic, thanks so much! Now I’m trying to figure out how to put each page in its own component and then use that inside the <Navigator /> component. I’ll keep working on it and hope that @argelius and/or @patrick notice this topic!
Edit:
Forgive me for the large post but I thought someone coming in the future may benefit! I think I got it:// Import pages as separate compents import Home from "./Home"; import TeamMember from "./TeamMember"; /* pushPage() instead of replacePage() */ load(route) { this.hideMenu(); this.nav.pushPage(route, {animation: "fade"}); }, /* Pass props to the page so it can render the toolbar */ renderPage: function(route, nav) { route.props = route.props || {}; route.props.renderToolbar = this.renderToolbar.bind(this, route); route.props.title = route.title; return React.createElement(route.page, route.props); },
And then in
render()
:<List dataSource={[ {title: "Home", page: Home}, {title: "Team Member", page: TeamMember} ]} renderRow={route => ( <ListItem key={route.title} onClick={this.load.bind(this, route)} tappable>{route.title}</ListItem> )} />
I am getting an interesting warning in the console though:
My Home.js file is super simple. In its entirety:
import React from "react"; import ReactDOM from "react-dom"; import {Page} from "react-onsenui"; export default React.createClass({ render() { return ( <Page key={this.props.title} renderToolbar={this.props.renderToolbar}> <p style={{textAlign: "center"}}> We're on the HOME page! </p> </Page> ); } });
-
@lifz Hi - I see you changed
replacePage
withpushPage
- note that this means you are adding more and more pages to the stack, so unless you are popping them at some point the situation may go bad.Seeing your error (warning) below though makes me think I know why you did it though, as I stumbled into the same problem when making the demo. Basically when you have some list of elements (children with the same tag) you should add a unique
key
attribute, so that React can know which child is which. It needs to know that since it’s doing some magic when you want to update the dom, making it more efficient (insert virtual dom is awesome comment here :D).So since it’s doing “magic” if you don’t give it the keys it may screw up. In our case - when we call
replacePage
we are adding a new page, doing an animation etc, but in the end we are settingdisplay: none
to the previous one.However after that since we are replacing it we are actually removing that page. React however doesn’t know which child is which - it knows that its first child had
display: none
and therefore it applies it after we remove the old page. But that just makes our new page disappear! :DSo to fix this you need to add the
key
attribute to each page and you will be fine. I see you tried to add it to the home page, maybe you forgot to add it to your other pages? Maybe somehow they didn’t get them - you can probably check your dom or your react inspector and see what’s going on.The final thing would be if for example you are trying to replace a page with itself - you should either prevent that or make sure they would have different
key
s (you can always add some incrementing number).And again - do take all my words with caution as I am not a react developer.
-
Aha! You are a wonderful person, @IliaSky, fantastic responses!
you can probably check your dom or your react inspector
I didn’t have a “react inspector” so I added the extension to Chrome and found out what was going on! React was angry that the newly separated components didn’t have the
key
attribute (not the<Page>
). This tiny addition made the errors go away:renderPage: function(route, nav) { route.props = route.props || {}; route.props.key = route.title; /* <-- React needs this key to be on <Home />, < Settings />, etc */ route.props.renderToolbar = this.renderToolbar.bind(this, route); route.props.title = route.title; return React.createElement(route.page, route.props); },
Also, there’s definitely issues replacing the page with itself so thanks for commenting on that. I’ll figure out a way to disallow that (disable the menu selection, possibly).
-
Why is the navigator inside the homepage, can it be outside (above all pages)?
-
@Moshe-Flam This was due to using the splitter as well.
-
Hello to everyone.
I’ve been trying with the splitter and the back button, both using the same page.
For example:
<ons-splitter> <ons-splitter-side id="menuSptt" side="left" width="220px" collapse swipeable> <ons-page> <ons-list> <ons-list-item onclick="fn.load('home.html')" tappable> Home <ons-list-item tappable> About </ons-list-item> </ons-list> </ons-page> </ons-splitter-side> <ons-splitter-content id="contenidoSptt" page="home.html"></ons-splitter-content> </ons-splitter> <ons-navigator swipeable id="myNavigator" page="home.html"></ons-navigator> <template id="home.html"> <ons-page id="home"> Home 1.0 </ons-page> </template>
I couldn’t make it work. The idea is bringing a list to home template and use the back button once the user goes inside an element.
Could anyone tell me how to fix it?
Thank you !