<commit_before>Rickshaw.namespace('Rickshaw.Graph.HoverDetail');

Rickshaw.Graph.HoverDetail = Rickshaw.Class.create({

	initialize: function(args) {

		var graph = this.graph = args.graph;

		this.xFormatter = args.xFormatter || function(x) {
			return new Date( x * 1000 ).toUTCString();
		};

		this.yFormatter = args.yFormatter || function(y) {
			return y === null ? y : y.toFixed(2);
		};

		var element = this.element = document.createElement('div');
		element.className = 'detail';

		this.visible = true;
		graph.element.appendChild(element);

		this.lastEvent = null;
		this._addListeners();

		this.onShow = args.onShow;
		this.onHide = args.onHide;
		this.onRender = args.onRender;

		this.formatter = args.formatter || this.formatter;

	},

	formatter: function(series, x, y, formattedX, formattedY, d) {
		return series.name + ': ' + formattedY;
	},

	update: function(e) {

		e = e || this.lastEvent;
		if (!e) return;
		this.lastEvent = e;

		if (!e.target.nodeName.match(/^(path|svg|rect|circle)$/)) return;

		var graph = this.graph;

		var eventX = e.offsetX || e.layerX;
		var eventY = e.offsetY || e.layerY;

		var j = 0;
		var points = [];
		var nearestPoint;

		this.graph.series.active().forEach( function(series) {

			var data = this.graph.stackedData[j++];

			if (!data.length)
				return;

			var domainX = graph.x.invert(eventX);

			var domainIndexScale = d3.scale.linear()
				.domain([data[0].x, data.slice(-1)[0].x])
				.range([0, data.length - 1]);

			var approximateIndex = Math.round(domainIndexScale(domainX));
			if (approximateIndex == data.length - 1) approximateIndex--;

			var dataIndex = Math.min(approximateIndex || 0, data.length - 1);

			for (var i = approximateIndex; i < data.length - 1;) {

				if (!data[i] || !data[i + 1]) break;

				if (data[i].x <= domainX && data[i + 1].x > domainX) {
					dataIndex = Math.abs(domainX - data[i].x) < Math.abs(domainX - data[i + 1].x) ? i : i + 1;
					break;
				}

				if (data[i + 1].x <= domainX) { i++ } else { i-- }
			}

			if (dataIndex < 0) dataIndex = 0;
			var value = data[dataIndex];

			var distance = Math.sqrt(
				Math.pow(Math.abs(graph.x(value.x) - eventX), 2) +
				Math.pow(Math.abs(graph.y(value.y + value.y0) - eventY), 2)
			);

			var xFormatter = series.xFormatter || this.xFormatter;
			var yFormatter = series.yFormatter || this.yFormatter;

			var point = {
				formattedXValue: xFormatter(value.x),
				formattedYValue: yFormatter(series.scale ? series.scale.invert(value.y) : value.y),
				series: series,
				value: value,
				distance: distance,
				order: j,
				name: series.name
			};

			if (!nearestPoint || distance < nearestPoint.distance) {
				nearestPoint = point;
			}

			points.push(point);

		}, this );

		if (!nearestPoint)
			return;

		nearestPoint.active = true;

		var domainX = nearestPoint.value.x;
		var formattedXValue = nearestPoint.formattedXValue;

		this.element.innerHTML = '';
		this.element.style.left = graph.x(domainX) + 'px';

		this.visible && this.render( {
			points: points,
			detail: points, // for backwards compatibility
			mouseX: eventX,
			mouseY: eventY,
			formattedXValue: formattedXValue,
			domainX: domainX
		} );
	},

	hide: function() {
		this.visible = false;
		this.element.classList.add('inactive');

		if (typeof this.onHide == 'function') {
			this.onHide();
		}
	},

	show: function() {
		this.visible = true;
		this.element.classList.remove('inactive');

		if (typeof this.onShow == 'function') {
			this.onShow();
		}
	},

	render: function(args) {

		var graph = this.graph;
		var points = args.points;
		var point = points.filter( function(p) { return p.active } ).shift();

		if (point.value.y === null) return;

		var formattedXValue = point.formattedXValue;
		var formattedYValue = point.formattedYValue;

		this.element.innerHTML = '';
		this.element.style.left = graph.x(point.value.x) + 'px';

		var xLabel = document.createElement('div');

		xLabel.className = 'x_label';
		xLabel.innerHTML = formattedXValue;
		this.element.appendChild(xLabel);

		var item = document.createElement('div');

		item.className = 'item';

		// invert the scale if this series displays using a scale
		var series = point.series;
		var actualY = series.scale ? series.scale.invert(point.value.y) : point.value.y;

		item.innerHTML = this.formatter(series, point.value.x, actualY, formattedXValue, formattedYValue, point);
		item.style.top = this.graph.y(point.value.y0 + point.value.y) + 'px';

		this.element.appendChild(item);

		var dot = document.createElement('div');

		dot.className = 'dot';
		dot.style.top = item.style.top;
		dot.style.borderColor = series.color;

		this.element.appendChild(dot);

		if (point.active) {
			item.classList.add('active');
			dot.classList.add('active');
		}

		// Assume left alignment until the element has been displayed and
		// bounding box calculations are possible.
		var alignables = [xLabel, item];
		alignables.forEach(function(el) {
			el.classList.add('left');
		});

		this.show();

		// If left-alignment results in any error, try right-alignment.
		var leftAlignError = this._calcLayoutError(alignables);
		if (leftAlignError > 0) {
			alignables.forEach(function(el) {
				el.classList.remove('left');
				el.classList.add('right');
			});

			// If right-alignment is worse than left alignment, switch back.
			var rightAlignError = this._calcLayoutError(alignables);
			if (rightAlignError > leftAlignError) {
				alignables.forEach(function(el) {
					el.classList.remove('right');
					el.classList.add('left');
				});
			}
		}

		if (typeof this.onRender == 'function') {
			this.onRender(args);
		}
	},

	_calcLayoutError: function(alignables) {
		// Layout error is calculated as the number of linear pixels by which
		// an alignable extends past the left or right edge of the parent.
		var parentRect = this.element.parentNode.getBoundingClientRect();

		var error = 0;
		var alignRight = alignables.forEach(function(el) {
			var rect = el.getBoundingClientRect();
			if (!rect.width) {
				return;
			}

			if (rect.right > parentRect.right) {
				error += rect.right - parentRect.right;
			}

			if (rect.left < parentRect.left) {
				error += parentRect.left - rect.left;
			}
		});
		return error;
	},

	_addListeners: function() {

		this.graph.element.addEventListener(
			'mousemove',
			function(e) {
				this.visible = true;
				this.update(e);
			}.bind(this),
			false
		);

		this.graph.onUpdate( function() { this.update() }.bind(this) );

		this.graph.element.addEventListener(
			'mouseout',
			function(e) {
				if (e.relatedTarget && !(e.relatedTarget.compareDocumentPosition(this.graph.element) & Node.DOCUMENT_POSITION_CONTAINS)) {
					this.hide();
				}
			}.bind(this),
			false
		);
	}
});
<commit_msg>Added method to remove HoverDetail's mouse listeners.<commit_after>Rickshaw.namespace('Rickshaw.Graph.HoverDetail');

Rickshaw.Graph.HoverDetail = Rickshaw.Class.create({

	initialize: function(args) {

		var graph = this.graph = args.graph;

		this.xFormatter = args.xFormatter || function(x) {
			return new Date( x * 1000 ).toUTCString();
		};

		this.yFormatter = args.yFormatter || function(y) {
			return y === null ? y : y.toFixed(2);
		};

		var element = this.element = document.createElement('div');
		element.className = 'detail';

		this.visible = true;
		graph.element.appendChild(element);

		this.lastEvent = null;
		this._addListeners();

		this.onShow = args.onShow;
		this.onHide = args.onHide;
		this.onRender = args.onRender;

		this.formatter = args.formatter || this.formatter;

	},

	formatter: function(series, x, y, formattedX, formattedY, d) {
		return series.name + ': ' + formattedY;
	},

	update: function(e) {

		e = e || this.lastEvent;
		if (!e) return;
		this.lastEvent = e;

		if (!e.target.nodeName.match(/^(path|svg|rect|circle)$/)) return;

		var graph = this.graph;

		var eventX = e.offsetX || e.layerX;
		var eventY = e.offsetY || e.layerY;

		var j = 0;
		var points = [];
		var nearestPoint;

		this.graph.series.active().forEach( function(series) {

			var data = this.graph.stackedData[j++];

			if (!data.length)
				return;

			var domainX = graph.x.invert(eventX);

			var domainIndexScale = d3.scale.linear()
				.domain([data[0].x, data.slice(-1)[0].x])
				.range([0, data.length - 1]);

			var approximateIndex = Math.round(domainIndexScale(domainX));
			if (approximateIndex == data.length - 1) approximateIndex--;

			var dataIndex = Math.min(approximateIndex || 0, data.length - 1);

			for (var i = approximateIndex; i < data.length - 1;) {

				if (!data[i] || !data[i + 1]) break;

				if (data[i].x <= domainX && data[i + 1].x > domainX) {
					dataIndex = Math.abs(domainX - data[i].x) < Math.abs(domainX - data[i + 1].x) ? i : i + 1;
					break;
				}

				if (data[i + 1].x <= domainX) { i++ } else { i-- }
			}

			if (dataIndex < 0) dataIndex = 0;
			var value = data[dataIndex];

			var distance = Math.sqrt(
				Math.pow(Math.abs(graph.x(value.x) - eventX), 2) +
				Math.pow(Math.abs(graph.y(value.y + value.y0) - eventY), 2)
			);

			var xFormatter = series.xFormatter || this.xFormatter;
			var yFormatter = series.yFormatter || this.yFormatter;

			var point = {
				formattedXValue: xFormatter(value.x),
				formattedYValue: yFormatter(series.scale ? series.scale.invert(value.y) : value.y),
				series: series,
				value: value,
				distance: distance,
				order: j,
				name: series.name
			};

			if (!nearestPoint || distance < nearestPoint.distance) {
				nearestPoint = point;
			}

			points.push(point);

		}, this );

		if (!nearestPoint)
			return;

		nearestPoint.active = true;

		var domainX = nearestPoint.value.x;
		var formattedXValue = nearestPoint.formattedXValue;

		this.element.innerHTML = '';
		this.element.style.left = graph.x(domainX) + 'px';

		this.visible && this.render( {
			points: points,
			detail: points, // for backwards compatibility
			mouseX: eventX,
			mouseY: eventY,
			formattedXValue: formattedXValue,
			domainX: domainX
		} );
	},

	hide: function() {
		this.visible = false;
		this.element.classList.add('inactive');

		if (typeof this.onHide == 'function') {
			this.onHide();
		}
	},

	show: function() {
		this.visible = true;
		this.element.classList.remove('inactive');

		if (typeof this.onShow == 'function') {
			this.onShow();
		}
	},

	render: function(args) {

		var graph = this.graph;
		var points = args.points;
		var point = points.filter( function(p) { return p.active } ).shift();

		if (point.value.y === null) return;

		var formattedXValue = point.formattedXValue;
		var formattedYValue = point.formattedYValue;

		this.element.innerHTML = '';
		this.element.style.left = graph.x(point.value.x) + 'px';

		var xLabel = document.createElement('div');

		xLabel.className = 'x_label';
		xLabel.innerHTML = formattedXValue;
		this.element.appendChild(xLabel);

		var item = document.createElement('div');

		item.className = 'item';

		// invert the scale if this series displays using a scale
		var series = point.series;
		var actualY = series.scale ? series.scale.invert(point.value.y) : point.value.y;

		item.innerHTML = this.formatter(series, point.value.x, actualY, formattedXValue, formattedYValue, point);
		item.style.top = this.graph.y(point.value.y0 + point.value.y) + 'px';

		this.element.appendChild(item);

		var dot = document.createElement('div');

		dot.className = 'dot';
		dot.style.top = item.style.top;
		dot.style.borderColor = series.color;

		this.element.appendChild(dot);

		if (point.active) {
			item.classList.add('active');
			dot.classList.add('active');
		}

		// Assume left alignment until the element has been displayed and
		// bounding box calculations are possible.
		var alignables = [xLabel, item];
		alignables.forEach(function(el) {
			el.classList.add('left');
		});

		this.show();

		// If left-alignment results in any error, try right-alignment.
		var leftAlignError = this._calcLayoutError(alignables);
		if (leftAlignError > 0) {
			alignables.forEach(function(el) {
				el.classList.remove('left');
				el.classList.add('right');
			});

			// If right-alignment is worse than left alignment, switch back.
			var rightAlignError = this._calcLayoutError(alignables);
			if (rightAlignError > leftAlignError) {
				alignables.forEach(function(el) {
					el.classList.remove('right');
					el.classList.add('left');
				});
			}
		}

		if (typeof this.onRender == 'function') {
			this.onRender(args);
		}
	},

	_calcLayoutError: function(alignables) {
		// Layout error is calculated as the number of linear pixels by which
		// an alignable extends past the left or right edge of the parent.
		var parentRect = this.element.parentNode.getBoundingClientRect();

		var error = 0;
		var alignRight = alignables.forEach(function(el) {
			var rect = el.getBoundingClientRect();
			if (!rect.width) {
				return;
			}

			if (rect.right > parentRect.right) {
				error += rect.right - parentRect.right;
			}

			if (rect.left < parentRect.left) {
				error += parentRect.left - rect.left;
			}
		});
		return error;
	},

	_addListeners: function() {

		// Keep reference for later removal.
		this.mousemoveListener = function(e) {
			this.visible = true;
			this.update(e);
		}.bind(this);

		// Add listener.
		this.graph.element.addEventListener(
			'mousemove',
			this.mousemoveListener,
			false
		);

		this.graph.onUpdate( function() { this.update() }.bind(this) );

		// Keep reference for later removal.
		this.mouseoutListener = function(e) {
			if (e.relatedTarget && !(e.relatedTarget.compareDocumentPosition(this.graph.element) & Node.DOCUMENT_POSITION_CONTAINS)) {
				this.hide();
			}
		}.bind(this);

		// Add listener.
		this.graph.element.addEventListener(
			'mouseout',
			this.mouseoutListener,
			false
		);
	},

	_removeListeners: function() {
		if (this.mousemoveListener) {
			this.graph.element.removeEventListener('mousemove', this.mousemoveListener, false);
		}
		if (this.mouseoutListener) {
			this.graph.element.removeEventListener('mouseout', this.mouseoutListener, false);
		}
	}
});
<|endoftext|>


<commit_before>import { h, render, Component } from 'preact';

const getLinkBehavior = (baseUrl, hashString, setExpanded) => {
  let href;
  let onClick;

  baseUrl = `/${baseUrl}`;

  if (baseUrl === location.pathname) {
    href = `#${hashString}`;
    onClick = ev => {
      ev && ev.preventDefault();
      window.scrollTo(href)();
      setExpanded(false);
    };
  } else {
    href = `${baseUrl}#${hashString}`;
    onClick = null;
  }

  return { href, onClick };
};

const SearchResult = props => {
  const { result, location, selected, setExpanded } = props;
  const { item, matches } = result;
  const { baseUrl, hashString, title, body, tags } = item;

  const { href, onClick } = getLinkBehavior(baseUrl, hashString, setExpanded);

  return (
    <a
      className={`search-result${selected ? " selected" : ""}`}
      href={`${href}`}
      onClick={onClick}
    >
      {title}
      {tags}

  );
};

// Result data-structure:
//
// {
//   "item": {
//     "baseUrl": "docs/extensibility.html",
//     "hashString": "learn-more",
//     "title": "Extensibility: Learn More",
//     "body": "Learn MoreTODO reach out in Gitter channel look over the cookbook examples read through the offical plugins ",
//     "tags": []
//   },
//   "matches": [
//     {
//       "indices": [
//         [
//           2,
//           2
//         ],
//         [
//           5,
//           5
//         ],
//         [
//           11,
//           11
//         ]
//       ],
//       "key": "title"
//     },
//     {
//       "indices": [
//         [
//           10,
//           10
//         ],
//         [
//           23,
//           23
//         ],
//         [
//           30,
//           31
//         ],
//         [
//           53,
//           53
//         ],
//         [
//           73,
//           73
//         ],
//         [
//           80,
//           80
//         ],
//         [
//           88,
//           88
//         ],
//         [
//           93,
//           94
//         ],
//         [
//           106,
//           106
//         ]
//       ],
//       "key": "body"
//     }
//   ]
// }

const SearchResults = props => {
  const { results, selectedIdx, setExpanded } = props;
  if (!results.length) { return null; }

  return (

      {results.map((result, idx) =>
        <SearchResult
          result={result}
          location={document.location}
          selected={idx === selectedIdx}
          setExpanded={setExpanded}
        />
      )}

  );
};

class SearchBar extends Component {
  constructor (props) {
    super(props);
  }

  componentDidMount () {
    this.base.querySelector(".search-bar").focus();
  }

  onKeyDown (ev) {
    const {
      selectedIdx,
      lastResultIdx,
      setSelectedIdx
    } = this.props;

    if (ev.keyCode === 13) {
      // ENTER
      ev.preventDefault();
      this.gotoSelected();
    } else if (ev.keyCode === 38) {
      // UP
      ev.preventDefault();
      if (selectedIdx > 0) {
        setSelectedIdx(selectedIdx - 1);
      }
    } else if (ev.keyCode === 40) {
      // DOWN
      ev.preventDefault();
      if (selectedIdx === null) {
        setSelectedIdx(0);
      } else if (selectedIdx < lastResultIdx) {
        setSelectedIdx(selectedIdx + 1);
      }
    }
  }

  gotoSelected () {
    const { selectedIdx, results, setExpanded } = this.props;
    const result = results[selectedIdx];
    const { item } = result;
    const { baseUrl, hashString } = item;
    const { onClick, href } = getLinkBehavior(baseUrl, hashString, setExpanded);

    if (onClick) { return onClick(); }

    document.location = href;
  }

  render () {
    const {
      results,
      setSearchText,
      selectedIdx,
      setExpanded
    } = this.props;

    return (

        <input
          type="text"
          className={`search-bar${results.length ? " has-results" : ""}`}
          onKeyDown={ev => this.onKeyDown(ev)}
          onInput={ev => setSearchText(ev.target.value)}
          placeholder="Enter your search term..."
        />
        { results.length ?
          <SearchResults
            results={results}
            selectedIdx={selectedIdx}
            setExpanded={setExpanded}
          /> :
          null }

    );
  }
}

export default class SearchWidget extends Component {
  constructor (props) {
    super(props);
    this.index = props.index;
    this.state = {
      expanded: false,
      selectedIdx: null,
      results: []
    };
    this.onEscapeKeyDown = this.onEscapeKeyDown.bind(this);
  }

  componentDidMount () {
    // Register global key-press listener for ESC.
    document.body.addEventListener("keydown", this.onEscapeKeyDown);
  }

  componentWillUnmount () {
    document.body.removeEventListener("keydown", this.onEscapeKeyDown);
  }

  onEscapeKeyDown (ev) {
    if (ev.keyCode === 27) {
      this.setState({ expanded: !this.state.expanded })
    }
  }

  setSearchText (searchText) {
    const results = searchText.length ?
      this.index.search(searchText) :
      [];

    this.setState({
      results,
      selectedIdx: null
    });
  }

  render () {
    const { expanded, results } = this.state;

    return (

         { this.setState({ expanded: !expanded }) }}
        />
        { expanded ?
          <SearchBar
            selectedIdx={this.state.selectedIdx}
            lastResultIdx={this.state.results.length - 1}
            setSelectedIdx={val => this.setState({ selectedIdx: val })}
            setExpanded={val => this.setState({ expanded: val })}
            results={results}
            setSearchText={this.setSearchText.bind(this)}
          /> :
          null }

    );
  }
}
<commit_msg>Auto-select the previous search when search bar opens again.<commit_after>import { h, render, Component } from 'preact';

const getLinkBehavior = (baseUrl, hashString, setExpanded) => {
  let href;
  let onClick;

  baseUrl = `/${baseUrl}`;

  if (baseUrl === location.pathname) {
    href = `#${hashString}`;
    onClick = ev => {
      ev && ev.preventDefault();
      window.scrollTo(href)();
      setExpanded(false);
    };
  } else {
    href = `${baseUrl}#${hashString}`;
    onClick = null;
  }

  return { href, onClick };
};

const SearchResult = props => {
  const { result, location, selected, setExpanded } = props;
  const { item, matches } = result;
  const { baseUrl, hashString, title, body, tags } = item;

  const { href, onClick } = getLinkBehavior(baseUrl, hashString, setExpanded);

  return (
    <a
      className={`search-result${selected ? " selected" : ""}`}
      href={`${href}`}
      onClick={onClick}
    >
      {title}
      {tags}

  );
};

// Result data-structure:
//
// {
//   "item": {
//     "baseUrl": "docs/extensibility.html",
//     "hashString": "learn-more",
//     "title": "Extensibility: Learn More",
//     "body": "Learn MoreTODO reach out in Gitter channel look over the cookbook examples read through the offical plugins ",
//     "tags": []
//   },
//   "matches": [
//     {
//       "indices": [
//         [
//           2,
//           2
//         ],
//         [
//           5,
//           5
//         ],
//         [
//           11,
//           11
//         ]
//       ],
//       "key": "title"
//     },
//     {
//       "indices": [
//         [
//           10,
//           10
//         ],
//         [
//           23,
//           23
//         ],
//         [
//           30,
//           31
//         ],
//         [
//           53,
//           53
//         ],
//         [
//           73,
//           73
//         ],
//         [
//           80,
//           80
//         ],
//         [
//           88,
//           88
//         ],
//         [
//           93,
//           94
//         ],
//         [
//           106,
//           106
//         ]
//       ],
//       "key": "body"
//     }
//   ]
// }

const SearchResults = props => {
  const { results, selectedIdx, setExpanded } = props;
  if (!results.length) { return null; }

  return (

      {results.map((result, idx) =>
        <SearchResult
          result={result}
          location={document.location}
          selected={idx === selectedIdx}
          setExpanded={setExpanded}
        />
      )}

  );
};

class SearchBar extends Component {
  constructor (props) {
    super(props);
  }

  componentDidMount () {
    const el = this.base.querySelector(".search-bar");
    el.select();
    el.focus();
  }

  onKeyDown (ev) {
    const {
      selectedIdx,
      lastResultIdx,
      setSelectedIdx
    } = this.props;

    if (ev.keyCode === 13) {
      // ENTER
      ev.preventDefault();
      this.gotoSelected();
    } else if (ev.keyCode === 38) {
      // UP
      ev.preventDefault();
      if (selectedIdx > 0) {
        setSelectedIdx(selectedIdx - 1);
      }
    } else if (ev.keyCode === 40) {
      // DOWN
      ev.preventDefault();
      if (selectedIdx === null) {
        setSelectedIdx(0);
      } else if (selectedIdx < lastResultIdx) {
        setSelectedIdx(selectedIdx + 1);
      }
    }
  }

  gotoSelected () {
    const { selectedIdx, results, setExpanded } = this.props;
    const result = results[selectedIdx];
    const { item } = result;
    const { baseUrl, hashString } = item;
    const { onClick, href } = getLinkBehavior(baseUrl, hashString, setExpanded);

    if (onClick) { return onClick(); }

    document.location = href;
  }

  render () {
    const {
      results,
      setSearchText,
      selectedIdx,
      setExpanded
    } = this.props;

    return (

        <input
          type="text"
          className={`search-bar${results.length ? " has-results" : ""}`}
          onKeyDown={ev => this.onKeyDown(ev)}
          onInput={ev => setSearchText(ev.target.value)}
          placeholder="Enter your search term..."
        />
        { results.length ?
          <SearchResults
            results={results}
            selectedIdx={selectedIdx}
            setExpanded={setExpanded}
          /> :
          null }

    );
  }
}

export default class SearchWidget extends Component {
  constructor (props) {
    super(props);
    this.index = props.index;
    this.state = {
      expanded: false,
      selectedIdx: null,
      results: []
    };
    this.onEscapeKeyDown = this.onEscapeKeyDown.bind(this);
  }

  componentDidMount () {
    // Register global key-press listener for ESC.
    document.body.addEventListener("keydown", this.onEscapeKeyDown);
  }

  componentWillUnmount () {
    document.body.removeEventListener("keydown", this.onEscapeKeyDown);
  }

  onEscapeKeyDown (ev) {
    if (ev.keyCode === 27) {
      this.setState({ expanded: !this.state.expanded })
    }
  }

  setSearchText (searchText) {
    const results = searchText.length ?
      this.index.search(searchText) :
      [];

    this.setState({
      results,
      selectedIdx: null
    });
  }

  render () {
    const { expanded, results } = this.state;

    return (

         { this.setState({ expanded: !expanded }) }}
        />
        { expanded ?
          <SearchBar
            selectedIdx={this.state.selectedIdx}
            lastResultIdx={this.state.results.length - 1}
            setSelectedIdx={val => this.setState({ selectedIdx: val })}
            setExpanded={val => this.setState({ expanded: val })}
            results={results}
            setSearchText={this.setSearchText.bind(this)}
          /> :
          null }

    );
  }
}
<|endoftext|>


<commit_before>angular.module('app.services').factory('notifications', function ($window, device, notificationsData, onNotificationGCM, onNotificationAPN, serverConfig) {

  $window.onNotificationGCM = onNotificationGCM;
  $window.onNotificationAPN = onNotificationAPN;

  function register() {
    var push = notificationsData.pushPlugin.init({
      "android": {"senderID": serverConfig.senderID},
      "ios": {"alert": "true", "badge": "true", "sound": "true"}
    });
    push.on('registration', function(data) {
      if(device.isAndroid){
        notificationsData.regId = data.registrationId;
      }else{
        notificationsData.token = data.registrationId;
      }
    });

    push.on('notification', function(data) {
      if(device.isAndroid){
        onNotificationGCM(data);
      }else{
        onNotificationAPN(data);
      }
    });
  }

  return {
    init: function () {
      notificationsData.pushPlugin = $window.PushNotification;
      register();
    }
  };

}).factory('onNotificationGCM', function (notificationsData) {

  return function (data) {
    notificationsData.$apply(function () {
      (notificationsData.actions[data.additionalData.type] || angular.noop)(!data.additionalData.foreground, data.additionalData.entity);
      if (data.additionalData.foreground) {
        navigator.notification.beep(1);
        navigator.notification.vibrate(500);
      }
    });
  };

}).factory('onNotificationAPN', function (notificationsData) {

  return function (data) {
    var entity = angular.isString(data.additionalData.entity) ? angular.fromJson(data.additionalData.entity) : data.additionalData.entity;
    var target = angular.isString(data.additionalData.target) ? angular.fromJson(data.additionalData.target) : data.additionalData.target;
    notificationsData.$apply(function () {
      (notificationsData.actions[data.additionalData.type] || angular.noop)(!Number(data.additionalData.foreground), entity || target);
      if (Number(data.additionalData.foreground)) {
        navigator.notification.beep(1);
        navigator.notification.vibrate(500);
      }
    });
  };

}).factory('notificationsData',
        function ($rootScope, session, serverConfig, $http, activity, groupsInvites, follows, announcements, invites,
                homeCtrlParams, socialActivityTabManager, navigateTo, socialActivity) {

          var scope = $rootScope.$new();

          function loadBadgeData() {
            homeCtrlParams.loaded = false;
            $rootScope.$broadcast('notification.received');
            announcements.load();
            groupsInvites.load();
            follows.load();
          }

          function question(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/questions/' + entity.id);
            }
          }

          function petition(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/question/leader-petition/' + entity.id);
            }
          }

          function leaderNews(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/questions/news/' + entity.id);
            }
          }

          function paymentRequest(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/payment-polls/payment-request/' + entity.id);
            }
          }

          function leaderEvent(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/leader-event/' + entity.id);
            }
          }

          function crowdfundingPaymentRequest(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/payment-polls/crowdfunding-payment-request/' + entity.id);
            }
          }

          scope.actions = {
            activity: function (redirect) {
              if (redirect) {
                $rootScope.path('/main');
              }
            },
            group_petition: petition,
            group_question: question,
            representative_question: question,
            representative_petition: petition,
            superuser_question: question,
            group_news: leaderNews,
            representative_news: leaderNews,
            group_payment_request: paymentRequest,
            representative_payment_request: paymentRequest,
            group_payment_request_crowdfunding: crowdfundingPaymentRequest,
            representative_payment_request_crowdfunding: crowdfundingPaymentRequest,
            group_event: leaderEvent,
            representative_event: leaderEvent,
            micro_petition: function (redirect, entity) {
              loadBadgeData();
              if (redirect && entity) {
                $rootScope.path('/petition/' + entity.id);
              }
            },
            announcement: function (redirect) {
              if ('/messages' === $rootScope.path() || redirect) {
                announcements.setViewed();
              }

              announcements.load().then(function () {
                if (redirect) {
                  $rootScope.path('/messages');
                }
              });
            },
            invite: function (redirect) {
              if (redirect && '/messages' !== $rootScope.path()) {
                $rootScope.path('/messages');
              } else {
                groupsInvites.load();
                invites.load();
              }
            },
            permissions: function (redirect, entity) {
              if (redirect) {
                $rootScope.path('/group/' + entity.id);
              }
            },
            influence: function (redirect) {
              if (redirect) {
                socialActivityTabManager.getState().reload = true;
                $rootScope.path('/influences/notifications');
              } else {
                follows.load().then(socialActivity.load);
              }
            },
            social_activity: function (redirect, entity) {
              if (redirect) {
                socialActivityTabManager.getState().reload = true;
                if (entity.target) {
                  navigateTo('target', entity.target.type, entity.target.id);
                } else {
                  $rootScope.path('/influences/notifications');
                }
              } else {
                socialActivity.load();
              }
            }
          };

          scope.$watch('regId', function (newValue, oldValue) {
            if (session.getToken() && newValue && newValue !== oldValue) {
              registerAndroid();
            }
          });

          scope.$watch('token', function (newValue, oldValue) {
            if (session.getToken() && newValue && newValue !== oldValue) {
              registerIOS();
            }
          });

          scope.$watch(session.getToken, function (newValue, oldValue) {
            if (newValue && scope.regId) {
              registerAndroid();
            }
            if (newValue && scope.token) {
              registerIOS();
            }
          });

          scope.$watch(activity.getNewActivities, function (newValue) {
            if (scope.pushPlugin && scope.token && newValue) {
              scope.pushPlugin.setApplicationIconBadgeNumber(angular.noop, newValue.length);
            }
          });

          scope.registerIOS = function (token) {
            scope.token = token;
          };

          function getEndpoints() {
            return $http.get(serverConfig.url + '/api/endpoints/').then(function (response) {
              return response.data;
            });
          }

          function handleDevice(data) {
            getEndpoints().then(function (endpoints) {
              if (!_(endpoints).chain().pluck('token').include(data.token).value()) {
                $http.post(serverConfig.url + '/api/endpoints/', data).catch(function (response) {
                  console.error('Cannot add notification token');
                  if (response.data) {
                    console.error(angular.toJson(response.data));
                  }
                });
              }
            });
          }

          function registerIOS() {
            handleDevice({type: 'ios', token: scope.token});
          }

          function registerAndroid() {
            handleDevice({type: 'android', token: scope.regId});
          }

          return scope;

        });<commit_msg>change the part that recieves push notifications<commit_after>angular.module('app.services').factory('notifications', function ($window, device, notificationsData, onNotificationGCM, onNotificationAPN, serverConfig) {

  $window.onNotificationGCM = onNotificationGCM;
  $window.onNotificationAPN = onNotificationAPN;

  function register() {
    var push = notificationsData.pushPlugin.init({
      "android": {"senderID": serverConfig.senderID},
      "ios": {"alert": "true", "badge": "true", "sound": "true"}
    });
    push.on('registration', function(data) {
      notificationsData.$apply(function(){
        if(device.isAndroid){
          notificationsData.regId = data.registrationId;
        }else{
          notificationsData.token = data.registrationId;
        }
      })
    });

    push.on('notification', function(data) {
      if(device.isAndroid){
        onNotificationGCM(data);
      }else{
        onNotificationAPN(data);
      }
    });
  }

  return {
    init: function () {
      notificationsData.pushPlugin = $window.PushNotification;
      register();
    }
  };

}).factory('onNotificationGCM', function (notificationsData) {

  return function (data) {
    notificationsData.$apply(function () {
      (notificationsData.actions[data.additionalData.type] || angular.noop)(!data.additionalData.foreground, data.additionalData.entity);
      if (data.additionalData.foreground) {
        navigator.notification.beep(1);
        navigator.notification.vibrate(500);
      }
    });
  };

}).factory('onNotificationAPN', function (notificationsData) {

  return function (data) {
    var entity = angular.isString(data.additionalData.entity) ? angular.fromJson(data.additionalData.entity) : data.additionalData.entity;
    var target = angular.isString(data.additionalData.target) ? angular.fromJson(data.additionalData.target) : data.additionalData.target;
    notificationsData.$apply(function () {
      (notificationsData.actions[data.additionalData.type] || angular.noop)(!Number(data.additionalData.foreground), entity || target);
      if (Number(data.additionalData.foreground)) {
        navigator.notification.beep(1);
        navigator.notification.vibrate(500);
      }
    });
  };

}).factory('notificationsData',
        function ($rootScope, session, serverConfig, $http, activity, groupsInvites, follows, announcements, invites,
                homeCtrlParams, socialActivityTabManager, navigateTo, socialActivity) {

          var scope = $rootScope.$new();

          function loadBadgeData() {
            homeCtrlParams.loaded = false;
            $rootScope.$broadcast('notification.received');
            announcements.load();
            groupsInvites.load();
            follows.load();
          }

          function question(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/questions/' + entity.id);
            }
          }

          function petition(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/question/leader-petition/' + entity.id);
            }
          }

          function leaderNews(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/questions/news/' + entity.id);
            }
          }

          function paymentRequest(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/payment-polls/payment-request/' + entity.id);
            }
          }

          function leaderEvent(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/leader-event/' + entity.id);
            }
          }

          function crowdfundingPaymentRequest(redirect, entity) {
            loadBadgeData();
            if (redirect && entity) {
              $rootScope.path('/payment-polls/crowdfunding-payment-request/' + entity.id);
            }
          }

          scope.actions = {
            activity: function (redirect) {
              if (redirect) {
                $rootScope.path('/main');
              }
            },
            group_petition: petition,
            group_question: question,
            representative_question: question,
            representative_petition: petition,
            superuser_question: question,
            group_news: leaderNews,
            representative_news: leaderNews,
            group_payment_request: paymentRequest,
            representative_payment_request: paymentRequest,
            group_payment_request_crowdfunding: crowdfundingPaymentRequest,
            representative_payment_request_crowdfunding: crowdfundingPaymentRequest,
            group_event: leaderEvent,
            representative_event: leaderEvent,
            micro_petition: function (redirect, entity) {
              loadBadgeData();
              if (redirect && entity) {
                $rootScope.path('/petition/' + entity.id);
              }
            },
            announcement: function (redirect) {
              if ('/messages' === $rootScope.path() || redirect) {
                announcements.setViewed();
              }

              announcements.load().then(function () {
                if (redirect) {
                  $rootScope.path('/messages');
                }
              });
            },
            invite: function (redirect) {
              if (redirect && '/messages' !== $rootScope.path()) {
                $rootScope.path('/messages');
              } else {
                groupsInvites.load();
                invites.load();
              }
            },
            permissions: function (redirect, entity) {
              if (redirect) {
                $rootScope.path('/group/' + entity.id);
              }
            },
            influence: function (redirect) {
              if (redirect) {
                socialActivityTabManager.getState().reload = true;
                $rootScope.path('/influences/notifications');
              } else {
                follows.load().then(socialActivity.load);
              }
            },
            social_activity: function (redirect, entity) {
              if (redirect) {
                socialActivityTabManager.getState().reload = true;
                if (entity.target) {
                  navigateTo('target', entity.target.type, entity.target.id);
                } else {
                  $rootScope.path('/influences/notifications');
                }
              } else {
                socialActivity.load();
              }
            }
          };

          scope.$watch('regId', function (newValue, oldValue) {
            if (session.getToken() && newValue && newValue !== oldValue) {
              registerAndroid();
            }
          });

          scope.$watch('token', function (newValue, oldValue) {
            alert('watch===='+scope.token);
            if (session.getToken() && newValue && newValue !== oldValue) {
              registerIOS();
            }
          });

          scope.$watch(session.getToken, function (newValue, oldValue) {
            if (newValue && scope.regId) {
              registerAndroid();
            }
            if (newValue && scope.token) {
              registerIOS();
            }
          });

          scope.$watch(activity.getNewActivities, function (newValue) {
            if (scope.pushPlugin && scope.token && newValue) {
              scope.pushPlugin.setApplicationIconBadgeNumber(angular.noop, newValue.length);
            }
          });

          function getEndpoints() {
            return $http.get(serverConfig.url + '/api/endpoints/').then(function (response) {
              return response.data;
            });
          }

          function handleDevice(data) {
            getEndpoints().then(function (endpoints) {
              if (!_(endpoints).chain().pluck('token').include(data.token).value()) {
                $http.post(serverConfig.url + '/api/endpoints/', data).catch(function (response) {
                  console.error('Cannot add notification token');
                  if (response.data) {
                    console.error(angular.toJson(response.data));
                  }
                });
              }
            });
          }

          function registerIOS() {
            handleDevice({type: 'ios', token: scope.token});
          }

          function registerAndroid() {
            handleDevice({type: 'android', token: scope.regId});
          }

          return scope;

        });<|endoftext|>


<commit_before>/*

  A component to standardize handling of "simple" form data submitted via AJAX.  This component:

  1.  Performs an initial render of the component using the template specified in `options.templates.initial`
  2.  Binds a form submission that sends the `model` data using the options specified in `options.ajax`
  3.  If the AJAX request returns an error or the result contains a "falsy" `ok` variable:
      a)  Any previous "success" messages are cleared.
      b)  the error is transformed using the rules found in `options.rules.error`
      c)  the results are passed along to the `error` subcomponent for display.
  4.  If the AJAX request is successful:
      a)  Any previous "error" messages are cleared.
      b)  The results are transformed using the rules found in `options.rules.success` and passed to the `success` subcomponent for display.
      c)  The results are transformed using the rules found in `options.rules.model`, and applied to the model using the change applier.

 */
/* global fluid, jQuery */
(function ($) {
    "use strict";
    var gpii = fluid.registerNamespace("gpii");
    fluid.registerNamespace("gpii.templates.hb.client.templateFormControl");

    gpii.templates.hb.client.templateFormControl.checkRequirements = function (that) {
        var errors = [];

        if (!that.options.templates || !that.options.templates.initial || !that.options.templates.error || !that.options.templates.success) {
            errors.push("You have not configured any templates for use with this component.");
        }

        else if (!that.options.ajaxUrl) {
            errors.push("Your AJAX options do not include a URL.  The AJAX request will not be able to complete as expected.");
        }

        if (!that.options.rules || !that.options.rules.error || !that.options.rules.success || !that.options.rules.submission) {
            errors.push("You have not configured the required rules regarding how to handle AJAX responses.");
        }

        if (errors.length > 0) {
            fluid.fail(errors);
        }
    };

    gpii.templates.hb.client.templateFormControl.submitForm = function (that, event) {
        event.preventDefault();

        var options = fluid.copy(that.options.ajaxOptions);

        var transformedModel = fluid.model.transformWithRules(that.model, that.options.rules.submission);

        if (options.json) {
            options.contentType = "application/json";
        }

        options.data = options.json ? JSON.stringify(transformedModel) : transformedModel;
        $.ajax(options);
    };

    // Make sure that we end up with a reasonable JSON object before we apply our rules.
    gpii.templates.hb.client.templateFormControl.jsonOrString = function (data) {
        if (typeof data === "string") {
            try {
                var jsonData = JSON.parse(data);
                return jsonData;
            }
            catch (e) {
                // Treat the data as a string message if we can't otherwise parse it.
                return { message: data };
            }
        }

        return data;
    };

    gpii.templates.hb.client.templateFormControl.handleSuccess = function (that, data) {
        var jsonData = gpii.templates.hb.client.templateFormControl.jsonOrString(data);

        // We assume that we are working with a success unless we have explicit data that suggests otherwise.
        if (jsonData.ok === undefined || jsonData.ok === null || jsonData.ok) {
            var transformedData = fluid.model.transformWithRules(jsonData, that.options.rules.model);

            // Any data that is stored in the `model` of the transformed result is used to update the component's `model`.
            if (transformedData.model) {
                Object.keys(transformedData.model).forEach(function (key) {
                    that.applier.change(key, transformedData.model[key]);
                });
            }

            // Clear out any "error" messages
            that.error.applier.change("message", null);

            // Pass along any "success" message
            var successData = fluid.model.transformWithRules(jsonData, that.options.rules.success);
            that.success.applier.change("message", successData);

            // Optionally hide the original content.
            if (that.options.hideOnSuccess) {
                that.locate("form").hide();
            }
        }
        // If the response is not OK, pass it along to be handled as an error instead.
        else {
            gpii.templates.hb.client.templateFormControl.handleError(that, data);
        }
    };

    gpii.templates.hb.client.templateFormControl.handleAjaxError = function (that, jqXHR) {
        gpii.templates.hb.client.templateFormControl.handleError(that, jqXHR.responseText);
    };

    gpii.templates.hb.client.templateFormControl.handleError = function (that, data) {
        // Clear out any "success" messages.
        that.success.applier.change("message", null);

        var jsonData = gpii.templates.hb.client.templateFormControl.jsonOrString(data);

        // Display the updated error message.
        var errorData = fluid.model.transformWithRules(jsonData, that.options.rules.error);
        that.error.applier.change("message", errorData);
    };

    gpii.templates.hb.client.templateFormControl.handleKeyPress = function (that, event) {
        if (event.keyCode === 13) { // Enter
            gpii.templates.hb.client.submitForm(that, event);
        }
    };

    fluid.defaults("gpii.templates.hb.client.templateFormControl", {
        gradeNames:    ["gpii.templates.hb.client.multiTemplateAware", "autoInit"],
        hideOnSuccess: true, // Whether to hide our form if the results are successful
        ajaxOptions: {
            url:     "{that}.options.ajaxUrl",
            success: "{that}.handleSuccess",
            error:   "{that}.handleAjaxError"
        },
        // Rules to control what (if any) feedback from successful response is displayed.
        rules: {
            model:   {}, // Rules to control what (if any) part of the response is used to update the model.
            success: {
                message: "message" // By default, assume we have been given a message to display on success.
            },
            error: {     // Rules to control how an error is parsed.
                message: "message" // By default, assume the error message can be found in a `message` element.
            },
            submission: { // Rules to control how our model is parsed before submitting to `options.ajaxOptions.url`
                "": ""    // By default, pass the model with no alterations.
            }
        },
        components: {
            success: {
                type:     "gpii.templates.hb.client.templateMessage",
                createOnEvent: "{templateFormControl}.events.onMarkupRendered",
                container: "{templateFormControl}.dom.success",
                options: {
                    template: "{templateFormControl}.options.templates.success"
                }
            },
            error: {
                type:          "gpii.templates.hb.client.templateMessage",
                createOnEvent: "{templateFormControl}.events.onMarkupRendered",
                container:     "{templateFormControl}.dom.error",
                options: {
                    template: "{templateFormControl}.options.templates.error"
                }
            }
        },
        selectors: {
            initial: "",         // The container that will be updated with template content on startup and on a full refresh.
            form:    "form",     // The form element whose submission we will control
            error:   ".error",   // The error message controlled by our sub-component
            success: ".success", // The positive feedback controlled by our sub-component
            submit:  ".submit"   // Clicking or hitting enter on our submit button will launch our AJAX request
        },
        invokers: {
            renderInitialMarkup: {
                funcName: "gpii.templates.hb.client.templateAware.renderMarkup",
                args: ["{that}", "initial", "{that}.options.templates.initial", "{that}.model", "html"]
            },
            submitForm: {
                funcName: "gpii.templates.hb.client.templateFormControl.submitForm",
                args: ["{that}", "{arguments}.0"]
            },
            handleSuccess: {
                funcName: "gpii.templates.hb.client.templateFormControl.handleSuccess",
                args: ["{that}", "{arguments}.0"]
            },
            handleAjaxError: {
                funcName: "gpii.templates.hb.client.templateFormControl.handleAjaxError",
                args: ["{that}", "{arguments}.0"]
            },
            handleKeyPress: {
                funcName: "gpii.templates.hb.client.templateFormControl.handleKeyPress",
                args: ["{that}", "{arguments}.0"]
            }
        },
        templates: {
            success: "common-success",
            error:   "common-error"
        },
        listeners: {
            "onMarkupRendered.wireControls": [
                {
                    "this": "{that}.dom.submit",
                    method: "on",
                    args:   ["keyup.handleKeyPress", "{that}.handleKeyPress"]
                },
                {
                    "this": "{that}.dom.submit",
                    method: "on",
                    args:   ["click.submitForm", "{that}.submitForm"]
                },
                {
                    "this": "{that}.dom.form",
                    method: "on",
                    args:   ["submit.submitForm", "{that}.submitForm"]
                }
            ]
        }
    });
})(jQuery);<commit_msg>GPII-1175:  Updated `templateFormControl` to make the `event` parameter optional.  This will allow for using the same function in response to model changes and other non-client events.<commit_after>/*

  A component to standardize handling of "simple" form data submitted via AJAX.  This component:

  1.  Performs an initial render of the component using the template specified in `options.templates.initial`
  2.  Binds a form submission that sends the `model` data using the options specified in `options.ajax`
  3.  If the AJAX request returns an error or the result contains a "falsy" `ok` variable:
      a)  Any previous "success" messages are cleared.
      b)  the error is transformed using the rules found in `options.rules.error`
      c)  the results are passed along to the `error` subcomponent for display.
  4.  If the AJAX request is successful:
      a)  Any previous "error" messages are cleared.
      b)  The results are transformed using the rules found in `options.rules.success` and passed to the `success` subcomponent for display.
      c)  The results are transformed using the rules found in `options.rules.model`, and applied to the model using the change applier.

 */
/* global fluid, jQuery */
(function ($) {
    "use strict";
    var gpii = fluid.registerNamespace("gpii");
    fluid.registerNamespace("gpii.templates.hb.client.templateFormControl");

    gpii.templates.hb.client.templateFormControl.checkRequirements = function (that) {
        var errors = [];

        if (!that.options.templates || !that.options.templates.initial || !that.options.templates.error || !that.options.templates.success) {
            errors.push("You have not configured any templates for use with this component.");
        }

        else if (!that.options.ajaxUrl) {
            errors.push("Your AJAX options do not include a URL.  The AJAX request will not be able to complete as expected.");
        }

        if (!that.options.rules || !that.options.rules.error || !that.options.rules.success || !that.options.rules.submission) {
            errors.push("You have not configured the required rules regarding how to handle AJAX responses.");
        }

        if (errors.length > 0) {
            fluid.fail(errors);
        }
    };

    gpii.templates.hb.client.templateFormControl.submitForm = function (that, event) {
        if (event) {
            event.preventDefault();
        }

        var options = fluid.copy(that.options.ajaxOptions);

        var transformedModel = fluid.model.transformWithRules(that.model, that.options.rules.submission);

        if (options.json) {
            options.contentType = "application/json";
        }

        options.data = options.json ? JSON.stringify(transformedModel) : transformedModel;
        $.ajax(options);
    };

    // Make sure that we end up with a reasonable JSON object before we apply our rules.
    gpii.templates.hb.client.templateFormControl.jsonOrString = function (data) {
        if (typeof data === "string") {
            try {
                var jsonData = JSON.parse(data);
                return jsonData;
            }
            catch (e) {
                // Treat the data as a string message if we can't otherwise parse it.
                return { message: data };
            }
        }

        return data;
    };

    gpii.templates.hb.client.templateFormControl.handleSuccess = function (that, data) {
        var jsonData = gpii.templates.hb.client.templateFormControl.jsonOrString(data);

        // We assume that we are working with a success unless we have explicit data that suggests otherwise.
        if (jsonData.ok === undefined || jsonData.ok === null || jsonData.ok) {
            var transformedData = fluid.model.transformWithRules(jsonData, that.options.rules.model);

            // Any data that is stored in the `model` of the transformed result is used to update the component's `model`.
            if (transformedData.model) {
                Object.keys(transformedData.model).forEach(function (key) {
                    that.applier.change(key, transformedData.model[key]);
                });
            }

            // Clear out any "error" messages
            that.error.applier.change("message", null);

            // Pass along any "success" message
            var successData = fluid.model.transformWithRules(jsonData, that.options.rules.success);
            that.success.applier.change("message", successData);

            // Optionally hide the original content.
            if (that.options.hideOnSuccess) {
                that.locate("form").hide();
            }
        }
        // If the response is not OK, pass it along to be handled as an error instead.
        else {
            gpii.templates.hb.client.templateFormControl.handleError(that, data);
        }
    };

    gpii.templates.hb.client.templateFormControl.handleAjaxError = function (that, jqXHR) {
        gpii.templates.hb.client.templateFormControl.handleError(that, jqXHR.responseText);
    };

    gpii.templates.hb.client.templateFormControl.handleError = function (that, data) {
        // Clear out any "success" messages.
        that.success.applier.change("message", null);

        var jsonData = gpii.templates.hb.client.templateFormControl.jsonOrString(data);

        // Display the updated error message.
        var errorData = fluid.model.transformWithRules(jsonData, that.options.rules.error);
        that.error.applier.change("message", errorData);
    };

    gpii.templates.hb.client.templateFormControl.handleKeyPress = function (that, event) {
        if (event.keyCode === 13) { // Enter
            gpii.templates.hb.client.submitForm(that, event);
        }
    };

    fluid.defaults("gpii.templates.hb.client.templateFormControl", {
        gradeNames:    ["gpii.templates.hb.client.multiTemplateAware", "autoInit"],
        hideOnSuccess: true, // Whether to hide our form if the results are successful
        ajaxOptions: {
            url:     "{that}.options.ajaxUrl",
            success: "{that}.handleSuccess",
            error:   "{that}.handleAjaxError"
        },
        // Rules to control what (if any) feedback from successful response is displayed.
        rules: {
            model:   {}, // Rules to control what (if any) part of the response is used to update the model.
            success: {
                message: "message" // By default, assume we have been given a message to display on success.
            },
            error: {     // Rules to control how an error is parsed.
                message: "message" // By default, assume the error message can be found in a `message` element.
            },
            submission: { // Rules to control how our model is parsed before submitting to `options.ajaxOptions.url`
                "": ""    // By default, pass the model with no alterations.
            }
        },
        components: {
            success: {
                type:     "gpii.templates.hb.client.templateMessage",
                createOnEvent: "{templateFormControl}.events.onMarkupRendered",
                container: "{templateFormControl}.dom.success",
                options: {
                    template: "{templateFormControl}.options.templates.success"
                }
            },
            error: {
                type:          "gpii.templates.hb.client.templateMessage",
                createOnEvent: "{templateFormControl}.events.onMarkupRendered",
                container:     "{templateFormControl}.dom.error",
                options: {
                    template: "{templateFormControl}.options.templates.error"
                }
            }
        },
        selectors: {
            initial: "",         // The container that will be updated with template content on startup and on a full refresh.
            form:    "form",     // The form element whose submission we will control
            error:   ".error",   // The error message controlled by our sub-component
            success: ".success", // The positive feedback controlled by our sub-component
            submit:  ".submit"   // Clicking or hitting enter on our submit button will launch our AJAX request
        },
        invokers: {
            renderInitialMarkup: {
                funcName: "gpii.templates.hb.client.templateAware.renderMarkup",
                args: ["{that}", "initial", "{that}.options.templates.initial", "{that}.model", "html"]
            },
            submitForm: {
                funcName: "gpii.templates.hb.client.templateFormControl.submitForm",
                args: ["{that}", "{arguments}.0"]
            },
            handleSuccess: {
                funcName: "gpii.templates.hb.client.templateFormControl.handleSuccess",
                args: ["{that}", "{arguments}.0"]
            },
            handleAjaxError: {
                funcName: "gpii.templates.hb.client.templateFormControl.handleAjaxError",
                args: ["{that}", "{arguments}.0"]
            },
            handleKeyPress: {
                funcName: "gpii.templates.hb.client.templateFormControl.handleKeyPress",
                args: ["{that}", "{arguments}.0"]
            }
        },
        templates: {
            success: "common-success",
            error:   "common-error"
        },
        listeners: {
            "onMarkupRendered.wireControls": [
                {
                    "this": "{that}.dom.submit",
                    method: "on",
                    args:   ["keyup.handleKeyPress", "{that}.handleKeyPress"]
                },
                {
                    "this": "{that}.dom.submit",
                    method: "on",
                    args:   ["click.submitForm", "{that}.submitForm"]
                },
                {
                    "this": "{that}.dom.form",
                    method: "on",
                    args:   ["submit.submitForm", "{that}.submitForm"]
                }
            ]
        }
    });
})(jQuery);<|endoftext|>


<commit_before>import React from 'react';

class DescriptionList extends React.Component {
  getHeadline() {
    let {headline, headlineClassName} = this.props;
    if (!headline) {
      return null;
    }

    // Wrap in headline element and classes
    return (

        {headline}

    );
  }

  getItems() {
    let {dtClassName, ddClassName, hash, renderKeys} = this.props;

    return Object.keys(hash).map((key, index) => {
      let value = hash[key];

      // Check whether we are trying to render an object that is not a
      // React component
      if (typeof value === 'object' && !Array.isArray(value) &&
        value !== null && !React.isValidElement(value)) {

        return (
          <DescriptionList
            {...this.props}
            hash={value}
            key={index}
            headline={key} />
        );
      }

      if (typeof value === 'boolean') {
        value = value.toString();
      }

      // Check if we need to render a component in the dt
      if (renderKeys.hasOwnProperty(key)) {
        key = renderKeys[key];
      }

      return (

{key}

{value}

      );
    });
  }

  render() {
    let {hash, className} = this.props;
    if (!hash || Object.keys(hash).length === 0) {
      return null;
    }

    return (

        {this.getHeadline()}
        {this.getItems()}

    );
  }
}

DescriptionList.defaultProps = {
  className: '',
  ddClassName: 'column-9 text-overflow-break-word',
  dtClassName: 'column-3 text-mute text-overflow-break-word',
  headlineClassName: 'inverse flush-top',
  key: '',
  renderKeys: {}
};

DescriptionList.propTypes = {
  className: React.PropTypes.string,
  ddClassName: React.PropTypes.string,
  dtClassName: React.PropTypes.string,
  headlineClassName: React.PropTypes.string,
  headline: React.PropTypes.node,
  hash: React.PropTypes.object,
  key: React.PropTypes.string,
  // Optional object with keys consisting of keys in `props.hash` to be
  // replaced, and with corresponding values of the replacement to be rendered.
  renderKeys: React.PropTypes.object
};

module.exports = DescriptionList;
<commit_msg>Allow components to be rendered in DescriptionList dt<commit_after>import React from 'react';

class DescriptionList extends React.Component {
  getHeadline() {
    let {headline, headlineClassName} = this.props;
    if (!headline) {
      return null;
    }

    // Wrap in headline element and classes
    return (

        {headline}

    );
  }

  getItems() {
    let {dtClassName, ddClassName, hash, renderKeys} = this.props;

    return Object.keys(hash).map((key, index) => {
      let value = hash[key];

      // Check if we need to render a component in the dt
      if (renderKeys.includes(key)) {
        return (

{value.key}

{value.value}

        );
      }

      // Check whether we are trying to render an object that is not a
      // React component
      if (typeof value === 'object' && !Array.isArray(value) &&
        value !== null && !React.isValidElement(value)) {

        return (
          <DescriptionList
            {...this.props}
            hash={value}
            key={index}
            headline={key} />
        );
      }

      if (typeof value === 'boolean') {
        value = value.toString();
      }

      // Check if we need to render a component in the dt
      if (renderKeys.hasOwnProperty(key)) {
        key = renderKeys[key];
      }

      return (

{key}

{value}

      );
    });
  }

  render() {
    let {hash, className} = this.props;
    if (!hash || Object.keys(hash).length === 0) {
      return null;
    }

    return (

        {this.getHeadline()}
        {this.getItems()}

    );
  }
}

DescriptionList.defaultProps = {
  className: '',
  ddClassName: 'column-9 text-overflow-break-word',
  dtClassName: 'column-3 text-mute text-overflow-break-word',
  headlineClassName: 'inverse flush-top',
  key: '',
  renderKeys: {}
};

DescriptionList.propTypes = {
  className: React.PropTypes.string,
  ddClassName: React.PropTypes.string,
  dtClassName: React.PropTypes.string,
  headlineClassName: React.PropTypes.string,
  headline: React.PropTypes.node,
  hash: React.PropTypes.object,
  key: React.PropTypes.string,
  // Optional object with keys consisting of keys in `props.hash` to be
  // replaced, and with corresponding values of the replacement to be rendered.
  renderKeys: React.PropTypes.object
};

module.exports = DescriptionList;
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react'
import {
  lightboxStart,
  lightboxStop,
  lightboxSetCurrent,
} from '../actions/lightbox'

// False component to launch lightbox actions
export default class LightboxStarter extends Component {

  startLightbox(pictures, current) {
    window.scrollTo(0, 0)
    this.props.dispatch(lightboxStart(pictures, current))
  }

  componentWillMount() {
    //console.log('lb starter', this.props.pictures)
    this.startLightbox(
        this.props.pictures,
        this.props.params.lightbox
    )
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.pictures != nextProps.pictures) {
      this.startLightbox(
          nextProps.pictures,
          nextProps.params.lightbox
      )
    }
    if (this.props.params.lightbox != nextProps.params.lightbox) {
      this.props.dispatch(lightboxSetCurrent(nextProps.params.lightbox))
    }
  }

  componentWillUnmount() {
    this.props.dispatch(lightboxStop())
  }

  render () {
    return null
  }
}
<commit_msg>start working on picture views hits.<commit_after>import React, { Component, PropTypes } from 'react'
import {
  lightboxStart,
  lightboxStop,
  lightboxSetCurrent,
} from '../actions/lightbox'
import { sendHit } from '../actions/hits'

// False component to launch lightbox actions
export default class LightboxStarter extends Component {

  static sendHit(dispatch, params, ip=null) {
    // send a hit to server for this weblog post
    let data = {
      type: 'PICT',
      related_key: params.lightbox,
      ip: ip
    }

    dispatch(sendHit(data))
  }

  startLightbox(pictures, current) {
    window.scrollTo(0, 0)
    this.props.dispatch(lightboxStart(pictures, current))
  }

  componentWillMount() {
    //console.log('lb starter', this.props.pictures)
    this.startLightbox(
        this.props.pictures,
        this.props.params.lightbox
    )
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.pictures != nextProps.pictures) {
      this.startLightbox(
          nextProps.pictures,
          nextProps.params.lightbox
      )
    }
    if (this.props.params.lightbox != nextProps.params.lightbox) {
      this.props.dispatch(lightboxSetCurrent(nextProps.params.lightbox))
    }
  }

  componentWillUnmount() {
    this.props.dispatch(lightboxStop())
  }

  render () {
    return null
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import Textarea from 'react-textarea-autosize';
import {Grid, Row, Col, Panel, FormGroup, Radio} from 'react-bootstrap';
import AddTransactionStore from '../stores/AddTransactionStore';
import AddTransactionActions from '../actions/AddTransactionActions';

class AddTransaction extends React.Component {
  constructor(props) {
    super(props);
    this.state = AddTransactionStore.getState();
    this.onChange = this.onChange.bind(this);
  }

  componentDidMount() {
    AddTransactionStore.listen(this.onChange);
  }

  componentWillUnmount() {
    AddTransactionStore.unlisten(this.onChange);
  }

  onChange(state) {
    this.setState(state);
  }

  /* Returns true if obj is defined and has content */
  validObject(obj) {
    if (obj === null || obj === undefined || obj === "") return false;
    else return true;
  }

  /* Returns true if the array of transaction links contains our defined structure */
  validTransactionPayload(txp) {
    if (!this.validObject(txp)) return false;
    else {
      if (typeof txp === "string") {
        try {
          txp = JSON.parse(txp);
        } catch(e) {
          return false;
        }
      }
      return this.validObject(txp.Data);
    }
  }

  handleSubmit(event) {
    var tx = event.target.textBox.value;
    var txType = event.target.typeOptions.value;

    event.preventDefault();

    if (!tx) {
      AddTransactionActions.emptyTx();
      this.refs.nameTextField.focus();
    //} else if (!this.validTransactionPayload(tx)) {
    //  AddTransactionActions.invalidPayload();
    } else {
      AddTransactionActions.addTransaction(tx, txType);
    }
  }

  render() {
    return (
      <Grid>
        <Row>
          <Col sm={12}>
            <Panel header="Add Transaction">
              <form onSubmit={this.handleSubmit.bind(this)}>

                  Transaction
                  <Textarea type='text' maxRows={20} className='form-control' name="textBox" ref='nameTextField' autoFocus/>
                  {this.state.helpBlock}

                <FormGroup>
                  <Radio inline defaultChecked name="typeOptions" value="data">
                    Data
                  </Radio>
                  {'   '}
                  <Radio inline name="typeOptions" value="address">
                    IPFS Address
                  </Radio>
                </FormGroup>
                <button type='submit' className='btn btn-primary'>Submit</button>
              </form>
            </Panel>
          </Col>
        </Row>
      </Grid>
    );
  }
}

export default AddTransaction;<commit_msg>Styling header for adding transactions.<commit_after>import React from 'react';
import Textarea from 'react-textarea-autosize';
import {Grid, Row, Col, Panel, FormGroup, Radio} from 'react-bootstrap';
import AddTransactionStore from '../stores/AddTransactionStore';
import AddTransactionActions from '../actions/AddTransactionActions';

class AddTransaction extends React.Component {
  constructor(props) {
    super(props);
    this.state = AddTransactionStore.getState();
    this.onChange = this.onChange.bind(this);
  }

  componentDidMount() {
    AddTransactionStore.listen(this.onChange);
  }

  componentWillUnmount() {
    AddTransactionStore.unlisten(this.onChange);
  }

  onChange(state) {
    this.setState(state);
  }

  /* Returns true if obj is defined and has content */
  validObject(obj) {
    if (obj === null || obj === undefined || obj === "") return false;
    else return true;
  }

  /* Returns true if the array of transaction links contains our defined structure */
  validTransactionPayload(txp) {
    if (!this.validObject(txp)) return false;
    else {
      if (typeof txp === "string") {
        try {
          txp = JSON.parse(txp);
        } catch(e) {
          return false;
        }
      }
      return this.validObject(txp.Data);
    }
  }

  handleSubmit(event) {
    var tx = event.target.textBox.value;
    var txType = event.target.typeOptions.value;

    event.preventDefault();

    if (!tx) {
      AddTransactionActions.emptyTx();
      this.refs.nameTextField.focus();
    //} else if (!this.validTransactionPayload(tx)) {
    //  AddTransactionActions.invalidPayload();
    } else {
      AddTransactionActions.addTransaction(tx, txType);
    }
  }

  render() {
    return (
      <Grid>
        <Row>
          <Col sm={12}>
            <Panel header={(Add Transaction)}>
              <form onSubmit={this.handleSubmit.bind(this)}>

                  Transaction
                  <Textarea type='text' maxRows={20} className='form-control' name="textBox" ref='nameTextField' autoFocus/>
                  {this.state.helpBlock}

                <FormGroup>
                  <Radio inline defaultChecked name="typeOptions" value="data">
                    Data
                  </Radio>
                  {'   '}
                  <Radio inline name="typeOptions" value="address">
                    IPFS Address
                  </Radio>
                </FormGroup>
                <button type='submit' className='btn btn-primary'>Submit</button>
              </form>
            </Panel>
          </Col>
        </Row>
      </Grid>
    );
  }
}

export default AddTransaction;<|endoftext|>


<commit_before>import * as d3 from "d3-selection";
import {zoom, zoomTransform, zoomIdentity} from "d3-zoom";
import {interpolate} from "d3-interpolate";

export default function(enable) {

    this._zoom = enable;

    if (this._zoom && !this._zoomBehavior) {
        createZoomBehavior.call(this);
    }

    return this;
}

export function createZoomBehavior() {

    function zoomed() {
        g.attr('transform', d3.event.transform);
    }

    var root = this._selection;
    var svg = d3.select(root.node().querySelector("svg"));
    if (svg.size() == 0) {
        return this;
    }
    this._zoomSelection = svg;
    var extent = [0.1, 10];
    var zoomBehavior = zoom()
        .scaleExtent(extent)
        .interpolate(interpolate)
        .on("zoom", zoomed);
    this._zoomBehavior = zoomBehavior;
    var g = d3.select(svg.node().querySelector("g"));
    svg.call(zoomBehavior);
    if (!this._active) {
        translateZoomBehaviorTransform.call(this, g);
    }
    this._originalTransform = zoomTransform(svg.node());

    return this;
};

export function getTranslatedZoomTransform(selection) {

    // Get the current zoom transform for the top level svg and
    // translate it uniformly with the given selection, using the
    // difference between the translation specified in the selection's
    // data and it's saved previous translation. The selection is
    // normally the top level g element of the graph.
    var oldTranslation = this._translation;
    var newTranslation = selection.datum().translation;
    var t = zoomTransform(this._zoomSelection.node());
    if (oldTranslation) {
        t = t.translate(-oldTranslation.x, -oldTranslation.y);
    }
    if (newTranslation) {
        t = t.translate(newTranslation.x, newTranslation.y);
    }
    return t;
}

export function translateZoomBehaviorTransform(selection) {

    // Translate the current zoom transform for the top level svg
    // uniformly with the given selection, using the difference
    // between the translation specified in the selection's data and
    // it's saved previous translation. The selection is normally the
    // top level g element of the graph.
    this._zoomBehavior.transform(this._zoomSelection, getTranslatedZoomTransform.call(this, selection));

    // Save the selections's new translation.
    this._translation = selection.datum().translation;

    // Set the original zoom transform to the translation specified in
    // the selection's data.
    if (selection.datum().translation) {
        this._originalTransform = zoomIdentity.translate(selection.datum().translation.x, selection.datum().translation.y);
    }
}

export function resetZoom(transition) {

    // Reset the zoom transform to the original zoom transform.
    var selection = this._zoomSelection;
    if (transition) {
        selection = selection
            .transition(transition);
    }
    selection
        .call(this._zoomBehavior.transform, this._originalTransform);

    return this;
}
<commit_msg>Removed unused stuff<commit_after>import * as d3 from "d3-selection";
import {zoom, zoomTransform, zoomIdentity} from "d3-zoom";
import {interpolate} from "d3-interpolate";

export default function(enable) {

    this._zoom = enable;

    if (this._zoom && !this._zoomBehavior) {
        createZoomBehavior.call(this);
    }

    return this;
}

export function createZoomBehavior() {

    function zoomed() {
        g.attr('transform', d3.event.transform);
    }

    var root = this._selection;
    var svg = d3.select(root.node().querySelector("svg"));
    if (svg.size() == 0) {
        return this;
    }
    this._zoomSelection = svg;
    var extent = [0.1, 10];
    var zoomBehavior = zoom()
        .scaleExtent(extent)
        .interpolate(interpolate)
        .on("zoom", zoomed);
    this._zoomBehavior = zoomBehavior;
    var g = d3.select(svg.node().querySelector("g"));
    svg.call(zoomBehavior);
    if (!this._active) {
        translateZoomBehaviorTransform.call(this, g);
    }
    this._originalTransform = zoomTransform(svg.node());

    return this;
};

export function getTranslatedZoomTransform(selection) {

    // Get the current zoom transform for the top level svg and
    // translate it uniformly with the given selection, using the
    // difference between the translation specified in the selection's
    // data and it's saved previous translation. The selection is
    // normally the top level g element of the graph.
    var oldTranslation = this._translation;
    var newTranslation = selection.datum().translation;
    var t = zoomTransform(this._zoomSelection.node());
    if (oldTranslation) {
        t = t.translate(-oldTranslation.x, -oldTranslation.y);
    }
    if (newTranslation) {
        t = t.translate(newTranslation.x, newTranslation.y);
    }
    return t;
}

export function translateZoomBehaviorTransform(selection) {

    // Translate the current zoom transform for the top level svg
    // uniformly with the given selection, using the difference
    // between the translation specified in the selection's data and
    // it's saved previous translation. The selection is normally the
    // top level g element of the graph.
    this._zoomBehavior.transform(this._zoomSelection, getTranslatedZoomTransform.call(this, selection));

    // Save the selections's new translation.
    this._translation = selection.datum().translation;

    // Set the original zoom transform to the translation specified in
    // the selection's data.
    this._originalTransform = zoomIdentity.translate(selection.datum().translation.x, selection.datum().translation.y);
}

export function resetZoom(transition) {

    // Reset the zoom transform to the original zoom transform.
    var selection = this._zoomSelection;
    if (transition) {
        selection = selection
            .transition(transition);
    }
    selection
        .call(this._zoomBehavior.transform, this._originalTransform);

    return this;
}
<|endoftext|>


<commit_before>/**
 *@flow
 */

import React, { Component } from 'react';
import type { Node } from 'react';
import queryString from 'query-string';
import PropTypes from 'prop-types';
import { createSelector } from 'reselect';
import createCachedSelector from 're-reselect';
import shallowEqual from 'shallowequal';

type QueryContainerProps = {
  history: Object,
  children: Node
}

export default class QueryContainer extends Component<QueryContainerProps> {
  listener: ?Function;

  components: Object = {};

  initialParsedQuery: (location:Object) => Object;

  isTransitioning: boolean = false;

  namespaceGc = {}

  constructor(props: QueryContainerProps) {
    super(props);
    const searchSelector = location => location.search;
    this.initialParsedQuery =
    createSelector(searchSelector, search => queryString.parse(search));
  }

  /**
   * Handles external push events
   */
  handlePush(previousState:Object) {
    this.isTransitioning = true;
    const parsedQuery = this.initialParsedQuery(this.props.history.location);
    // we skip one cycle to support proper handling of route switches / prop updates in fromHistory
    requestAnimationFrame(() => {
      Object.keys(this.components).forEach((cmp) => {
        const { blankState, options, props, state } = this.components[cmp];
        const serialized = {};
        const nextState = Object.keys(options).reduce((initial, optionKey) => {
          const queryValue = parsedQuery[`${cmp}.${optionKey}`];
          const blankStateValue = blankState[optionKey];
          let newState = blankStateValue;
          if (queryValue !== undefined) {
            newState = options[optionKey].fromQueryString(queryValue, props);
            serialized[`${cmp}.${optionKey}`] = queryValue;
          } else if (blankStateValue !== undefined) {
            const oldValue = state[optionKey];
            if (!shallowEqual(oldValue, blankStateValue)) {
              options[optionKey].fromHistory(blankStateValue, props);
            }
          }
          initial[optionKey] = newState;
          return initial;
        }, {});
        this.components[cmp] = { ...this.components[cmp], state: nextState, serialized };
      });
      this.props.history.replace({
        ...this.props.history.location,
        state: { ...previousState, __componentState: this.currentComponentState() }
      });
      this.isTransitioning = false;
    });
  }

  handlePop(previousState:Object) {
    this.isTransitioning = true;
    Object.keys(previousState).forEach((key) => {
      if (this.components[key]) {
        Object.keys(previousState[key]).forEach((propKey) => {
          const oldValue = previousState[key][propKey];
          if (oldValue === undefined || (!shallowEqual(oldValue, this.components[key].state[propKey]))) {
            this.components[key].options[propKey].fromHistory(
              oldValue, this.components[key].props);
            // mutate current state with old value,
            // this way we can only call fromHistory where required
            this.components[key].state[propKey] = oldValue;
          }
        });
      }
    });
    this.isTransitioning = false;
  }

  componentDidMount() {
    const { history } = this.props;
    // save initial state
    history.replace(
      { ...history.location,
        state: { ...history.location.state, __componentState: this.currentComponentState(), isInitial: true }
      }
      );
    this.listener = history.listen((location, action) => {
      const historyState = location.state && location.state.__componentState;
      // react to external events
      if (action === 'PUSH' && !historyState) {
        this.handlePush(location.state);
        return;
      }
      const previousState = historyState ?
        location.state.__componentState : this.blankComponentState();
      if (action !== 'POP' || !previousState) {
        return;
      }
      this.handlePop(previousState);
    });
  }

  static childContextTypes = {
    queryManager: PropTypes.object
  };

  currentComponentState() {
    return Object.keys(this.components).reduce((initial, key) => ({
      ...initial,
      [key]: this.components[key].state
    }), {});
  }

  blankComponentState() {
    return Object.keys(this.components).reduce((initial, key) => ({
      ...initial,
      [key]: this.components[key].blankState
    }), {});
  }

  calculateQueryString() {
    return Object.keys(this.components).map((key) => {
      return queryString.stringify(this.components[key].serialized);
    }).join('&');
  }

  getChildContext() {
    return {
      queryManager: {
        pushChanges: (namespace: string, props: Object) => {
          const options = this.components[namespace].options;
          const optionsSelector = this.components[namespace].optionsSelector;
          const next = Object.keys(options).reduce((initial, key) => {
            const value = props[key];
            if (value !== undefined) {
              const nextValue = optionsSelector({ key, value }, key);
              return {
                state: { ...initial.state, [key]: value },
                serialized: {
                  ...initial.serialized,
                  ...nextValue
                }
              };
            }
            return initial;
          }, {
            state: {},
            serialized: {}
          });
          this.components[namespace] = { ...this.components[namespace], ...next };
          this.props.history.push(
            { pathname: location.pathname, search: this.calculateQueryString() },
            { __componentState: this.currentComponentState() }
          );
        },
        updateProps: (namespace:string, props:Object) => {
          this.components[namespace] = { ...this.components[namespace], props };
        },
        register: (namespace: string, options: Object, props: Object) => {
          if (this.components[namespace]) {
            this.namespaceGc[namespace] += 1;
            return this.components[namespace].state;
          }
          const keySelector = state => state.key;
          const valueSelector = state => state.value;
          const optionsSelector = createCachedSelector(keySelector, valueSelector, (key, value) => {
            return !(options[key].skip && options[key].skip(value)) ? {
              [`${namespace}.${key}`]: options[key].toQueryString(value)
            } : {};
          })((state, key) => key);
          // blank state = state without query parameters applied
          const blankState = {};
          // initial state = state with queryParameters applied (or not if not set)
          const initialState = {};
          // serialized query parameter state
          const serialized = {};
          const state = Object.keys(options).reduce((initial, key) => {
            const initialQueryValue = this.initialParsedQuery(this.props.history.location)[`${namespace}.${key}`];
            if (props[key] !== undefined) {
              blankState[key] = props[key];
              initialState[key] = props[key];
            }
            if (initialQueryValue !== undefined) {
              const value = options[key].fromQueryString(initialQueryValue, props);
              initialState[key] = value;
              serialized[`${namespace}.${key}`] = initialQueryValue;
              return { ...initial, [key]: value };
            }
            return initial;
          }, props);
          this.components[namespace] = {
            options, props, optionsSelector, blankState, state: initialState, serialized
          };
          this.namespaceGc[namespace] = 1;
          this.props.history.replace({
            ...this.props.history.location,
            state: { ...this.props.history.location.state, __componentState: this.currentComponentState() }
          });
          return state;
        },
        /* will replace the blank state with the current state */
        persistCurrentState: (namespace: string): boolean => {
          if (!this.components[namespace]) {
            return false;
          }
          this.components[namespace].blankState = this.components[namespace].state;
          return true;
        },
        /* generates the query string for the given namespace(s) */
        createQueryString: (...namespaces: Array<string>) => {
          return namespaces.filter(n => this.components[n]).map((key) => {
            return queryString.stringify(this.components[key].serialized);
          }).join('&');
        },
        unregister: (namespace:string) => {
          this.namespaceGc[namespace] -= 1;
          if (this.namespaceGc[namespace] === 0) {
            this.components[namespace].optionsSelector.clearCache();
            delete this.components[namespace];
            delete this.namespaceGc[namespace];
          }
        },
        isTransitioning: () => this.isTransitioning
      }
    };
  }

  componentWillUnmount() {
    if (this.listener) {
      this.listener();
    }
    this.components = {};
    this.isTransitioning = false;
  }

  render() {
    return React.Children.only(this.props.children);
  }
}
<commit_msg>fix(registration): overwrite registration on multiple register<commit_after>/**
 *@flow
 */

import React, { Component } from 'react';
import type { Node } from 'react';
import queryString from 'query-string';
import PropTypes from 'prop-types';
import { createSelector } from 'reselect';
import createCachedSelector from 're-reselect';
import shallowEqual from 'shallowequal';

type QueryContainerProps = {
  history: Object,
  children: Node
}

export default class QueryContainer extends Component<QueryContainerProps> {
  listener: ?Function;

  components: Object = {};

  initialParsedQuery: (location:Object) => Object;

  isTransitioning: boolean = false;

  namespaceGc = {}

  constructor(props: QueryContainerProps) {
    super(props);
    const searchSelector = location => location.search;
    this.initialParsedQuery =
    createSelector(searchSelector, search => queryString.parse(search));
  }

  /**
   * Handles external push events
   */
  handlePush(previousState:Object) {
    this.isTransitioning = true;
    const parsedQuery = this.initialParsedQuery(this.props.history.location);
    // we skip one cycle to support proper handling of route switches / prop updates in fromHistory
    requestAnimationFrame(() => {
      Object.keys(this.components).forEach((cmp) => {
        const { blankState, options, props, state } = this.components[cmp];
        const serialized = {};
        const nextState = Object.keys(options).reduce((initial, optionKey) => {
          const queryValue = parsedQuery[`${cmp}.${optionKey}`];
          const blankStateValue = blankState[optionKey];
          let newState = blankStateValue;
          if (queryValue !== undefined) {
            newState = options[optionKey].fromQueryString(queryValue, props);
            serialized[`${cmp}.${optionKey}`] = queryValue;
          } else if (blankStateValue !== undefined) {
            const oldValue = state[optionKey];
            if (!shallowEqual(oldValue, blankStateValue)) {
              options[optionKey].fromHistory(blankStateValue, props);
            }
          }
          initial[optionKey] = newState;
          return initial;
        }, {});
        this.components[cmp] = { ...this.components[cmp], state: nextState, serialized };
      });
      this.props.history.replace({
        ...this.props.history.location,
        state: { ...previousState, __componentState: this.currentComponentState() }
      });
      this.isTransitioning = false;
    });
  }

  handlePop(previousState:Object) {
    this.isTransitioning = true;
    Object.keys(previousState).forEach((key) => {
      if (this.components[key]) {
        Object.keys(previousState[key]).forEach((propKey) => {
          const oldValue = previousState[key][propKey];
          if (oldValue === undefined || (!shallowEqual(oldValue, this.components[key].state[propKey]))) {
            this.components[key].options[propKey].fromHistory(
              oldValue, this.components[key].props);
            // mutate current state with old value,
            // this way we can only call fromHistory where required
            this.components[key].state[propKey] = oldValue;
          }
        });
      }
    });
    this.isTransitioning = false;
  }

  componentDidMount() {
    const { history } = this.props;
    // save initial state
    history.replace(
      { ...history.location,
        state: { ...history.location.state, __componentState: this.currentComponentState(), isInitial: true }
      }
      );
    this.listener = history.listen((location, action) => {
      const historyState = location.state && location.state.__componentState;
      // react to external events
      if (action === 'PUSH' && !historyState) {
        this.handlePush(location.state);
        return;
      }
      const previousState = historyState ?
        location.state.__componentState : this.blankComponentState();
      if (action !== 'POP' || !previousState) {
        return;
      }
      this.handlePop(previousState);
    });
  }

  static childContextTypes = {
    queryManager: PropTypes.object
  };

  currentComponentState() {
    return Object.keys(this.components).reduce((initial, key) => ({
      ...initial,
      [key]: this.components[key].state
    }), {});
  }

  blankComponentState() {
    return Object.keys(this.components).reduce((initial, key) => ({
      ...initial,
      [key]: this.components[key].blankState
    }), {});
  }

  calculateQueryString() {
    return Object.keys(this.components).map((key) => {
      return queryString.stringify(this.components[key].serialized);
    }).join('&');
  }

  getChildContext() {
    return {
      queryManager: {
        pushChanges: (namespace: string, props: Object) => {
          const options = this.components[namespace].options;
          const optionsSelector = this.components[namespace].optionsSelector;
          const next = Object.keys(options).reduce((initial, key) => {
            const value = props[key];
            if (value !== undefined) {
              const nextValue = optionsSelector({ key, value }, key);
              return {
                state: { ...initial.state, [key]: value },
                serialized: {
                  ...initial.serialized,
                  ...nextValue
                }
              };
            }
            return initial;
          }, {
            state: {},
            serialized: {}
          });
          this.components[namespace] = { ...this.components[namespace], ...next };
          this.props.history.push(
            { pathname: location.pathname, search: this.calculateQueryString() },
            { __componentState: this.currentComponentState() }
          );
        },
        updateProps: (namespace:string, props:Object) => {
          this.components[namespace] = { ...this.components[namespace], props };
        },
        register: (namespace: string, options: Object, props: Object) => {
          if (this.components[namespace]) {
            this.namespaceGc[namespace] += 1;
          }
          const keySelector = state => state.key;
          const valueSelector = state => state.value;
          const optionsSelector = createCachedSelector(keySelector, valueSelector, (key, value) => {
            return !(options[key].skip && options[key].skip(value)) ? {
              [`${namespace}.${key}`]: options[key].toQueryString(value)
            } : {};
          })((state, key) => key);
          // blank state = state without query parameters applied
          const blankState = {};
          // initial state = state with queryParameters applied (or not if not set)
          const initialState = {};
          // serialized query parameter state
          const serialized = {};
          const state = Object.keys(options).reduce((initial, key) => {
            const initialQueryValue = this.initialParsedQuery(this.props.history.location)[`${namespace}.${key}`];
            if (props[key] !== undefined) {
              blankState[key] = props[key];
              initialState[key] = props[key];
            }
            if (initialQueryValue !== undefined) {
              const value = options[key].fromQueryString(initialQueryValue, props);
              initialState[key] = value;
              serialized[`${namespace}.${key}`] = initialQueryValue;
              return { ...initial, [key]: value };
            }
            return initial;
          }, props);
          this.components[namespace] = {
            options, props, optionsSelector, blankState, state: initialState, serialized
          };
          if(!this.namespaceGc[namespace]) {
            this.namespaceGc[namespace] = 1;
          }
          this.props.history.replace({
            ...this.props.history.location,
            state: { ...this.props.history.location.state, __componentState: this.currentComponentState() }
          });
          return state;
        },
        /* will replace the blank state with the current state */
        persistCurrentState: (namespace: string): boolean => {
          if (!this.components[namespace]) {
            return false;
          }
          this.components[namespace].blankState = this.components[namespace].state;
          return true;
        },
        /* generates the query string for the given namespace(s) */
        createQueryString: (...namespaces: Array<string>) => {
          return namespaces.filter(n => this.components[n]).map((key) => {
            return queryString.stringify(this.components[key].serialized);
          }).join('&');
        },
        unregister: (namespace:string) => {
          this.namespaceGc[namespace] -= 1;
          if (this.namespaceGc[namespace] === 0) {
            this.components[namespace].optionsSelector.clearCache();
            delete this.components[namespace];
            delete this.namespaceGc[namespace];
          }
        },
        isTransitioning: () => this.isTransitioning
      }
    };
  }

  componentWillUnmount() {
    if (this.listener) {
      this.listener();
    }
    this.components = {};
    this.isTransitioning = false;
  }

  render() {
    return React.Children.only(this.props.children);
  }
}
<|endoftext|>