text: /**
  * Combodate - 1.0.7
  * Dropdown date and time picker.
  * Converts text input into dropdowns to pick day, month, year, hour, minute and second.
  * Uses momentjs as datetime library http://momentjs.com.
  * For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang 
  *
  * Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight
  * In combodate: 
  * 12:00 pm --> 12:00 (24-h format, midday)
  * 12:00 am --> 00:00 (24-h format, midnight, start of day)
  * 
  * Differs from momentjs parse rules:
  * 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change)
  * 00:00 am, 12:00 am --> 00:00 (24-h format, day not change)
  * 
  * 
  * Author: Vitaliy Potapov
  * Project page: http://github.com/vitalets/combodate
  * Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
  **/
  (function ($) {
  
      var Combodate = function (element, options) {
          this.$element = $(element);
          if(!this.$element.is('input')) {
              $.error('Combodate should be applied to INPUT element');
              return;
          }
          this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data());
          this.init();  
       };
  
      Combodate.prototype = {
          constructor: Combodate, 
          init: function () {
              this.map = {
                  //key   regexp    moment.method
                  day:    ['D',    'date'], 
                  month:  ['M',    'month'], 
                  year:   ['Y',    'year'], 
                  hour:   ['[Hh]', 'hours'],
                  minute: ['m',    'minutes'], 
                  second: ['s',    'seconds'],
                  ampm:   ['[Aa]', ''] 
              };
              
              this.$widget = $('<span class="combodate"></span>').html(this.getTemplate());
                        
              this.initCombos();
              
              //update original input on change 
              this.$widget.on('change', 'select', $.proxy(function(e) {
                  this.$element.val(this.getValue()).change();
                  // update days count if month or year changes
                  if (this.options.smartDays) {
                      if ($(e.target).is('.month') || $(e.target).is('.year')) {
                          this.fillCombo('day');
                      }
                  }
              }, this));
              
              this.$widget.find('select').css('width', 'auto');
                                         
              // hide original input and insert widget                                       
              this.$element.hide().after(this.$widget);
              
              // set initial value
              this.setValue(this.$element.val() || this.options.value);
          },
          
          /*
           Replace tokens in template with <select> elements 
          */         
          getTemplate: function() {
              var tpl = this.options.template;
              var customClass = this.options.customClass;
  
              //first pass
              $.each(this.map, function(k, v) {
                  v = v[0]; 
                  var r = new RegExp(v+'+'),
                      token = v.length > 1 ? v.substring(1, 2) : v;
                      
                  tpl = tpl.replace(r, '{'+token+'}');
              });
  
              //replace spaces with &nbsp;
              tpl = tpl.replace(/ /g, '&nbsp;');
  
              //second pass
              $.each(this.map, function(k, v) {
                  v = v[0];
                  var token = v.length > 1 ? v.substring(1, 2) : v;
                      
                  tpl = tpl.replace('{'+token+'}', '<select class="'+k+' '+customClass+'"></select>');
              });   
  
              return tpl;
          },
          
          /*
           Initialize combos that presents in template 
          */        
          initCombos: function() {
              for (var k in this.map) {
                  var $c = this.$widget.find('.'+k);
                  // set properties like this.$day, this.$month etc.
                  this['$'+k] = $c.length ? $c : null;
                  // fill with items
                  this.fillCombo(k);
              }
          },
  
          /*
           Fill combo with items 
          */        
          fillCombo: function(k) {
              var $combo = this['$'+k];
              if (!$combo) {
                  return;
              }
  
              // define method name to fill items, e.g `fillDays`
              var f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1); 
              var items = this[f]();
              var value = $combo.val();
  
              $combo.empty();
              for(var i=0; i<items.length; i++) {
                  $combo.append('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');
              }
  
              $combo.val(value);
          },
  
          /*
           Initialize items of combos. Handles `firstItem` option 
          */
          fillCommon: function(key) {
              var values = [],
                  relTime;
                  
              if(this.options.firstItem === 'name') {
                  //need both to support moment ver < 2 and  >= 2
                  relTime = moment.relativeTime || moment.langData()._relativeTime; 
                  var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];
                  //take last entry (see momentjs lang files structure) 
                  header = header.split(' ').reverse()[0];                
                  values.push(['', header]);
              } else if(this.options.firstItem === 'empty') {
                  values.push(['', '']);
              }
              return values;
          },  
  
  
          /*
          fill day
          */
          fillDay: function() {
              var items = this.fillCommon('d'), name, i,
                  twoDigit = this.options.template.indexOf('DD') !== -1,
                  daysCount = 31;
  
              // detect days count (depends on month and year)
              // originally https://github.com/vitalets/combodate/pull/7
              if (this.options.smartDays && this.$month && this.$year) {
                  var month = parseInt(this.$month.val(), 10);
                  var year = parseInt(this.$year.val(), 10);
  
                  if (!isNaN(month) && !isNaN(year)) {
                      daysCount = moment([year, month]).daysInMonth();
                  }
              }
  
              for (i = 1; i <= daysCount; i++) {
                  name = twoDigit ? this.leadZero(i) : i;
                  items.push([i, name]);
              }
              return items;        
          },
          
          /*
          fill month
          */
          fillMonth: function() {
              var items = this.fillCommon('M'), name, i, 
                  longNames = this.options.template.indexOf('MMMM') !== -1,
                  shortNames = this.options.template.indexOf('MMM') !== -1,
                  twoDigit = this.options.template.indexOf('MM') !== -1;
                  
              for(i=0; i<=11; i++) {
                  if(longNames) {
                      //see https://github.com/timrwood/momentjs.com/pull/36
                      name = moment().date(1).month(i).format('MMMM');
                  } else if(shortNames) {
                      name = moment().date(1).month(i).format('MMM');
                  } else if(twoDigit) {
                      name = this.leadZero(i+1);
                  } else {
                      name = i+1;
                  }
                  items.push([i, name]);
              } 
              return items;
          },  
          
          /*
          fill year
          */
          fillYear: function() {
              var items = [], name, i, 
                  longNames = this.options.template.indexOf('YYYY') !== -1;
             
              for(i=this.options.maxYear; i>=this.options.minYear; i--) {
                  name = longNames ? i : (i+'').substring(2);
                  items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);
              }
              
              items = this.fillCommon('y').concat(items);
              
              return items;              
          },    
          
          /*
          fill hour
          */
          fillHour: function() {
              var items = this.fillCommon('h'), name, i,
                  h12 = this.options.template.indexOf('h') !== -1,
                  h24 = this.options.template.indexOf('H') !== -1,
                  twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
                  min = h12 ? 1 : 0, 
                  max = h12 ? 12 : 23;
                  
              for(i=min; i<=max; i++) {
                  name = twoDigit ? this.leadZero(i) : i;
                  items.push([i, name]);
              } 
              return items;                 
          },    
          
          /*
          fill minute
          */
          fillMinute: function() {
              var items = this.fillCommon('m'), name, i,
                  twoDigit = this.options.template.indexOf('mm') !== -1;
  
              for(i=0; i<=59; i+= this.options.minuteStep) {
                  name = twoDigit ? this.leadZero(i) : i;
                  items.push([i, name]);
              }    
              return items;              
          },  
          
          /*
          fill second
          */
          fillSecond: function() {
              var items = this.fillCommon('s'), name, i,
                  twoDigit = this.options.template.indexOf('ss') !== -1;
  
              for(i=0; i<=59; i+= this.options.secondStep) {
                  name = twoDigit ? this.leadZero(i) : i;
                  items.push([i, name]);
              }    
              return items;              
          },  
          
          /*
          fill ampm
          */
          fillAmpm: function() {
              var ampmL = this.options.template.indexOf('a') !== -1,
                  ampmU = this.options.template.indexOf('A') !== -1,            
                  items = [
                      ['am', ampmL ? 'am' : 'AM'],
                      ['pm', ampmL ? 'pm' : 'PM']
                  ];
              return items;                              
          },                                       
  
          /*
           Returns current date value from combos. 
           If format not specified - `options.format` used.
           If format = `null` - Moment object returned.
          */
          getValue: function(format) {
              var dt, values = {}, 
                  that = this,
                  notSelected = false;
                  
              //getting selected values    
              $.each(this.map, function(k, v) {
                  if(k === 'ampm') {
                      return;
                  }
                  var def = k === 'day' ? 1 : 0;
                    
                  values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def; 
                  
                  if(isNaN(values[k])) {
                     notSelected = true;
                     return false; 
                  }
              });
              
              //if at least one visible combo not selected - return empty string
              if(notSelected) {
                 return '';
              }
              
              //convert hours 12h --> 24h 
              if(this.$ampm) {
                  //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
                  if(values.hour === 12) {
                      values.hour = this.$ampm.val() === 'am' ? 0 : 12;                    
                  } else {
                      values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
                  }
              }    
              
              dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
              
              //highlight invalid date
              this.highlight(dt);
                                
              format = format === undefined ? this.options.format : format;
              if(format === null) {
                 return dt.isValid() ? dt : null; 
              } else {
                 return dt.isValid() ? dt.format(format) : ''; 
              }           
          },
          
          setValue: function(value) {
              if(!value) {
                  return;
              }
              
              // parse in strict mode (third param `true`)
              var dt = typeof value === 'string' ? moment(value, this.options.format, true) : moment(value),
                  that = this,
                  values = {};
              
              //function to find nearest value in select options
              function getNearest($select, value) {
                  var delta = {};
                  $select.children('option').each(function(i, opt){
                      var optValue = $(opt).attr('value'),
                      distance;
  
                      if(optValue === '') return;
                      distance = Math.abs(optValue - value); 
                      if(typeof delta.distance === 'undefined' || distance < delta.distance) {
                          delta = {value: optValue, distance: distance};
                      } 
                  }); 
                  return delta.value;
              }             
              
              if(dt.isValid()) {
                  //read values from date object
                  $.each(this.map, function(k, v) {
                      if(k === 'ampm') {
                         return; 
                      }
                      values[k] = dt[v[1]]();
                  });
                 
                  if(this.$ampm) {
                      //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
                      if(values.hour >= 12) {
                          values.ampm = 'pm';
                          if(values.hour > 12) {
                              values.hour -= 12;
                          }
                      } else {
                          values.ampm = 'am';
                          if(values.hour === 0) {
                              values.hour = 12;
                          }
                      } 
                  }
                 
                  $.each(values, function(k, v) {
                      //call val() for each existing combo, e.g. this.$hour.val()
                      if(that['$'+k]) {
                         
                          if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) {
                             v = getNearest(that['$'+k], v);
                          }
                         
                          if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) {
                             v = getNearest(that['$'+k], v);
                          }                       
                         
                          that['$'+k].val(v);
                      }
                  });
  
                  // update days count
                  if (this.options.smartDays) {
                      this.fillCombo('day');
                  }
                 
                 this.$element.val(dt.format(this.options.format)).change();
              }
          },
          
          /*
           highlight combos if date is invalid
          */
          highlight: function(dt) {
              if(!dt.isValid()) {
                  if(this.options.errorClass) {
                      this.$widget.addClass(this.options.errorClass);
                  } else {
                      //store original border color
                      if(!this.borderColor) {
                          this.borderColor = this.$widget.find('select').css('border-color'); 
                      }
                      this.$widget.find('select').css('border-color', 'red');
                  } 
              } else {
                  if(this.options.errorClass) {
                      this.$widget.removeClass(this.options.errorClass);
                  } else {
                      this.$widget.find('select').css('border-color', this.borderColor);
                  }  
              }
          },
          
          leadZero: function(v) {
              return v <= 9 ? '0' + v : v; 
          },
          
          destroy: function() {
              this.$widget.remove();
              this.$element.removeData('combodate').show();
          }
          
          //todo: clear method        
      };
  
      $.fn.combodate = function ( option ) {
          var d, args = Array.apply(null, arguments);
          args.shift();
  
          //getValue returns date as string / object (not jQuery object)
          if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) {
            return d.getValue.apply(d, args);
          }        
          
          return this.each(function () {
              var $this = $(this),
              data = $this.data('combodate'),
              options = typeof option == 'object' && option;
              if (!data) {
                  $this.data('combodate', (data = new Combodate(this, options)));
              }
              if (typeof option == 'string' && typeof data[option] == 'function') {
                  data[option].apply(data, args);
              }
          });
      };  
      
      $.fn.combodate.defaults = {
           //in this format value stored in original input
          format: 'DD-MM-YYYY HH:mm',      
          //in this format items in dropdowns are displayed
          template: 'D / MMM / YYYY   H : mm',
          //initial value, can be `new Date()`    
          value: null,
          minYear: 1970,
          maxYear: 2018,
          yearDescending: true,
          minuteStep: 5,
          secondStep: 1,
          firstItem: 'name', //'name', 'empty', 'none'
          errorClass: null,
          customClass: '',
          roundTime: true, // whether to round minutes and seconds if step > 1
          smartDays: false // whether days in combo depend on selected month: 31, 30, 28
      };
  
  }(window.jQuery));