<commit_before>(function(EsriLeaflet){

  var tileProtocol = (window.location.protocol !== 'https:') ? 'http:' : 'https:';

  EsriLeaflet.Layers.BasemapLayer = L.TileLayer.extend({
    statics: {
      TILES: {
        Streets: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
          attributionUrl: 'https://static.arcgis.com/attribution/World_Street_Map',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 19,
            subdomains: ['server', 'services'],
            attribution: 'Esri'
          }
        },
        Topographic: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',
          attributionUrl: 'https://static.arcgis.com/attribution/World_Topo_Map',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 19,
            subdomains: ['server', 'services'],
            attribution: 'Esri'
          }
        },
        Oceans: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{z}/{y}/{x}',
          attributionUrl: 'https://static.arcgis.com/attribution/Ocean_Basemap',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services'],
            attribution: 'Esri'
          }
        },
        OceansLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Reference/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services']
          }
        },
        NationalGeographic: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services'],
            attribution: 'Esri'
          }
        },
        DarkGray: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services'],
            attribution: 'Esri, DeLorme, HERE'
          }
        },
        DarkGrayLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Reference/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services']
          }
        },
        Gray: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services'],
            attribution: 'Esri, NAVTEQ, DeLorme'
          }
        },
        GrayLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Reference/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services']
          }
        },
        Imagery: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 19,
            subdomains: ['server', 'services'],
            attribution: 'Esri, DigitalGlobe, GeoEye, i-cubed, USDA, USGS, AEX, Getmapping, Aerogrid, IGN, IGP, swisstopo, and the GIS User Community'
          }
        },
        ImageryLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 19,
            subdomains: ['server', 'services']
          }
        },
        ImageryTransportation: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}',
          //pane: 'esri-label',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 19,
            subdomains: ['server', 'services']
          }
        },
        ShadedRelief: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 13,
            subdomains: ['server', 'services'],
            attribution: 'ESRI, NAVTEQ, DeLorme'
          }
        },
        ShadedReliefLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places_Alternate/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 12,
            subdomains: ['server', 'services']
          }
        },
        Terrain: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 13,
            subdomains: ['server', 'services'],
            attribution: 'Esri, USGS, NOAA'
          }
        },
        TerrainLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Reference_Overlay/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 13,
            subdomains: ['server', 'services']
          }
        }
      }
    },
    initialize: function(key, options){
      var config;

      // set the config variable with the appropriate config object
      if (typeof key === 'object' && key.urlTemplate && key.options){
        config = key;
      } else if(typeof key === 'string' && EsriLeaflet.BasemapLayer.TILES[key]){
        config = EsriLeaflet.BasemapLayer.TILES[key];
      } else {
        throw new Error('L.esri.BasemapLayer: Invalid parameter. Use one of "Streets", "Topographic", "Oceans", "OceansLabels", "NationalGeographic", "Gray", "GrayLabels", "DarkGray", "DarkGrayLabels", "Imagery", "ImageryLabels", "ImageryTransportation", "ShadedRelief", "ShadedReliefLabels", "Terrain" or "TerrainLabels"');
      }

      // merge passed options into the config options
      var tileOptions = L.Util.extend(config.options, options);

      // call the initialize method on L.TileLayer to set everything up
      L.TileLayer.prototype.initialize.call(this, config.urlTemplate, L.Util.setOptions(this, tileOptions));

      // if this basemap requires dynamic attribution set it up
      if(config.attributionUrl){
        this._getAttributionData(config.attributionUrl);
      }
      this._logo = new EsriLeaflet.Controls.Logo({
        position: this.options.logoPosition
      });
    },
    onAdd: function(map){
      if(!this.options.hideLogo && !map._hasEsriLogo){
        this._logo.addTo(map);
        map._hasEsriLogo = true;
      }

      // if(this.options.pane && EsriLeaflet.Support.pointerEvents){
      //   this._initPane();
      // }

      L.TileLayer.prototype.onAdd.call(this, map);

      map.on('moveend', this._updateMapAttribution, this);
    },
    onRemove: function(map){
      if(this._logo){
        map.removeControl(this._logo);
      }

      L.TileLayer.prototype.onRemove.call(this, map);

      map.off('moveend', this._updateMapAttribution, this);
    },
    getAttribution:function(){
      var attribution = '' + this.options.attribution + ''/* + logo*/;
      return attribution;
    },
    // _initPane: function(){
    //   if(!this._map.getPane(this.options.pane)){
    //     var pane = this._map.createPane(this.options.pane);
    //     pane.style.pointerEvents = 'none';
    //     pane.style.zIndex = 5;
    //   }
    // },
    _getAttributionData: function(url){
      EsriLeaflet.get(url, {}, function(error, attributions){
        this._attributions = [];
        for (var c = 0; c < attributions.contributors.length; c++) {
          var contributor = attributions.contributors[c];
          for (var i = 0; i < contributor.coverageAreas.length; i++) {
            var coverageArea = contributor.coverageAreas[i];
            var southWest = new L.LatLng(coverageArea.bbox[0], coverageArea.bbox[1]);
            var northEast = new L.LatLng(coverageArea.bbox[2], coverageArea.bbox[3]);
            this._attributions.push({
              attribution: contributor.attribution,
              score: coverageArea.score,
              bounds: new L.LatLngBounds(southWest, northEast),
              minZoom: coverageArea.zoomMin,
              maxZoom: coverageArea.zoomMax
            });
          }
        }

        this._attributions.sort(function(a, b){
          return b.score - a.score;
        });

        this._updateMapAttribution();
      }, this);
    },
    _updateMapAttribution: function(){
      if(this._map && this._map.attributionControl && this._attributions){
        var newAttributions = '';
        var bounds = this._map.getBounds();
        var zoom = this._map.getZoom();

        for (var i = 0; i < this._attributions.length; i++) {
          var attribution = this._attributions[i];
          var text = attribution.attribution;
          if(!newAttributions.match(text) && bounds.intersects(attribution.bounds) && zoom >= attribution.minZoom && zoom <= attribution.maxZoom) {
            newAttributions += (', ' + text);
          }
        }
        newAttributions = newAttributions.substr(2);
        var attributionElement = this._map.attributionControl._container.querySelector('.esri-attributions');
        attributionElement.innerHTML = newAttributions;
        attributionElement.style.maxWidth =  (this._map.getSize().x * 0.65) + 'px';
        this.fire('attributionupdated', {
          attribution: newAttributions
        });
      }
    }
  });

  EsriLeaflet.BasemapLayer = EsriLeaflet.Layers.BasemapLayer;

  EsriLeaflet.Layers.basemapLayer = function(key, options){
    return new EsriLeaflet.Layers.BasemapLayer(key, options);
  };

  EsriLeaflet.basemapLayer = function(key, options){
    return new EsriLeaflet.Layers.BasemapLayer(key, options);
  };

})(EsriLeaflet);<commit_msg>set map._hasEsriLogo to false when a basemap is removed<commit_after>(function(EsriLeaflet){

  var tileProtocol = (window.location.protocol !== 'https:') ? 'http:' : 'https:';

  EsriLeaflet.Layers.BasemapLayer = L.TileLayer.extend({
    statics: {
      TILES: {
        Streets: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
          attributionUrl: 'https://static.arcgis.com/attribution/World_Street_Map',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 19,
            subdomains: ['server', 'services'],
            attribution: 'Esri'
          }
        },
        Topographic: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',
          attributionUrl: 'https://static.arcgis.com/attribution/World_Topo_Map',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 19,
            subdomains: ['server', 'services'],
            attribution: 'Esri'
          }
        },
        Oceans: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{z}/{y}/{x}',
          attributionUrl: 'https://static.arcgis.com/attribution/Ocean_Basemap',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services'],
            attribution: 'Esri'
          }
        },
        OceansLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Reference/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services']
          }
        },
        NationalGeographic: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services'],
            attribution: 'Esri'
          }
        },
        DarkGray: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services'],
            attribution: 'Esri, DeLorme, HERE'
          }
        },
        DarkGrayLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Reference/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services']
          }
        },
        Gray: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services'],
            attribution: 'Esri, NAVTEQ, DeLorme'
          }
        },
        GrayLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Reference/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 16,
            subdomains: ['server', 'services']
          }
        },
        Imagery: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 19,
            subdomains: ['server', 'services'],
            attribution: 'Esri, DigitalGlobe, GeoEye, i-cubed, USDA, USGS, AEX, Getmapping, Aerogrid, IGN, IGP, swisstopo, and the GIS User Community'
          }
        },
        ImageryLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 19,
            subdomains: ['server', 'services']
          }
        },
        ImageryTransportation: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}',
          //pane: 'esri-label',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 19,
            subdomains: ['server', 'services']
          }
        },
        ShadedRelief: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 13,
            subdomains: ['server', 'services'],
            attribution: 'ESRI, NAVTEQ, DeLorme'
          }
        },
        ShadedReliefLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places_Alternate/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 12,
            subdomains: ['server', 'services']
          }
        },
        Terrain: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: false,
            logoPosition: 'bottomright',
            minZoom: 1,
            maxZoom: 13,
            subdomains: ['server', 'services'],
            attribution: 'Esri, USGS, NOAA'
          }
        },
        TerrainLabels: {
          urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Reference_Overlay/MapServer/tile/{z}/{y}/{x}',
          options: {
            hideLogo: true,
            logoPosition: 'bottomright',
            //pane: 'esri-label',
            minZoom: 1,
            maxZoom: 13,
            subdomains: ['server', 'services']
          }
        }
      }
    },
    initialize: function(key, options){
      var config;

      // set the config variable with the appropriate config object
      if (typeof key === 'object' && key.urlTemplate && key.options){
        config = key;
      } else if(typeof key === 'string' && EsriLeaflet.BasemapLayer.TILES[key]){
        config = EsriLeaflet.BasemapLayer.TILES[key];
      } else {
        throw new Error('L.esri.BasemapLayer: Invalid parameter. Use one of "Streets", "Topographic", "Oceans", "OceansLabels", "NationalGeographic", "Gray", "GrayLabels", "DarkGray", "DarkGrayLabels", "Imagery", "ImageryLabels", "ImageryTransportation", "ShadedRelief", "ShadedReliefLabels", "Terrain" or "TerrainLabels"');
      }

      // merge passed options into the config options
      var tileOptions = L.Util.extend(config.options, options);

      // call the initialize method on L.TileLayer to set everything up
      L.TileLayer.prototype.initialize.call(this, config.urlTemplate, L.Util.setOptions(this, tileOptions));

      // if this basemap requires dynamic attribution set it up
      if(config.attributionUrl){
        this._getAttributionData(config.attributionUrl);
      }
      this._logo = new EsriLeaflet.Controls.Logo({
        position: this.options.logoPosition
      });
    },
    onAdd: function(map){
      if(!this.options.hideLogo && !map._hasEsriLogo){
        this._logo.addTo(map);
        map._hasEsriLogo = true;
      }

      // if(this.options.pane && EsriLeaflet.Support.pointerEvents){
      //   this._initPane();
      // }

      L.TileLayer.prototype.onAdd.call(this, map);

      map.on('moveend', this._updateMapAttribution, this);
    },
    onRemove: function(map){
      if(this._logo){
        map.removeControl(this._logo);
        map._hasEsriLogo = false;
      }

      L.TileLayer.prototype.onRemove.call(this, map);

      map.off('moveend', this._updateMapAttribution, this);
    },
    getAttribution:function(){
      var attribution = '' + this.options.attribution + ''/* + logo*/;
      return attribution;
    },
    // _initPane: function(){
    //   if(!this._map.getPane(this.options.pane)){
    //     var pane = this._map.createPane(this.options.pane);
    //     pane.style.pointerEvents = 'none';
    //     pane.style.zIndex = 5;
    //   }
    // },
    _getAttributionData: function(url){
      EsriLeaflet.get(url, {}, function(error, attributions){
        this._attributions = [];
        for (var c = 0; c < attributions.contributors.length; c++) {
          var contributor = attributions.contributors[c];
          for (var i = 0; i < contributor.coverageAreas.length; i++) {
            var coverageArea = contributor.coverageAreas[i];
            var southWest = new L.LatLng(coverageArea.bbox[0], coverageArea.bbox[1]);
            var northEast = new L.LatLng(coverageArea.bbox[2], coverageArea.bbox[3]);
            this._attributions.push({
              attribution: contributor.attribution,
              score: coverageArea.score,
              bounds: new L.LatLngBounds(southWest, northEast),
              minZoom: coverageArea.zoomMin,
              maxZoom: coverageArea.zoomMax
            });
          }
        }

        this._attributions.sort(function(a, b){
          return b.score - a.score;
        });

        this._updateMapAttribution();
      }, this);
    },
    _updateMapAttribution: function(){
      if(this._map && this._map.attributionControl && this._attributions){
        var newAttributions = '';
        var bounds = this._map.getBounds();
        var zoom = this._map.getZoom();

        for (var i = 0; i < this._attributions.length; i++) {
          var attribution = this._attributions[i];
          var text = attribution.attribution;
          if(!newAttributions.match(text) && bounds.intersects(attribution.bounds) && zoom >= attribution.minZoom && zoom <= attribution.maxZoom) {
            newAttributions += (', ' + text);
          }
        }
        newAttributions = newAttributions.substr(2);
        var attributionElement = this._map.attributionControl._container.querySelector('.esri-attributions');
        attributionElement.innerHTML = newAttributions;
        attributionElement.style.maxWidth =  (this._map.getSize().x * 0.65) + 'px';
        this.fire('attributionupdated', {
          attribution: newAttributions
        });
      }
    }
  });

  EsriLeaflet.BasemapLayer = EsriLeaflet.Layers.BasemapLayer;

  EsriLeaflet.Layers.basemapLayer = function(key, options){
    return new EsriLeaflet.Layers.BasemapLayer(key, options);
  };

  EsriLeaflet.basemapLayer = function(key, options){
    return new EsriLeaflet.Layers.BasemapLayer(key, options);
  };

})(EsriLeaflet);<|endoftext|>


<commit_before>module.exports = Chainable

/**
 * An object with chainable methods
 *
 * @param {Boolean} checkCallback - set to false to skip callback checking
 *                                when adding new chainable methods
 * @param {Object} customAPI - api names (optional)
 */
function Chainable (checkCallback, customAPI) {
  var defaultAPI = {
    chainable: 'chainable',
    then: 'then',
    done: 'done',
    catch: 'catch',
    results: 'results',
    lastResult: 'lastResult'
  }

  // handle usage of Chainable(customAPI)
  if (checkCallback && checkCallback.constructor === Object) customAPI = checkCallback

  // private variables
  this.__chainable__ = {
    api: defaultAPI,
    methods: {}, // private collection of method functions
    tasks: [], // queue of tasks created from chainable method calls
              // tasks will call real functions in methods {}
    results: [], // results from chained methods call
    executing: 0, // chain execution status
    onError: 'fn', // error handler
    onFinished: 'fn', // done handler
    checkCallback: (checkCallback !== false && checkCallback !== 0) ? 1 : 0
  }

  if ((this instanceof Chainable)) return

  // extend existing object
  customAPI = customAPI || {}
  for (var key in defaultAPI) {
    if (customAPI.hasOwnProperty(key) && customAPI[key].constructor === String) {
      defaultAPI[key] = customAPI[key]
    }
    // alias the api key to prototype function
    this[defaultAPI[key]] = Chainable.prototype[key]
  }
}

/**
 * Register a chainable method
 */
Chainable.prototype.chainable = function (method, fn) {
  // check if method name is a string and not a reserved keyword
  if (!method || method.constructor !== String) throw new Error('Method name must be a string')
  if (this[method]) throw new Error('Duplicated method name: ' + method)

  // check if fn is an async function
  checkAsync(fn, 'Method', this.__chainable__.checkCallback)

  // add fn method to private collection methods{}
  this.__chainable__.methods[method] = fn

  // create a public chainable method
  this[method] = function () {
    var args = []
    var l = arguments.length
    for (var i = 0; i < l; i++) {
      args[i] = arguments[i]
    }
    queueTask(this, this.__chainable__.methods[method], args) // queue the method call
    return this // enable methods chaining .methodA().methodB()
  }

  return this
}

/**
 * Api to chain a custom function fn
 * params will be passed to fn by this structure:
 * .then(
 *   function fn (p1, p2, p3, done) {
 *     done(err, result) // callback is optional for .then
 *   },
 *   [p1, p2, p3]
 *       -OR-
 *    p1, p2, p3
 * )
 *
 * Note: if fn(hasOnlyP1, done) and P1 takes an array V1[] as value,
 * then the calling structure must be: .then(fn, [ V1[] ])
 */
Chainable.prototype.then = function (fn) {
  // check if fn is an async function
  checkAsync(fn, 'Then handler')

  // prepare args[] to apply to real methods[methodName]
  var args, i, l

  if (arguments.length === 2 && arguments[1] && arguments[1].constructor === Array) {
    // calling .then( fn(p1, p2, p3, done), [v1, v2, v3] )
    args = arguments[1]
  } else {
    // calling .then( fn(p1, p2, p3, done), v1, v2, v3)
    args = []
    l = arguments.length
    for (i = 1; i < l; i++) {
      args[i] = arguments[i]
    }
  }

  // queue the custom function call
  queueTask(this, fn, args)

  return this
}

/**
 * Handle when chains execution finished successfully
 * Usage: .done(function (results) {})
 */
Chainable.prototype.done = function (fn) {
  checkAsync(fn, 'Done handler')
  this.__chainable__.onFinished = fn
  return this
}

/**
 * Handle chains execution error
 * Usage: .catch(function (err, results) {})
 */
Chainable.prototype.catch = function (fn) {
  checkAsync(fn, 'Error handler')
  this.__chainable__.onError = fn
  return this
}

/**
 * Get all results[]
 */
Chainable.prototype.results = function () {
  return this.__chainable__.results
}

/**
 * Get the last result
 */
Chainable.prototype.lastResult = function () {
  var results = this.__chainable__.results
  return results[results.length - 1]
}

// HELPERS
// =======================================================================

/**
 * Check if fn is an async function
 * - should has at least one parameter which is a callback function
 * - should call the callback function
 *
 * @param {Function} fn - an async function (...params, done)
 * @param {string} desc - description of fn, e.g. Error handler
 * @param {boolean} checkCallback - set to true to check fn source for callback
 */
function checkAsync (fn, desc, checkCallback) {
  if (!fn || fn.constructor !== Function) throw new Error(desc + ' is not a function: ' + fn)
  if (!checkCallback) return

  var src = fn.toString().replace(/\/\/.*/g, '')
  var params
  try {
    params = src.match(/^function[^(]*\(([^)]*)\)/)[1].replace(/\s+/g, '').split(',')
  } catch (error) {
    throw error
  }
  if (params[0] === '') throw new Error('There is no callback in the function below\n\n' + fn.toString())

  var lastparam = params[params.length - 1]
  var cb = new RegExp(lastparam + '\\s*\\(')
  if (!cb.test(src)) throw new Error(lastparam + ' is not a callback, or is not called anywhere inside the function below\n\n' + fn.toString())
}

/**
 * Queue an async function or a method call with arguments.
 * By calling internally, fn is guaranteed
 * to be a valid function
 */
function queueTask (chainable, fn, args) {
  var tasks = chainable.__chainable__.tasks
  tasks[tasks.length] = function (done) {
    args[args.length] = done
    fn.apply(chainable, args) // fn(...params, done)
  }
  // execute tasks
  if (!chainable.__chainable__.executing) exec(chainable.__chainable__)
}

/**
 * Execute the chain of queued methods
 */
function exec (chain) {
  if (chain.executing) return

  // chain.results = []
  // if the chain has no done callback, results will not be cleared

  // tasks' done callback
  function _done (err, result) {
    if (result !== undefined) chain.results[chain.results.length] = result
    if (err) {
      // error-first
      if (chain.onError.constructor !== Function) throw new Error(err) // expect error handler
      chain.onError(err, chain.results)
      chain.tasks = [] // nothing more to do
      chain.results = []
      chain.executing = 0
    } else if (_exec(chain)) {
      // has tasks to run, do nothing here
    } else if (chain.onFinished.constructor === Function) {
      // all tasks executed && there's onFinished()
      chain.onFinished(chain.results)
      chain.results = []
    } else {
      // tasks finished but there's no onFinished() handler, do nothing
    }
  }

  // tasks runner; passing chain param for performance optimization
  function _exec (chain) {
    if (chain.executing) {
      chain.tasks[0] = null
      chain.tasks.shift()
    } else {
      chain.executing = 1
    }
    if (chain.tasks.length === 0) {
      chain.executing = 0
      return false
    } else {
      chain.tasks[0](_done)
      return true
    }
  }

  _exec(chain)
}
<commit_msg>Fix args assign in .then()<commit_after>module.exports = Chainable

/**
 * An object with chainable methods
 *
 * @param {Boolean} checkCallback - set to false to skip callback checking
 *                                when adding new chainable methods
 * @param {Object} customAPI - api names (optional)
 */
function Chainable (checkCallback, customAPI) {
  var defaultAPI = {
    chainable: 'chainable',
    then: 'then',
    done: 'done',
    catch: 'catch',
    results: 'results',
    lastResult: 'lastResult'
  }

  // private variables
  this.__chainable__ = {
    api: defaultAPI,
    methods: {}, // private collection of method functions
    tasks: [], // queue of tasks created from chainable method calls
              // tasks will call real functions in methods {}
    results: [], // results from chained methods call
    executing: 0, // chain execution status
    onError: 'fn', // error handler
    onFinished: 'fn', // done handler
    checkCallback: (checkCallback !== false && checkCallback !== 0) ? 1 : 0
  }

  if ((this instanceof Chainable)) return

  // handle usage of Chainable(customAPI)
  if (checkCallback && checkCallback.constructor === Object) customAPI = checkCallback
  else customAPI = customAPI || {}
  // extend existing object
  for (var key in defaultAPI) {
    if (customAPI.hasOwnProperty(key) && customAPI[key].constructor === String) {
      defaultAPI[key] = customAPI[key]
    }
    // alias the api key to prototype function
    this[defaultAPI[key]] = Chainable.prototype[key]
  }
}

/**
 * Register a chainable method
 */
Chainable.prototype.chainable = function (method, fn) {
  // check if method name is a string and not a reserved keyword
  if (!method || method.constructor !== String) throw new Error('Method name must be a string')
  if (this[method]) throw new Error('Duplicated method name: ' + method)

  // check if fn is an async function
  checkAsync(fn, 'Method', this.__chainable__.checkCallback)

  // add fn method to private collection methods{}
  this.__chainable__.methods[method] = fn

  // create a public chainable method
  this[method] = function () {
    var args = []
    var l = arguments.length
    for (var i = 0; i < l; i++) {
      args[i] = arguments[i]
    }
    queueTask(this, this.__chainable__.methods[method], args) // queue the method call
    return this // enable methods chaining .methodA().methodB()
  }

  return this
}

/**
 * Api to chain a custom function fn
 * params will be passed to fn by this structure:
 * .then(
 *   function fn (p1, p2, p3, done) {
 *     done(err, result) // callback is optional for .then
 *   },
 *   [p1, p2, p3]
 *       -OR-
 *    p1, p2, p3
 * )
 *
 * Note: if fn(hasOnlyP1, done) and P1 takes an array V1[] as value,
 * then the calling structure must be: .then(fn, [ V1[] ])
 */
Chainable.prototype.then = function (fn) {
  // check if fn is an async function
  checkAsync(fn, 'Then handler')

  // prepare args[] to apply to real methods[methodName]
  var args, i, l

  if (arguments.length === 2 && arguments[1] && arguments[1].constructor === Array) {
    // calling .then( fn(p1, p2, p3, done), [v1, v2, v3] )
    args = arguments[1]
  } else {
    // calling .then( fn(p1, p2, p3, done), v1, v2, v3)
    args = []
    l = arguments.length
    for (i = 1; i < l; i++) {
      args[args.length] = arguments[i]
    }
  }

  // queue the custom function call
  queueTask(this, fn, args)

  return this
}

/**
 * Handle when chains execution finished successfully
 * Usage: .done(function (results) {})
 */
Chainable.prototype.done = function (fn) {
  checkAsync(fn, 'Done handler')
  this.__chainable__.onFinished = fn
  return this
}

/**
 * Handle chains execution error
 * Usage: .catch(function (err, results) {})
 */
Chainable.prototype.catch = function (fn) {
  checkAsync(fn, 'Error handler')
  this.__chainable__.onError = fn
  return this
}

/**
 * Get all results[]
 */
Chainable.prototype.results = function () {
  return this.__chainable__.results
}

/**
 * Get the last result
 */
Chainable.prototype.lastResult = function () {
  var results = this.__chainable__.results
  return results[results.length - 1]
}

// HELPERS
// =======================================================================

/**
 * Check if fn is an async function
 * - should has at least one parameter which is a callback function
 * - should call the callback function
 *
 * @param {Function} fn - an async function (...params, done)
 * @param {string} desc - description of fn, e.g. Error handler
 * @param {boolean} checkCallback - set to true to check fn source for callback
 */
function checkAsync (fn, desc, checkCallback) {
  if (!fn || fn.constructor !== Function) throw new Error(desc + ' is not a function: ' + fn)
  if (!checkCallback) return

  var src = fn.toString().replace(/\/\/.*/g, '')
  var params
  try {
    params = src.match(/^function[^(]*\(([^)]*)\)/)[1].replace(/\s+/g, '').split(',')
  } catch (error) {
    throw error
  }
  if (params[0] === '') throw new Error('There is no callback in the function below\n\n' + fn.toString())

  var lastparam = params[params.length - 1]
  var cb = new RegExp(lastparam + '\\s*\\(')
  if (!cb.test(src)) throw new Error(lastparam + ' is not a callback, or is not called anywhere inside the function below\n\n' + fn.toString())
}

/**
 * Queue an async function or a method call with arguments.
 * By calling internally, fn is guaranteed
 * to be a valid function
 */
function queueTask (chainable, fn, args) {
  var tasks = chainable.__chainable__.tasks
  tasks[tasks.length] = function (done) {
    args[args.length] = done
    fn.apply(chainable, args) // fn(...params, done)
  }
  // execute tasks
  if (!chainable.__chainable__.executing) exec(chainable.__chainable__)
}

/**
 * Execute the chain of queued methods
 */
function exec (chain) {
  if (chain.executing) return

  // chain.results = []
  // if the chain has no done callback, results will not be cleared

  // tasks' done callback
  function _done (err, result) {
    if (result !== undefined) chain.results[chain.results.length] = result
    if (err) {
      // error-first
      if (chain.onError.constructor !== Function) throw new Error(err) // expect error handler
      chain.onError(err, chain.results)
      chain.tasks = [] // nothing more to do
      chain.results = []
      chain.executing = 0
    } else if (_exec(chain)) {
      // has tasks to run, do nothing here
    } else if (chain.onFinished.constructor === Function) {
      // all tasks executed && there's onFinished()
      chain.onFinished(chain.results)
      chain.results = []
    } else {
      // tasks finished but there's no onFinished() handler, do nothing
    }
  }

  // tasks runner; passing chain param for performance optimization
  function _exec (chain) {
    if (chain.executing) {
      chain.tasks[0] = null
      chain.tasks.shift()
    } else {
      chain.executing = 1
    }
    if (chain.tasks.length === 0) {
      chain.executing = 0
      return false
    } else {
      chain.tasks[0](_done)
      return true
    }
  }

  _exec(chain)
}
<|endoftext|>


<commit_before>/**
 *
 * Responsive menu v1.0.0
 * A vanilla JS responsive menu plugin, by Robin Poort - Timble
 * http://robinpoort.com - http://www.timble.net
 *
 * Browser support: IE9+ (IE8 doesn't need a responsive menu since it's not responsive)
 *
 * Dependency: apollo JS | https://github.com/toddmotto/apollo
 * Plugin boilerplate by | http://gomakethings.com/mit/
 *
 * Free to use under the MIT License.
 *
 */

(function (root, factory) {
    if ( typeof define === 'function' && define.amd ) {
        define('responsivemenu', factory(root));
    } else if ( typeof exports === 'object' ) {
        module.responsivemenu = factory(root);
    } else {
        root.responsivemenu = factory(root);
    }
})(this, function (root) {

    'use strict';

    // Variables
    var exports = {}; // Object for public APIs
    var supports = !!document.querySelector && !!root.addEventListener; // Feature test
    var settings; // Plugin settings
    var menu; // The actual menu item
    var hasChildren = false;
    var subtoggles = false;

    // Default settings
    var defaults = {
        menu: '',
        initiated_class: 'rm-initiated',
        before_element: '',
        toggletype: 'button',
        toggleclass: 'rm-togglebutton',
        toggleclosedclass: 'rm-togglebutton--closed',
        togglecontent: 'menu',
        subtoggletype: 'button',
        subtoggleclass: 'rm-subtoggle',
        subtogglecontent: '+',
        sticky: 0,
        absolute: 0,
        hideclass: 'rm-closed',
        openclass: 'rm-opened',
        focusedclass: 'rm-focused',
        animateopenclass: 'is-opening',
        animatecloseclass: 'is-closing',
        animateduration: 0, // (Animated with CSS so set to same duration as CSS value)
        width: 600,
        parentclass: 'rm-parent',
        fullmenuclass: 'rm-fullmenu',
        absolutemenuclass: 'rm-absolutemenu',
        bodyoverflowhiddenclass: 'rm-bodyoverflowhidden',
        menuoverflowautoclass: 'rm-menuoverflowauto',
        stickyclass: 'rm-sticky',
        stickyinitiatedclass: 'rm-sticky-initiated',
        noresponsivemenuclass: 'rm-no-responsive-menu',
        mobileindicatorid: 'rm-mobile-indicator'
    };

    // Methods
    /**
     * A simple forEach() implementation for Arrays, Objects and NodeLists
     * @private
     * @param {Array|Object|NodeList} collection Collection of items to iterate
     * @param {Function} callback Callback function for each iteration
     * @param {Array|Object|NodeList} scope Object/NodeList/Array that forEach is iterating over (aka `this`)
     */
    var forEach = function (collection, callback, scope) {
        if (Object.prototype.toString.call(collection) === '[object Object]') {
            for (var prop in collection) {
                if (Object.prototype.hasOwnProperty.call(collection, prop)) {
                    callback.call(scope, collection[prop], prop, collection);
                }
            }
        } else {
            for (var i = 0, len = collection.length; i < len; i++) {
                callback.call(scope, collection[i], i, collection);
            }
        }
    };

    /**
     * Merge defaults with user options
     * @private
     * @param {Object} defaults Default settings
     * @param {Object} options User options
     * @returns {Object} Merged values of defaults and options
     */
    var extend = function ( defaults, options ) {
        var extended = {};
        forEach(defaults, function (value, prop) {
            extended[prop] = defaults[prop];
        });
        forEach(options, function (value, prop) {
            extended[prop] = options[prop];
        });
        return extended;
    };

    /**
     * Remove whitespace from a string
     * @private
     * @param {String} string
     * @returns {String}
     */
    var trim = function ( string ) {
        return string.replace(/^\s+|\s+$/g, '');
    };

    /**
     * Convert data-options attribute into an object of key/value pairs
     * @private
     * @param {String} options Link-specific options as a data attribute string
     * @returns {Object}
     */
    var getDataOptions = function ( options ) {
        var settings = {};
        // Create a key/value pair for each setting
        if ( options ) {
            options = options.split(';');
            options.forEach( function(option) {
                option = trim(option);
                if ( option !== '' ) {
                    option = option.split(':');
                    settings[option[0]] = trim(option[1]);
                }
            });
        }
        return settings;
    };

    /**
     * Run when window resize is done (after x ms)
     */
    var waitForFinalEvent = (function () {
        var timers = {};
        return function (callback, ms, uniqueId) {
            if (!uniqueId) {
                uniqueId = "Don't call this twice without a uniqueId";
            }
            if (timers[uniqueId]) {
                clearTimeout (timers[uniqueId]);
            }
            timers[uniqueId] = setTimeout(callback, ms);
        };
    })();

    /**
     * Get parents
     */
    function getParents(element, tag, stop) {
        var nodes = [];
        while (element.parentNode && element.parentNode != stop) {
            element = element.parentNode;
            if (element.tagName == tag) {
                nodes.push(element);
            }
        }
        return nodes
    }

    /**
     * Get style
     */
    function getStyle(el,styleProp)
    {
        var x = document.getElementById(el);

        if (window.getComputedStyle)
        {
            var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(styleProp);
        }
        else if (x.currentStyle)
        {
            var y = x.currentStyle[styleProp];
        }

        return y;
    }

    // Responsive menu
    function initialize(settings) {

        // Define what the actual menu object is
        if ( settings.menu == '' ) {
            menu = settings.wrapper.getElementsByTagName('ul')[0];
        } else {
            menu = settings.menu;
        }

        // Add a class when JS is initiated
        apollo.addClass(settings.wrapper, settings.initiated_class);

        // See if menu has children
        var parents = menu.querySelectorAll('li ul');
        if ( parents.length ) {
            hasChildren = true;
            subtoggles = document.getElementsByClassName(settings.subtoggleclass);
        }

        // Create mobile width indicator
        var mobileindicator = document.createElement('div');
        document.body.appendChild(mobileindicator);
        mobileindicator.id = settings.mobileindicatorid;
        var mobileindicatorZindex = 0;

        // Creating the main toggle button
        var toggle_element = document.createElement(settings.toggletype);
        apollo.addClass(toggle_element, [settings.toggleclass, settings.hideclass]);
        if ( settings.before_element == '' ) { settings.before_element = settings.wrapper.firstChild }
        settings.before_element.parentNode.insertBefore(toggle_element, settings.before_element);
        var togglebutton = document.getElementsByClassName(settings.toggleclass)[0];
        togglebutton.innerHTML = settings.togglecontent;
        togglebutton.setAttribute('aria-hidden', 'true');
        togglebutton.setAttribute('aria-pressed', 'false');
        togglebutton.setAttribute('type', 'button');

        // Subtoggles and parent classes
        if ( hasChildren ) {
            for (var i = 0; i < parents.length; i++) {
                var subtoggle_element = document.createElement(settings.subtoggletype);
                apollo.addClass(subtoggle_element, [settings.subtoggleclass, settings.hideclass]);
                var parent = parents[i].parentNode;
                parent.insertBefore(subtoggle_element, parent.firstChild);
                subtoggle_element.innerHTML = settings.subtogglecontent;
                subtoggle_element.setAttribute('aria-hidden', 'true');
                subtoggle_element.setAttribute('aria-pressed', 'false');
                subtoggle_element.setAttribute('type', 'button');
                apollo.addClass(parents[i].parentNode, settings.parentclass);
            }
        }

        // Adding classes
        function classes() {

            mobileindicatorZindex = getStyle(settings.mobileindicatorid, "z-index");

            // If wrapper is small and if the menu is not already opened
            if ( mobileindicatorZindex == 0 && !apollo.hasClass(menu, settings.openclass) ) {

                // Show the toggle button(s)
                apollo.removeClass(togglebutton, settings.hideclass);
                if ( hasChildren ) {
                    forEach(subtoggles, function (value, prop) {
                        apollo.addClass(subtoggles[prop].parentNode.getElementsByTagName('ul')[0], settings.hideclass);
                        apollo.removeClass(subtoggles[prop], settings.hideclass);
                    });
                }

                // Hide the menu
                apollo.removeClass(menu, [settings.openclass, settings.fullmenuclass]);
                apollo.addClass(menu, settings.hideclass);

                // Make the menu absolute positioned
                if ( settings.absolute == 1 ) {
                    apollo.addClass(menu, settings.absolutemenuclass);
                }

            } else if ( mobileindicatorZindex == 1 ) {

                // Hide the toggle button(s)
                apollo.addClass(togglebutton, settings.hideclass);
                apollo.removeClass(togglebutton, settings.toggleclosedclass);
                if (hasChildren) {
                    forEach(subtoggles, function(value, prop) {
                        apollo.removeClass(subtoggles[prop].parentNode.getElementsByTagName('ul')[0], settings.hideclass);
                        apollo.addClass(subtoggles[prop], settings.hideclass);
                    });
                }

                // Show the menu and remove all classes
                apollo.removeClass(menu, [settings.openclass, settings.hideclass]);
                apollo.addClass(menu, settings.fullmenuclass);

                // Undo absolute positioning
                if ( settings.absolute == 1 && apollo.hasClass(menu, settings.absolutemenuclass) ) {
                    apollo.removeClass(menu, settings.absolutemenuclass);
                }
            }
        }

        // Sticky menu body height
        function stickyMenu() {

            if ( settings.sticky == 1 ) {

                // The current menu and viewport heights
                var menuheight = settings.wrapper.offsetHeight;
                var viewportheight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);

                // Add the overflow class but only if there is space
                if ( viewportheight <= menuheight && !apollo.hasClass(document.body, settings.bodyoverflowhiddenclass) ) {

                    apollo.addClass(document.body, settings.bodyoverflowhiddenclass);
                    apollo.addClass(settings.wrapper, settings.menuoverflowautoclass);

                } else if ( viewportheight > menuheight ) {

                    if ( apollo.hasClass(document.body, settings.bodyoverflowhiddenclass) ) {
                        apollo.removeClass(document.body, settings.bodyoverflowhiddenclass);
                        apollo.removeClass(settings.wrapper, settings.menuoverflowautoclass);
                    }

                    // Make sticky
                    if ( !apollo.hasClass(settings.wrapper, settings.stickyclass) ) {
                        apollo.addClass(settings.wrapper, settings.stickyclass);
                    }

                    // Add padding only if menu is closed or when value is stored
                    if ( !apollo.hasClass(menu, settings.openclass) && !apollo.hasClass(document.body, settings.stickyinitiatedclass) ) {

                        // Calculate the height
                        var paddingtop = menuheight.toString() + 'px';

                        // Set the padding on the body
                        document.body.setAttribute('style', 'padding-top:' + paddingtop);
                        apollo.addClass(document.body, settings.stickyinitiatedclass);
                    }
                }
            }
        }

        // Initial load
        window.addEventListener('load', function() {
            classes();
            stickyMenu();
        }, true);

        // On resize
        window.addEventListener('resize', function() {

            // Run immediately
            classes();
            stickyMenu();

            // Run again after 150 ms for safari OSX when scrollbars are visible and you're resizing to a smaller window
            waitForFinalEvent(function(){
                classes();
                stickyMenu();
            }, 200);

        }, true);

        // Accessible focus menu
        var menulinks = menu.getElementsByTagName('a');
        for (var i = 0; i < menulinks.length; i++) {
            menulinks[i].onblur = function() {
                var focusedItems = document.getElementsByClassName('rm-focused');
                for (var f = 0; f < focusedItems.length; f++) {
                    apollo.removeClass(focusedItems[f], settings.focusedclass);
                }
            };
            menulinks[i].onfocus = function() {
                // Remove the class
                var siblings = this.parentNode.parentNode.querySelectorAll('li');
                if (siblings.length) {
                    for (var f = 0; f < siblings.length; f++) {
                        apollo.removeClass(siblings[f], settings.focusedclass);
                    }
                }
                // Add the class
                var parent = getParents(this, "LI", menu);
                if (parent.length) {
                    for (var f = 0; f < parent.length; f++) {
                        apollo.addClass(parent[f], settings.focusedclass);
                    }
                }
            };
        }

        // Clicking the toggle button
        togglebutton.onclick = function() {

            // Show the menu
            if ( apollo.hasClass(menu, settings.hideclass) ) {

                // Show menu immediately so it can be animated
                apollo.removeClass(menu, settings.hideclass);
                apollo.addClass(menu, settings.openclass);

                // Set and remove animate class after duration
                apollo.addClass(menu, settings.animateopenclass);
                setTimeout(function() { apollo.removeClass(menu, settings.animateopenclass); }, settings.animateduration);

                // Set toggled class to toggle button
                apollo.addClass(togglebutton, settings.toggleclosedclass);
            }

            // Hide the menu
            else if ( apollo.hasClass(menu, settings.openclass) ) {

                // Properly set animating classes
                apollo.addClass(menu, settings.animatecloseclass);
                setTimeout(function() { apollo.removeClass(menu, settings.animatecloseclass); }, settings.animateduration);

                // Hide menu after animation is done (Animated with CSS so set to same duration as CSS value)
                setTimeout(function() { apollo.removeClass(menu, settings.openclass); }, settings.animateduration);
                setTimeout(function() { apollo.addClass(menu, settings.hideclass); }, settings.animateduration);

                // Remove toggled class to toggle button
                apollo.removeClass(togglebutton, settings.toggleclosedclass);
            }

            // Check if the menu still fits
            stickyMenu();

            return false;
        };

        // Clicking the sub toggles button
        if ( hasChildren ) {
            forEach(subtoggles, function(value, prop) {

                // Variables
                var subtoggle = subtoggles[prop];
                var submenu = subtoggle.parentNode.getElementsByTagName('ul')[0];

                // Click buttons and show submenu
                subtoggle.onclick = function() {

                    // Add classes accordingly
                    if ( apollo.hasClass(submenu, settings.hideclass) ) {
                        apollo.removeClass(submenu, settings.hideclass);
                        apollo.addClass(subtoggle, settings.toggleclosedclass);
                    } else if ( !apollo.hasClass(submenu, settings.hideclass) ) {
                        apollo.addClass(submenu, settings.hideclass);
                        apollo.removeClass(subtoggle, settings.toggleclosedclass);
                    }

                    // Check if the menu still fits
                    stickyMenu();
                }
            });
        }
    }

    /**
     * Initialize Plugin
     * @public
     * @param {Object} options User settings
     */
    exports.init = function ( options ) {
        // feature test
        if ( !supports ) {
            document.documentElement.className += ' ' + settings.noresponsivemenuclass;
            return;
        }
        settings = extend( defaults, options || {} ); // Merge user options with defaults
        initialize(settings);
    };

    // Public APIs
    return exports;

});<commit_msg>Add open and close class to body element<commit_after>/**
 *
 * Responsive menu v1.0.0
 * A vanilla JS responsive menu plugin, by Robin Poort - Timble
 * http://robinpoort.com - http://www.timble.net
 *
 * Browser support: IE9+ (IE8 doesn't need a responsive menu since it's not responsive)
 *
 * Dependency: apollo JS | https://github.com/toddmotto/apollo
 * Plugin boilerplate by | http://gomakethings.com/mit/
 *
 * Free to use under the MIT License.
 *
 */

(function (root, factory) {
    if ( typeof define === 'function' && define.amd ) {
        define('responsivemenu', factory(root));
    } else if ( typeof exports === 'object' ) {
        module.responsivemenu = factory(root);
    } else {
        root.responsivemenu = factory(root);
    }
})(this, function (root) {

    'use strict';

    // Variables
    var exports = {}; // Object for public APIs
    var supports = !!document.querySelector && !!root.addEventListener; // Feature test
    var settings; // Plugin settings
    var menu; // The actual menu item
    var hasChildren = false;
    var subtoggles = false;

    // Default settings
    var defaults = {
        menu: '',
        initiated_class: 'rm-initiated',
        before_element: '',
        toggletype: 'button',
        toggleclass: 'rm-togglebutton',
        toggleclosedclass: 'rm-togglebutton--closed',
        togglecontent: 'menu',
        subtoggletype: 'button',
        subtoggleclass: 'rm-subtoggle',
        subtogglecontent: '+',
        sticky: 0,
        absolute: 0,
        hideclass: 'rm-closed',
        openclass: 'rm-opened',
        openbodyclass: 'has-opened-menu',
        focusedclass: 'rm-focused',
        animateopenclass: 'is-opening',
        animatecloseclass: 'is-closing',
        animateduration: 0, // (Animated with CSS so set to same duration as CSS value)
        width: 600,
        parentclass: 'rm-parent',
        fullmenuclass: 'rm-fullmenu',
        absolutemenuclass: 'rm-absolutemenu',
        bodyoverflowhiddenclass: 'rm-bodyoverflowhidden',
        menuoverflowautoclass: 'rm-menuoverflowauto',
        stickyclass: 'rm-sticky',
        stickyinitiatedclass: 'rm-sticky-initiated',
        noresponsivemenuclass: 'rm-no-responsive-menu',
        mobileindicatorid: 'rm-mobile-indicator'
    };

    // Methods
    /**
     * A simple forEach() implementation for Arrays, Objects and NodeLists
     * @private
     * @param {Array|Object|NodeList} collection Collection of items to iterate
     * @param {Function} callback Callback function for each iteration
     * @param {Array|Object|NodeList} scope Object/NodeList/Array that forEach is iterating over (aka `this`)
     */
    var forEach = function (collection, callback, scope) {
        if (Object.prototype.toString.call(collection) === '[object Object]') {
            for (var prop in collection) {
                if (Object.prototype.hasOwnProperty.call(collection, prop)) {
                    callback.call(scope, collection[prop], prop, collection);
                }
            }
        } else {
            for (var i = 0, len = collection.length; i < len; i++) {
                callback.call(scope, collection[i], i, collection);
            }
        }
    };

    /**
     * Merge defaults with user options
     * @private
     * @param {Object} defaults Default settings
     * @param {Object} options User options
     * @returns {Object} Merged values of defaults and options
     */
    var extend = function ( defaults, options ) {
        var extended = {};
        forEach(defaults, function (value, prop) {
            extended[prop] = defaults[prop];
        });
        forEach(options, function (value, prop) {
            extended[prop] = options[prop];
        });
        return extended;
    };

    /**
     * Remove whitespace from a string
     * @private
     * @param {String} string
     * @returns {String}
     */
    var trim = function ( string ) {
        return string.replace(/^\s+|\s+$/g, '');
    };

    /**
     * Convert data-options attribute into an object of key/value pairs
     * @private
     * @param {String} options Link-specific options as a data attribute string
     * @returns {Object}
     */
    var getDataOptions = function ( options ) {
        var settings = {};
        // Create a key/value pair for each setting
        if ( options ) {
            options = options.split(';');
            options.forEach( function(option) {
                option = trim(option);
                if ( option !== '' ) {
                    option = option.split(':');
                    settings[option[0]] = trim(option[1]);
                }
            });
        }
        return settings;
    };

    /**
     * Run when window resize is done (after x ms)
     */
    var waitForFinalEvent = (function () {
        var timers = {};
        return function (callback, ms, uniqueId) {
            if (!uniqueId) {
                uniqueId = "Don't call this twice without a uniqueId";
            }
            if (timers[uniqueId]) {
                clearTimeout (timers[uniqueId]);
            }
            timers[uniqueId] = setTimeout(callback, ms);
        };
    })();

    /**
     * Get parents
     */
    function getParents(element, tag, stop) {
        var nodes = [];
        while (element.parentNode && element.parentNode != stop) {
            element = element.parentNode;
            if (element.tagName == tag) {
                nodes.push(element);
            }
        }
        return nodes
    }

    /**
     * Get style
     */
    function getStyle(el,styleProp)
    {
        var x = document.getElementById(el);

        if (window.getComputedStyle)
        {
            var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(styleProp);
        }
        else if (x.currentStyle)
        {
            var y = x.currentStyle[styleProp];
        }

        return y;
    }

    // Responsive menu
    function initialize(settings) {

        // Define what the actual menu object is
        if ( settings.menu == '' ) {
            menu = settings.wrapper.getElementsByTagName('ul')[0];
        } else {
            menu = settings.menu;
        }

        // Add a class when JS is initiated
        apollo.addClass(settings.wrapper, settings.initiated_class);

        // See if menu has children
        var parents = menu.querySelectorAll('li ul');
        if ( parents.length ) {
            hasChildren = true;
            subtoggles = document.getElementsByClassName(settings.subtoggleclass);
        }

        // Create mobile width indicator
        var mobileindicator = document.createElement('div');
        document.body.appendChild(mobileindicator);
        mobileindicator.id = settings.mobileindicatorid;
        var mobileindicatorZindex = 0;

        // Creating the main toggle button
        var toggle_element = document.createElement(settings.toggletype);
        apollo.addClass(toggle_element, [settings.toggleclass, settings.hideclass]);
        if ( settings.before_element == '' ) { settings.before_element = settings.wrapper.firstChild }
        settings.before_element.parentNode.insertBefore(toggle_element, settings.before_element);
        var togglebutton = document.getElementsByClassName(settings.toggleclass)[0];
        togglebutton.innerHTML = settings.togglecontent;
        togglebutton.setAttribute('aria-hidden', 'true');
        togglebutton.setAttribute('aria-pressed', 'false');
        togglebutton.setAttribute('type', 'button');

        // Subtoggles and parent classes
        if ( hasChildren ) {
            for (var i = 0; i < parents.length; i++) {
                var subtoggle_element = document.createElement(settings.subtoggletype);
                apollo.addClass(subtoggle_element, [settings.subtoggleclass, settings.hideclass]);
                var parent = parents[i].parentNode;
                parent.insertBefore(subtoggle_element, parent.firstChild);
                subtoggle_element.innerHTML = settings.subtogglecontent;
                subtoggle_element.setAttribute('aria-hidden', 'true');
                subtoggle_element.setAttribute('aria-pressed', 'false');
                subtoggle_element.setAttribute('type', 'button');
                apollo.addClass(parents[i].parentNode, settings.parentclass);
            }
        }

        // Adding classes
        function classes() {

            mobileindicatorZindex = getStyle(settings.mobileindicatorid, "z-index");

            // If wrapper is small and if the menu is not already opened
            if ( mobileindicatorZindex == 0 && !apollo.hasClass(menu, settings.openclass) ) {

                // Show the toggle button(s)
                apollo.removeClass(togglebutton, settings.hideclass);
                if ( hasChildren ) {
                    forEach(subtoggles, function (value, prop) {
                        apollo.addClass(subtoggles[prop].parentNode.getElementsByTagName('ul')[0], settings.hideclass);
                        apollo.removeClass(subtoggles[prop], settings.hideclass);
                    });
                }

                // Hide the menu
                apollo.removeClass(menu, [settings.openclass, settings.fullmenuclass]);
                apollo.addClass(menu, settings.hideclass);
                apollo.removeClass(document.body, settings.openbodyclass);

                // Make the menu absolute positioned
                if ( settings.absolute == 1 ) {
                    apollo.addClass(menu, settings.absolutemenuclass);
                }

            } else if ( mobileindicatorZindex == 1 ) {

                // Hide the toggle button(s)
                apollo.addClass(togglebutton, settings.hideclass);
                apollo.removeClass(togglebutton, settings.toggleclosedclass);
                if (hasChildren) {
                    forEach(subtoggles, function(value, prop) {
                        apollo.removeClass(subtoggles[prop].parentNode.getElementsByTagName('ul')[0], settings.hideclass);
                        apollo.addClass(subtoggles[prop], settings.hideclass);
                    });
                }

                // Show the menu and remove all classes
                apollo.removeClass(menu, [settings.openclass, settings.hideclass]);
                apollo.addClass(menu, settings.fullmenuclass);
                apollo.removeClass(document.body, settings.openbodyclass);

                // Undo absolute positioning
                if ( settings.absolute == 1 && apollo.hasClass(menu, settings.absolutemenuclass) ) {
                    apollo.removeClass(menu, settings.absolutemenuclass);
                }
            }
        }

        // Sticky menu body height
        function stickyMenu() {

            if ( settings.sticky == 1 ) {

                // The current menu and viewport heights
                var menuheight = settings.wrapper.offsetHeight;
                var viewportheight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);

                // Add the overflow class but only if there is space
                if ( viewportheight <= menuheight && !apollo.hasClass(document.body, settings.bodyoverflowhiddenclass) ) {

                    apollo.addClass(document.body, settings.bodyoverflowhiddenclass);
                    apollo.addClass(settings.wrapper, settings.menuoverflowautoclass);

                } else if ( viewportheight > menuheight ) {

                    if ( apollo.hasClass(document.body, settings.bodyoverflowhiddenclass) ) {
                        apollo.removeClass(document.body, settings.bodyoverflowhiddenclass);
                        apollo.removeClass(settings.wrapper, settings.menuoverflowautoclass);
                    }

                    // Make sticky
                    if ( !apollo.hasClass(settings.wrapper, settings.stickyclass) ) {
                        apollo.addClass(settings.wrapper, settings.stickyclass);
                    }

                    // Add padding only if menu is closed or when value is stored
                    if ( !apollo.hasClass(menu, settings.openclass) && !apollo.hasClass(document.body, settings.stickyinitiatedclass) ) {

                        // Calculate the height
                        var paddingtop = menuheight.toString() + 'px';

                        // Set the padding on the body
                        document.body.setAttribute('style', 'padding-top:' + paddingtop);
                        apollo.addClass(document.body, settings.stickyinitiatedclass);
                    }
                }
            }
        }

        // Initial load
        window.addEventListener('load', function() {
            classes();
            stickyMenu();
        }, true);

        // On resize
        window.addEventListener('resize', function() {

            // Run immediately
            classes();
            stickyMenu();

            // Run again after 150 ms for safari OSX when scrollbars are visible and you're resizing to a smaller window
            waitForFinalEvent(function(){
                classes();
                stickyMenu();
            }, 200);

        }, true);

        // Accessible focus menu
        var menulinks = menu.getElementsByTagName('a');
        for (var i = 0; i < menulinks.length; i++) {
            menulinks[i].onblur = function() {
                var focusedItems = document.getElementsByClassName('rm-focused');
                for (var f = 0; f < focusedItems.length; f++) {
                    apollo.removeClass(focusedItems[f], settings.focusedclass);
                }
            };
            menulinks[i].onfocus = function() {
                // Remove the class
                var siblings = this.parentNode.parentNode.querySelectorAll('li');
                if (siblings.length) {
                    for (var f = 0; f < siblings.length; f++) {
                        apollo.removeClass(siblings[f], settings.focusedclass);
                    }
                }
                // Add the class
                var parent = getParents(this, "LI", menu);
                if (parent.length) {
                    for (var f = 0; f < parent.length; f++) {
                        apollo.addClass(parent[f], settings.focusedclass);
                    }
                }
            };
        }

        // Clicking the toggle button
        togglebutton.onclick = function() {

            // Show the menu
            if ( apollo.hasClass(menu, settings.hideclass) ) {

                // Show menu immediately so it can be animated
                apollo.removeClass(menu, settings.hideclass);
                apollo.addClass(menu, settings.openclass);

                // Add class to body element you could use for styling
                apollo.addClass(document.body, settings.openbodyclass);

                // Set and remove animate class after duration
                apollo.addClass(menu, settings.animateopenclass);
                setTimeout(function() { apollo.removeClass(menu, settings.animateopenclass); }, settings.animateduration);

                // Set toggled class to toggle button
                apollo.addClass(togglebutton, settings.toggleclosedclass);
            }

            // Hide the menu
            else if ( apollo.hasClass(menu, settings.openclass) ) {

                // Properly set animating classes
                apollo.addClass(menu, settings.animatecloseclass);
                setTimeout(function() { apollo.removeClass(menu, settings.animatecloseclass); }, settings.animateduration);

                // Hide menu after animation is done (Animated with CSS so set to same duration as CSS value)
                setTimeout(function() { apollo.removeClass(menu, settings.openclass); }, settings.animateduration);
                setTimeout(function() { apollo.addClass(menu, settings.hideclass); }, settings.animateduration);

                // Remove class from body element you could use for styling
                apollo.removeClass(document.body, settings.openbodyclass);

                // Remove toggled class to toggle button
                apollo.removeClass(togglebutton, settings.toggleclosedclass);
            }

            // Check if the menu still fits
            stickyMenu();

            return false;
        };

        // Clicking the sub toggles button
        if ( hasChildren ) {
            forEach(subtoggles, function(value, prop) {

                // Variables
                var subtoggle = subtoggles[prop];
                var submenu = subtoggle.parentNode.getElementsByTagName('ul')[0];

                // Click buttons and show submenu
                subtoggle.onclick = function() {

                    // Add classes accordingly
                    if ( apollo.hasClass(submenu, settings.hideclass) ) {
                        apollo.removeClass(submenu, settings.hideclass);
                        apollo.addClass(subtoggle, settings.toggleclosedclass);
                    } else if ( !apollo.hasClass(submenu, settings.hideclass) ) {
                        apollo.addClass(submenu, settings.hideclass);
                        apollo.removeClass(subtoggle, settings.toggleclosedclass);
                    }

                    // Check if the menu still fits
                    stickyMenu();
                }
            });
        }
    }

    /**
     * Initialize Plugin
     * @public
     * @param {Object} options User settings
     */
    exports.init = function ( options ) {
        // feature test
        if ( !supports ) {
            document.documentElement.className += ' ' + settings.noresponsivemenuclass;
            return;
        }
        settings = extend( defaults, options || {} ); // Merge user options with defaults
        initialize(settings);
    };

    // Public APIs
    return exports;

});<|endoftext|>


<commit_before>/*
 * Appcelerator Titanium Mobile
 * Copyright (c) 2011-2012 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 */

module.exports = new function() {
	var finish;
	var valueOf;
	this.init = function(testUtils) {
		finish = testUtils.finish;
		valueOf = testUtils.valueOf;
	};

	this.name = "ui_layout_horizontal_vertical";
	this.tests = [
		{name: "horizontalTopBottomUndefinedHeight"},
		{name: "horizontalLeftRightUndefinedWidth"},
		{name: "horizontalLeftRightUndefinedWidthNoWrap"},
		{name: "horizontalTopBottomUndefinedHeightNoWrap"},
		{name: "horizontalWrapWithSIZEHeight"},
		{name: "horizontalNoWrapWithSIZEHeight"}
	];

	this.horizontalTopBottomUndefinedHeight = function(testRun) {
		var win = Ti.UI.createWindow({ backgroundColor: 'white'});
		var parent = Ti.UI.createView({backgroundColor:'red',layout:'horizontal', horizontalWrap: true, width:200, height:300});

		var child1 =Ti.UI.createView({backgroundColor:'green', width: 40, top: 10,bottom:10, height: 50});
		var child2 =Ti.UI.createView({backgroundColor:'blue', left: 5, right:20, top: 20, bottom: 10,width:55});
		var child3 =Ti.UI.createView({backgroundColor:'#eee',height:120,width:50});

		parent.add(child1);
		parent.add(child2);
		parent.add(child3);

		win.addEventListener("open", function(e) {
			valueOf(testRun, child1.rect.height).shouldBe(50);
			valueOf(testRun, child1.rect.width).shouldBe(40);
			valueOf(testRun, child1.rect.y).shouldBe(10);
			valueOf(testRun, child1.rect.x).shouldBe(0);

			valueOf(testRun, child2.rect.height).shouldBe(270);
			valueOf(testRun, child2.rect.width).shouldBe(55);
			valueOf(testRun, child2.rect.y).shouldBe(20);
			valueOf(testRun, child2.rect.x).shouldBe(45);

			valueOf(testRun, child3.rect.height).shouldBe(120);
			valueOf(testRun, child3.rect.width).shouldBe(50);
			valueOf(testRun, child3.rect.y).shouldBe(90);
			valueOf(testRun, child3.rect.x).shouldBe(120);

			finish(testRun);
		});

		win.add(parent);
		win.open();
	};

	this.horizontalLeftRightUndefinedWidth = function(testRun) {
		var win = Ti.UI.createWindow({ backgroundColor: 'white'});
		var parent = Ti.UI.createView({backgroundColor:'red',layout:'horizontal', horizontalWrap: true, width:200, height:300});

		var child1 =Ti.UI.createView({backgroundColor:'green', left: 10, right:10, height: 50});
		var child2 =Ti.UI.createView({backgroundColor:'blue', left: 5, right:20, height: 90,width:55});
		var child3 =Ti.UI.createView({backgroundColor:'#eee',left: 5,height:120,width:50});

		parent.add(child1);
		parent.add(child2);
		parent.add(child3);

		win.addEventListener("open", function(e) {
			valueOf(testRun, child1.rect.height).shouldBe(50);
			valueOf(testRun, child1.rect.width).shouldBe(180);
			valueOf(testRun, child1.rect.y).shouldBe(0);
			valueOf(testRun, child1.rect.x).shouldBe(10);

			valueOf(testRun, child2.rect.height).shouldBe(90);
			valueOf(testRun, child2.rect.width).shouldBe(55);
			// ((120 - 90) / 2) + 50
			// child3 determines the maximum height of that row, so we have to calculate accordingly.
			// We have to add 50 since the previous row just fills and this is in the second row
			valueOf(testRun, child2.rect.y).shouldBe(65);
			valueOf(testRun, child2.rect.x).shouldBe(5);

			valueOf(testRun, child3.rect.height).shouldBe(120);
			valueOf(testRun, child3.rect.width).shouldBe(50);
			valueOf(testRun, child3.rect.y).shouldBe(50);
			valueOf(testRun, child3.rect.x).shouldBe(85);

			finish(testRun);
		});

		win.add(parent);
		win.open();
	};

	this.horizontalLeftRightUndefinedWidthNoWrap = function(testRun) {
		var win = Ti.UI.createWindow({ backgroundColor: 'white'});
		var parent = Ti.UI.createView({backgroundColor:'red',layout:'horizontal', horizontalWrap: false, width:200, height:300});

		var child1 =Ti.UI.createView({backgroundColor:'green', left: 10, right:10, height: 50});
		var child2 =Ti.UI.createView({backgroundColor:'blue', left: 5, right:20, height: 90,width:55});
		var child3 =Ti.UI.createView({backgroundColor:'#eee',left: 5,height:120,width:50});

		parent.add(child1);
		parent.add(child2);
		parent.add(child3);

		win.addEventListener("open", function(e) {
			valueOf(testRun, child1.rect.height).shouldBe(50);
			valueOf(testRun, child1.rect.width).shouldBe(180);
			// (300-50)/2
			valueOf(testRun, child1.rect.y).shouldBe(125);
			valueOf(testRun, child1.rect.x).shouldBe(10);

			valueOf(testRun, child2.rect.height).shouldBe(90);
			valueOf(testRun, child2.rect.width).shouldBe(55);
			valueOf(testRun, child2.rect.y).shouldBe(105);
			valueOf(testRun, child2.rect.x).shouldBe(205);

			valueOf(testRun, child3.rect.height).shouldBe(120);
			valueOf(testRun, child3.rect.width).shouldBe(50);
			valueOf(testRun, child3.rect.y).shouldBe(90);
			valueOf(testRun, child3.rect.x).shouldBe(285);

			finish(testRun);
		});
		win.add(parent);
		win.open();
	};

	this.horizontalTopBottomUndefinedHeightNoWrap = function(testRun) {
		var win = Ti.UI.createWindow({ backgroundColor: 'white'});
		var parent = Ti.UI.createView({backgroundColor:'red',layout:'horizontal', horizontalWrap: false, width:200, height:300});

		var child1 =Ti.UI.createView({backgroundColor:'green', width: 40, top: 10,bottom:10, height: 50});
		var child2 =Ti.UI.createView({backgroundColor:'blue', left: 5, right:20, top: 20, bottom: 10,width:55});
		var child3 =Ti.UI.createView({backgroundColor:'#eee',height:120,width:50});

		parent.add(child1);
		parent.add(child2);
		parent.add(child3);

		win.addEventListener("open", function(e) {
			valueOf(testRun, child1.rect.height).shouldBe(50);
			valueOf(testRun, child1.rect.width).shouldBe(40);
			valueOf(testRun, child1.rect.y).shouldBe(10);
			valueOf(testRun, child1.rect.x).shouldBe(0);

			valueOf(testRun, child2.rect.height).shouldBe(270);
			valueOf(testRun, child2.rect.width).shouldBe(55);
			valueOf(testRun, child2.rect.y).shouldBe(20);
			valueOf(testRun, child2.rect.x).shouldBe(45);

			valueOf(testRun, child3.rect.height).shouldBe(120);
			valueOf(testRun, child3.rect.width).shouldBe(50);
			valueOf(testRun, child3.rect.y).shouldBe(90);
			valueOf(testRun, child3.rect.x).shouldBe(120);

			finish(testRun);
		});

		win.add(parent);
		win.open();
	};

	this.horizontalWrapWithSIZEHeight = function(testRun) {
		var win = Ti.UI.createWindow({
			navBarHidden : true,
			backgroundColor : '#000'
		});

		var topView = Ti.UI.createView({
			backgroundColor : 'white',
			height : Ti.UI.SIZE,
			layout : 'horizontal'
		});

		topView.add(Ti.UI.createView({width: Ti.UI.FILL, height: 100, backgroundColor:'blue'}));
		topView.add(Ti.UI.createView({width: Ti.UI.FILL, height: 100, backgroundColor:'red'}));
		topView.add(Ti.UI.createView({width: Ti.UI.FILL, height: 100, backgroundColor:'purple'}));
		topView.add(Ti.UI.createView({width: Ti.UI.FILL, height: 100, backgroundColor:'orange'}));

		win.addEventListener("postlayout", function(e){
			valueOf(testRun, topView.rect.height).shouldBe(400);
			finish(testRun);
		});

		win.add(topView);
		win.open();
	};

	this.horizontalNoWrapWithSIZEHeight = function(testRun) {
		var win = Ti.UI.createWindow({
			navBarHidden : true,
			backgroundColor : '#000'
		});

		var topView = Ti.UI.createView({
			backgroundColor : 'white',
			width : Ti.UI.SIZE,
			height : Ti.UI.SIZE,
			layout : 'horizontal',
			horizontalWrap: false
		});

		topView.add(Ti.UI.createView({width: 50, height: 100, backgroundColor:'blue'}));
		topView.add(Ti.UI.createView({width: 50, height: 150, backgroundColor:'red'}));
		topView.add(Ti.UI.createView({width:50, height: 200, backgroundColor:'purple'}));
		topView.add(Ti.UI.createView({width: 100, height: 100, backgroundColor:'orange'}));

		win.addEventListener("postlayout", function(e){
			valueOf(testRun, topView.rect.width).shouldBe(250);
			valueOf(testRun, topView.rect.height).shouldBe(200);

			finish(testRun);
		});

		win.add(topView);
		win.open();
	};
};
<commit_msg>TIMOB-10003: Added more tests regarding horizontal layout with padding.<commit_after>/*
 * Appcelerator Titanium Mobile
 * Copyright (c) 2011-2012 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 */

module.exports = new function() {
	var finish;
	var valueOf;
	this.init = function(testUtils) {
		finish = testUtils.finish;
		valueOf = testUtils.valueOf;
	};

	this.name = "ui_layout_horizontal_vertical";
	this.tests = [
		{name: "horizontalTopBottomUndefinedHeight"},
		{name: "horizontalLeftRightUndefinedWidth"},
		{name: "horizontalLeftRightUndefinedWidthNoWrap"},
		{name: "horizontalTopBottomUndefinedHeightNoWrap"},
		{name: "horizontalWrapWithSIZEHeight"},
		{name: "horizontalNoWrapWithSIZEHeight"},
		{name: "horizontalNoWrapTopPaddingSIZEHeight"},
		{name: "horizontalWrapWithSIZEHeight"}
	];

	this.horizontalTopBottomUndefinedHeight = function(testRun) {
		var win = Ti.UI.createWindow({ backgroundColor: 'white'});
		var parent = Ti.UI.createView({backgroundColor:'red',layout:'horizontal', horizontalWrap: true, width:200, height:300});

		var child1 =Ti.UI.createView({backgroundColor:'green', width: 40, top: 10,bottom:10, height: 50});
		var child2 =Ti.UI.createView({backgroundColor:'blue', left: 5, right:20, top: 20, bottom: 10,width:55});
		var child3 =Ti.UI.createView({backgroundColor:'#eee',height:120,width:50});

		parent.add(child1);
		parent.add(child2);
		parent.add(child3);

		win.addEventListener("open", function(e) {
			valueOf(testRun, child1.rect.height).shouldBe(50);
			valueOf(testRun, child1.rect.width).shouldBe(40);
			valueOf(testRun, child1.rect.y).shouldBe(10);
			valueOf(testRun, child1.rect.x).shouldBe(0);

			valueOf(testRun, child2.rect.height).shouldBe(270);
			valueOf(testRun, child2.rect.width).shouldBe(55);
			valueOf(testRun, child2.rect.y).shouldBe(20);
			valueOf(testRun, child2.rect.x).shouldBe(45);

			valueOf(testRun, child3.rect.height).shouldBe(120);
			valueOf(testRun, child3.rect.width).shouldBe(50);
			valueOf(testRun, child3.rect.y).shouldBe(90);
			valueOf(testRun, child3.rect.x).shouldBe(120);

			finish(testRun);
		});

		win.add(parent);
		win.open();
	};

	this.horizontalLeftRightUndefinedWidth = function(testRun) {
		var win = Ti.UI.createWindow({ backgroundColor: 'white'});
		var parent = Ti.UI.createView({backgroundColor:'red',layout:'horizontal', horizontalWrap: true, width:200, height:300});

		var child1 =Ti.UI.createView({backgroundColor:'green', left: 10, right:10, height: 50});
		var child2 =Ti.UI.createView({backgroundColor:'blue', left: 5, right:20, height: 90,width:55});
		var child3 =Ti.UI.createView({backgroundColor:'#eee',left: 5,height:120,width:50});

		parent.add(child1);
		parent.add(child2);
		parent.add(child3);

		win.addEventListener("open", function(e) {
			valueOf(testRun, child1.rect.height).shouldBe(50);
			valueOf(testRun, child1.rect.width).shouldBe(180);
			valueOf(testRun, child1.rect.y).shouldBe(0);
			valueOf(testRun, child1.rect.x).shouldBe(10);

			valueOf(testRun, child2.rect.height).shouldBe(90);
			valueOf(testRun, child2.rect.width).shouldBe(55);
			// ((120 - 90) / 2) + 50
			// child3 determines the maximum height of that row, so we have to calculate accordingly.
			// We have to add 50 since the previous row just fills and this is in the second row
			valueOf(testRun, child2.rect.y).shouldBe(65);
			valueOf(testRun, child2.rect.x).shouldBe(5);

			valueOf(testRun, child3.rect.height).shouldBe(120);
			valueOf(testRun, child3.rect.width).shouldBe(50);
			valueOf(testRun, child3.rect.y).shouldBe(50);
			valueOf(testRun, child3.rect.x).shouldBe(85);

			finish(testRun);
		});

		win.add(parent);
		win.open();
	};

	this.horizontalLeftRightUndefinedWidthNoWrap = function(testRun) {
		var win = Ti.UI.createWindow({ backgroundColor: 'white'});
		var parent = Ti.UI.createView({backgroundColor:'red',layout:'horizontal', horizontalWrap: false, width:200, height:300});

		var child1 =Ti.UI.createView({backgroundColor:'green', left: 10, right:10, height: 50});
		var child2 =Ti.UI.createView({backgroundColor:'blue', left: 5, right:20, height: 90,width:55});
		var child3 =Ti.UI.createView({backgroundColor:'#eee',left: 5,height:120,width:50});

		parent.add(child1);
		parent.add(child2);
		parent.add(child3);

		win.addEventListener("open", function(e) {
			valueOf(testRun, child1.rect.height).shouldBe(50);
			valueOf(testRun, child1.rect.width).shouldBe(180);
			// (300-50)/2
			valueOf(testRun, child1.rect.y).shouldBe(125);
			valueOf(testRun, child1.rect.x).shouldBe(10);

			valueOf(testRun, child2.rect.height).shouldBe(90);
			valueOf(testRun, child2.rect.width).shouldBe(55);
			valueOf(testRun, child2.rect.y).shouldBe(105);
			valueOf(testRun, child2.rect.x).shouldBe(205);

			valueOf(testRun, child3.rect.height).shouldBe(120);
			valueOf(testRun, child3.rect.width).shouldBe(50);
			valueOf(testRun, child3.rect.y).shouldBe(90);
			valueOf(testRun, child3.rect.x).shouldBe(285);

			finish(testRun);
		});
		win.add(parent);
		win.open();
	};

	this.horizontalTopBottomUndefinedHeightNoWrap = function(testRun) {
		var win = Ti.UI.createWindow({ backgroundColor: 'white'});
		var parent = Ti.UI.createView({backgroundColor:'red',layout:'horizontal', horizontalWrap: false, width:200, height:300});

		var child1 =Ti.UI.createView({backgroundColor:'green', width: 40, top: 10,bottom:10, height: 50});
		var child2 =Ti.UI.createView({backgroundColor:'blue', left: 5, right:20, top: 20, bottom: 10,width:55});
		var child3 =Ti.UI.createView({backgroundColor:'#eee',height:120,width:50});

		parent.add(child1);
		parent.add(child2);
		parent.add(child3);

		win.addEventListener("open", function(e) {
			valueOf(testRun, child1.rect.height).shouldBe(50);
			valueOf(testRun, child1.rect.width).shouldBe(40);
			valueOf(testRun, child1.rect.y).shouldBe(10);
			valueOf(testRun, child1.rect.x).shouldBe(0);

			valueOf(testRun, child2.rect.height).shouldBe(270);
			valueOf(testRun, child2.rect.width).shouldBe(55);
			valueOf(testRun, child2.rect.y).shouldBe(20);
			valueOf(testRun, child2.rect.x).shouldBe(45);

			valueOf(testRun, child3.rect.height).shouldBe(120);
			valueOf(testRun, child3.rect.width).shouldBe(50);
			valueOf(testRun, child3.rect.y).shouldBe(90);
			valueOf(testRun, child3.rect.x).shouldBe(120);

			finish(testRun);
		});

		win.add(parent);
		win.open();
	};

	this.horizontalWrapWithSIZEHeight = function(testRun) {
		var win = Ti.UI.createWindow({
			navBarHidden : true,
			backgroundColor : '#000'
		});

		var topView = Ti.UI.createView({
			backgroundColor : 'white',
			height : Ti.UI.SIZE,
			layout : 'horizontal'
		});

		topView.add(Ti.UI.createView({width: Ti.UI.FILL, height: 100, backgroundColor:'blue'}));
		topView.add(Ti.UI.createView({width: Ti.UI.FILL, height: 100, backgroundColor:'red'}));
		topView.add(Ti.UI.createView({width: Ti.UI.FILL, height: 100, backgroundColor:'purple'}));
		topView.add(Ti.UI.createView({width: Ti.UI.FILL, height: 100, backgroundColor:'orange'}));

		win.addEventListener("postlayout", function(e){
			valueOf(testRun, topView.rect.height).shouldBe(400);
			finish(testRun);
		});

		win.add(topView);
		win.open();
	};

	this.horizontalNoWrapWithSIZEHeight = function(testRun) {
		var win = Ti.UI.createWindow({
			navBarHidden : true,
			backgroundColor : '#000'
		});

		var topView = Ti.UI.createView({
			backgroundColor : 'white',
			width : Ti.UI.SIZE,
			height : Ti.UI.SIZE,
			layout : 'horizontal',
			horizontalWrap: false
		});

		topView.add(Ti.UI.createView({width: 50, height: 100, backgroundColor:'blue'}));
		topView.add(Ti.UI.createView({width: 50, height: 150, backgroundColor:'red'}));
		topView.add(Ti.UI.createView({width:50, height: 200, backgroundColor:'purple'}));
		topView.add(Ti.UI.createView({width: 100, height: 100, backgroundColor:'orange'}));

		win.addEventListener("postlayout", function(e){
			valueOf(testRun, topView.rect.width).shouldBe(250);
			valueOf(testRun, topView.rect.height).shouldBe(200);

			finish(testRun);
		});

		win.add(topView);
		win.open();
	};

	this.horizontalNoWrapTopPaddingSIZEHeight = function(testRun) {
		var win = Ti.UI.createWindow({
			navBarHidden : true,
			backgroundColor : '#000'
		});

		var topView = Ti.UI.createView({
			backgroundColor : 'white',
			width : Ti.UI.SIZE,
			height : Ti.UI.SIZE,
			layout : 'horizontal',
			horizontalWrap: false
		});

		topView.add(Ti.UI.createView({width: 50, height: 100, backgroundColor:'blue'}));
		topView.add(Ti.UI.createView({width: 50, height: 150, backgroundColor:'red'}));
		topView.add(Ti.UI.createView({width:50, top: 10, bottom: 25, height: 200, backgroundColor:'purple'}));
		topView.add(Ti.UI.createView({width: 100, height: 100, backgroundColor:'orange'}));

		win.addEventListener("postlayout", function(e){
			valueOf(testRun, topView.rect.width).shouldBe(250);
			valueOf(testRun, topView.rect.height).shouldBe(235);

			finish(testRun);
		});

		win.add(topView);
		win.open();
	};

	this.horizontalWrapWithSIZEHeight = function(testRun) {
		var win = Ti.UI.createWindow({
			navBarHidden : true,
			backgroundColor : '#000'
		});

		var topView = Ti.UI.createView({
			backgroundColor : 'white',
			height : Ti.UI.SIZE,
			layout : 'horizontal'
		});

		topView.add(Ti.UI.createView({width: Ti.UI.FILL, height: 100, backgroundColor:'blue'}));
		topView.add(Ti.UI.createView({width: 50, height: 100, backgroundColor:'red'}));
		topView.add(Ti.UI.createView({width: 50, top: 50, bottom: 20, height: 100, backgroundColor:'purple'}));
		topView.add(Ti.UI.createView({width: 50, height: 100, backgroundColor:'orange'}));

		win.addEventListener("postlayout", function(e){
			valueOf(testRun, topView.rect.height).shouldBe(270);
			finish(testRun);
		});

		win.add(topView);
		win.open();
	};
};
<|endoftext|>