Showing posts with label jquery. Show all posts
Showing posts with label jquery. Show all posts

Monday, October 7, 2013

Animated Responsive Dropdown Menu

Because the navigation bar of Bootstrap provided didn't have and transition effect, so I customize one. Actually it is nearly rebuild the whole UI widget, except the navbar (I tread it as a container) and collapse feature.
PC view of the menu bar.

Mobile view of the menu bar.

Live Demo Here
(You can drag and resize the width of "result" frame in order to view the different visual effect between mobile view and PC view)
As you know, it is good enough to use CSS to build dropdown menu. To make transition animation during hover, you may use CSS3 transition effect. So what is the tricks to make responsive web design? I is nothing difficult but what you need is @media tag in stylesheet. eg:
/* some style for mobile phone view */
.example {
    /* some styles here */
}
/* Style for screen width > 768px,
   Bootstrap tread it as taplet or PC */
@media (min-width: 768px) {
    .example {
        /* some styles here */
    }
}

It is also called mobile first responsive web design, because you may find that the default style is for mobile phone, and the PC style will overwrite it when the screen is wide enough. With this trick, I make two view of the same HTML content, one is the horizontal menu bar, one is the vertical expendable list.
Since in CSS you can only trace the hover event, which is used to show sub-menu in PC view, but in mobile view, we should click on a item and then expend its sub-menu, so what I do next is to add different event handling in different view. Actually it is the main usage of my javascript file, also I extend $.fn to make them controlable by javascript through:
$('.anav-sub').asubnav('toggle');
$('.anav-sub').asubnav('open');
$('.anav-sub').asubnav('close');

Actually it is not very difficult to make such a responsive UI widget, but you may need to spend some times to play with the CSS, the javascript part is relatively simpler. Below is the whole source code of the example:
<!DOCTYPE html>
<html>
<head>
 <title>Animated Responsive Dropdown Menu</title>
 <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
 <link href="anavbar.css" rel="stylesheet">
 <style type="text/css">
  body {
      margin: 10px;
  }
  @media (min-width: 768px) {
   .navbar-ex2-collapse { float: left; }
  }
 </style>
</head>
<body>
 <!-- Bootstrap Menu Bar -->
 <nav class="navbar navbar-default" role="navigation">
   <div class="navbar-header">
     <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
       <span class="icon-bar"></span>
       <span class="icon-bar"></span>
       <span class="icon-bar"></span>
     </button>
     <a class="navbar-brand" href="#">Bootstrap Menu Bar</a>
   </div>
   <div class="collapse navbar-collapse navbar-ex1-collapse">
     <ul class="nav navbar-nav">
       <li><a href="#">Item 1</a></li>
       <li><a href="#">Item 2</a></li>
       <li class="dropdown">
         <a href="#" class="dropdown-toggle" data-toggle="dropdown">Item 3<b class="caret"></b></a>
         <ul class="dropdown-menu">
           <li><a href="#">Item3.1</a></li>
           <li><a href="#">Item3.2</a></li>
           <li><a href="#">Item3.3</a></li>
         </ul>
       </li>
     </ul>
   </div>
 </nav>

 <div class="spacer" style="height:150px;"></div>

 <!-- My animated responsive navigation bar -->
 <nav id="main_navbar" class="navbar navbar-default anavbar" role="navigation">
  <!-- Navbar header for mobile view -->
  <div class="navbar-header">
   <div class="navbar-brand">
    My Menu Bar
   </div>
   <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex2-collapse">
    <span class="sr-only">Toggle navigation</span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
   </button>
  </div>
     
  <!-- Navbar menu -->
  <div class="collapse navbar-collapse navbar-ex2-collapse">
   <ul id="main_nav" class="anav">
    <li><a href="#">Item 1</a></li>
    <li><a href="#">Item 2</a></li>
    <li>
     <a href="#">Item 3<div class="arrow">+</div></a>
     <ul class="anav-sub">
      <li><a href="#">Item 3.1</a></li>
      <li><a href="#">Item 3.2</a></li>
      <li><a href="#">Item 3.3</a></li>
     </ul>
    </li>
   </ul>
  </div>
 </nav>

 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
 <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
 <script src="anavbar.js"></script>
</body>
</html>

$(function(){
 var ANAV_CLASS = ".anav",
  ASUBNAV_CLASS = ".anav-sub";
 function getParent(el){
  return $(el).filter(ASUBNAV_CLASS).parent();
 }
 function toggleHandler(event){
  var $this = $(this),
   $subNavbar = $this.children(ASUBNAV_CLASS),
   isVertical = $this.css('float') == 'none' ? true : false;

  if(isVertical && event && event.type == 'click'){
   toggle.call($subNavbar);
   event.preventDefault();
   event.stopPropagation();
  }
  else if(!isVertical && event && event.type == 'mouseenter'){
   toggle.call($subNavbar, true);
  }
  else if(!isVertical && event && event.type == 'mouseleave'){
   toggle.call($subNavbar, false);
  }
 }
 function ASubNav(el){
  this.element = el;
  getParent(el)
   .on('click mouseenter mouseleave', toggleHandler);
 }
 var toggle = ASubNav.prototype.toggle = function(_isOpen){
  var $this = $(this),
   $parent = getParent(this);
   isOpen = typeof _isOpen == 'boolean' ? _isOpen : !$parent.hasClass('open');
  if(isOpen){
   $parent.addClass('open');
   $this.clearQueue().finish().slideDown();
  }else{
   $parent.removeClass('open');
   $this.clearQueue().finish().slideUp();
  }
 };
 ASubNav.prototype.open = function(){
  toggle.call(this, true);
 };
 ASubNav.prototype.close = function(){
  toggle.call(this, false);
 };

 $.fn.asubnav = function(option){
  return this.each(function(){
   var $this = $(this),
    data = $this.data('anav-sub');

   !data && $this.data('anav-sub', (data = new ASubNav(this)));
   if(typeof option == 'string'){
    data[option].call($this);
   }
  });
 }
 $.fn.asubnav.Constructor = ASubNav;

 $(ANAV_CLASS + " " + ASUBNAV_CLASS).asubnav();
});

.anav, .anav-sub {
 position: relative;
 list-style: none;
 margin: 0;
 padding: 0;
}
.anav > li > a,
.anav-sub > li > a {
 text-decoration: none;
 display: block;
}

/* nav collapse button */
.anavbar .navbar-toggle {
 border-color: #ddd;
}
.anavbar .navbar-toggle:hover {
 background: #ddd;
}
.anavbar .navbar-toggle .icon-bar{
 background: #ccc;
}

/* navbar div */
.anavbar {
 min-height: 40px;
 background: background-color: #f8f8f8;
    border: 1px solid #e7e7e7;
}

/* 1st layer ul */
.anav {
 float: left;
 display: block;
 width: 100%;
}
.anav > li {
 position: relative;
}
.anav > li > a {
 padding: 10px 5px;
 color: #777;
}
.anav > li:hover > a {
 color: #333;
}

/* 2nd layer ul */
.anav .anav-sub {
 overflow: hidden;
 display: none;
 white-space: nowrap;
}
.anav .arrow{
 display: inline-block;
    height: 0;
    width: 0;
    overflow: hidden;
    vertical-align: middle;
    margin-left: 4px;
    background: transparent;
    border-top: 4px solid #777;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-bottom: 0 solid transparent;
}
.anav > li:hover .arrow {
    border-top-color: #333;
}
.anav .anav-sub > li > a {
 padding: 5px 15px;
 color: #777;
}
.anav .anav-sub > li:hover > a {
 color: #333;
}

/* Responsive */
@media (min-width: 768px) {
 .anav > li { float: left; width: auto; }
 .anav > li > a { padding: 15px; min-width: 85px; text-align: center; }
 .anav .anav-sub {
  position: absolute;
  -webkit-border-radius: 0 0 4px 4px;
  border-radius: 0 0 4px 4px;
  box-shadow: 0 6px 12px rgba(0,0,0,0.175);
  min-width: 160px;
 }
 .open > .anav-sub { padding-bottom: 4px; border: 1px solid #ccc; }
 .anav .anav-sub > li > a { padding: 5px 20px; min-width: 85px; color: #333; }
 .anav .anav-sub > li:hover > a { background: #428bca; color: #fff; }
}

Friday, September 27, 2013

Twitter Bootstrap - Super fast development for responsive website

In this week, what makes me so exciting is not D3 again, although I still have some skills about D3 which I would like to share, I'll talk about another front-end UI framework in this post, the Twitter Bootstrap. According to the official slogan, Bootstrap is a sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development. Actually, Bootstrap is base on jQuery, and is not something new. But when I accidentally had a chance to study it, I really feel exciting.
The first selling point of Bootstrap is rapid development. It can really boost up the development of front-end design, it remedies the weakness of layout design in jQuery. Just with some well formatted HTML (no javascript), you can get a good looking and interactive web page, with navigation bar and slideshow banner or other basic website element.
The second selling point of Bootstrap is responsive web design, actually is its greatest selling point. It makes you web page fit to all kind of devices. It automatically change the look and view according to you devices's resolution, so you would get well-fit outlook adaptive to you mobile phone, tablet or PC, but would not affect you functionality.
Actually, although Bootstrap is good, it can't satisfy my desire. Because I want a responsive animated navigation bar, so I try to customize one. Through studying how Bootstrap is implemented, I learnt a lot about how to build you own jQuery widget, how to write response web design and also some tricks to play with CSS. Since it isn't quite related to Bootstrap, and I need times to simplify my code I'll do it in the next blog post.

Sunday, September 15, 2013

Hack on jQuery UI widget

If you are going build your own jQuery UI widget / plugin, you may face a problem like me, how to determine the entry and exit point of the widget.
Let's say your widget is something like a date picker, initially it is a input box, when you click on it or tab to it, it popup a picker for you, then if you click outside the input box or the picker, the widget hide the popuped picker. The life cycle is so straight forward, but actually you would find focusout and blur tell you nothing about the new focus target, so you can't treat these two event as a exit point of the UI widget.
If you bind a exit point on these two events, when you click from the input box to the popup picker, it must fire one pair of blur and focus event, which make you popup stuffs close once and immediate re-open again, cause a bad flicking visual effect to user.
So after I read every lines on the jQuery's Datepicker, I suggest a widget should be something like this (just pseudo code):
$.widget('myWidget', {
    _init : function(){
        // Bind the enter and exit event handler
        this._on(this.element, {
            'focus' : this._enter
        });
        this._on(document, {
            'mousedown' : this._exit,
            'focusin' : this.exit
        });
    },
    _enter : function(event){
        // Entry point, create the popup picker
        this._picker = new Picker();
        // Append to some where below the input box
    },
    _exit : function(event){
        if(event.target != this.element &&
           !$.contains(this._picker, event.target)){
              // Exit point, remove popup picker
              this._picker.remove()
        }
    }
});

Wednesday, September 11, 2013

A simple mouse click, not easy mouse event

My very first task in my job is to code refactoring the UI widgets developed by our company (mainly some mutation of jquery UI). Through studying the current UI widgets' life cycle, I found that most of the bugs were came from mishandling of mouse events. After the hard studies of the behaviors across the 3 main browsers, IE, Firefox and Chrome, I have a small note and summary as below.
Include the jquery custom focusin and focusout events, there are total 8 events which are trigger by a Single Mouse Click, and they are: mousedown, mouseup, focusin, focusout, focus, blur, change and click. Actually only the first two events, mousedown and mouseup, are directly related to the physical mouse left button press and release, all others are some logical events. As excepted, different browsers has its own implementation, but surprisingly different jquery version handle the events differently, as the result jquery dosen't help you to standardize the event sequences, and make things so unexpected. But after thousands of tests and experiments, I figuer out the evnet sequence should like:
1) mousedown -> 2) change(A Type) -> 3) focusout -> 4&5) blur / focusin -> 6) focus
7) mouseup -> 8&9) change(B Type) / click
This sequences is generalized for all focusable and editalbe elements, such as a button, input box, radio button, div with tabindex, etc. Lets assume A Type element is something like input box and textarea, and the B Type element is something like check box, radio button and select list. With this knowledge, there are several thing we shoud be care.
  1. The event sequence would always preserve, which means they are always fire out one by one, even if you had call preventDefault and stopPropagation. But if you call preventDefault during mousedown, the 2nd to 6th events would not be fired.
  2. blur and focusin event would come out dependent on browser, normally it is blur -> focusin, in IE it is focusin -> blur, so you should choose only one of them to use in the whole system, to prevent logic error or dead lock between elements.
  3. For B Type elements, the change and click event sequece is also dependent on browser, IE8, IE9+, Firefox and Chrome would have totally different strange behaviors. If you use jquery to bind event handlers, the intermediate element's value would be different also. You must bind handler to change event, if you need to trace the element's intermediate value.