<commit_before>var small_chart_height = 250;
var medium_chart_height = 500;
var large_chart_height = 650;
var small_width = 480; //544
var medium_width = 720; //768
var result_chart_width = 768;
var valueAccessor = function (d) {return d.Value < 1 ? 0 : d.Value};
var our_colors = ["#9df5e7","#b2bfdb","#a1eda1","#fc9898", "#afedf0","#afede1", "#fc6565"];
var team_default = d3.scale.ordinal().range(["#015B64"]);
var year_default = d3.scale.ordinal().range(["#1C293B"]);
var default_colors = d3.scale.ordinal().range(our_colors);
//For pie chart
var donut_inner = 40
var donut_outer = 80
var donut_height = 100
var perc_view = false;

grey_undefined = function(chart) {
  chart.selectAll("text.row").classed("grey",function(d) {return d.value.not_real || d.value.count == 0});
}

//---------------------CLEANUP functions-------------------------

var yearDom = [];

function cleanup(d) {

  d.Year = d.Date.slice(-2);
  d.Year = d.Year > 16 ? 1900+parseInt(d.Year) : 2000+parseInt(d.Year);
  d.Value = 1;
	d.properDate = new Date(d.Date.split('-')[1] + " " + d.Date.split('-')[0] + ", " + d.Year);
  d.matchAgainst = d.Team + '/' + d.Opposition;
  if (d.Result != "tied") d.resultStatus = d.Team + '@' + d.Result;

  return d;
}

function isOdd(num) {
	return num % 2;
}

function capitalizeFirst(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

function change_result_view() {
  result_year_chart = dc.barChart('#result_year')
      .group(result_year_group, "won")
      .valueAccessor(function(d){return d.value["won"]})
      .stack(result_year_group, "lost", function(d) { return d.value["lost"] })
      .stack(result_year_group, "tied", function(d) { return d.value["tied"] })
      .dimension(result_year)
      .centerBar(true)
      .height(medium_chart_height/2)
      .width(result_chart_width)
      .colors(d3.scale.ordinal().domain(["won", "lost", "tied"]).range(["#45936E","#92332F", "#3E70A1"]))
      .x(d3.scale.linear().domain([1995, 2016]))
      .elasticX(false)
      .elasticY(true)
      .transitionDuration(200)
      .mouseZoomable(false)
      .yAxisLabel("Games")
      .renderHorizontalGridLines(true)
      .renderVerticalGridLines(true)
      .brushOn(true)
      .margins({top: 10, right: 50, bottom: 30, left: 50})
      .on("renderlet.result_year", function (chart) {
          //Check if labels exist
          var gLabels = chart.select(".labels");
          if (gLabels.empty()){
            gLabels = chart.select(".chart-body").append('g').classed('labels', true);
          }

          var gLabelsData = gLabels.selectAll("text").data(chart.selectAll(".bar")[0]);

          gLabelsData.exit().remove(); //Remove unused elements

          gLabelsData.enter().append("text") //Add new elements

          gLabelsData
            .attr('text-anchor', 'middle  ')
            .attr('fill', 'white')
            .attr("font-size", "11px")
            .text(function(d){
              return d3.select(d).data()[0].y
            })
            .attr('x', function(d){
              return +d.getAttribute('x') + (d.getAttribute('width')/2);
            })
            .attr('y', function(d){ return +d.getAttribute('y') + 15; })
            .attr('style', function(d){
              if (+d.getAttribute('height') < 18) return "display:none";
            });
        })
        .on("preRedraw", function (chart) {
            chart.rescale();
        })
        .on("preRender", function (chart) {
            chart.rescale();
        })
        .on("pretransition", function (chart) {
            chart.rescale();
        });

    result_year_chart.xAxis().ticks(10).tickFormat(d3.format("d"));

  if (perc_view == false) {
    $('.result_year_title').text("Overall Team Performance by Games");
    result_year_chart
      .label(function(d) { return d; })
      .title(function(d) {
        d3.selectAll("rect.bar")
          .on('mouseover', function(d){
          });
          return d.key+": "+d3.format(',')(d.value[this.layer])+" ("+this.layer+")";
      });

  }
  else {
    $('.result_year_title').text("Overall Team Performance by Percentage");
    result_year_chart
      .group(result_year_group, "won")
      .valueAccessor(function(d){
        var v = parseFloat(d.value["won"]/(d.value["won"]+d.value["lost"]+d.value["tied"]));
        if (isNaN(v)) return 0;
        return parseFloat((v*100).toFixed(1));
      })
      .stack(result_year_group, "lost", function(d) {
        var v = parseFloat(d.value["lost"]/(d.value["won"]+d.value["lost"]+d.value["tied"]));
        if (isNaN(v)) return 0;
        return parseFloat((v*100).toFixed(1));
      })
      .stack(result_year_group, "tied", function(d) {
      if (d.value["tied"] == 0) return 0
      else
        var v = parseFloat(d.value["tied"] /(d.value["won"]+d.value["lost"]+d.value["tied"]))
        if (isNaN(v)) return 0;
        return parseFloat((v*100).toFixed(1));
      })
      .label(function(d) { return d; })
      .title(function(d) {
        d3.selectAll("rect.bar")
          .on('mouseover', function(d){
          });
          return d.key+": "+d3.format(',')(d.value[this.layer])+" ("+this.layer+")";
      });

  }

  result_year_chart.yAxis().ticks(5).tickFormat(d3.format("g"));
  grey_undefined(result_year_chart);

  //Need to reset the filter when the view changes or else, filter lingers
  result_year_chart.filterAll();
  dc.renderAll();
  perc_view = !perc_view;

}

//Queueing defer ensures that all our datasets get loaded before any work is done

queue()
    .defer(d3.csv, "data/cricket-odi.csv")
    // .defer(d3.csv, "import-data.csv") //change name here to load more than 1 file
    .await(showCharts);

function showCharts(err, data) {
  _data = [];

  for (i in data) {
		data[i] = cleanup(data[i]);
  }
  _data = data;
//	console.log(data);

  function configureableReduce(field, value, init) {
    return {
      add: function(v,d){
        v[d[field]] = (v[d[field]] || 0) + d[value];
        return v
      },
      remove: function(v,d){
        v[d[field]] -= d[value];
        return v
      },
      init: function() {
        return init ? JSON.parse(JSON.stringify(init)) : {}
      }
    }
  }

	resultByYear = configureableReduce("Result", "Value", {
		"won":0,
		"lost":0,
		"tied":0
	});

	//---------------------------------FILTERS-----------------------------------------
  ndx = crossfilter(_data);
  //---------------------------ORDINARY CHARTS --------------------------------------
	year = ndx.dimension(function(d){return d.Year});

  year_group = year.group().reduceSum(function(d){return d.Value});

	year_chart = dc.rowChart('#year')
    .dimension(year)
    .group(year_group)
    .colors(year_default)
    .transitionDuration(200)
    .height(large_chart_height/1.5)
		.width(small_width)
    .ordering(function(d){ return -d.key })
//		.x(d3.scale.linear().domain([-25, 25]))
    .elasticX(true);

	year_chart.xAxis().ticks(10).tickFormat(d3.format("g"));
	grey_undefined(year_chart);

  year_bar = ndx.dimension(function(d){return d.Year});

  year_bar_group = year_bar.group().reduceSum(function(d){return d.Value});

	year_bar_chart = dc.barChart('#year-bar')
      .dimension(year_bar)
      .group(year_bar_group)
      .colors(default_colors)
      .transitionDuration(200)
      .height(medium_chart_height)
      .width(small_width)
      .ordering(function(d){ return -d.key })
      .x(d3.scale.linear().domain([-25, 25]))
      .brushOn(true)
      .elasticY(true)
      .elasticX(true);

	year_bar_chart.xAxis().ticks(5).tickFormat(d3.format("g"));
	grey_undefined(year_bar_chart);

	opposition = ndx.dimension(function(d){return d.Opposition});

  opposition_group = opposition.group().reduceSum(function(d){return d.Value});

	opposition_chart = dc.rowChart('#opposition')
    .dimension(opposition)
    .group(opposition_group)
    .colors(team_default)
    .transitionDuration(200)
    .height(small_chart_height)
		.width(small_width-50)
    .ordering(function(d){ return -d.key })
//		.xAxisLabel('Total Games')
//		.xAxisPadding(500)
    .elasticX(true);

	opposition_chart.xAxis().ticks(5).tickFormat(d3.format("d"));
	grey_undefined(opposition_chart);

	team = ndx.dimension(function(d){return d.Team});
  team_group = team.group().reduceSum(function(d){return d.Value});

  team_chart = dc.rowChart('#team')
    .dimension(team)
    .group(team_group)
    .colors(team_default)
    .transitionDuration(200)
    .height(small_chart_height)
    .width(small_width-50)
    .ordering(function(d){ return -d.key })
    .elasticX(true);

  team_chart.xAxis().ticks(5).tickFormat(d3.format("d"));
  grey_undefined(team_chart);

	result = ndx.dimension(function(d){return d.Result});

  result_group = result.group().reduceSum(function(d){return d.Value});

	result_chart = dc.pieChart('#result')
    .dimension(result)
    .group(result_group)
    .transitionDuration(200)
    .legend(dc.legend().x(0).y(25).itemHeight(18).gap(5))
    .height(small_chart_height-50)
    .title(function(d) {
      var title = capitalizeFirst(d.key) + ": " + d.value;
      if (d.key == "won"){
        return "Team A \n" + title;
      }
      else if (d.key == "lost"){
        return "Team B \n" + title;
      }
      return title;
    })
    .label(function(d) {
      if (d.key == "won") return "Team A: " + d.value;
      else if (d.key == "lost") return "Team B: " + d.value;
      return capitalizeFirst(d.key) + ": " + d.value;
    })
    .radius(donut_outer)
    .colors(d3.scale.ordinal().domain(["won", "lost", "tied"])
                              .range(["#45936E","#92332F", "#3E70A1"]))
    .colorAccessor(function(d) {
			if (d.key == "won") return "won";
			else if (d.key == "lost") return "lost";
			return "tied";
		});

	result_year = ndx.dimension(function(d){return d.Year});
	result_year_group = result_year.group().reduce(resultByYear.add, resultByYear.remove, resultByYear.init);

  change_result_view();

  var all = ndx.groupAll();
  data_count_chart = dc.dataCount('#data_count')
    .dimension(ndx)
    .group(all)
    .html({
        some: '%filter-count selected out of %total-count records' +
            ' | Reset All',
        all: 'All records selected. Please click on the graph to apply filters.'
    });

  //pyramid chart
  resultStatus = ndx.dimension(function(d) {return d.resultStatus});
  result_group = resultStatus.group().reduceSum(function(d){return d.Value})

  team_opp_chart = dc.pyramidChart('#team_opp')
    .dimension(resultStatus)
    .group(result_group)
    .colors(d3.scale.ordinal().domain(["won", "lost"])
                              .range(["#45936E","#92332F"]))
    .colorAccessor(function(d) {
      if (d.key.split('@')[1] == "won") return "won";
      else if (d.key.split('@')[1] == "lost") return "lost";
    })
    .height(small_chart_height)
    .width(medium_width-50)
    .leftColumn(function (d){ return d.key.split('@')[1] == "won"})
    .rowAccessor(function(d){ return d.key.split('@')[0]})
    .label(function(d){return d.key.split('@')[0]})
    .title(function(d){
      if (d.key.split('@')[1] == "won") return "Won: " + d.value;
      else if (d.key.split('@')[1] == "lost") return "Lost: " + d.value;
    })
    .elasticX(true)
    .twoLabels(false)
    .rowOrdering(d3.ascending)
    .columnLabels(['Won','Lost'])
    .columnLabelPosition([0,250]) //[in,down], in pix. defaults to [5,10]
    .transitionDuration(200);

  team_opp_chart.xAxis().ticks(7).tickFormat(function(x) {return d3.format('s')(Math.abs(x))})

	dc.renderAll();

  initialize();
};

function initialize(){
  //odd are wins, even are losses
  var a = $('#team_opp g .row text:eq(7)');
//  var b = $('#team_opp g .row rect:eq(6)');

  a.simulate('click');
//  b.simulate('click');

};

function hideshow(id){
  var a = $(id);
  a.toggle('show');
};

function showAll() {
  var idList = ["#result_year", "#team_opp", "#result", "#team", "#opposition"]

  for (x in idList) {
    var a = $(idList[x]);
    if (a.css('display') == "none"){
      a.toggle('show');
    }
  }
  $('[type=checkbox]').prop('checked', true);
};<commit_msg>minor fix to the pyramid<commit_after>var small_chart_height = 250;
var medium_chart_height = 500;
var large_chart_height = 650;
var small_width = 480; //544
var medium_width = 720; //768
var result_chart_width = 768;
var valueAccessor = function (d) {return d.Value < 1 ? 0 : d.Value};
var our_colors = ["#9df5e7","#b2bfdb","#a1eda1","#fc9898", "#afedf0","#afede1", "#fc6565"];
var team_default = d3.scale.ordinal().range(["#015B64"]);
var year_default = d3.scale.ordinal().range(["#1C293B"]);
var default_colors = d3.scale.ordinal().range(our_colors);
//For pie chart
var donut_inner = 40
var donut_outer = 80
var donut_height = 100
var perc_view = false;

grey_undefined = function(chart) {
  chart.selectAll("text.row").classed("grey",function(d) {return d.value.not_real || d.value.count == 0});
}

//---------------------CLEANUP functions-------------------------

var yearDom = [];

function cleanup(d) {

  d.Year = d.Date.slice(-2);
  d.Year = d.Year > 16 ? 1900+parseInt(d.Year) : 2000+parseInt(d.Year);
  d.Value = 1;
	d.properDate = new Date(d.Date.split('-')[1] + " " + d.Date.split('-')[0] + ", " + d.Year);
  d.matchAgainst = d.Team + '/' + d.Opposition;
  if (d.Result != "tied") d.resultStatus = d.Team + '@' + d.Result;

  return d;
}

function isOdd(num) {
	return num % 2;
}

function capitalizeFirst(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

function change_result_view() {
  result_year_chart = dc.barChart('#result_year')
      .group(result_year_group, "won")
      .valueAccessor(function(d){return d.value["won"]})
      .stack(result_year_group, "lost", function(d) { return d.value["lost"] })
      .stack(result_year_group, "tied", function(d) { return d.value["tied"] })
      .dimension(result_year)
      .centerBar(true)
      .height(medium_chart_height/2)
      .width(result_chart_width)
      .colors(d3.scale.ordinal().domain(["won", "lost", "tied"]).range(["#45936E","#92332F", "#3E70A1"]))
      .x(d3.scale.linear().domain([1995, 2016]))
      .elasticX(false)
      .elasticY(true)
      .transitionDuration(200)
      .mouseZoomable(false)
      .yAxisLabel("Games")
      .renderHorizontalGridLines(true)
      .renderVerticalGridLines(true)
      .brushOn(true)
      .margins({top: 10, right: 50, bottom: 30, left: 50})
      .on("renderlet.result_year", function (chart) {
          //Check if labels exist
          var gLabels = chart.select(".labels");
          if (gLabels.empty()){
            gLabels = chart.select(".chart-body").append('g').classed('labels', true);
          }

          var gLabelsData = gLabels.selectAll("text").data(chart.selectAll(".bar")[0]);

          gLabelsData.exit().remove(); //Remove unused elements

          gLabelsData.enter().append("text") //Add new elements

          gLabelsData
            .attr('text-anchor', 'middle  ')
            .attr('fill', 'white')
            .attr("font-size", "11px")
            .text(function(d){
              return d3.select(d).data()[0].y
            })
            .attr('x', function(d){
              return +d.getAttribute('x') + (d.getAttribute('width')/2);
            })
            .attr('y', function(d){ return +d.getAttribute('y') + 15; })
            .attr('style', function(d){
              if (+d.getAttribute('height') < 18) return "display:none";
            });
        })
        .on("preRedraw", function (chart) {
            chart.rescale();
        })
        .on("preRender", function (chart) {
            chart.rescale();
        })
        .on("pretransition", function (chart) {
            chart.rescale();
        });

    result_year_chart.xAxis().ticks(10).tickFormat(d3.format("d"));

  if (perc_view == false) {
    $('.result_year_title').text("Overall Team Performance by Games");
    result_year_chart
      .label(function(d) { return d; })
      .title(function(d) {
        d3.selectAll("rect.bar")
          .on('mouseover', function(d){
          });
          return d.key+": "+d3.format(',')(d.value[this.layer])+" ("+this.layer+")";
      });

  }
  else {
    $('.result_year_title').text("Overall Team Performance by Percentage");
    result_year_chart
      .group(result_year_group, "won")
      .valueAccessor(function(d){
        var v = parseFloat(d.value["won"]/(d.value["won"]+d.value["lost"]+d.value["tied"]));
        if (isNaN(v)) return 0;
        return parseFloat((v*100).toFixed(1));
      })
      .stack(result_year_group, "lost", function(d) {
        var v = parseFloat(d.value["lost"]/(d.value["won"]+d.value["lost"]+d.value["tied"]));
        if (isNaN(v)) return 0;
        return parseFloat((v*100).toFixed(1));
      })
      .stack(result_year_group, "tied", function(d) {
      if (d.value["tied"] == 0) return 0
      else
        var v = parseFloat(d.value["tied"] /(d.value["won"]+d.value["lost"]+d.value["tied"]))
        if (isNaN(v)) return 0;
        return parseFloat((v*100).toFixed(1));
      })
      .label(function(d) { return d; })
      .title(function(d) {
        d3.selectAll("rect.bar")
          .on('mouseover', function(d){
          });
          return d.key+": "+d3.format(',')(d.value[this.layer])+" ("+this.layer+")";
      });

  }

  result_year_chart.yAxis().ticks(5).tickFormat(d3.format("g"));
  grey_undefined(result_year_chart);

  //Need to reset the filter when the view changes or else, filter lingers
  result_year_chart.filterAll();
  dc.renderAll();
  perc_view = !perc_view;

}

//Queueing defer ensures that all our datasets get loaded before any work is done

queue()
    .defer(d3.csv, "data/cricket-odi.csv")
    // .defer(d3.csv, "import-data.csv") //change name here to load more than 1 file
    .await(showCharts);

function showCharts(err, data) {
  _data = [];

  for (i in data) {
		data[i] = cleanup(data[i]);
  }
  _data = data;
//	console.log(data);

  function configureableReduce(field, value, init) {
    return {
      add: function(v,d){
        v[d[field]] = (v[d[field]] || 0) + d[value];
        return v
      },
      remove: function(v,d){
        v[d[field]] -= d[value];
        return v
      },
      init: function() {
        return init ? JSON.parse(JSON.stringify(init)) : {}
      }
    }
  }

	resultByYear = configureableReduce("Result", "Value", {
		"won":0,
		"lost":0,
		"tied":0
	});

	//---------------------------------FILTERS-----------------------------------------
  ndx = crossfilter(_data);
  //---------------------------ORDINARY CHARTS --------------------------------------
	year = ndx.dimension(function(d){return d.Year});

  year_group = year.group().reduceSum(function(d){return d.Value});

	year_chart = dc.rowChart('#year')
    .dimension(year)
    .group(year_group)
    .colors(year_default)
    .transitionDuration(200)
    .height(large_chart_height/1.5)
		.width(small_width)
    .ordering(function(d){ return -d.key })
//		.x(d3.scale.linear().domain([-25, 25]))
    .elasticX(true);

	year_chart.xAxis().ticks(10).tickFormat(d3.format("g"));
	grey_undefined(year_chart);

  year_bar = ndx.dimension(function(d){return d.Year});

  year_bar_group = year_bar.group().reduceSum(function(d){return d.Value});

	year_bar_chart = dc.barChart('#year-bar')
      .dimension(year_bar)
      .group(year_bar_group)
      .colors(default_colors)
      .transitionDuration(200)
      .height(medium_chart_height)
      .width(small_width)
      .ordering(function(d){ return -d.key })
      .x(d3.scale.linear().domain([-25, 25]))
      .brushOn(true)
      .elasticY(true)
      .elasticX(true);

	year_bar_chart.xAxis().ticks(5).tickFormat(d3.format("g"));
	grey_undefined(year_bar_chart);

	opposition = ndx.dimension(function(d){return d.Opposition});

  opposition_group = opposition.group().reduceSum(function(d){return d.Value});

	opposition_chart = dc.rowChart('#opposition')
    .dimension(opposition)
    .group(opposition_group)
    .colors(team_default)
    .transitionDuration(200)
    .height(small_chart_height)
		.width(small_width-50)
    .ordering(function(d){ return -d.key })
//		.xAxisLabel('Total Games')
//		.xAxisPadding(500)
    .elasticX(true);

	opposition_chart.xAxis().ticks(5).tickFormat(d3.format("d"));
	grey_undefined(opposition_chart);

	team = ndx.dimension(function(d){return d.Team});
  team_group = team.group().reduceSum(function(d){return d.Value});

  team_chart = dc.rowChart('#team')
    .dimension(team)
    .group(team_group)
    .colors(team_default)
    .transitionDuration(200)
    .height(small_chart_height)
    .width(small_width-50)
    .ordering(function(d){ return -d.key })
    .elasticX(true);

  team_chart.xAxis().ticks(5).tickFormat(d3.format("d"));
  grey_undefined(team_chart);

	result = ndx.dimension(function(d){return d.Result});

  result_group = result.group().reduceSum(function(d){return d.Value});

	result_chart = dc.pieChart('#result')
    .dimension(result)
    .group(result_group)
    .transitionDuration(200)
    .legend(dc.legend().x(0).y(25).itemHeight(18).gap(5))
    .height(small_chart_height-50)
    .title(function(d) {
      var title = capitalizeFirst(d.key) + ": " + d.value;
      if (d.key == "won"){
        return "Team A \n" + title;
      }
      else if (d.key == "lost"){
        return "Team B \n" + title;
      }
      return title;
    })
    .label(function(d) {
      if (d.key == "won") return "Team A: " + d.value;
      else if (d.key == "lost") return "Team B: " + d.value;
      return capitalizeFirst(d.key) + ": " + d.value;
    })
    .radius(donut_outer)
    .colors(d3.scale.ordinal().domain(["won", "lost", "tied"])
                              .range(["#45936E","#92332F", "#3E70A1"]))
    .colorAccessor(function(d) {
			if (d.key == "won") return "won";
			else if (d.key == "lost") return "lost";
			return "tied";
		});

	result_year = ndx.dimension(function(d){return d.Year});
	result_year_group = result_year.group().reduce(resultByYear.add, resultByYear.remove, resultByYear.init);

  change_result_view();

  var all = ndx.groupAll();
  data_count_chart = dc.dataCount('#data_count')
    .dimension(ndx)
    .group(all)
    .html({
        some: '%filter-count selected out of %total-count records' +
            ' | Reset All',
        all: 'All records selected. Please click on the graph to apply filters.'
    });

  //pyramid chart
  resultStatus = ndx.dimension(function(d) {return d.resultStatus});
  result_group = resultStatus.group().reduceSum(function(d){return d.Value})

  team_opp_chart = dc.pyramidChart('#team_opp')
    .dimension(resultStatus)
    .group(result_group)
    .colors(d3.scale.ordinal().domain(["won", "lost"])
                              .range(["#45936E","#92332F"]))
    .colorAccessor(function(d) {
      if (d.key.split('@')[1] == "won") return "won";
      else if (d.key.split('@')[1] == "lost") return "lost";
    })
    .height(small_chart_height)
    .width(medium_width-50)
    .leftColumn(function (d){ return d.key.split('@')[1] == "won"})
    .rowAccessor(function(d){ return d.key.split('@')[0]})
    .label(function(d){return d.key.split('@')[0]})
    .title(function(d){
      if (d.key.split('@')[1] == "won") return "Won: " + d.value;
      else if (d.key.split('@')[1] == "lost") return "Lost: " + d.value;
      else return NaN;
    })
    .elasticX(false)
    .twoLabels(false)
    .rowOrdering(d3.ascending)
    .columnLabels(['Won','Lost'])
    .columnLabelPosition([0,0])
    .transitionDuration(200);

  team_opp_chart.xAxis().ticks(7).tickFormat(function(x) {return d3.format('s')(Math.abs(x))})

	dc.renderAll();

  initialize();
};

function initialize(){
  //odd are wins, even are losses
  var a = $('#team_opp g .row text:eq(7)');
//  var b = $('#team_opp g .row rect:eq(6)');

  a.simulate('click');
//  b.simulate('click');

};

function hideshow(id){
  var a = $(id);
  a.toggle('show');
};

function showAll() {
  var idList = ["#result_year", "#team_opp", "#result", "#team", "#opposition"]

  for (x in idList) {
    var a = $(idList[x]);
    if (a.css('display') == "none"){
      a.toggle('show');
    }
  }
  $('[type=checkbox]').prop('checked', true);
};<|endoftext|>


<commit_before>/**
 * Copyright (c) 2016-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

'use strict';

const BatchProcessor = require('./BatchProcessor');
const FetchError = require('node-fetch/lib/fetch-error');

const crypto = require('crypto');
const fetch = require('node-fetch');
const jsonStableStringify = require('json-stable-stringify');
const path = require('path');

import type {
  Options as TransformWorkerOptions,
  TransformOptions,
} from '../JSTransformer/worker/worker';
import type {CachedResult, GetTransformCacheKey} from './TransformCache';

/**
 * The API that a global transform cache must comply with. To implement a
 * custom cache, implement this interface and pass it as argument to the
 * application's top-level `Server` class.
 */
export type GlobalTransformCache = {
  /**
   * Synchronously determine if it is worth trying to fetch a result from the
   * cache. This can be used, for instance, to exclude sets of options we know
   * will never be cached.
   */
  shouldFetch(props: FetchProps): boolean,

  /**
   * Try to fetch a result. It doesn't actually need to fetch from a server,
   * the global cache could be instantiated locally for example.
   */
  fetch(props: FetchProps): Promise<?CachedResult>,

  /**
   * Try to store a result, without waiting for the success or failure of the
   * operation. Consequently, the actual storage operation could be done at a
   * much later point if desired. It is recommended to actually have this
   * function be a no-op in production, and only do the storage operation from
   * a script running on your Continuous Integration platform.
   */
  store(props: FetchProps, result: CachedResult): void,
};

type FetchResultURIs = (keys: Array<string>) => Promise<Map<string, string>>;
type FetchResultFromURI = (uri: string) => Promise<?CachedResult>;
type StoreResults = (resultsByKey: Map<string, CachedResult>) => Promise<void>;

export type FetchProps = {
  filePath: string,
  sourceCode: string,
  getTransformCacheKey: GetTransformCacheKey,
  transformOptions: TransformWorkerOptions,
};

type URI = string;

/**
 * We aggregate the requests to do a single request for many keys. It also
 * ensures we do a single request at a time to avoid pressuring the I/O.
 */
class KeyURIFetcher {

  _batchProcessor: BatchProcessor<string, ?URI>;
  _fetchResultURIs: FetchResultURIs;

  /**
   * When a batch request fails for some reason, we process the error locally
   * and we proceed as if there were no result for these keys instead. That way
   * a build will not fail just because of the cache.
   */
  async _processKeys(keys: Array<string>): Promise<Array<?URI>> {
    const URIsByKey = await this._fetchResultURIs(keys);
    return keys.map(key => URIsByKey.get(key));
  }

  async fetch(key: string): Promise<?string> {
    return await this._batchProcessor.queue(key);
  }

  constructor(fetchResultURIs: FetchResultURIs) {
    this._fetchResultURIs = fetchResultURIs;
    this._batchProcessor = new BatchProcessor({
      maximumDelayMs: 10,
      maximumItems: 500,
      concurrency: 25,
    }, this._processKeys.bind(this));
  }

}

type KeyedResult = {key: string, result: CachedResult};

class KeyResultStore {

  _storeResults: StoreResults;
  _batchProcessor: BatchProcessor<KeyedResult, void>;

  async _processResults(keyResults: Array<KeyedResult>): Promise<Array<void>> {
    const resultsByKey = new Map(keyResults.map(pair => [pair.key, pair.result]));
    await this._storeResults(resultsByKey);
    return new Array(keyResults.length);
  }

  store(key: string, result: CachedResult) {
    this._batchProcessor.queue({key, result});
  }

  constructor(storeResults: StoreResults) {
    this._storeResults = storeResults;
    this._batchProcessor = new BatchProcessor({
      maximumDelayMs: 1000,
      maximumItems: 100,
      concurrency: 10,
    }, this._processResults.bind(this));
  }

}

export type TransformProfile = {+dev: boolean, +minify: boolean, +platform: string};

function profileKey({dev, minify, platform}: TransformProfile): string {
  return jsonStableStringify({dev, minify, platform});
}

/**
 * We avoid doing any request to the server if we know the server is not
 * going to have any key at all for a particular set of transform options.
 */
class TransformProfileSet {
  _profileKeys: Set<string>;
  constructor(profiles: Iterable<TransformProfile>) {
    this._profileKeys = new Set();
    for (const profile of profiles) {
      this._profileKeys.add(profileKey(profile));
    }
  }
  has(profile: TransformProfile): boolean {
    return this._profileKeys.has(profileKey(profile));
  }
}

type FetchFailedDetails =
  {+type: 'unhandled_http_status', +statusCode: number} | {+type: 'unspecified'};

class FetchFailedError extends Error {
  /** Separate object for details allows us to have a type union. */
  +details: FetchFailedDetails;

  constructor(message: string, details: FetchFailedDetails) {
    super();
    this.message = message;
    (this: any).details = details;
  }
}

/**
 * For some reason the result stored by the server for a key might mismatch what
 * we expect a result to be. So we need to verify carefully the data.
 */
function validateCachedResult(cachedResult: mixed): ?CachedResult {
  if (
    cachedResult != null &&
    typeof cachedResult === 'object' &&
    typeof cachedResult.code === 'string' &&
    Array.isArray(cachedResult.dependencies) &&
    cachedResult.dependencies.every(dep => typeof dep === 'string') &&
    Array.isArray(cachedResult.dependencyOffsets) &&
    cachedResult.dependencyOffsets.every(offset => typeof offset === 'number')
  ) {
    return (cachedResult: any);
  }
  return null;
}

class URIBasedGlobalTransformCache {

  _fetcher: KeyURIFetcher;
  _fetchResultFromURI: FetchResultFromURI;
  _profileSet: TransformProfileSet;
  _optionsHasher: OptionsHasher;
  _store: ?KeyResultStore;

  static FetchFailedError;

  /**
   * For using the global cache one needs to have some kind of central key-value
   * store that gets prefilled using keyOf() and the transformed results. The
   * fetching function should provide a mapping of keys to URIs. The files
   * referred by these URIs contains the transform results. Using URIs instead
   * of returning the content directly allows for independent and parallel
   * fetching of each result, that may be arbitrarily large JSON blobs.
   */
  constructor(props: {
    fetchResultFromURI: FetchResultFromURI,
    fetchResultURIs: FetchResultURIs,
    profiles: Iterable<TransformProfile>,
    rootPath: string,
    storeResults: StoreResults | null,
  }) {
    this._fetcher = new KeyURIFetcher(props.fetchResultURIs);
    this._profileSet = new TransformProfileSet(props.profiles);
    this._fetchResultFromURI = props.fetchResultFromURI;
    this._optionsHasher = new OptionsHasher(props.rootPath);
    if (props.storeResults != null) {
      this._store = new KeyResultStore(props.storeResults);
    }
  }

  /**
   * Return a key for identifying uniquely a source file.
   */
  keyOf(props: FetchProps) {
    const hash = crypto.createHash('sha1');
    const {sourceCode, filePath, transformOptions} = props;
    this._optionsHasher.hashTransformWorkerOptions(hash, transformOptions);
    const cacheKey = props.getTransformCacheKey(sourceCode, filePath, transformOptions);
    hash.update(JSON.stringify(cacheKey));
    hash.update(crypto.createHash('sha1').update(sourceCode).digest('hex'));
    const digest = hash.digest('hex');
    return `${digest}-${path.basename(filePath)}`;
  }

  /**
   * We may want to improve that logic to return a stream instead of the whole
   * blob of transformed results. However the results are generally only a few
   * megabytes each.
   */
  static async _fetchResultFromURI(uri: string): Promise<CachedResult> {
    const response = await fetch(uri, {method: 'GET', timeout: 8000});
    if (response.status !== 200) {
      const msg = `Unexpected HTTP status: ${response.status} ${response.statusText} `;
      throw new FetchFailedError(msg, {
        type: 'unhandled_http_status',
        statusCode: response.status,
      });
    }
    const unvalidatedResult = await response.json();
    const result = validateCachedResult(unvalidatedResult);
    if (result == null) {
      throw new FetchFailedError('Server returned invalid result.', {type: 'unspecified'});
    }
    return result;
  }

  /**
   * It happens from time to time that a fetch fails, we want to try these again
   * a second time if we expect them to be transient. We might even consider
   * waiting a little time before retring if experience shows it's useful.
   */
  static fetchResultFromURI(uri: string): Promise<CachedResult> {
    return URIBasedGlobalTransformCache._fetchResultFromURI(uri).catch(error => {
      if (!URIBasedGlobalTransformCache.shouldRetryAfterThatError(error)) {
        throw error;
      }
      return this._fetchResultFromURI(uri);
    });
  }

  /**
   * We want to retry timeouts as they're likely temporary. We retry 503
   * (Service Unavailable) and 502 (Bad Gateway) because they may be caused by a
   * some rogue server, or because of throttling.
   *
   * There may be other types of error we'd want to retry for, but these are
   * the ones we experienced the most in practice.
   */
  static shouldRetryAfterThatError(error: Error): boolean {
    return (
      error instanceof FetchError && error.type === 'request-timeout' || (
        error instanceof FetchFailedError &&
        error.details.type === 'wrong_http_status' &&
        (error.details.statusCode === 503 || error.details.statusCode === 502)
      )
    );
  }

  shouldFetch(props: FetchProps): boolean {
    return this._profileSet.has(props.transformOptions);
  }

  /**
   * This may return `null` if either the cache doesn't have a value for that
   * key yet, or an error happened, processed separately.
   */
  async fetch(props: FetchProps): Promise<?CachedResult> {
    const uri = await this._fetcher.fetch(this.keyOf(props));
    if (uri == null) {
      return null;
    }
    return await this._fetchResultFromURI(uri);
  }

  store(props: FetchProps, result: CachedResult) {
    if (this._store != null) {
      this._store.store(this.keyOf(props), result);
    }
  }

}

class OptionsHasher {
  _rootPath: string;

  constructor(rootPath: string) {
    this._rootPath = rootPath;
  }

  /**
   * This function is extra-conservative with how it hashes the transform
   * options. In particular:
   *
   *     * we need to hash paths relative to the root, not the absolute paths,
   *       otherwise everyone would have a different cache, defeating the
   *       purpose of global cache;
   *     * we need to reject any additional field we do not know of, because
   *       they could contain absolute path, and we absolutely want to process
   *       these.
   *
   * Theorically, Flow could help us prevent any other field from being here by
   * using *exact* object type. In practice, the transform options are a mix of
   * many different fields including the optional Babel fields, and some serious
   * cleanup will be necessary to enable rock-solid typing.
   */
  hashTransformWorkerOptions(hash: crypto$Hash, options: TransformWorkerOptions): crypto$Hash {
    const {dev, minify, platform, transform, extern, ...unknowns} = options;
    const unknownKeys = Object.keys(unknowns);
    if (unknownKeys.length > 0) {
      const message = `these worker option fields are unknown: ${JSON.stringify(unknownKeys)}`;
      throw new CannotHashOptionsError(message);
    }
    // eslint-disable-next-line no-undef, no-bitwise
    hash.update(new Buffer([+dev | +minify << 1 | +!!extern << 2]));
    hash.update(JSON.stringify(platform));
    return this.hashTransformOptions(hash, transform);
  }

  /**
   * The transform options contain absolute paths. This can contain, for
   * example, the username if someone works their home directory (very likely).
   * We get rid of this local data for the global cache, otherwise nobody would
   * share the same cache keys. The project roots should not be needed as part
   * of the cache key as they should not affect the transformation of a single
   * particular file.
   */
  hashTransformOptions(hash: crypto$Hash, options: TransformOptions): crypto$Hash {
    const {generateSourceMaps, dev, hot, inlineRequires, platform,
      preloadedModules, projectRoots, ramGroups, ...unknowns} = options;
    const unknownKeys = Object.keys(unknowns);
    if (unknownKeys.length > 0) {
      const message = `these transform option fields are unknown: ${JSON.stringify(unknownKeys)}`;
      throw new CannotHashOptionsError(message);
    }
    // eslint-disable-next-line no-undef
    hash.update(new Buffer([
      // eslint-disable-next-line no-bitwise
      +dev | +generateSourceMaps << 1 | +hot << 2 | +!!inlineRequires << 3,
    ]));
    hash.update(JSON.stringify(platform));
    let relativeBlacklist = [];
    if (typeof inlineRequires === 'object') {
      relativeBlacklist = this.relativizeFilePaths(Object.keys(inlineRequires.blacklist));
    }
    const relativeProjectRoots = this.relativizeFilePaths(projectRoots);
    const optionTuple = [relativeBlacklist, preloadedModules, relativeProjectRoots, ramGroups];
    hash.update(JSON.stringify(optionTuple));
    return hash;
  }

  relativizeFilePaths(filePaths: Array<string>): Array<string> {
    return filePaths.map(filepath => path.relative(this._rootPath, filepath));
  }
}

class CannotHashOptionsError extends Error {
  constructor(message: string) {
    super();
    this.message = message;
  }
}

URIBasedGlobalTransformCache.FetchFailedError = FetchFailedError;

module.exports = {URIBasedGlobalTransformCache, CannotHashOptionsError};
<commit_msg>packager: GlobalTransformCache: fix bug in retry logic<commit_after>/**
 * Copyright (c) 2016-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

'use strict';

const BatchProcessor = require('./BatchProcessor');
const FetchError = require('node-fetch/lib/fetch-error');

const crypto = require('crypto');
const fetch = require('node-fetch');
const jsonStableStringify = require('json-stable-stringify');
const path = require('path');

import type {
  Options as TransformWorkerOptions,
  TransformOptions,
} from '../JSTransformer/worker/worker';
import type {CachedResult, GetTransformCacheKey} from './TransformCache';

/**
 * The API that a global transform cache must comply with. To implement a
 * custom cache, implement this interface and pass it as argument to the
 * application's top-level `Server` class.
 */
export type GlobalTransformCache = {
  /**
   * Synchronously determine if it is worth trying to fetch a result from the
   * cache. This can be used, for instance, to exclude sets of options we know
   * will never be cached.
   */
  shouldFetch(props: FetchProps): boolean,

  /**
   * Try to fetch a result. It doesn't actually need to fetch from a server,
   * the global cache could be instantiated locally for example.
   */
  fetch(props: FetchProps): Promise<?CachedResult>,

  /**
   * Try to store a result, without waiting for the success or failure of the
   * operation. Consequently, the actual storage operation could be done at a
   * much later point if desired. It is recommended to actually have this
   * function be a no-op in production, and only do the storage operation from
   * a script running on your Continuous Integration platform.
   */
  store(props: FetchProps, result: CachedResult): void,
};

type FetchResultURIs = (keys: Array<string>) => Promise<Map<string, string>>;
type FetchResultFromURI = (uri: string) => Promise<?CachedResult>;
type StoreResults = (resultsByKey: Map<string, CachedResult>) => Promise<void>;

export type FetchProps = {
  filePath: string,
  sourceCode: string,
  getTransformCacheKey: GetTransformCacheKey,
  transformOptions: TransformWorkerOptions,
};

type URI = string;

/**
 * We aggregate the requests to do a single request for many keys. It also
 * ensures we do a single request at a time to avoid pressuring the I/O.
 */
class KeyURIFetcher {

  _batchProcessor: BatchProcessor<string, ?URI>;
  _fetchResultURIs: FetchResultURIs;

  /**
   * When a batch request fails for some reason, we process the error locally
   * and we proceed as if there were no result for these keys instead. That way
   * a build will not fail just because of the cache.
   */
  async _processKeys(keys: Array<string>): Promise<Array<?URI>> {
    const URIsByKey = await this._fetchResultURIs(keys);
    return keys.map(key => URIsByKey.get(key));
  }

  async fetch(key: string): Promise<?string> {
    return await this._batchProcessor.queue(key);
  }

  constructor(fetchResultURIs: FetchResultURIs) {
    this._fetchResultURIs = fetchResultURIs;
    this._batchProcessor = new BatchProcessor({
      maximumDelayMs: 10,
      maximumItems: 500,
      concurrency: 25,
    }, this._processKeys.bind(this));
  }

}

type KeyedResult = {key: string, result: CachedResult};

class KeyResultStore {

  _storeResults: StoreResults;
  _batchProcessor: BatchProcessor<KeyedResult, void>;

  async _processResults(keyResults: Array<KeyedResult>): Promise<Array<void>> {
    const resultsByKey = new Map(keyResults.map(pair => [pair.key, pair.result]));
    await this._storeResults(resultsByKey);
    return new Array(keyResults.length);
  }

  store(key: string, result: CachedResult) {
    this._batchProcessor.queue({key, result});
  }

  constructor(storeResults: StoreResults) {
    this._storeResults = storeResults;
    this._batchProcessor = new BatchProcessor({
      maximumDelayMs: 1000,
      maximumItems: 100,
      concurrency: 10,
    }, this._processResults.bind(this));
  }

}

export type TransformProfile = {+dev: boolean, +minify: boolean, +platform: string};

function profileKey({dev, minify, platform}: TransformProfile): string {
  return jsonStableStringify({dev, minify, platform});
}

/**
 * We avoid doing any request to the server if we know the server is not
 * going to have any key at all for a particular set of transform options.
 */
class TransformProfileSet {
  _profileKeys: Set<string>;
  constructor(profiles: Iterable<TransformProfile>) {
    this._profileKeys = new Set();
    for (const profile of profiles) {
      this._profileKeys.add(profileKey(profile));
    }
  }
  has(profile: TransformProfile): boolean {
    return this._profileKeys.has(profileKey(profile));
  }
}

type FetchFailedDetails =
  {+type: 'unhandled_http_status', +statusCode: number} | {+type: 'unspecified'};

class FetchFailedError extends Error {
  /** Separate object for details allows us to have a type union. */
  +details: FetchFailedDetails;

  constructor(message: string, details: FetchFailedDetails) {
    super();
    this.message = message;
    (this: any).details = details;
  }
}

/**
 * For some reason the result stored by the server for a key might mismatch what
 * we expect a result to be. So we need to verify carefully the data.
 */
function validateCachedResult(cachedResult: mixed): ?CachedResult {
  if (
    cachedResult != null &&
    typeof cachedResult === 'object' &&
    typeof cachedResult.code === 'string' &&
    Array.isArray(cachedResult.dependencies) &&
    cachedResult.dependencies.every(dep => typeof dep === 'string') &&
    Array.isArray(cachedResult.dependencyOffsets) &&
    cachedResult.dependencyOffsets.every(offset => typeof offset === 'number')
  ) {
    return (cachedResult: any);
  }
  return null;
}

class URIBasedGlobalTransformCache {

  _fetcher: KeyURIFetcher;
  _fetchResultFromURI: FetchResultFromURI;
  _profileSet: TransformProfileSet;
  _optionsHasher: OptionsHasher;
  _store: ?KeyResultStore;

  static FetchFailedError;

  /**
   * For using the global cache one needs to have some kind of central key-value
   * store that gets prefilled using keyOf() and the transformed results. The
   * fetching function should provide a mapping of keys to URIs. The files
   * referred by these URIs contains the transform results. Using URIs instead
   * of returning the content directly allows for independent and parallel
   * fetching of each result, that may be arbitrarily large JSON blobs.
   */
  constructor(props: {
    fetchResultFromURI: FetchResultFromURI,
    fetchResultURIs: FetchResultURIs,
    profiles: Iterable<TransformProfile>,
    rootPath: string,
    storeResults: StoreResults | null,
  }) {
    this._fetcher = new KeyURIFetcher(props.fetchResultURIs);
    this._profileSet = new TransformProfileSet(props.profiles);
    this._fetchResultFromURI = props.fetchResultFromURI;
    this._optionsHasher = new OptionsHasher(props.rootPath);
    if (props.storeResults != null) {
      this._store = new KeyResultStore(props.storeResults);
    }
  }

  /**
   * Return a key for identifying uniquely a source file.
   */
  keyOf(props: FetchProps) {
    const hash = crypto.createHash('sha1');
    const {sourceCode, filePath, transformOptions} = props;
    this._optionsHasher.hashTransformWorkerOptions(hash, transformOptions);
    const cacheKey = props.getTransformCacheKey(sourceCode, filePath, transformOptions);
    hash.update(JSON.stringify(cacheKey));
    hash.update(crypto.createHash('sha1').update(sourceCode).digest('hex'));
    const digest = hash.digest('hex');
    return `${digest}-${path.basename(filePath)}`;
  }

  /**
   * We may want to improve that logic to return a stream instead of the whole
   * blob of transformed results. However the results are generally only a few
   * megabytes each.
   */
  static async _fetchResultFromURI(uri: string): Promise<CachedResult> {
    const response = await fetch(uri, {method: 'GET', timeout: 8000});
    if (response.status !== 200) {
      const msg = `Unexpected HTTP status: ${response.status} ${response.statusText} `;
      throw new FetchFailedError(msg, {
        type: 'unhandled_http_status',
        statusCode: response.status,
      });
    }
    const unvalidatedResult = await response.json();
    const result = validateCachedResult(unvalidatedResult);
    if (result == null) {
      throw new FetchFailedError('Server returned invalid result.', {type: 'unspecified'});
    }
    return result;
  }

  /**
   * It happens from time to time that a fetch fails, we want to try these again
   * a second time if we expect them to be transient. We might even consider
   * waiting a little time before retring if experience shows it's useful.
   */
  static fetchResultFromURI(uri: string): Promise<CachedResult> {
    return URIBasedGlobalTransformCache._fetchResultFromURI(uri).catch(error => {
      if (!URIBasedGlobalTransformCache.shouldRetryAfterThatError(error)) {
        throw error;
      }
      return this._fetchResultFromURI(uri);
    });
  }

  /**
   * We want to retry timeouts as they're likely temporary. We retry 503
   * (Service Unavailable) and 502 (Bad Gateway) because they may be caused by a
   * some rogue server, or because of throttling.
   *
   * There may be other types of error we'd want to retry for, but these are
   * the ones we experienced the most in practice.
   */
  static shouldRetryAfterThatError(error: Error): boolean {
    return (
      error instanceof FetchError && error.type === 'request-timeout' || (
        error instanceof FetchFailedError &&
        error.details.type === 'unhandled_http_status' &&
        (error.details.statusCode === 503 || error.details.statusCode === 502)
      )
    );
  }

  shouldFetch(props: FetchProps): boolean {
    return this._profileSet.has(props.transformOptions);
  }

  /**
   * This may return `null` if either the cache doesn't have a value for that
   * key yet, or an error happened, processed separately.
   */
  async fetch(props: FetchProps): Promise<?CachedResult> {
    const uri = await this._fetcher.fetch(this.keyOf(props));
    if (uri == null) {
      return null;
    }
    return await this._fetchResultFromURI(uri);
  }

  store(props: FetchProps, result: CachedResult) {
    if (this._store != null) {
      this._store.store(this.keyOf(props), result);
    }
  }

}

class OptionsHasher {
  _rootPath: string;

  constructor(rootPath: string) {
    this._rootPath = rootPath;
  }

  /**
   * This function is extra-conservative with how it hashes the transform
   * options. In particular:
   *
   *     * we need to hash paths relative to the root, not the absolute paths,
   *       otherwise everyone would have a different cache, defeating the
   *       purpose of global cache;
   *     * we need to reject any additional field we do not know of, because
   *       they could contain absolute path, and we absolutely want to process
   *       these.
   *
   * Theorically, Flow could help us prevent any other field from being here by
   * using *exact* object type. In practice, the transform options are a mix of
   * many different fields including the optional Babel fields, and some serious
   * cleanup will be necessary to enable rock-solid typing.
   */
  hashTransformWorkerOptions(hash: crypto$Hash, options: TransformWorkerOptions): crypto$Hash {
    const {dev, minify, platform, transform, extern, ...unknowns} = options;
    const unknownKeys = Object.keys(unknowns);
    if (unknownKeys.length > 0) {
      const message = `these worker option fields are unknown: ${JSON.stringify(unknownKeys)}`;
      throw new CannotHashOptionsError(message);
    }
    // eslint-disable-next-line no-undef, no-bitwise
    hash.update(new Buffer([+dev | +minify << 1 | +!!extern << 2]));
    hash.update(JSON.stringify(platform));
    return this.hashTransformOptions(hash, transform);
  }

  /**
   * The transform options contain absolute paths. This can contain, for
   * example, the username if someone works their home directory (very likely).
   * We get rid of this local data for the global cache, otherwise nobody would
   * share the same cache keys. The project roots should not be needed as part
   * of the cache key as they should not affect the transformation of a single
   * particular file.
   */
  hashTransformOptions(hash: crypto$Hash, options: TransformOptions): crypto$Hash {
    const {generateSourceMaps, dev, hot, inlineRequires, platform,
      preloadedModules, projectRoots, ramGroups, ...unknowns} = options;
    const unknownKeys = Object.keys(unknowns);
    if (unknownKeys.length > 0) {
      const message = `these transform option fields are unknown: ${JSON.stringify(unknownKeys)}`;
      throw new CannotHashOptionsError(message);
    }
    // eslint-disable-next-line no-undef
    hash.update(new Buffer([
      // eslint-disable-next-line no-bitwise
      +dev | +generateSourceMaps << 1 | +hot << 2 | +!!inlineRequires << 3,
    ]));
    hash.update(JSON.stringify(platform));
    let relativeBlacklist = [];
    if (typeof inlineRequires === 'object') {
      relativeBlacklist = this.relativizeFilePaths(Object.keys(inlineRequires.blacklist));
    }
    const relativeProjectRoots = this.relativizeFilePaths(projectRoots);
    const optionTuple = [relativeBlacklist, preloadedModules, relativeProjectRoots, ramGroups];
    hash.update(JSON.stringify(optionTuple));
    return hash;
  }

  relativizeFilePaths(filePaths: Array<string>): Array<string> {
    return filePaths.map(filepath => path.relative(this._rootPath, filepath));
  }
}

class CannotHashOptionsError extends Error {
  constructor(message: string) {
    super();
    this.message = message;
  }
}

URIBasedGlobalTransformCache.FetchFailedError = FetchFailedError;

module.exports = {URIBasedGlobalTransformCache, CannotHashOptionsError};
<|endoftext|>


<commit_before>define(function(require, exports, module) {
    main.consumes = [
        "Editor", "editors", "ui", "tabManager", "settings", "Form",
        "commands", "fs", "ace", "layout", "c9", "menus"
    ];
    main.provides = ["welcome"];
    return main;

     /*
        Change Ace Theme
            - Get List from Ace
    */

    function main(options, imports, register) {
        var Editor = imports.Editor;
        var editors = imports.editors;
        var ui = imports.ui;
        var fs = imports.fs;
        var c9 = imports.c9;
        var ace = imports.ace;
        var commands = imports.commands;
        var layout = imports.layout;
        var tabManager = imports.tabManager;
        var settings = imports.settings;
        var Form = imports.Form;
        var menus = imports.menus;

        var join = require("path").join;

        /***** Initialization *****/

        var handle = editors.register("welcome", "URL Viewer", Welcome, []);
        var intro;

        var WELCOME_INTRO = (options.intro || "").replace(/\n/g, "
");
        var OS_INTRO = "\n You can now use sudo and apt-get to manage your workspace!";

        var defaults = {
            "flat-light" : "#F8FDFF",
            "flat-dark"  : "#203947",
            "light" : "#b7c9d4",
            "light-gray" : "#b7c9d4",
            "dark"  : "#203947",
            "dark-gray"  : "#203947"
        };

        var loaded = false;
        function load() {
            if (loaded) return false;
            loaded = true;

            menus.addItemByPath("Support/Welcome Page", new ui.item({
                onclick: function(){ tabManager.openEditor("welcome", true, function(){}) }
            }), 150, handle);

            tabManager.once("ready", function(){
                settings.on("read", function(e) {
                    if (e.reset) {
                        settings.set("user/welcome/@first", true);
                        return;
                    }

                    if (!settings.getBool("user/welcome/@first")) {
                        show(function(){
                            settings.set("user/welcome/@first", true);
                        });
                    }
                }, handle);

                if (window.location.hash.match(/#openfile-(.*)/)) {
                    var file = "/" + RegExp.$1;
                    fs.exists(file, function(exists) {
                        if (!exists) return;
                        commands.exec("preview", null, {
                            path: file,
                            focus: options.focusOpenFile || false
                        });
                    });
                }
            }, handle);
        }

        var drawn = false;
        function draw() {
            if (drawn) return;
            drawn = true;

            if (options.checkOS) {
                fs.stat("~/" + c9.projectId, function(err, stat){
                    if (!err && stat.fullPath == join(c9.home, "workspace")) {
                        if (drawn)
                            intro.innerHTML = WELCOME_INTRO + OS_INTRO;
                        else
                            WELCOME_INTRO += OS_INTRO;
                    }
                });
            }

            // Insert CSS
            ui.insertCss(require("text!./style.css"),
                options.staticPrefix, handle);
        }

        handle.on("load", load);

        /***** Methods *****/

        function search(){
            var found;
            var tabs = tabManager.getTabs();
            tabs.every(function(tab) {
                if (tab.document.meta.welcome) {
                    found = tab;
                    return false;
                }
                return true;
            });
            return found;
        }

        function show(cb) {
            var tab = search();
            if (tab)
                return tabManager.focusTab(tab);

            tabManager.open({
                editorType: "welcome",
                noanim: true,
                active: true
            }, cb);
        }

        function Welcome(){
            var plugin = new Editor("Ajax.org", main.consumes, []);
            //var emit = plugin.getEmitter();

            var container;

            plugin.on("draw", function(e) {
                draw();

                // Create UI elements
                container = e.htmlNode;

                var html = require("text!./welcome.html");
                var nodes = ui.insertHtml(container, html, plugin);
                var node = nodes[0];

                intro = node.querySelector(".intro");
                intro.innerHTML = WELCOME_INTRO;

                var list = [];
                var themes = ace.themes
                for (var base in themes) {
                    if (themes[base] instanceof Array)
                        themes[base].forEach(function (n) {
                            var themeprop = Object.keys(n)[0];
                            list.push({ caption: themeprop, value: n[themeprop] });
                        });
                    else
                        list.push({ caption: base, value: themes[base] });
                }

                var presetClick = function(){
                    var value = this.id;

                    current.className = "preset";
                    this.className = "preset active";
                    current = this;

                    if (value != "default" && !settings.getBool("user/welcome/@switched")) {
                        setTimeout(function(){
                            var div = container.querySelector(".switched");
                            div.style.display = "block";
                            if (!apf.isMac)
                                div.innerHTML = div.innerHTML.replace(/Command/g, "Ctrl");
                            settings.set("user/welcome/@switched", true);
                        }, 500);
                    }

                    setTimeout(function(){
                        layout.setBaseLayout(value);
                    });
                };

                var presets = node.querySelectorAll(".preset");
                var current, preset;
                for (var i = 0; i < presets.length; i++) {
                    if (~(preset = presets[i]).className.indexOf("active"))
                        current = preset;

                    preset.addEventListener("click", presetClick);
                }

                var form = new Form({
                    edge: "3 3 8 3",
                    rowheight: 40,
                    colwidth: 150,
                    style: "padding:10px;",
                    form: [
                        {
                            title : "Main Theme",
                            type  : "dropdown",
                            path  : "user/general/@skin",
                            width : 190,
                            items : [
                                { caption: "Cloud9 Classic Dark Theme", value: "dark" },
                                { caption: "Cloud9 Flat Light Theme", value: "flat-light" }
                                // { caption: "Cloud9 Flat Dark Theme", value: "flat-dark" }
                            ],
                            position : 100
                        },
                        {
                            title: "Split Layout",
                            type: "dropdown",
                            width: 190,
                            defaultValue: "nosplit",
                            onchange: function(e) {
                                commands.exec(e.value);
                            },
                            items: [
                                { caption: "No Split", value: "nosplit" },
                                { caption: "Two Vertical Split", value: "twovsplit" },
                                { caption: "Two Horizontal Split", value: "twohsplit" },
                                { caption: "Four Split", value: "foursplit" },
                                { caption: "Three Split (Left)", value: "threeleft" },
                                { caption: "Three Split (Right)", value: "threeright" }
                            ],
                            position: 150
                        },
                        {
                            title: "Editor (Ace) Theme",
                            type: "dropdown",
                            path: "user/ace/@theme",
                            width: 190,
                            onchange: function(e) {
                                ace.setTheme(e.value);
                            },
                            items: list,
                            position: 180
                        },
                        {
                            title: "Keyboard Mode",
                            type: "dropdown",
                            path: "user/ace/@keyboardmode",
                            width: 190,
                            items: [
                                { caption: "Default", value: "default" },
                                { caption: "Vim", value: "vim" },
                                { caption: "Emacs", value: "emacs" },
                                { caption: "Sublime", value: "sublime" }
                            ],
                            position: 190
                        },
                        {
                            title: "Soft Tabs",
                            type: "checked-spinner",
                            checkboxPath: "project/ace/@useSoftTabs",
                            path: "project/ace/@tabSize",
                            min: "1",
                            max: "64",
                            width: "50",
                            position: 200
                        }
                    ]
                });

                form.attachTo(container.querySelector(".configure .form"));

                container.querySelector(".configure .more").onclick = function(){
                    commands.exec("openpreferences");
                };
                container.querySelector(".openterminal").onclick = function(){
                    tabManager.openEditor("terminal", true, function(){});
                };
                container.querySelector(".openconsole").onclick = function(){
                    commands.exec("toggleconsole");
                };
                container.querySelector(".newfile").onclick = function(){
                    commands.exec("newfile");
                };
            });

            /***** Method *****/

            /***** Lifecycle *****/

            plugin.on("load", function(){

            });
            plugin.on("documentLoad", function(e) {
                var doc = e.doc;
                var tab = doc.tab;

                function setTheme(e) {
                    var isDark = e.theme == "dark";
                    var backgroundColor = defaults[e.theme];
                    if (!backgroundColor) return;
                    tab.backgroundColor = backgroundColor;
                    if (isDark) tab.classList.add("dark");
                    else tab.classList.remove("dark");
                }

                layout.on("themeChange", setTheme, doc);
                setTheme({ theme: settings.get("user/general/@skin") });

                doc.title = "Welcome",
                doc.meta.welcome = true;
            });

            /***** Register and define API *****/

            plugin.freezePublicAPI({

            });

            plugin.load(null, "welcome");

            return plugin;
        }

        register(null, {
            welcome: handle
        });
    }
});<commit_msg>Revert "Make auto-save an experimental feature"<commit_after>define(function(require, exports, module) {
    main.consumes = [
        "Editor", "editors", "ui", "tabManager", "settings", "Form",
        "commands", "fs", "ace", "layout", "c9", "menus"
    ];
    main.provides = ["welcome"];
    return main;

     /*
        Change Ace Theme
            - Get List from Ace
    */

    function main(options, imports, register) {
        var Editor = imports.Editor;
        var editors = imports.editors;
        var ui = imports.ui;
        var fs = imports.fs;
        var c9 = imports.c9;
        var ace = imports.ace;
        var commands = imports.commands;
        var layout = imports.layout;
        var tabManager = imports.tabManager;
        var settings = imports.settings;
        var Form = imports.Form;
        var menus = imports.menus;

        var join = require("path").join;

        /***** Initialization *****/

        var handle = editors.register("welcome", "URL Viewer", Welcome, []);
        var intro;

        var WELCOME_INTRO = (options.intro || "").replace(/\n/g, "
");
        var OS_INTRO = "\n You can now use sudo and apt-get to manage your workspace!";

        var defaults = {
            "flat-light" : "#F8FDFF",
            "flat-dark"  : "#203947",
            "light" : "#b7c9d4",
            "light-gray" : "#b7c9d4",
            "dark"  : "#203947",
            "dark-gray"  : "#203947"
        };

        var loaded = false;
        function load() {
            if (loaded) return false;
            loaded = true;

            menus.addItemByPath("Support/Welcome Page", new ui.item({
                onclick: function(){ tabManager.openEditor("welcome", true, function(){}) }
            }), 150, handle);

            tabManager.once("ready", function(){
                settings.on("read", function(e) {
                    if (e.reset) {
                        settings.set("user/welcome/@first", true);
                        return;
                    }

                    if (!settings.getBool("user/welcome/@first")) {
                        show(function(){
                            settings.set("user/welcome/@first", true);
                        });
                    }
                }, handle);

                if (window.location.hash.match(/#openfile-(.*)/)) {
                    var file = "/" + RegExp.$1;
                    fs.exists(file, function(exists) {
                        if (!exists) return;
                        commands.exec("preview", null, {
                            path: file,
                            focus: options.focusOpenFile || false
                        });
                    });
                }
            }, handle);
        }

        var drawn = false;
        function draw() {
            if (drawn) return;
            drawn = true;

            if (options.checkOS) {
                fs.stat("~/" + c9.projectId, function(err, stat){
                    if (!err && stat.fullPath == join(c9.home, "workspace")) {
                        if (drawn)
                            intro.innerHTML = WELCOME_INTRO + OS_INTRO;
                        else
                            WELCOME_INTRO += OS_INTRO;
                    }
                });
            }

            // Insert CSS
            ui.insertCss(require("text!./style.css"),
                options.staticPrefix, handle);
        }

        handle.on("load", load);

        /***** Methods *****/

        function search(){
            var found;
            var tabs = tabManager.getTabs();
            tabs.every(function(tab) {
                if (tab.document.meta.welcome) {
                    found = tab;
                    return false;
                }
                return true;
            });
            return found;
        }

        function show(cb) {
            var tab = search();
            if (tab)
                return tabManager.focusTab(tab);

            tabManager.open({
                editorType: "welcome",
                noanim: true,
                active: true
            }, cb);
        }

        function Welcome(){
            var plugin = new Editor("Ajax.org", main.consumes, []);
            //var emit = plugin.getEmitter();

            var container;

            plugin.on("draw", function(e) {
                draw();

                // Create UI elements
                container = e.htmlNode;

                var html = require("text!./welcome.html");
                var nodes = ui.insertHtml(container, html, plugin);
                var node = nodes[0];

                intro = node.querySelector(".intro");
                intro.innerHTML = WELCOME_INTRO;

                var list = [];
                var themes = ace.themes
                for (var base in themes) {
                    if (themes[base] instanceof Array)
                        themes[base].forEach(function (n) {
                            var themeprop = Object.keys(n)[0];
                            list.push({ caption: themeprop, value: n[themeprop] });
                        });
                    else
                        list.push({ caption: base, value: themes[base] });
                }

                var presetClick = function(){
                    var value = this.id;

                    current.className = "preset";
                    this.className = "preset active";
                    current = this;

                    if (value != "default" && !settings.getBool("user/welcome/@switched")) {
                        setTimeout(function(){
                            var div = container.querySelector(".switched");
                            div.style.display = "block";
                            if (!apf.isMac)
                                div.innerHTML = div.innerHTML.replace(/Command/g, "Ctrl");
                            settings.set("user/welcome/@switched", true);
                        }, 500);
                    }

                    setTimeout(function(){
                        layout.setBaseLayout(value);
                    });
                };

                var presets = node.querySelectorAll(".preset");
                var current, preset;
                for (var i = 0; i < presets.length; i++) {
                    if (~(preset = presets[i]).className.indexOf("active"))
                        current = preset;

                    preset.addEventListener("click", presetClick);
                }

                var form = new Form({
                    edge: "3 3 8 3",
                    rowheight: 40,
                    colwidth: 150,
                    style: "padding:10px;",
                    form: [
                        {
                            title : "Main Theme",
                            type  : "dropdown",
                            path  : "user/general/@skin",
                            width : 190,
                            items : [
                                { caption: "Cloud9 Classic Dark Theme", value: "dark" },
                                { caption: "Cloud9 Flat Light Theme", value: "flat-light" }
                                // { caption: "Cloud9 Flat Dark Theme", value: "flat-dark" }
                            ],
                            position : 100
                        },
                        {
                            title: "Split Layout",
                            type: "dropdown",
                            width: 190,
                            defaultValue: "nosplit",
                            onchange: function(e) {
                                commands.exec(e.value);
                            },
                            items: [
                                { caption: "No Split", value: "nosplit" },
                                { caption: "Two Vertical Split", value: "twovsplit" },
                                { caption: "Two Horizontal Split", value: "twohsplit" },
                                { caption: "Four Split", value: "foursplit" },
                                { caption: "Three Split (Left)", value: "threeleft" },
                                { caption: "Three Split (Right)", value: "threeright" }
                            ],
                            position: 150
                        },
                        {
                            title: "Editor (Ace) Theme",
                            type: "dropdown",
                            path: "user/ace/@theme",
                            width: 190,
                            onchange: function(e) {
                                ace.setTheme(e.value);
                            },
                            items: list,
                            position: 180
                        },
                        {
                            title: "Keyboard Mode",
                            type: "dropdown",
                            path: "user/ace/@keyboardmode",
                            width: 190,
                            items: [
                                { caption: "Default", value: "default" },
                                { caption: "Vim", value: "vim" },
                                { caption: "Emacs", value: "emacs" },
                                { caption: "Sublime", value: "sublime" }
                            ],
                            position: 190
                        },
                        {
                            title: "Soft Tabs",
                            type: "checked-spinner",
                            checkboxPath: "project/ace/@useSoftTabs",
                            path: "project/ace/@tabSize",
                            min: "1",
                            max: "64",
                            width: "50",
                            position: 200
                        },
                        {
                            title: "Enable Auto-Save",
                            type: "checkbox",
                            position: 300,
                            path: "user/general/@autosave"
                        }
                    ]
                });

                form.attachTo(container.querySelector(".configure .form"));

                container.querySelector(".configure .more").onclick = function(){
                    commands.exec("openpreferences");
                };
                container.querySelector(".openterminal").onclick = function(){
                    tabManager.openEditor("terminal", true, function(){});
                };
                container.querySelector(".openconsole").onclick = function(){
                    commands.exec("toggleconsole");
                };
                container.querySelector(".newfile").onclick = function(){
                    commands.exec("newfile");
                };
            });

            /***** Method *****/

            /***** Lifecycle *****/

            plugin.on("load", function(){

            });
            plugin.on("documentLoad", function(e) {
                var doc = e.doc;
                var tab = doc.tab;

                function setTheme(e) {
                    var isDark = e.theme == "dark";
                    var backgroundColor = defaults[e.theme];
                    if (!backgroundColor) return;
                    tab.backgroundColor = backgroundColor;
                    if (isDark) tab.classList.add("dark");
                    else tab.classList.remove("dark");
                }

                layout.on("themeChange", setTheme, doc);
                setTheme({ theme: settings.get("user/general/@skin") });

                doc.title = "Welcome",
                doc.meta.welcome = true;
            });

            /***** Register and define API *****/

            plugin.freezePublicAPI({

            });

            plugin.load(null, "welcome");

            return plugin;
        }

        register(null, {
            welcome: handle
        });
    }
});<|endoftext|>


<commit_before>import request from 'request';
import cheerio from 'cheerio';
import Q from 'q';

class Utils {

  scrapeNYT(url) {
    const defer = Q.defer(),
        self = this;

    request(url, (error, response, html) => {
      if (!error) {
        const $ = cheerio.load(html),
        date = {
          prev: new Date($('#bestsellersPreviousList a span').text()).toDateString(),
          curr: new Date($('.element1 p').text()).toDateString(),
          next: new Date($('#bestsellersNextList a span').text()).toDateString()
        },
        lists = [],
        promises = [];

        $('div.bColumn div.columnGroup.first div.story').each(function (i, story) {
          if ($(this).find('h3 a').attr('href')) {
            promises.push(self.scrapeSubPage($(this).find('h3').text(), $(this).find('h3 a').attr('href'), date));
          }
        });

        Q.all(promises).then((data) => {
          defer.resolve(data);
        });
      }
    });

    return defer.promise;
  }

  scrapeSubPage(title, url, date) {
    const data = [],
    defer = Q.defer();

    request(url, (error, response, html) => {
      if (!error) {
        const $ = cheerio.load(html);

        $('table.bestSellersList tbody tr.bookDetails').each(function () {
          data.push({
            id: Math.floor(100000 + Math.random() * 900000),
            index: $(this).find('td.index').text(),
            summary: $(this).find('td.summary').text().trim()
          });
        });

        defer.resolve({ date: date, title: title, books: data });
      }
    });

    return defer.promise;
  }
}

export default Utils;
<commit_msg>cleanup, use es6 promises.<commit_after>import request from 'request';
import cheerio from 'cheerio';
import { Promise } from 'es6-promise';

export default class Utils {

  scrapeNYT(url) {
    const self = this;

    return new Promise((resolve, reject) => {
      request(url, (error, response, html) => {
        if (!error) {
          const $ = cheerio.load(html),
          date = {
            prev: new Date($('#bestsellersPreviousList a span').text()).toDateString(),
            curr: new Date($('.element1 p').text()).toDateString(),
            next: new Date($('#bestsellersNextList a span').text()).toDateString()
          },
          lists = [],
          promises = [];

          $('div.bColumn div.columnGroup.first div.story').each(function (i, story) {
            if ($(this).find('h3 a').attr('href')) {
              promises.push(self.scrapeSubPage($(this).find('h3').text(), $(this).find('h3 a').attr('href'), date));
            }
          });

          Promise.all(promises).then((data) => {
            resolve(data);
          });
        }
      });
    });
  }

  scrapeSubPage(title, url, date) {
    const data = [];

    return new Promise((resolve, reject) => {
      request(url, (error, response, html) => {
        if (!error) {
          const $ = cheerio.load(html);

          $('table.bestSellersList tbody tr.bookDetails').each(function () {
            data.push({
              id: Math.floor(100000 + Math.random() * 900000),
              index: $(this).find('td.index').text(),
              summary: $(this).find('td.summary').text().trim()
            });
          });

          resolve({ date: date, title: title, books: data });
        }
      });
    });
  }
}
<|endoftext|>


<commit_before>/*
 * i5ting-jquery-tab
 * https://github.com/i5ting/i5ting-jquery-tab
 *
 * Copyright (c) 2013 i5ting
 * Licensed under the MIT license.
 */

(function($) {

	function _tab_list_li_hover(){
	    //tab 头hover时间处理
	  	$(".i5ting_tab_list li").hover(function(){
	  		$(this).addClass("over");
	  	},function(){
	  		$(this).removeClass("over");
	  	});
	}

	function _init_tab_content_btn(container){
		$(container).find('.i5ting_tab_list #up_sen').click(function(){
			if($(this).hasClass('up')){
				$(this).removeClass('up').addClass('down');
				$(container).find('.i5ting_tab_content').hide();
			}else{
				$(this).removeClass('down').addClass('up');
				$(container).find('.i5ting_tab_content').show();
			}
		});
	}

	function _init_tab_content(container,opts){
		$(container).find('.i5ting_tab_content div.i5ting_tab_content_item')
			.eq( opts.current_tab_index ).show().siblings().hide();
	}

	function _add_tab_content_btn_if_need(container,opts){
		if(opts.is_tab_content_btn_show){
			$('',
				{
				 	'href'  : "javascript:;",
					'class' : 'up',
					'id'    : 'up_sen'
				}
			).prependTo( $(container).find('.i5ting_tab_list') );

			console.log('already show tab control button'+ $(container));
		}

		_init_tab_content_btn( $(container) );
	}

	function _add_fix_height_if_need(container,opts){
		if(opts.fix_height){
			$(container).find('.i5ting_tab_content_item').css('height',opts.fix_height);
		}
	}

	function _add_class_for_last_li(container,opts){
		$(container).find('.i5ting_tab_list li').last().addClass('last');
	}

	function event_process(container,opts){
		switch(opts.event_trigger_type){
			case 'hover':
				//tab 头点击处理
				$(container).find('.i5ting_tab_list li').hover(function(e){
				 	opts.current_tab_index = $(this).prevAll().length;
					// console.log(opts.current_index );
					$(this).addClass('current').siblings().removeClass('current');
					console.log($('.i5ting_tab_content div.i5ting_tab_content_item'));
					$(container).find('.i5ting_tab_content div.i5ting_tab_content_item').eq( opts.current_tab_index ).show().siblings().hide();
				});

				break;

			 case 'click':
				 //tab 头点击处理
				 $(container).find('.i5ting_tab_list li').click(function(e){
					 opts.current_tab_index = $(this).prevAll().length;
					 // console.log(opts.current_index );
					 $(this).addClass('current').siblings().removeClass('current');
					 $(container).find('.i5ting_tab_content div.i5ting_tab_content_item').eq( opts.current_tab_index ).show().siblings().hide();
			     });
				 break;

			  default:
				  break;
		 }
	}

	function init_tab_ui(container,opts){
		_tab_list_li_hover();

		//如果is_tab_content_btn_show=yes，则显示上下箭头
		_add_tab_content_btn_if_need($(container),opts);

		_add_class_for_last_li($(container),opts);

		_init_tab_content($(container),opts);

		// 如果配置项里有opts.fix_height
		_add_fix_height_if_need($(container),opts);
	}

  	$.fn.i5ting_jquery_tab = function(options) {
	  	var opts = $.extend({}, $.fn.i5ting_jquery_tab.defaults, options);

	    return this.each(function() {
			init_tab_ui($(this) ,opts);
			event_process($(this) ,opts);
	  	});

  	};//end i5ting_jquery_tab

  	$.fn.i5ting_jquery_tab.defaults = {
		current_tab_index: 0, //从0开始
		is_tab_content_btn_show: true, /*显示上下箭头*/
    	// fix_height :'200px',  /*如果没有配置fix_height，则自适应*/
		event_trigger_type:'click', /*现在支持2种类型：  click | hover */
  	};

}(jQuery));
<commit_msg>refact<commit_after>/*
 * i5ting-jquery-tab
 * https://github.com/i5ting/i5ting-jquery-tab
 *
 * Copyright (c) 2013 i5ting
 * Licensed under the MIT license.
 */

(function($) {

	/**
	 * private: 给li绑定hover效果
	 */
	function _tab_list_li_hover(){
	    //tab 头hover时间处理
	  	$(".i5ting_tab_list li").hover(function(){
	  		$(this).addClass("over");
	  	},function(){
	  		$(this).removeClass("over");
	  	});
	}

	/**
	 * private: 初始化tab content
	 */
	function _init_tab_content(container,opts){
		$(container).find('.i5ting_tab_content div.i5ting_tab_content_item')
			.eq( opts.current_tab_index ).show().siblings().hide();
	}

	/**
	 * private: 如果is_tab_content_btn_show=yes，则显示上下箭头
	 */
	function _add_tab_content_btn_if_need(container,opts){
		if(opts.is_tab_content_btn_show){
			$('',
				{
				 	'href'  : "javascript:;",
					'class' : 'up',
					'id'    : 'up_sen'
				}
			).prependTo( $(container).find('.i5ting_tab_list') );

			$(container).find('.i5ting_tab_list #up_sen').click(function(){
				if($(this).hasClass('up')){
					$(this).removeClass('up').addClass('down');
					$(container).find('.i5ting_tab_content').hide();
				}else{
					$(this).removeClass('down').addClass('up');
					$(container).find('.i5ting_tab_content').show();
				}
			});
		}
	}

	/**
	 * private: 给tab header的最后一个里增加样式，fixed
	 */
	function _add_fix_height_if_need(container,opts){
		if(opts.fix_height){
			$(container).find('.i5ting_tab_content_item').css('height',opts.fix_height);
		}
	}

	/**
	 * private:
	 */
	function _add_class_for_last_li(container,opts){
		$(container).find('.i5ting_tab_list li').last().addClass('last');
	}

	/**
	 * private: 应用配置，完成对应效果
	 */
	function _apply_render_with_config(container,opts){
		// 如果配置项里有opts.fix_height
		_add_fix_height_if_need($(container),opts);
	}

	/**
	 * public: 事件处理
	 */
	function event_process(container,opts){
		switch(opts.event_trigger_type){
			case 'hover':
				//tab 头点击处理
				$(container).find('.i5ting_tab_list li').hover(function(e){
				 	opts.current_tab_index = $(this).prevAll().length;
					// console.log(opts.current_index );
					$(this).addClass('current').siblings().removeClass('current');
					console.log($('.i5ting_tab_content div.i5ting_tab_content_item'));
					$(container).find('.i5ting_tab_content div.i5ting_tab_content_item').eq( opts.current_tab_index ).show().siblings().hide();
				});

				break;

			 case 'click':
				 //tab 头点击处理
				 $(container).find('.i5ting_tab_list li').click(function(e){
					 opts.current_tab_index = $(this).prevAll().length;
					 // console.log(opts.current_index );
					 $(this).addClass('current').siblings().removeClass('current');
					 $(container).find('.i5ting_tab_content div.i5ting_tab_content_item').eq( opts.current_tab_index ).show().siblings().hide();
			     });
				 break;

			  default:
				  break;
		 }
	}

	/**
	 * public: 初始化tab ui界面
	 */
	function init_tab_ui(container,opts){
		// 给li绑定hover效果
		_tab_list_li_hover();

		// 如果is_tab_content_btn_show=yes，则显示上下箭头
		_add_tab_content_btn_if_need($(container),opts);

		// 给tab header的最后一个里增加样式，fixed
		_add_class_for_last_li($(container),opts);

		// 初始化tab content
		_init_tab_content($(container),opts);

		// 应用配置，完成对应效果
		_apply_render_with_config($(container),opts);
	}

	/**
	 * 插件
	 */
  	$.fn.i5ting_jquery_tab = function(options) {
	  	var opts = $.extend({}, $.fn.i5ting_jquery_tab.defaults, options);

	    return this.each(function() {
			init_tab_ui($(this) ,opts);
			event_process($(this) ,opts);
	  	});

  	};//end i5ting_jquery_tab

	/**
	 * 插件默认项
	 */
  	$.fn.i5ting_jquery_tab.defaults = {
		current_tab_index: 0, //从0开始
		is_tab_content_btn_show: true, /*显示上下箭头*/
    	// fix_height :'200px',  /*如果没有配置fix_height，则自适应*/
		event_trigger_type:'click', /*现在支持2种类型：  click | hover */
  	};

}(jQuery));
<|endoftext|>