In the switch to 'mobile first', one of the things we've had to address is all of those modal windows we created. In a mobile device, how do we create those easily? Let's use a straightforward example that builds on the datepicker we discussed in the last post.
Since we're working with mobile, we're going to use an inline date selector, so we know a little about the design, but we also want to be able to accept the range selected (we'll use an 'OK' button) and we want to have a few other buttons - like a reset that will return the date inputs to their original values - and a 'cancel' button so we can dismiss the modal without making a change.
As always, we're going to start with the markup.
HTML
You'll notice the markup is almost exactly the same as it is in the 'Pick a date' post, with the addition of the action buttons and initial values for the date fields.
<form class="modal" method="post">
<header>
Travel Details
<input class="close" type="button" value="×" />
</header>
<fieldset>
<legend>Date Range</legend>
<div class="date">
<label for="dtfrom">From</label>
<input data-original="1/1/1970" id="dtfrom" type="text" value="1/1/1970"/>
</div>
<div class="date">
<label for="dtto">To</label>
<input data-original="12/31/2013" id="dtto" type="text" value="12/31/2013"/>
</div>
</fieldset>
<!-- don't bother adding this 'inline-calendar' element if you're not displaying the calendar inline -->
<div id="inline-calendar"></div>
<fieldset>
<legend>Departure and Arrival Airports</legend>
<div class="airport">
<label for="depart">Depart</label>
<input id="depart" type="text" />
</div>
<div class="airport">
<label for="arrive">Arrive</label>
<input id="arrive" type="text" />
</div>
</fieldset>
<div class="btns">
<input type="submit" value="OK" />
<input id="btnReset" type="button" value="RESET" />
<input class="close" type="button" value="CANCEL" />
</div>
<a aria-hidden="true" class="backToTop" name="backToTop">back to top</a>
</form>
<header>
Travel Details
<input class="close" type="button" value="×" />
</header>
<fieldset>
<legend>Date Range</legend>
<div class="date">
<label for="dtfrom">From</label>
<input data-original="1/1/1970" id="dtfrom" type="text" value="1/1/1970"/>
</div>
<div class="date">
<label for="dtto">To</label>
<input data-original="12/31/2013" id="dtto" type="text" value="12/31/2013"/>
</div>
</fieldset>
<!-- don't bother adding this 'inline-calendar' element if you're not displaying the calendar inline -->
<div id="inline-calendar"></div>
<fieldset>
<legend>Departure and Arrival Airports</legend>
<div class="airport">
<label for="depart">Depart</label>
<input id="depart" type="text" />
</div>
<div class="airport">
<label for="arrive">Arrive</label>
<input id="arrive" type="text" />
</div>
</fieldset>
<div class="btns">
<input type="submit" value="OK" />
<input id="btnReset" type="button" value="RESET" />
<input class="close" type="button" value="CANCEL" />
</div>
<a aria-hidden="true" class="backToTop" name="backToTop">back to top</a>
</form>
As with the 'Pick a date' post, you'll want to be sure to include the 'back to top' link and attach a listener for the focus event so that when the focus event occurs, focus is moved to the first element in the modal form. If you haven't read the 'Pick a date' post, go read the 'Inline Implementation' section for clues about what to do with this 'back to top' link, including the CSS to hide it. If you skip this step, focus will move from your modal to the page behind it, and your user experience will degrade.
JavaScript
We're going to keep the same JavaScript as we have for the Inline Implementation in the 'Pick a date' post, but we're going to add a function to reset the date to the original values as a listener on the reset button. We also need a function to dismiss the modal that we tie to the cancel button. The functions provided assume that the buttons are the only matching elements on the page - you may need to 'find' them within the modal if you have several modals or other matching elements on the page.
$('#btnReset').on('click', function() {
var $inputs = $('input:not(type="hidden")')
, $input
, $index = $inputs.length
;
while ($index-- > 0) {
$input = $($inputs[$index]);
$input.val($input.get('data-original'));
}
});
$('input.close').on('click', function() {
var $container = $(event.currentTarget).closest('modal');
if ($container) {
$container.removeClass('in');
}
});
Now you have JavaScript that will close the container (by removing the 'in' class) when the cancel button or the close button are clicked and JavaScript that will loop through all the non-hidden inputs and reset them. Of course if you have SELECT tags or TEXTAREA tags you'll need to reset them, and you'll also need to reset the 'checked' attribute on radio and checkbox type inputs, but this is sufficient for our example.var $inputs = $('input:not(type="hidden")')
, $input
, $index = $inputs.length
;
while ($index-- > 0) {
$input = $($inputs[$index]);
$input.val($input.get('data-original'));
}
});
$('input.close').on('click', function() {
var $container = $(event.currentTarget).closest('modal');
if ($container) {
$container.removeClass('in');
}
});
CSS
Now for the fun part - the CSS is going to be wrapped in a media query so that we're not modifying anything for the desktop - we want that to be a plain vanilla style. The modal window, though should animate by floating up from the bottom of the screen and should have evenly sized and spaced buttons at the bottom. To do this, we're going to use a transition for the animation and a pseudo-classes for the buttons.
.modal {
@media (max-width: 480px) {
border-top: 1px solid #999;
height: 100%;
padding: 16px;
width: 100%;
display: block;
position: absolute;
top: 100%; /* This puts the modal at the bottom of the screen */
z-index: -1; /* This puts the modal under everything */
background-color: #fff;
transition: top 0.5s, z-index 0.6s;
-webkit-transition: top 0.5s, z-index 0.6s;
header {
height: 44px;
margin: 0;
overflow: hidden; /* This will make the header 'contain' the close button */
padding: 0;
font-size: 20px;
line-height: 44px; /* Making this the same as the height will make the text vertically center */
text-align: center;
}
.close {
float: right;
height: 44px;
margin: 0;
width: 44px;
font-size: 36px;
text-align: center;
}
.btns {
text-align: center;
input {
margin: auto 1%;
}
input:first-child:nth-last-child(1) {
width: 100%;
}
input:first-child:nth-last-child(2), input:first-child:nth-last-child(2) ~ input {
width: 48%;
}
input:first-child:nth-last-child(3), input:first-child:nth-last-child(3) ~ input {
width: 31.3333%;
}
input:first-child:nth-last-child(4), input:first-child:nth-last-child(4) ~ input {
width: 23%;
}
input:first-child {
margin-left: 0;
}
input:last-child {
margin-right: 0;
}
}
&.in {
top: 0;
z-index: 10000;
}
}
}
@media (max-width: 480px) {
border-top: 1px solid #999;
height: 100%;
padding: 16px;
width: 100%;
display: block;
position: absolute;
top: 100%; /* This puts the modal at the bottom of the screen */
z-index: -1; /* This puts the modal under everything */
background-color: #fff;
transition: top 0.5s, z-index 0.6s;
-webkit-transition: top 0.5s, z-index 0.6s;
header {
height: 44px;
margin: 0;
overflow: hidden; /* This will make the header 'contain' the close button */
padding: 0;
font-size: 20px;
line-height: 44px; /* Making this the same as the height will make the text vertically center */
text-align: center;
}
.close {
float: right;
height: 44px;
margin: 0;
width: 44px;
font-size: 36px;
text-align: center;
}
.btns {
text-align: center;
input {
margin: auto 1%;
}
input:first-child:nth-last-child(1) {
width: 100%;
}
input:first-child:nth-last-child(2), input:first-child:nth-last-child(2) ~ input {
width: 48%;
}
input:first-child:nth-last-child(3), input:first-child:nth-last-child(3) ~ input {
width: 31.3333%;
}
input:first-child:nth-last-child(4), input:first-child:nth-last-child(4) ~ input {
width: 23%;
}
input:first-child {
margin-left: 0;
}
input:last-child {
margin-right: 0;
}
}
&.in {
top: 0;
z-index: 10000;
}
}
}
A little explanation about the CSS is in order.
First, I'm using LESS. In my opinion, LESS syntax is easily understood even for those who haven't fallen into that rabbit hole, and if you haven't fallen into that particular rabbit hole, I'd encourage you to explore it - which you can do at http://lesscss.org/.
Second, there are a few lines that are documented inline. This is a practice that, if you are not doing it already, you should start. There are always at least two developers working on a project - present you and future you - and inline documentation like this will help you remember why you did something.
Third, although it's not documented inline, I'm using 44 pixels because it's really the smallest object you can poke. If you make something smaller, your users will be frustrated. Of course you should feel free to make things larger as fits your design.
Fourth, there are two animations because we're hiding the panel from the user in two ways. Using a longer duration for the z-index animation makes sure that it flows on and off the screen before dropping behind the other layers. Using a high value for the 'in' z-index makes sure that it rises to the top layer earlier in the transition duration. Although the maximum value you can use in modern browsers for a z-index is 2147483647 (32-bit), in practice there's no need for one that high - which is why I'm using 10000.
Fifth, and finally, a note about the button row of the modal window. First, keep elements that are not INPUT tags out of the button row - it's not semantic and it will degrade the user experience by altering the display. Second, the style rules are written to accommodate up to four buttons in the button row. If you find that you really need more than four buttons, use more button rows. Third, keep in mind that in order for this to work, you will need to keep the text displayed inside the buttons within reason. As you will notice, the width of the buttons is 100 divided by the number of buttons in the row minus two. The 'two' that we're subtracting is the margin on the left and right sides of the buttons. The first and last buttons in the row (left-most and right-most) have the corresponding margin removed.
Conclusion
Using this design pattern, you can easily add a modal window to your mobile devices that is easily maintained by keeping the markup simple and semantic. The pattern is also easily modified - for example, you may choose to have the modal window slide in from the left or right side, which is simply a matter of changing the left or right position and changing the transition property from top to left or right.Just remember you can accomplish much, even on mobile, by keeping it POSH and adding a little CSS3 and a little JavaScript.
No comments:
Post a Comment