text: ﻿var gj = {};
  
  gj.widget = function () {
      var self = this;
  
      self.xhr = null;
  
      self.generateGUID = function () {
          function s4() {
              return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
          }
          return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
      };
  
      self.mouseX = function (e) {
          if (e) {
              if (e.pageX) {
                  return e.pageX;
              } else if (e.clientX) {
                  return e.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
              } else if (e.touches && e.touches.length) {
                  return e.touches[0].pageX;
              } else if (e.changedTouches && e.changedTouches.length) {
                  return e.changedTouches[0].pageX;
              } else if (e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length) {
                  return e.originalEvent.touches[0].pageX;
              } else if (e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
                  return e.originalEvent.touches[0].pageX;
              }
          }
          return null;
      };
  
      self.mouseY = function (e) {
          if (e) {
              if (e.pageY) {
                  return e.pageY;
              } else if (e.clientY) {
                  return e.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
              } else if (e.touches && e.touches.length) {
                  return e.touches[0].pageY;
              } else if (e.changedTouches && e.changedTouches.length) {
                  return e.changedTouches[0].pageY;
              } else if (e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length) {
                  return e.originalEvent.touches[0].pageY;
              } else if (e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
                  return e.originalEvent.touches[0].pageY;
              }
          }
          return null;
      };
  };
  
  gj.widget.prototype.init = function (jsConfig, type) {
      var option, clientConfig, fullConfig;
  
      this.attr('data-type', type);
      clientConfig = $.extend(true, {}, this.getHTMLConfig() || {});
      $.extend(true, clientConfig, jsConfig || {});
      fullConfig = this.getConfig(clientConfig, type);
      this.attr('data-guid', fullConfig.guid);
      this.data(fullConfig);
  
      // Initialize events configured as options
      for (option in fullConfig) {
          if (gj[type].events.hasOwnProperty(option)) {
              this.on(option, fullConfig[option]);
              delete fullConfig[option];
          }
      }
  
      // Initialize all plugins
      for (plugin in gj[type].plugins) {
          if (gj[type].plugins.hasOwnProperty(plugin)) {
              gj[type].plugins[plugin].configure(this, fullConfig, clientConfig);
          }
      }
  
      return this;
  };
  
  gj.widget.prototype.getConfig = function (clientConfig, type) {
      var config, uiLibrary, iconsLibrary, plugin;
  
      config = $.extend(true, {}, gj[type].config.base);
  
      uiLibrary = clientConfig.hasOwnProperty('uiLibrary') ? clientConfig.uiLibrary : config.uiLibrary;
      if (gj[type].config[uiLibrary]) {
          $.extend(true, config, gj[type].config[uiLibrary]);
      }
  
      iconsLibrary = clientConfig.hasOwnProperty('iconsLibrary') ? clientConfig.iconsLibrary : config.iconsLibrary;
      if (gj[type].config[iconsLibrary]) {
          $.extend(true, config, gj[type].config[iconsLibrary]);
      }
  
      for (plugin in gj[type].plugins) {
          if (gj[type].plugins.hasOwnProperty(plugin)) {
              $.extend(true, config, gj[type].plugins[plugin].config.base);
              if (gj[type].plugins[plugin].config[uiLibrary]) {
                  $.extend(true, config, gj[type].plugins[plugin].config[uiLibrary]);
              }
              if (gj[type].plugins[plugin].config[iconsLibrary]) {
                  $.extend(true, config, gj[type].plugins[plugin].config[iconsLibrary]);
              }
          }
      }
  
      $.extend(true, config, clientConfig);
  
      if (!config.guid) {
          config.guid = this.generateGUID();
      }
  
      return config;
  }
  
  gj.widget.prototype.getHTMLConfig = function () {
      var result = this.data(),
          attrs = this[0].attributes;
      if (attrs['width']) {
          result.width = attrs['width'].value;
      }
      if (attrs['height']) {
          result.height = attrs['height'].value;
      }
      if (attrs['value']) {
          result.value = attrs['value'].value;
      }
      if (attrs['align']) {
          result.align = attrs['align'].value;
      }
      if (result && result.source) {
          result.dataSource = result.source;
          delete result.source;
      }
      return result;
  };
  
  gj.widget.prototype.createDoneHandler = function () {
      var $widget = this;
      return function (response) {
          if (typeof (response) === 'string' && JSON) {
              response = JSON.parse(response);
          }
          gj[$widget.data('type')].methods.render($widget, response);
      };
  };
  
  gj.widget.prototype.createErrorHandler = function () {
      var $widget = this;
      return function (response) {
          if (response && response.statusText && response.statusText !== 'abort') {
              alert(response.statusText);
          }
      };
  };
  
  gj.widget.prototype.reload = function (params) {
      var ajaxOptions, result, data = this.data(), type = this.data('type');
      if (data.dataSource === undefined) {
          gj[type].methods.useHtmlDataSource(this, data);
      }
      $.extend(data.params, params);
      if ($.isArray(data.dataSource)) {
          result = gj[type].methods.filter(this);
          gj[type].methods.render(this, result);
      } else if (typeof(data.dataSource) === 'string') {
          ajaxOptions = { url: data.dataSource, data: data.params };
          if (this.xhr) {
              this.xhr.abort();
          }
          this.xhr = $.ajax(ajaxOptions).done(this.createDoneHandler()).fail(this.createErrorHandler());
      } else if (typeof (data.dataSource) === 'object') {
          if (!data.dataSource.data) {
              data.dataSource.data = {};
          }
          $.extend(data.dataSource.data, data.params);
          ajaxOptions = $.extend(true, {}, data.dataSource); //clone dataSource object
          if (ajaxOptions.dataType === 'json' && typeof(ajaxOptions.data) === 'object') {
              ajaxOptions.data = JSON.stringify(ajaxOptions.data);
          }
          if (!ajaxOptions.success) {
              ajaxOptions.success = this.createDoneHandler();
          }
          if (!ajaxOptions.error) {
              ajaxOptions.error = this.createErrorHandler();
          }
          if (this.xhr) {
              this.xhr.abort();
          }
          this.xhr = $.ajax(ajaxOptions);
      }
      return this;
  }
  
  gj.documentManager = {
      events: {},
  
      subscribeForEvent: function (eventName, widgetId, callback) {
          if (!gj.documentManager.events[eventName] || gj.documentManager.events[eventName].length === 0) {
              gj.documentManager.events[eventName] = [{ widgetId: widgetId, callback: callback }];
              $(document).on(eventName, gj.documentManager.executeCallbacks);
          } else if (!gj.documentManager.events[eventName][widgetId]) {
              gj.documentManager.events[eventName].push({ widgetId: widgetId, callback: callback });
          } else {
              throw 'Event ' + eventName + ' for widget with guid="' + widgetId + '" is already attached.';
          }
      },
  
      executeCallbacks: function (e) {
          var callbacks = gj.documentManager.events[e.type];
          if (callbacks) {
              for (var i = 0; i < callbacks.length; i++) {
                  callbacks[i].callback(e);
              }
          }
      },
  
      unsubscribeForEvent: function (eventName, widgetId) {
          var success = false,
              events = gj.documentManager.events[eventName];
          if (events) {
              for (var i = 0; i < events.length; i++) {
                  if (events[i].widgetId === widgetId) {
                      events.splice(i, 1);
                      success = true;
                      if (events.length === 0) {
                          $(document).off(eventName);
                          delete gj.documentManager.events[eventName];
                      }
                  }
              }
          }
          if (!success) {
              throw 'The "' + eventName + '" for widget with guid="' + widgetId + '" can\'t be removed.';
          }
      }
  };
  
  /**
    * @widget Core
    * @plugin Base
    */
  gj.core = {
      messages: {
          'en-us': {
              monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
              monthShortNames: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],            
              weekDaysMin: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
              weekDaysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
              weekDays: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
              am: 'AM',
              pm: 'PM',
              ok: 'Ok',
              cancel: 'Cancel'
          }
      },
  
      /** 
       * @method
       * @example String.1
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.parseDate('02/03/17', 'mm/dd/yy'));
       * </script>
       * @example String.2
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.parseDate('2017 2.3', 'yyyy m.d'));
       * </script>
       * @example String.dd.mmm.yyyy
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.parseDate('05 Feb 2017', 'dd mmm yyyy'));
       * </script>
       * @example String.dd.mmmm.yyyy
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.parseDate('05 February 2017', 'dd mmmm yyyy'));
       * </script>
       * @example String.HH:MM
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.parseDate('10:57', 'HH:MM'));
       * </script>
       * @example ASP.NET.JSON.Date
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.parseDate("\/Date(349653600000)\/"));
       * </script>
       * @example UNIX.Timestamp
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.parseDate(349653600000));
       * </script>
       */
      parseDate: function (value, format, locale) {
          var i, year = 0, month = 0, date = 0, hour = 0, minute = 0, dateParts, formatParts, result;
  
          if (value && typeof value === 'string') {
              if (/^\d+$/.test(value)) {
                  result = new Date(value);
              } else if (value.indexOf('/Date(') > -1) {
                  result = new Date(parseInt(value.substr(6), 10));
              } else if (value) {
                  dateParts = value.split(/[\s,-\.//\:]+/);
                  formatParts = format.split(/[\s,-\.//\:]+/);
                  for (i = 0; i < formatParts.length; i++) {
                      if (['d', 'dd'].indexOf(formatParts[i]) > -1) {
                          date = parseInt(dateParts[i], 10);
                      } else if (['m', 'mm'].indexOf(formatParts[i]) > -1) {
                          month = parseInt(dateParts[i], 10) - 1;
                      } else if ('mmm' === formatParts[i]) {
                          month = gj.core.messages[locale || 'en-us'].monthShortNames.indexOf(dateParts[i]);
                      } else if ('mmmm' === formatParts[i]) {
                          month = gj.core.messages[locale || 'en-us'].monthNames.indexOf(dateParts[i]);
                      } else if (['yy', 'yyyy'].indexOf(formatParts[i]) > -1) {
                          year = parseInt(dateParts[i], 10);
                          if (formatParts[i] === 'yy') {
                              year += 2000;
                          }
                      } else if (['h', 'hh', 'H', 'HH'].indexOf(formatParts[i]) > -1) {
                          hour = parseInt(dateParts[i], 10);
                      } else if (['M', 'MM'].indexOf(formatParts[i]) > -1) {
                          minute = parseInt(dateParts[i], 10);
                      }
                  }
                  result = new Date(year, month, date, hour, minute);
              }
          } else if (typeof value === 'number') {
              result = new Date(value);
          } else if (value instanceof Date) {
              result = value;
          }
  
          return result;
      },
  
      /** 
       * @method
       * @example Sample.1
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.formatDate(new Date(2017, 1, 3), 'mm/dd/yy'));
       * </script>
       * @example Sample.2
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.formatDate(new Date(2017, 1, 3), 'yyyy m.d'));
       * </script>
       * @example Sample.dd.mmm.yyyy
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.formatDate(new Date(2017, 1, 3), 'dd mmm yyyy'));
       * </script>
       * @example Sample.dd.mmmm.yyyy
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.formatDate(new Date(2017, 1, 3), 'dd mmmm yyyy'));
       * </script>
       * @example Sample.5
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.formatDate(new Date(2017, 1, 3, 20, 43, 53), 'hh:MM:ss tt mm/dd/yyyy'));
       * </script>
       * @example Sample.6
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.formatDate(new Date(2017, 1, 3, 20, 43, 53), 'hh:MM TT'));
       * </script>
       * @example Short.WeekDay
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.formatDate(new Date(2017, 1, 3), 'ddd, mmm dd'));
       * </script>
       * @example Full.WeekDay
       * <div id="date"></div>
       * <script>
       *     $('#date').text(gj.core.formatDate(new Date(2017, 1, 3), 'dddd, mmm dd'));
       * </script>
       */
      formatDate: function (date, format, locale) {
          var result = '', separator, tmp,
              formatParts = format.split(/[\s,-\.//\:]+/),
              separators = format.split(/s+|M+|H+|h+|t+|T+|d+|m+|y+/);
  
          separators = separators.splice(1, separators.length - 2);
  
          for (i = 0; i < formatParts.length; i++) {
              separator = (separators[i] || '');
              switch (formatParts[i]) {
                  case 's':
                      result += date.getSeconds() + separator;
                      break;
                  case 'ss':
                      result += gj.core.pad(date.getSeconds()) + separator;
                      break;
                  case 'M':
                      result += date.getMinutes() + separator;
                      break;
                  case 'MM':
                      result += gj.core.pad(date.getMinutes()) + separator;
                      break;
                  case 'H':
                      result += date.getHours() + separator;
                      break;
                  case 'HH':
                      result += gj.core.pad(date.getHours()) + separator;
                      break;
                  case 'h':
                      tmp = date.getHours() > 12 ? date.getHours() % 12 : date.getHours();
                      result += tmp + separator;
                      break;
                  case 'hh':
                      tmp = date.getHours() > 12 ? date.getHours() % 12 : date.getHours();
                      result += gj.core.pad(tmp) + separator;
                      break;
                  case 'tt':
                      result += (date.getHours() >= 12 ? 'pm' : 'am') + separator;
                      break;
                  case 'TT':
                      result += (date.getHours() >= 12 ? 'PM' : 'AM') + separator;
                      break;
                  case 'd':
                      result += date.getDate() + separator;
                      break;
                  case 'dd':
                      result += gj.core.pad(date.getDate()) + separator;
                      break;
                  case 'ddd':
                      result += gj.core.messages[locale || 'en-us'].weekDaysShort[date.getDay()] + separator;
                      break;
                  case 'dddd':
                      result += gj.core.messages[locale || 'en-us'].weekDays[date.getDay()] + separator;
                      break;
                  case 'm' :
                      result += (date.getMonth() + 1) + separator;
                      break;
                  case 'mm':
                      result += gj.core.pad(date.getMonth() + 1) + separator;
                      break;
                  case 'mmm':
                      result += gj.core.messages[locale || 'en-us'].monthShortNames[date.getMonth()] + separator;
                      break;
                  case 'mmmm':
                      result += gj.core.messages[locale || 'en-us'].monthNames[date.getMonth()] + separator;
                      break;
                  case 'yy' :
                      result += date.getFullYear().toString().substr(2) + separator;
                      break;
                  case 'yyyy':
                      result += date.getFullYear() + separator;
                      break;
              }
          }
  
          return result;
      },
  
      pad: function (val, len) {
          val = String(val);
          len = len || 2;
          while (val.length < len) {
              val = '0' + val;
          }
          return val;
      },
  
      center: function ($dialog) {
          var left = ($(window).width() / 2) - ($dialog.width() / 2),
              top = ($(window).height() / 2) - ($dialog.height() / 2);
          $dialog.css('position', 'absolute');
          $dialog.css('left', left > 0 ? left : 0);
          $dialog.css('top', top > 0 ? top : 0);
      },
  
      isIE: function () {
          return !!navigator.userAgent.match(/Trident/g) || !!navigator.userAgent.match(/MSIE/g);
      },
  
      setChildPosition: function (mainEl, childEl) {
          var mainElRect = mainEl.getBoundingClientRect(),
              mainElHeight = gj.core.height(mainEl, true),
              childElHeight = gj.core.height(childEl, true),
              mainElWidth = gj.core.width(mainEl, true),
              childElWidth = gj.core.width(childEl, true),
              scrollY = window.scrollY || window.pageYOffset || 0,
              scrollX = window.scrollX || window.pageXOffset || 0;
  
          if ((mainElRect.top + mainElHeight + childElHeight) > window.innerHeight && mainElRect.top > childElHeight) {
              childEl.style.top = Math.round(mainElRect.top + scrollY - childElHeight - 3) + 'px';
          } else {
              childEl.style.top = Math.round(mainElRect.top + scrollY + mainElHeight + 3) + 'px';
          }
  
          if (mainElRect.left + childElWidth > document.body.clientWidth) {
              childEl.style.left = Math.round(mainElRect.left + scrollX + mainElWidth - childElWidth) + 'px';
          } else {
              childEl.style.left = Math.round(mainElRect.left + scrollX) + 'px';
          }
      },
  
      height: function (el, margin) {
          var result, style = window.getComputedStyle(el);
  
          if (style.boxSizing === 'border-box') { // border-box include padding and border within the height
              result = parseInt(style.height, 10);
          } else {
              result = parseInt(style.height, 10);
              result += parseInt(style.paddingTop || 0, 10) + parseInt(style.paddingBottom || 0, 10);
              result += parseInt(style.borderTop || 0, 10) + parseInt(style.borderBottom || 0, 10);
          }
  
          if (margin) {
              result += parseInt(style.marginTop || 0, 10) + parseInt(style.marginBottom || 0, 10);
          }
  
          return result;
      },
  
      width: function (el, margin) {
          var result, style = window.getComputedStyle(el);
  
          if (style.boxSizing === 'border-box') { // border-box include padding and border within the width
              result = parseInt(style.width, 10);
          } else {
              result = parseInt(style.width, 10);
              result += parseInt(style.paddingLeft || 0, 10) + parseInt(style.paddingRight || 0, 10);
              result += parseInt(style.borderLeft || 0, 10) + parseInt(style.borderRight || 0, 10);
          }
  
          if (margin) {
              result += parseInt(style.marginLeft || 0, 10) + parseInt(style.marginRight || 0, 10);
          }
  
          return result;
      },
  
      addClasses: function (el, classes) {
          var i, arr;
          if (classes) {
              arr = classes.split(' ');
              for (i = 0; i < arr.length; i++) {
                  el.classList.add(arr[i]);
              }
          }
      }
  };