<commit_before>/**
 * Binds a TinyMCE widget to <textarea> elements.
 */
angular.module('ui.tinymce', [])
  .value('uiTinymceConfig', {})
  .directive('uiTinymce', ['$rootScope', '$window', 'uiTinymceConfig', function($rootScope, $window, uiTinymceConfig) {
    uiTinymceConfig = uiTinymceConfig || {};
    var generatedIds = 0;
    return {
      priority: 10,
      require: 'ngModel',
      link: function(scope, element, attrs, ngModel) {
        if (!$window.tinymce) {
          return;
        }

        var expression, options, tinyInstance,
          updateView = function (editor) {
            if(typeof options.raw !== 'undefined' && options.raw == true) {
              ngModel.$setViewValue(editor.getContent({format: 'text'}).trim());
            } else {
              ngModel.$setViewValue(elm.val());
            }
            if (!$rootScope.$$phase) {
              scope.$apply();
            }
          };

        // generate an ID if not present
        if (!attrs.id) {
          attrs.$set('id', 'uiTinymce' + generatedIds++);
        }

        expression = {};

        angular.extend(expression, scope.$eval(attrs.uiTinymce));

        // make config'ed setup method available
        if (expression.setup) {
          var configSetup = expression.setup;
          delete expression.setup;
        }

        options = {
          // Update model when calling setContent (such as from the source editor popup)
          setup: function(ed) {
            var args;
            ed.on('init', function(args) {
              ngModel.$render();
              ngModel.$setPristine();
            });
            // Update model on button click
            ed.on('ExecCommand', function(e) {
              ed.save();
              updateView(ed);
            });
            // Update model on keypress
            ed.on('KeyUp', function(e) {
              ed.save();
              updateView(ed);
            });
            // Update model on change, i.e. copy/pasted text, plugins altering content
            ed.on('SetContent', function(e) {
              if (!e.initial && ngModel.$viewValue !== e.content) {
                ed.save();
                updateView(ed);
              }
            });
            ed.on('blur', function(e) {
              element[0].blur();
            });
            // Update model when an object has been resized (table, image)
            ed.on('ObjectResized', function(e) {
              ed.save();
              updateView(ed);
            });
            if (configSetup) {
              configSetup(ed);
            }
          },
          selector: '#' + attrs.id
        };
        // extend options with initial uiTinymceConfig and options from directive attribute value
        angular.extend(options, uiTinymceConfig, expression);
        tinymce.init(options);

        ngModel.$render = function() {
          if (!tinyInstance) {
            tinyInstance = tinymce.get(attrs.id);
          }
          if (tinyInstance) {
            tinyInstance.setContent(ngModel.$viewValue || '');
          }
        };

        scope.$on('$destroy', function() {
          if (!tinyInstance) { tinyInstance = tinymce.get(attrs.id); }
          if (tinyInstance) {
            tinyInstance.remove();
            tinyInstance = null;
          }
        });
      }
    };
  }]);
<commit_msg>Fix broken code<commit_after>/**
 * Binds a TinyMCE widget to <textarea> elements.
 */
angular.module('ui.tinymce', [])
  .value('uiTinymceConfig', {})
  .directive('uiTinymce', ['$rootScope', '$window', 'uiTinymceConfig', function($rootScope, $window, uiTinymceConfig) {
    uiTinymceConfig = uiTinymceConfig || {};
    var generatedIds = 0;
    return {
      priority: 10,
      require: 'ngModel',
      link: function(scope, element, attrs, ngModel) {
        if (!$window.tinymce) {
          return;
        }

        var expression, options, tinyInstance,
          updateView = function (editor) {
            if(typeof options.raw !== 'undefined' && options.raw === true) {
              ngModel.$setViewValue(editor.getContent({format: 'text'}).trim());
            } else {
              ngModel.$setViewValue(element.val());
            }
            if (!$rootScope.$$phase) {
              scope.$apply();
            }
          };

        // generate an ID if not present
        if (!attrs.id) {
          attrs.$set('id', 'uiTinymce' + generatedIds++);
        }

        expression = {};

        angular.extend(expression, scope.$eval(attrs.uiTinymce));

        // make config'ed setup method available
        if (expression.setup) {
          var configSetup = expression.setup;
          delete expression.setup;
        }

        options = {
          // Update model when calling setContent (such as from the source editor popup)
          setup: function(ed) {
            var args;
            ed.on('init', function(args) {
              ngModel.$render();
              ngModel.$setPristine();
            });
            // Update model on button click
            ed.on('ExecCommand', function(e) {
              ed.save();
              updateView(ed);
            });
            // Update model on keypress
            ed.on('KeyUp', function(e) {
              ed.save();
              updateView(ed);
            });
            // Update model on change, i.e. copy/pasted text, plugins altering content
            ed.on('SetContent', function(e) {
              if (!e.initial && ngModel.$viewValue !== e.content) {
                ed.save();
                updateView(ed);
              }
            });
            ed.on('blur', function(e) {
              element[0].blur();
            });
            // Update model when an object has been resized (table, image)
            ed.on('ObjectResized', function(e) {
              ed.save();
              updateView(ed);
            });
            if (configSetup) {
              configSetup(ed);
            }
          },
          selector: '#' + attrs.id
        };
        // extend options with initial uiTinymceConfig and options from directive attribute value
        angular.extend(options, uiTinymceConfig, expression);
        tinymce.init(options);

        ngModel.$render = function() {
          if (!tinyInstance) {
            tinyInstance = tinymce.get(attrs.id);
          }
          if (tinyInstance) {
            tinyInstance.setContent(ngModel.$viewValue || '');
          }
        };

        scope.$on('$destroy', function() {
          if (!tinyInstance) { tinyInstance = tinymce.get(attrs.id); }
          if (tinyInstance) {
            tinyInstance.remove();
            tinyInstance = null;
          }
        });
      }
    };
  }]);
<|endoftext|>


<commit_before>import React from "react"
import ReactDOM from "react-dom"
import Bacon from "baconjs"
import {Model as Atom} from "bacon.model"
import Reify from "bacon.react"

const model = () => {
  const counterAtom = Atom(0)
  const itemsAtom = Atom([])

  const inc = () =>
    {var i = counterAtom.get()
     counterAtom.set(i+1)
     return i}

  const addItem = title =>
    itemsAtom.modify(is => is.concat({id: inc(), title, isDone: false}))

  const setItem = ({id, title, isDone}) =>
    itemsAtom.modify(is =>
    is.map(i => i.id === id ? {id, title, isDone} : i))

  const remItem = ({id}) =>
    itemsAtom.modify(is => is.filter(i => i.id !== id))

  const toggleAll = () =>
    itemsAtom.modify(is =>
    is.map(is.some(i => !i.isDone)
           ? (i => ({...i, isDone: true}))
           : (i => ({...i, isDone: false}))))

  const clean = () => itemsAtom.modify(is => is.filter(i => !i.isDone))

  return {all: itemsAtom,
          active: itemsAtom.map(is => is.filter(i => !i.isDone)),
          completed: itemsAtom.map(is => is.filter(i => i.isDone)),
          addItem, setItem, remItem, toggleAll, clean}
}

const web = m => {
  const filterAtom = Atom(m.all)
  const editingAtom = Atom(null)

  const textInput = ({text, placeholder, className, save, exit}) => {
    const textAtom = Atom(text || "")
    const Exit = _ => {textAtom.set(""); editingAtom.set(null)}
    const Save = _ => {
      const title = textAtom.get().trim()
      title !== "" && save(title)
      Exit()
    }
    return <Reify didMount={c => ReactDOM.findDOMNode(c).focus()}>
      <input className={className} type="text" placeholder={placeholder}
         onChange={e => textAtom.set(e.target.value)}
         onBlur={Save} value={textAtom}
         onKeyDown={e => e.which === 13 ? Save() :
                         e.which === 27 ? Exit() : null}/>
    </Reify>
  }

  const filterItem = (title, stream) =>

       f === stream ? "selected" : "")}
         onClick={_ => filterAtom.set(stream)}>{title}

  const todos = (editing, items) =>
    items.map(({id, title, isDone}) =>

        <input className="toggle" type="checkbox" checked={isDone}
               onChange={_ => m.setItem({id, title, isDone: !isDone})}/>
         editingAtom.set(id)}
               className="view">{title}
        <button className="destroy" onClick={_ => m.remItem({id})}/>
        {editing === id
         ? textInput({text: title, className: "edit",
                      save: title => m.setItem({id, title, isDone})})
         : }

)

  return <Reify>

todos

        {textInput({className: "new-todo", save: m.addItem,
                    placeholder: "What needs to be done?"})}

          <input className="toggle-all" onChange={m.toggleAll}
           type="checkbox" checked={m.active.map(a => a.length === 0)}/>

            {filterAtom.flatMapLatest(filter =>
             Bacon.combineWith(editingAtom, filter, todos))}

            {m.active.map(i => i.length)} {
            m.active.map(i => i.length === 1 ? "item" : "items")} left

            {filterItem("All", m.all)}
            {filterItem("Active", m.active)}
            {filterItem("Completed", m.completed)}

          {m.completed.map(completed =>
           completed.length !== 0
           ? <button className="clear-completed" onClick={m.clean}>
               Clear completed {completed.length}</button>
           : )}

Double-click to edit a todo

  </Reify>
}

ReactDOM.render(web(model()), document.getElementById("app"))
<commit_msg>Persistence.<commit_after>import React from "react"
import ReactDOM from "react-dom"
import Bacon from "baconjs"
import {Model as Atom} from "bacon.model"
import Reify from "bacon.react"

const normalizeIds = (all) =>
  (i => all.map(item => ({...item, id: i++})))(0)

const model = (initialRaw) => {
  const initial = normalizeIds(initialRaw || [])

  const itemsAtom = Atom(initial)
  const counterAtom = Atom(initial.length)

  const inc = () =>
    {var i = counterAtom.get()
     counterAtom.set(i+1)
     return i}

  const addItem = title =>
    itemsAtom.modify(is => is.concat({id: inc(), title, isDone: false}))

  const setItem = ({id, title, isDone}) =>
    itemsAtom.modify(is =>
    is.map(i => i.id === id ? {id, title, isDone} : i))

  const remItem = ({id}) =>
    itemsAtom.modify(is => is.filter(i => i.id !== id))

  const toggleAll = () =>
    itemsAtom.modify(is =>
    is.map(is.some(i => !i.isDone)
           ? (i => ({...i, isDone: true}))
           : (i => ({...i, isDone: false}))))

  const clean = () => itemsAtom.modify(is => is.filter(i => !i.isDone))

  return {all: itemsAtom,
          active: itemsAtom.map(is => is.filter(i => !i.isDone)),
          completed: itemsAtom.map(is => is.filter(i => i.isDone)),
          addItem, setItem, remItem, toggleAll, clean}
}

const web = m => {
  const filterAtom = Atom(m.all)
  const editingAtom = Atom(null)

  const textInput = ({text, placeholder, className, save, exit}) => {
    const textAtom = Atom(text || "")
    const Exit = _ => {textAtom.set(""); editingAtom.set(null)}
    const Save = _ => {
      const title = textAtom.get().trim()
      title !== "" && save(title)
      Exit()
    }
    return <Reify didMount={c => ReactDOM.findDOMNode(c).focus()}>
      <input className={className} type="text" placeholder={placeholder}
         onChange={e => textAtom.set(e.target.value)}
         onBlur={Save} value={textAtom}
         onKeyDown={e => e.which === 13 ? Save() :
                         e.which === 27 ? Exit() : null}/>
    </Reify>
  }

  const filterItem = (title, stream) =>

       f === stream ? "selected" : "")}
         onClick={_ => filterAtom.set(stream)}>{title}

  const todos = (editing, items) =>
    items.map(({id, title, isDone}) =>

        <input className="toggle" type="checkbox" checked={isDone}
               onChange={_ => m.setItem({id, title, isDone: !isDone})}/>
         editingAtom.set(id)}
               className="view">{title}
        <button className="destroy" onClick={_ => m.remItem({id})}/>
        {editing === id
         ? textInput({text: title, className: "edit",
                      save: title => m.setItem({id, title, isDone})})
         : }

)

  return <Reify>

todos

        {textInput({className: "new-todo", save: m.addItem,
                    placeholder: "What needs to be done?"})}

          <input className="toggle-all" onChange={m.toggleAll}
           type="checkbox" checked={m.active.map(a => a.length === 0)}/>

            {filterAtom.flatMapLatest(filter =>
             Bacon.combineWith(editingAtom, filter, todos))}

            {m.active.map(i => i.length)} {
            m.active.map(i => i.length === 1 ? "item" : "items")} left

            {filterItem("All", m.all)}
            {filterItem("Active", m.active)}
            {filterItem("Completed", m.completed)}

          {m.completed.map(completed =>
           completed.length !== 0
           ? <button className="clear-completed" onClick={m.clean}>
               Clear completed {completed.length}</button>
           : )}

Double-click to edit a todo

  </Reify>
}

const storeKey = "react.bacon-todos"

const m = model(JSON.parse(localStorage.getItem(storeKey) || "[]"))
m.all.onValue(is => localStorage.setItem(storeKey, JSON.stringify(is)))

ReactDOM.render(web(m), document.getElementById("app"))
<|endoftext|>


<commit_before>/**
 * Created by liekkas on 16/2/19.
 */
import React, { PropTypes } from 'react'
import { ECharts } from '../../components'
import { getInitLineBarChart } from '../../components/ECharts/initOptions'
class Home extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      foo: 'bar',
    }
  }

  render() {
    const { foo } = this.props
    return (

        <ECharts config={getInitLineBarChart('bar')}/>

    )
  }
}

Home.propTypes = {
  foo: PropTypes.string.isRequired,
}
Home.defaultProps = {
  foo: 'Home',
}

export default Home
<commit_msg> layout "Home" module<commit_after>/**
 * Created by liekkas on 16/2/19.
 */
import React, { PropTypes } from 'react'
import { ECharts, Panel } from '../../components'
import style from './style.scss'

class Home extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      foo: 'bar',
    }
  }

  render() {
    const { foo } = this.props
    return (

        <Panel title="全国故障投诉情况" height="300">
          <ECharts config={{type: 'map',mode: 'local',}}/>
        </Panel>

          <Panel title="工单投诉" height="300">
            <ECharts config={{type: 'bar',mode: 'local',}}/>
          </Panel>
          <Panel title="故障申报" height="300" style={{ marginRight: 0 }}>
            <ECharts config={{type: 'bar',mode: 'local',}}/>
          </Panel>

          <Panel title="工单投诉" height="300">
            <ECharts config={{type: 'pie',mode: 'local',}}/>
          </Panel>
          <Panel title="故障申报" height="300" style={{ marginRight: 0 }}>
            <ECharts config={{type: 'line',mode: 'local',}}/>
          </Panel>

    )
  }
}

Home.propTypes = {
  foo: PropTypes.string.isRequired,
}
Home.defaultProps = {
  foo: 'Home',
}

export default Home
<|endoftext|>


<commit_before>/* XEP-0313: Message Archive Management
 * Copyright (C) 2012 Kim Alvefur
 *
 * This file is MIT/X11 licensed. Please see the
 * LICENSE.txt file in the source package for more information.
 *
 * Modified by: Chris Tunbridge (github.com/Destreyf/)
 * Updated to support v0.3 of the XMPP XEP-0313 standard
 * http://xmpp.org/extensions/xep-0313.html
 *
 */
(function(){
'use strict';

Strophe.addConnectionPlugin('mam', {
    _c: null,
    _p: [ 'with', 'start', 'end' ],
    init: function (conn) {
        this._c = conn;
        Strophe.addNamespace('MAM', 'urn:xmpp:mam:0');
    },
    query: function (jid, options) {
        var _p = this._p;
        var attr = {
            type:'set',
            id:jid
        };
        var mamAttr = {xmlns: Strophe.NS.MAM};
        if (!!options['queryid']) {
            mamAttr.queryid = options['queryid'];
            delete options['queryid'];
        }
        var iq = $iq(attr).c('query', mamAttr).c('x',{xmlns:'jabber:x:data', type:'submit'});

        iq.c('field',{var:'FORM_TYPE', type:'hidden'}).c('value').t('urn:xmpp:mam:0').up().up();
        var i;
        for (i = 0; i < this._p.length; i++) {
            var pn = _p[i];
            var p = options[pn];
            delete options[pn];
            if (!!p) {
                iq.c('field',{var:pn}).c('value').t(p).up().up();
            }
        }
        iq.up();

        var onMessage = options['onMessage'];
        delete options['onMessage'];
        var onComplete = options['onComplete'];
        delete options['onComplete'];
        iq.cnode(new Strophe.RSM(options).toXML());

        this._c.addHandler(onMessage, Strophe.NS.MAM, 'message', null);
        return this._c.sendIQ(iq, onComplete);
    }
});

})();
<commit_msg>use dot notation<commit_after>/* XEP-0313: Message Archive Management
 * Copyright (C) 2012 Kim Alvefur
 *
 * This file is MIT/X11 licensed. Please see the
 * LICENSE.txt file in the source package for more information.
 *
 * Modified by: Chris Tunbridge (github.com/Destreyf/)
 * Updated to support v0.3 of the XMPP XEP-0313 standard
 * http://xmpp.org/extensions/xep-0313.html
 *
 */
(function(){
'use strict';

Strophe.addConnectionPlugin('mam', {
    _c: null,
    _p: [ 'with', 'start', 'end' ],
    init: function (conn) {
        this._c = conn;
        Strophe.addNamespace('MAM', 'urn:xmpp:mam:0');
    },
    query: function (jid, options) {
        var _p = this._p;
        var attr = {
            type:'set',
            id:jid
        };
        options = options || {};
        var mamAttr = {xmlns: Strophe.NS.MAM};
        if (!!options.queryid) {
            mamAttr.queryid = options.queryid;
            delete options.queryid;
        }
        var iq = $iq(attr).c('query', mamAttr).c('x',{xmlns:'jabber:x:data', type:'submit'});

        iq.c('field',{var:'FORM_TYPE', type:'hidden'}).c('value').t('urn:xmpp:mam:0').up().up();
        var i;
        for (i = 0; i < this._p.length; i++) {
            var pn = _p[i];
            var p = options[pn];
            delete options[pn];
            if (!!p) {
                iq.c('field',{var:pn}).c('value').t(p).up().up();
            }
        }
        iq.up();

        var onMessage = options.onMessage;
        delete options.onMessage;
        var onComplete = options.onComplete;
        delete options.onComplete;
        iq.cnode(new Strophe.RSM(options).toXML());

        this._c.addHandler(onMessage, Strophe.NS.MAM, 'message', null);
        return this._c.sendIQ(iq, onComplete);
    }
});

})();
<|endoftext|>


<commit_before>import React from "react";
import { push } from "react-router-redux";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import {
  increment,
  incrementAsync,
  decrement,
  decrementAsync
} from "../../redux/modules/counter";

import { Header } from "semantic-ui-react";

import DynamicForm from "../../components/dynamicForm";

const Home = props => (

    Home

This is a basic fixed menu template using fixed size containers.

      All ui is scemantic ui for React - just plugged it in for convenience as a temp measure

The jury is out on what if any styling lib to use.

Count: {props.count}

      <button onClick={props.increment} disabled={props.isIncrementing}>
        Increment
      </button>
      <button onClick={props.incrementAsync} disabled={props.isIncrementing}>
        Increment Async
      </button>

      <button onClick={props.decrement} disabled={props.isDecrementing}>
        Decrementing
      </button>
      <button onClick={props.decrementAsync} disabled={props.isDecrementing}>
        Decrement Async
      </button>

      <button onClick={() => props.changePage()}>Go to faq via redux</button>

    <DynamicForm {...props.form_data} />

);

const mapStateToProps = state => ({
  form_data: state.form_data,
  count: state.counter.count,
  isIncrementing: state.counter.isIncrementing,
  isDecrementing: state.counter.isDecrementing
});

const mapDispatchToProps = dispatch =>
  bindActionCreators(
    {
      increment,
      incrementAsync,
      decrement,
      decrementAsync,
      changePage: () => push("/faq")
    },
    dispatch
  );

export default connect(mapStateToProps, mapDispatchToProps)(Home);
<commit_msg>Removed counter demo from ui left reducer<commit_after>import React from "react";
import { push } from "react-router-redux";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import {
  increment,
  incrementAsync,
  decrement,
  decrementAsync
} from "../../redux/modules/counter";

import { Header } from "semantic-ui-react";

import DynamicForm from "../../components/dynamicForm";

const Home = props => (

    Home

This is a basic fixed menu template using fixed size containers.

      All ui is scemantic ui for React - just plugged it in for convenience as a temp measure

The jury is out on what if any styling lib to use.

    <DynamicForm {...props.form_data} />

);

const mapStateToProps = state => ({
  form_data: state.form_data,
  count: state.counter.count,
  isIncrementing: state.counter.isIncrementing,
  isDecrementing: state.counter.isDecrementing
});

const mapDispatchToProps = dispatch =>
  bindActionCreators(
    {
      increment,
      incrementAsync,
      decrement,
      decrementAsync,
      changePage: () => push("/faq")
    },
    dispatch
  );

export default connect(mapStateToProps, mapDispatchToProps)(Home);
<|endoftext|>


<commit_before><commit_msg>Add userEvents container<commit_after>import { connect } from 'react-redux';
import { getEvents } from '../actions/index';

const mapStateToProps = (state) => {
  return {
    events: state.events
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    getEvents: (events) => {
       dispatch(getEvents(events));
     }
  };
};

export default connect(mapStateToProps, mapDispatchToProps);
<|endoftext|>


<commit_before>import { Guard } from '../infrastructure/guard';
import { noop } from '../utility/kit';
import { guid } from '../services/guid';
import { columnFactory } from '../column/column.factory';
import { generateFactory } from '../column-list/column.list.generate';
import { columnIndexPipe } from './column.index.pipe';
import { Node } from '../node/node';
import { bend, copy } from '../node/node.service';
import { preOrderDFS } from '../node/node.service';
import { sortIndexFactory } from '../column-list/column.list.sort';

export function columnPipe(memo, context, next) {
	Guard.hasProperty(memo, 'pivot');
	Guard.hasProperty(memo, 'nodes');

	const { model } = context;
	const { pivot, nodes } = memo;
	const { head } = pivot;

	const createColumn = columnFactory(model);
	const root = new Node(createColumn('cohort', { key: '$root' }), 0);
	const addDataColumns = dataColumnsFactory(model);
	const addSelectColumn = selectColumnFactory(model);
	const addGroupColumn = groupColumnFactory(model, nodes);
	const addRowExpandColumn = rowExpandColumnFactory(model);
	const addRowIndicatorColumn = rowIndicatorColumnFactory(model);
	const addPivotColumns = pivotColumnsFactory(model);
	const addPadColumn = padColumnFactory(model);

	/*
	 * We need to invoke addDataColumns earlier that others because it setups data.columns model property
	 *
	 */
	addDataColumns(root);

	/**
	 * Control columns should be filled in reverse order because they use unshift inside.
	 */

	/*
	 * Add row expand column
	 */
	addRowExpandColumn(root);

	/*
	 * Add group column with nodes
	 *
	 */
	addGroupColumn(root);

	/*
	 * Add column with select boxes
	 * if selection unit is row
	 *
	 */
	addSelectColumn(root);

	/*
	 * Add row indicator column
	 * if rows can be dragged or resized
	 *
	 */
	addRowIndicatorColumn(root);

	/*
	 * Add column rows for pivoted data
	 * if pivot is turned on
	 *
	 */
	addPivotColumns(root, head);

	const { columnList } = model;
	const buildIndex = sortIndexFactory(model);
	const tree = sort(root, columnList().index, buildIndex);

	/*
	 * Add special column type
	 * that fills remaining place (width = 100%)
	 *
	 */
	addPadColumn(tree);

	columnIndexPipe(tree, context, ({ columns, index }) => {
		memo.columns = columns;

		columnList(
			{
				index
			},
			{
				behavior: 'core',
				source: 'column.pipe'
			}
		);

		next(memo);
	});
}

function selectColumnFactory(model) {
	const dataColumns = model.columnList().line;
	const selection = model.selection();

	const selectColumn = dataColumns.find(item => item.type === 'select');
	const indicatorColumn = dataColumns.find(
		item => item.type === 'row-indicator'
	);

	if (!indicatorColumn && selection.unit === 'mix') {
		const createColumn = columnFactory(model);
		return node => {
			const indicatorColumn = createColumn('row-indicator');
			indicatorColumn.model.source = 'generation';
			if (indicatorColumn.model.isVisible) {
				node.children.unshift(
					new Node(indicatorColumn, node.level + 1)
				);
				return indicatorColumn;
			}
		};
	}

	if (
		!selectColumn &&
		selection.unit === 'row' &&
		selection.mode !== 'range'
	) {
		const createColumn = columnFactory(model);
		return node => {
			const selectColumn = createColumn('select');
			selectColumn.model.source = 'generation';
			if (selectColumn.model.isVisible) {
				node.children.unshift(new Node(selectColumn, node.level + 1));
				return selectColumn;
			}
		};
	}

	return noop;
}

function groupColumnFactory(model, nodes) {
	const dataColumns = model.columnList().line;
	const groupColumn = dataColumns.find(item => item.type === 'group');
	const { by, mode } = model.group();
	const createColumn = columnFactory(model);

	if (!groupColumn && (nodes.length || by.length)) {
		switch (mode) {
			case 'nest': {
				return node => {
					const groupColumn = createColumn('group');
					groupColumn.model.source = 'generation';
					if (groupColumn.model.isVisible) {
						node.children.unshift(
							new Node(groupColumn, node.level + 1)
						);
						return groupColumn;
					}
				};
			}
			case 'rowspan':
			case 'flat': {
				return node =>
					by.forEach(key => {
						const groupColumn = createColumn('group');
						groupColumn.model.source = 'generation';
						groupColumn.model.key = `$group-${key}`;
						groupColumn.model.title = key;
						groupColumn.model.by = key;

						if (groupColumn.model.isVisible) {
							node.children.unshift(
								new Node(groupColumn, node.level + 1)
							);
						}
					});
			}
		}
	}

	return noop;
}

function rowExpandColumnFactory(model) {
	const dataColumns = model.columnList().line;
	const expandColumn = dataColumns.find(item => item.type === 'row-expand');
	if (model.row().unit === 'details' && !expandColumn) {
		const createColumn = columnFactory(model);
		return node => {
			const expandColumn = createColumn('row-expand');
			expandColumn.model.source = 'generation';
			if (expandColumn.model.isVisible) {
				node.children.unshift(new Node(expandColumn, node.level + 1));
				return expandColumn;
			}
		};
	}

	return noop;
}

function rowIndicatorColumnFactory(model) {
	const dataColumns = model.columnList().line;
	const rowIndicatorColumn = dataColumns.find(
		item => item.type === 'row-indicator'
	);
	const { canMove, canResize } = model.row();
	if ((canMove || canResize) && !rowIndicatorColumn) {
		const createColumn = columnFactory(model);
		return node => {
			const expandColumn = createColumn('row-indicator');
			expandColumn.model.source = 'generation';
			if (expandColumn.model.isVisible) {
				node.children.unshift(new Node(expandColumn, node.level + 1));
				return expandColumn;
			}
		};
	}

	return noop;
}

function dataColumnsFactory(model) {
	const getColumns = generateFactory(model);
	const createColumn = columnFactory(model);
	const { hasChanges, columns } = getColumns();
	if (hasChanges) {
		model.data({ columns }, { source: 'column.pipe', behavior: 'core' });
	}

	function fill(node, columns) {
		for (let column of columns) {
			const view = createColumn(column.type, column);
			const child = new Node(view, node.level + 1);
			node.children.push(child);
			fill(child, view.model.children);
		}
	}

	return node => fill(node, columns);
}

function padColumnFactory(model) {
	const createColumn = columnFactory(model);
	return node => {
		const padColumn = createColumn('pad');
		padColumn.model.key = `$pad-${guid()}`;
		node.children.push(new Node(padColumn, node.level + 1));
		return padColumn;
	};
}

function pivotColumnsFactory(model) {
	const createColumn = columnFactory(model);
	return function fill(node, head) {
		const { children } = head;
		for (let i = 0, length = children.length; i < length; i++) {
			const child = children[i];
			const pivotColumn = createColumn('pivot');
			const pivotColumnModel = pivotColumn.model;
			pivotColumnModel.title = child.key;
			pivotColumnModel.key = `$pivot-${child.key}`;
			const pivotNode = new Node(pivotColumn, node.level + 1);
			node.children.push(pivotNode);
			fill(pivotNode, child);
		}
	};
}

function running(tree, buildIndex) {
	const result = {
		line: [],
		set: new Set()
	};

	preOrderDFS([tree], node => {
		result.line.push(node);
		result.set.add(node.key.model.key);

		// As we use pre order direction we can manipulate with children without affecting on algorithm.
		// Below we sort columns in appropriate order.
		const columns = node.children.map(child => child.key.model);
		const index = buildIndex(columns);

		let cursor = 0;
		const indexMap = index.reduce((memo, key) => {
			memo[key] = cursor++;
			return memo;
		}, {});

		node.children.sort(
			(x, y) => indexMap[x.key.model.key] - indexMap[y.key.model.key]
		);
	});

	return result;
}

function former(tree, current) {
	const result = {
		line: [],
		set: new Set()
	};

	preOrderDFS([tree], node => {
		// Filter out nodes if they were deleted from newTree.
		const { key } = node.key.model;
		if (current.set.has(key)) {
			result.line.push(copy(node));
			result.set.add(key);
		}
	});

	return result;
}

function insertFactory(screen) {
	const { line } = screen;
	return (prevNode, node) => {
		const pos = line.findIndex(
			n => n.key.model.key === prevNode.key.model.key
		);

		const level = line[pos].level;
		const target = copy(node);
		target.level = level + (node.level - prevNode.level);
		line.splice(pos + 1, 0, target);
	};
}

function insertCohortFactory(screen) {
	const insertNear = insertFactory(screen);
	const { line } = screen;
	return (prevNode, node) => {
		const set = new Set(node.children.map(n => n.key.model.key));
		const index = line.findIndex(n => set.has(n.key.model.key));

		if (index < 0) {
			insertNear(prevNode, node);
			return;
		}

		const target = copy(node);
		const { level } =  line[index];
		target.level = level;
		line.splice(index, 0, target);

		for (let i = index + 1, end = line.length; i < end; i++ ) {
			const child = line[i];
			if (child.level !== level) {
				break;
			}

			if (set.has(child.key.model.key)) {
				child.level = level + 1;
			}
		}
	};
}

function sort(newTree, oldTree, buildIndex) {
	const current = running(newTree, buildIndex);
	const screen = former(oldTree, current);
	const insertNear = insertFactory(screen);
	const insertCohort = insertCohortFactory(screen);

	const root = current.line[0];
	if (!screen.set.has(root.key.model.key)) {
		screen.line.unshift(copy(root));
		screen.line.forEach(n => n.level++);
	}

	for (let i = 1, length = current.line.length; i < length; i++) {
		const node = current.line[i];
		const { model } = node.key;
		if (screen.set.has(model.key)) {
			continue;
		}

		const prevNode = current.line[i - 1];
		if (model.type === 'cohort') {
			insertCohort(prevNode, node);
		} else {
			insertNear(prevNode, node);
		}
	}

	const bendedTree = bend(screen.line);
	return bendedTree;
}
<commit_msg>fix formatting<commit_after>import { Guard } from '../infrastructure/guard';
import { noop } from '../utility/kit';
import { guid } from '../services/guid';
import { columnFactory } from '../column/column.factory';
import { generateFactory } from '../column-list/column.list.generate';
import { columnIndexPipe } from './column.index.pipe';
import { Node } from '../node/node';
import { bend, copy } from '../node/node.service';
import { preOrderDFS } from '../node/node.service';
import { sortIndexFactory } from '../column-list/column.list.sort';

export function columnPipe(memo, context, next) {
	Guard.hasProperty(memo, 'pivot');
	Guard.hasProperty(memo, 'nodes');

	const { model } = context;
	const { pivot, nodes } = memo;
	const { head } = pivot;

	const createColumn = columnFactory(model);
	const root = new Node(createColumn('cohort', { key: '$root' }), 0);
	const addDataColumns = dataColumnsFactory(model);
	const addSelectColumn = selectColumnFactory(model);
	const addGroupColumn = groupColumnFactory(model, nodes);
	const addRowExpandColumn = rowExpandColumnFactory(model);
	const addRowIndicatorColumn = rowIndicatorColumnFactory(model);
	const addPivotColumns = pivotColumnsFactory(model);
	const addPadColumn = padColumnFactory(model);

	/*
	 * We need to invoke addDataColumns earlier that others because it setups data.columns model property
	 *
	 */
	addDataColumns(root);

	/**
	 * Control columns should be filled in reverse order because they use unshift inside.
	 */

	/*
	 * Add row expand column
	 */
	addRowExpandColumn(root);

	/*
	 * Add group column with nodes
	 *
	 */
	addGroupColumn(root);

	/*
	 * Add column with select boxes
	 * if selection unit is row
	 *
	 */
	addSelectColumn(root);

	/*
	 * Add row indicator column
	 * if rows can be dragged or resized
	 *
	 */
	addRowIndicatorColumn(root);

	/*
	 * Add column rows for pivoted data
	 * if pivot is turned on
	 *
	 */
	addPivotColumns(root, head);

	const { columnList } = model;
	const buildIndex = sortIndexFactory(model);
	const tree = sort(root, columnList().index, buildIndex);

	/*
	 * Add special column type
	 * that fills remaining place (width = 100%)
	 *
	 */
	addPadColumn(tree);

	columnIndexPipe(tree, context, ({ columns, index }) => {
		memo.columns = columns;

		columnList(
			{
				index
			},
			{
				behavior: 'core',
				source: 'column.pipe'
			}
		);

		next(memo);
	});
}

function selectColumnFactory(model) {
	const dataColumns = model.columnList().line;
	const selection = model.selection();

	const selectColumn = dataColumns.find(item => item.type === 'select');
	const indicatorColumn = dataColumns.find(item => item.type === 'row-indicator');

	if (!indicatorColumn && selection.unit === 'mix') {
		const createColumn = columnFactory(model);
		return node => {
			const indicatorColumn = createColumn('row-indicator');
			indicatorColumn.model.source = 'generation';
			if (indicatorColumn.model.isVisible) {
				node.children.unshift(new Node(indicatorColumn, node.level + 1));
				return indicatorColumn;
			}
		};
	}

	if (!selectColumn && selection.unit === 'row' && selection.mode !== 'range') {
		const createColumn = columnFactory(model);
		return node => {
			const selectColumn = createColumn('select');
			selectColumn.model.source = 'generation';
			if (selectColumn.model.isVisible) {
				node.children.unshift(new Node(selectColumn, node.level + 1));
				return selectColumn;
			}
		};
	}

	return noop;
}

function groupColumnFactory(model, nodes) {
	const dataColumns = model.columnList().line;
	const groupColumn = dataColumns.find(item => item.type === 'group');
	const { by, mode } = model.group();
	const createColumn = columnFactory(model);

	if (!groupColumn && (nodes.length || by.length)) {
		switch (mode) {
			case 'nest': {
				return node => {
					const groupColumn = createColumn('group');
					groupColumn.model.source = 'generation';
					if (groupColumn.model.isVisible) {
						node.children.unshift(new Node(groupColumn, node.level + 1));
						return groupColumn;
					}
				};
			}
			case 'rowspan':
			case 'flat': {
				return node =>
					by.forEach(key => {
						const groupColumn = createColumn('group');
						groupColumn.model.source = 'generation';
						groupColumn.model.key = `$group-${key}`;
						groupColumn.model.title = key;
						groupColumn.model.by = key;

						if (groupColumn.model.isVisible) {
							node.children.unshift(new Node(groupColumn, node.level + 1));
						}
					});
			}
		}
	}

	return noop;
}

function rowExpandColumnFactory(model) {
	const dataColumns = model.columnList().line;
	const expandColumn = dataColumns.find(item => item.type === 'row-expand');
	if (model.row().unit === 'details' && !expandColumn) {
		const createColumn = columnFactory(model);
		return node => {
			const expandColumn = createColumn('row-expand');
			expandColumn.model.source = 'generation';
			if (expandColumn.model.isVisible) {
				node.children.unshift(new Node(expandColumn, node.level + 1));
				return expandColumn;
			}
		};
	}

	return noop;
}

function rowIndicatorColumnFactory(model) {
	const dataColumns = model.columnList().line;
	const rowIndicatorColumn = dataColumns.find(item => item.type === 'row-indicator');
	const { canMove, canResize } = model.row();
	if ((canMove || canResize) && !rowIndicatorColumn) {
		const createColumn = columnFactory(model);
		return node => {
			const expandColumn = createColumn('row-indicator');
			expandColumn.model.source = 'generation';
			if (expandColumn.model.isVisible) {
				node.children.unshift(new Node(expandColumn, node.level + 1));
				return expandColumn;
			}
		};
	}

	return noop;
}

function dataColumnsFactory(model) {
	const getColumns = generateFactory(model);
	const createColumn = columnFactory(model);
	const { hasChanges, columns } = getColumns();
	if (hasChanges) {
		model.data({ columns }, { source: 'column.pipe', behavior: 'core' });
	}

	function fill(node, columns) {
		for (let column of columns) {
			const view = createColumn(column.type, column);
			const child = new Node(view, node.level + 1);
			node.children.push(child);
			fill(child, view.model.children);
		}
	}

	return node => fill(node, columns);
}

function padColumnFactory(model) {
	const createColumn = columnFactory(model);
	return node => {
		const padColumn = createColumn('pad');
		padColumn.model.key = `$pad-${guid()}`;
		node.children.push(new Node(padColumn, node.level + 1));
		return padColumn;
	};
}

function pivotColumnsFactory(model) {
	const createColumn = columnFactory(model);
	return function fill(node, head) {
		const { children } = head;
		for (let i = 0, length = children.length; i < length; i++) {
			const child = children[i];
			const pivotColumn = createColumn('pivot');
			const pivotColumnModel = pivotColumn.model;
			pivotColumnModel.title = child.key;
			pivotColumnModel.key = `$pivot-${child.key}`;
			const pivotNode = new Node(pivotColumn, node.level + 1);
			node.children.push(pivotNode);
			fill(pivotNode, child);
		}
	};
}

function running(tree, buildIndex) {
	const result = {
		line: [],
		set: new Set()
	};

	preOrderDFS([tree], node => {
		result.line.push(node);
		result.set.add(node.key.model.key);

		// As we use pre order direction we can manipulate with children without affecting on algorithm.
		// Below we sort columns in appropriate order.
		const columns = node.children.map(child => child.key.model);
		const index = buildIndex(columns);

		let cursor = 0;
		const indexMap = index.reduce((memo, key) => {
			memo[key] = cursor++;
			return memo;
		}, {});

		node.children.sort((x, y) => indexMap[x.key.model.key] - indexMap[y.key.model.key]);
	});

	return result;
}

function former(tree, current) {
	const result = {
		line: [],
		set: new Set()
	};

	preOrderDFS([tree], node => {
		// Filter out nodes if they were deleted from newTree.
		const { key } = node.key.model;
		if (current.set.has(key)) {
			result.line.push(copy(node));
			result.set.add(key);
		}
	});

	return result;
}

function insertFactory(screen) {
	const { line } = screen;
	return (prevNode, node) => {
		const pos = line.findIndex(n => n.key.model.key === prevNode.key.model.key);

		const level = line[pos].level;
		const target = copy(node);
		target.level = level + (node.level - prevNode.level);
		line.splice(pos + 1, 0, target);
	};
}

function insertCohortFactory(screen) {
	const insertNear = insertFactory(screen);
	const { line } = screen;
	return (prevNode, node) => {
		const set = new Set(node.children.map(n => n.key.model.key));
		const index = line.findIndex(n => set.has(n.key.model.key));

		if (index < 0) {
			insertNear(prevNode, node);
			return;
		}

		const target = copy(node);
		const { level } = line[index];
		target.level = level;
		line.splice(index, 0, target);

		for (let i = index + 1, end = line.length; i < end; i++) {
			const child = line[i];
			if (child.level !== level) {
				break;
			}

			if (set.has(child.key.model.key)) {
				child.level = level + 1;
			}
		}
	};
}

function sort(newTree, oldTree, buildIndex) {
	const current = running(newTree, buildIndex);
	const screen = former(oldTree, current);
	const insertNear = insertFactory(screen);
	const insertCohort = insertCohortFactory(screen);

	const root = current.line[0];
	if (!screen.set.has(root.key.model.key)) {
		screen.line.unshift(copy(root));
		screen.line.forEach(n => n.level++);
	}

	for (let i = 1, length = current.line.length; i < length; i++) {
		const node = current.line[i];
		const { model } = node.key;
		if (screen.set.has(model.key)) {
			continue;
		}

		const prevNode = current.line[i - 1];
		if (model.type === 'cohort') {
			insertCohort(prevNode, node);
		} else {
			insertNear(prevNode, node);
		}
	}

	const bendedTree = bend(screen.line);
	return bendedTree;
}
<|endoftext|>


<commit_before>
// stats->stat->
// add stat, wipe stats, update stat
// min, max, avg, last, first, count, total
// start stop
// stats windows
// limits on windows, limits on stats
// key, value unique, values

//Why? because stats like .min and .max need to be run over an entire array each time, these stay up to date. normally it's cheap to just dump into an array and run min and max etc when you need them, but in some cases min and max are neded to determine what to do often.

/*
everything is optional!
objConfig={
	 limit: 1000
	,stats:
		{
			 key:{
				 path:'path of key within an object'
				,type:'numeric, uniq, freq, co-occurence'
				,values:{starting values
			 }
		}
}
*/
var _ = require('lodash');
var suddenStats = function(objConfig){
	//start with some defaults, the global stat will require nuothing but a number passed in an array.
	this.config = {limit:10000,stats:{primary:{type:'numeric'}}}
	//simple mode is when only the global stat is used and only arrays of numbers are passed in. by setting this once for the object it avoids many typechecks
	this.simple = false;
	this.stats = {};

	this.init(objConfig);

	this.init = function(objConfig){
		//TODO: validate the structure of config passed in
		//TODO: consider using _.defaultsDeep instead
		//merge defaults and config provided.
		this.config = _.merge(this.config,objConfig);
		//create all of the stats properties for what will be tracked
		_.forOwn(this.config.stats,function(objV,k){
			//set defaults for stat
			this.stats[k] = {min:0,max:0,avg:0,count:0,total:0,first:false,last:false,lastAvg:false,diff:0,fs:Date.now(),ls:Date.now(),type:'numeric'};
			//add any user overrides to things like type
			_.merge(this.stats[k],objV);
		});
		//can we go into simple mode? if so it will be much faster, and generic of course
		if(_.keys(this.stats)==1 && this.stats.hasOwnProperty('primary')){ this.simple = true; }
	}

	this.addData = function(arrData){
		//always work with an array of data. Data is expected be in such vlume that it should be buffered into batches
		if(this.simple){ this.updateStat(arrData,'primary'); }
		else{ this.addCustomData(arrData);
			var arrBatch={};
				_.forOwn(this.config.stats,function(objStat,strStat){
					//init the batch so all we have to do is push.
					arrBatch[strStat].data = [];
				});
			//data loop
			_.forEach(arrData,function(objData,k){
				//stats loop
				_.forOwn(this.config.stats,function(objStat,strStat){
					//get the numbers from the object based on the path
					arrBatch[strStat].data.push( _.get(objData,objStat.path); );
				});
			});
			_.forOwn(arrBatch,function(objStat,strStat){
				//init the batch so all we have to do is push.
				this.updateStat(objStat.data,arrBatch[strStat]);
			});
		}
		return this.stats;
	};

	this.updateStat = function(arrData,key){
		//these are light, but do them as little as necessary
		var intMin = _.min(arrData);
		var intMax = _.max(arrData);
		var intSum = _.sum(arrData);
		var intCount = arrData.length;
		var intAvg = intSum/intCount;
		if( intMin < this.stats[key].min ){ this.stats[key].min = intMin; }
		if( intMax > this.stats[key] ){ this.stats[key].max = intMax; }
		this.stats[key].count = this.stats[key].count + intCount;
		this.stats[key].total = this.stats[key].total + intSum;
		this.stats[key].last = _.last(arrData);
		if( this.stats[key].first===false ){ this.stats[key].first = _.first(arrData); this.stats[key].fs:Date.now(); }
		this.stats[key].ls=Date.now();
		this.stats[key].avg=this.stats[key].total/this.stats[key].count;
		this.stats[key].diff = intAvg-this.stats[key].lastAvg;
		this.stats[key].lastAvg = intAvg;
	}

};<commit_msg>batching and throttling start<commit_after>
// stats->stat->
// add stat, wipe stats, update stat
// min, max, avg, last, first, count, total
// start stop
// stats windows
// limits on windows, limits on stats
// key, value unique, values

//Why? because stats like .min and .max need to be run over an entire array each time, these stay up to date. normally it's cheap to just dump into an array and run min and max etc when you need them, but in some cases min and max are neded to determine what to do often.

/*
everything is optional!
objConfig={
	 limit: 1000
	,stats:
		{
			 key:{
				 path:'path of key within an object'
				,type:'numeric, uniq, freq, co-occurence'
				,values:{starting values
			 }
		}
}
*/
var _ = require('lodash');
var suddenStats = function(objConfig){
	//start with some defaults, the global stat will require nuothing but a number passed in an array.
	this.config = {limit:10000,throttle:100,stats:{primary:{type:'numeric'}}}
	//simple mode is when only the global stat is used and only arrays of numbers are passed in. by setting this once for the object it avoids many typechecks
	this.simple = false;
	this.stats = {};
	this.batch = [];
	this.intBatch = 0;
	this.init(objConfig);

	this.init = function(objConfig){
		//TODO: validate the structure of config passed in
		//TODO: consider using _.defaultsDeep instead
		//merge defaults and config provided.
		this.config = _.merge(this.config,objConfig);
		//create all of the stats properties for what will be tracked
		_.forOwn(this.config.stats,function(objV,k){
			//set defaults for stat
			this.stats[k] = {min:0,max:0,avg:0,count:0,total:0,first:false,last:false,lastAvg:false,diff:0,fs:Date.now(),ls:Date.now(),type:'numeric'};
			//add any user overrides to things like type
			_.merge(this.stats[k],objV);
		});
		//can we go into simple mode? if so it will be much faster, and generic of course
		if(_.keys(this.stats)==1 && this.stats.hasOwnProperty('primary')){ this.simple = true; }
	}

	this.qData = function(varData){
		//add data as often as you want, but batch it by time, using the deboucne config, defined in miliseconds
		//config.limit is to start kciking things out of the batch after too many per batch / time period
		if(this.intBatch<this.config.limit){
			this.batch.push(varData);
			/*
			if(fnInprocess){
				if(this.config.limit > 2){this.config.limit--;}
				this.config.throttle++;
			}
			*/
			var fnInprocess = _.throttle(this.addData(this.batch),this.config.throttle);
		}
	}

	this.addData = function(arrData){
		//always work with an array of data. Data is expected be in such vlume that it should be buffered into batches

		//clear the batch
		this.batch=[]; this.intBatch=0;

		if(this.simple){ this.updateStat(arrData,'primary'); }
		else{ this.addCustomData(arrData);
			var arrBatch={};
				_.forOwn(this.config.stats,function(objStat,strStat){
					//init the batch so all we have to do is push.
					arrBatch[strStat].data = [];
				});
			//data loop
			_.forEach(arrData,function(objData,k){
				//stats loop
				_.forOwn(this.config.stats,function(objStat,strStat){
					//get the numbers from the object based on the path
					arrBatch[strStat].data.push( _.get(objData,objStat.path); );
				});
			});
			_.forOwn(arrBatch,function(objStat,strStat){
				//init the batch so all we have to do is push.
				this.updateStat(objStat.data,arrBatch[strStat]);
			});
		}
		return this.stats;
	};

	this.updateStat = function(arrData,key){
		//these are light, but do them as little as necessary
		var intMin = _.min(arrData);
		var intMax = _.max(arrData);
		var intSum = _.sum(arrData);
		var intCount = arrData.length;
		var intAvg = intSum/intCount;
		if( intMin < this.stats[key].min ){ this.stats[key].min = intMin; }
		if( intMax > this.stats[key] ){ this.stats[key].max = intMax; }
		this.stats[key].count = this.stats[key].count + intCount;
		this.stats[key].total = this.stats[key].total + intSum;
		this.stats[key].last = _.last(arrData);
		if( this.stats[key].first===false ){ this.stats[key].first = _.first(arrData); this.stats[key].fs:Date.now(); }
		this.stats[key].ls=Date.now();
		this.stats[key].avg=this.stats[key].total/this.stats[key].count;
		this.stats[key].diff = intAvg-this.stats[key].lastAvg;
		this.stats[key].lastAvg = intAvg;
	}

};<|endoftext|>


<commit_before>angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position'])

.value('$datepickerPopupLiteralWarning', true)

.constant('uibDatepickerPopupConfig', {
  altInputFormats: [],
  appendToBody: false,
  clearText: 'Clear',
  closeOnDateSelection: true,
  closeText: 'Done',
  currentText: 'Today',
  datepickerPopup: 'yyyy-MM-dd',
  datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html',
  datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
  html5Types: {
    date: 'yyyy-MM-dd',
    'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
    'month': 'yyyy-MM'
  },
  onOpenFocus: true,
  showButtonBar: true,
  placement: 'auto bottom-left'
})

.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning',
function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) {
  var cache = {},
    isHtml5DateInput = false;
  var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
    datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
    ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [];

  this.init = function(_ngModel_) {
    ngModel = _ngModel_;
    ngModelOptions = _ngModel_.$options;
    closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
      $scope.$parent.$eval($attrs.closeOnDateSelection) :
      datepickerPopupConfig.closeOnDateSelection;
    appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?
      $scope.$parent.$eval($attrs.datepickerAppendToBody) :
      datepickerPopupConfig.appendToBody;
    onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?
      $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
    datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?
      $attrs.datepickerPopupTemplateUrl :
      datepickerPopupConfig.datepickerPopupTemplateUrl;
    datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?
      $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
    altInputFormats = angular.isDefined($attrs.altInputFormats) ?
      $scope.$parent.$eval($attrs.altInputFormats) :
      datepickerPopupConfig.altInputFormats;

    $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?
      $scope.$parent.$eval($attrs.showButtonBar) :
      datepickerPopupConfig.showButtonBar;

    if (datepickerPopupConfig.html5Types[$attrs.type]) {
      dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
      isHtml5DateInput = true;
    } else {
      dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
      $attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
        var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
        // Invalidate the $modelValue to ensure that formatters re-run
        // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
        if (newDateFormat !== dateFormat) {
          dateFormat = newDateFormat;
          ngModel.$modelValue = null;

          if (!dateFormat) {
            throw new Error('uibDatepickerPopup must have a date format specified.');
          }
        }
      });
    }

    if (!dateFormat) {
      throw new Error('uibDatepickerPopup must have a date format specified.');
    }

    if (isHtml5DateInput && $attrs.uibDatepickerPopup) {
      throw new Error('HTML5 date input types do not support custom formats.');
    }

    // popup element used to display calendar
    popupEl = angular.element('

');

    popupEl.attr({
      'ng-model': 'date',
      'ng-change': 'dateSelection(date)',
      'template-url': datepickerPopupTemplateUrl
    });

    // datepicker element
    datepickerEl = angular.element(popupEl.children()[0]);
    datepickerEl.attr('template-url', datepickerTemplateUrl);

    if (!$scope.datepickerOptions) {
      $scope.datepickerOptions = {};
    }

    if (isHtml5DateInput) {
      if ($attrs.type === 'month') {
        $scope.datepickerOptions.datepickerMode = 'month';
        $scope.datepickerOptions.minMode = 'month';
      }
    }

    datepickerEl.attr('datepicker-options', 'datepickerOptions');

    if (!isHtml5DateInput) {
      // Internal API to maintain the correct ng-invalid-[key] class
      ngModel.$$parserName = 'date';
      ngModel.$validators.date = validator;
      ngModel.$parsers.unshift(parseDate);
      ngModel.$formatters.push(function(value) {
        if (ngModel.$isEmpty(value)) {
          $scope.date = value;
          return value;
        }

        if (angular.isNumber(value)) {
          value = new Date(value);
        }

        $scope.date = value;

        return dateParser.filter($scope.date, dateFormat);
      });
    } else {
      ngModel.$formatters.push(function(value) {
        $scope.date = value;
        return value;
      });
    }

    // Detect changes in the view from the text box
    ngModel.$viewChangeListeners.push(function() {
      $scope.date = parseDateString(ngModel.$viewValue);
    });

    $element.on('keydown', inputKeydownBind);

    $popup = $compile(popupEl)($scope);
    // Prevent jQuery cache memory leak (template is now redundant after linking)
    popupEl.remove();

    if (appendToBody) {
      $document.find('body').append($popup);
    } else {
      $element.after($popup);
    }

    $scope.$on('$destroy', function() {
      if ($scope.isOpen === true) {
        if (!$rootScope.$$phase) {
          $scope.$apply(function() {
            $scope.isOpen = false;
          });
        }
      }

      $popup.remove();
      $element.off('keydown', inputKeydownBind);
      $document.off('click', documentClickBind);
      if (scrollParentEl) {
        scrollParentEl.off('scroll', positionPopup);
      }
      angular.element($window).off('resize', positionPopup);

      //Clear all watch listeners on destroy
      while (watchListeners.length) {
        watchListeners.shift()();
      }
    });
  };

  $scope.getText = function(key) {
    return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
  };

  $scope.isDisabled = function(date) {
    if (date === 'today') {
      date = new Date();
    }

    var dates = {};
    angular.forEach(['minDate', 'maxDate'], function(key) {
      if (!$scope.datepickerOptions[key]) {
        dates[key] = null;
      } else if (angular.isDate($scope.datepickerOptions[key])) {
        dates[key] = new Date($scope.datepickerOptions[key]);
      } else {
        if ($datepickerPopupLiteralWarning) {
          $log.warn('Literal date support has been deprecated, please switch to date object usage');
        }

        dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium'));
      }
    });

    return $scope.datepickerOptions &&
      dates.minDate && $scope.compare(date, dates.minDate) < 0 ||
      dates.maxDate && $scope.compare(date, dates.maxDate) > 0;
  };

  $scope.compare = function(date1, date2) {
    return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
  };

  // Inner change
  $scope.dateSelection = function(dt) {
    $scope.date = dt;
    var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
    $element.val(date);
    ngModel.$setViewValue(date);

    if (closeOnDateSelection) {
      $scope.isOpen = false;
      $element[0].focus();
    }
  };

  $scope.keydown = function(evt) {
    if (evt.which === 27) {
      evt.stopPropagation();
      $scope.isOpen = false;
      $element[0].focus();
    }
  };

  $scope.select = function(date, evt) {
    evt.stopPropagation();

    if (date === 'today') {
      var today = new Date();
      if (angular.isDate($scope.date)) {
        date = new Date($scope.date);
        date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
      } else {
        date = new Date(today.setHours(0, 0, 0, 0));
      }
    }
    $scope.dateSelection(date);
  };

  $scope.close = function(evt) {
    evt.stopPropagation();

    $scope.isOpen = false;
    $element[0].focus();
  };

  $scope.disabled = angular.isDefined($attrs.disabled) || false;
  if ($attrs.ngDisabled) {
    watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) {
      $scope.disabled = disabled;
    }));
  }

  $scope.$watch('isOpen', function(value) {
    if (value) {
      if (!$scope.disabled) {
        $timeout(function() {
          positionPopup();

          if (onOpenFocus) {
            $scope.$broadcast('uib:datepicker.focus');
          }

          $document.on('click', documentClickBind);

          var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
          if (appendToBody || $position.parsePlacement(placement)[2]) {
            scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));
            if (scrollParentEl) {
              scrollParentEl.on('scroll', positionPopup);
            }
          } else {
            scrollParentEl = null;
          }

          angular.element($window).on('resize', positionPopup);
        }, 0, false);
      } else {
        $scope.isOpen = false;
      }
    } else {
      $document.off('click', documentClickBind);
      if (scrollParentEl) {
        scrollParentEl.off('scroll', positionPopup);
      }
      angular.element($window).off('resize', positionPopup);
    }
  });

  function cameltoDash(string) {
    return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
  }

  function parseDateString(viewValue) {
    var date = dateParser.parse(viewValue, dateFormat, $scope.date);
    if (isNaN(date)) {
      for (var i = 0; i < altInputFormats.length; i++) {
        date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);
        if (!isNaN(date)) {
          return date;
        }
      }
    }
    return date;
  }

  function parseDate(viewValue) {
    if (angular.isNumber(viewValue)) {
      // presumably timestamp to date object
      viewValue = new Date(viewValue);
    }

    if (!viewValue) {
      return null;
    }

    if (angular.isDate(viewValue) && !isNaN(viewValue)) {
      return viewValue;
    }

    if (angular.isString(viewValue)) {
      var date = parseDateString(viewValue);
      if (!isNaN(date)) {
        return date;
      }
    }

    return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
  }

  function validator(modelValue, viewValue) {
    var value = modelValue || viewValue;

    if (!$attrs.ngRequired && !value) {
      return true;
    }

    if (angular.isNumber(value)) {
      value = new Date(value);
    }

    if (!value) {
      return true;
    }

    if (angular.isDate(value) && !isNaN(value)) {
      return true;
    }

    if (angular.isString(value)) {
      return !isNaN(parseDateString(viewValue));
    }

    return false;
  }

  function documentClickBind(event) {
    if (!$scope.isOpen && $scope.disabled) {
      return;
    }

    var popup = $popup[0];
    var dpContainsTarget = $element[0].contains(event.target);
    // The popup node may not be an element node
    // In some browsers (IE) only element nodes have the 'contains' function
    var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
    if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
      $scope.$apply(function() {
        $scope.isOpen = false;
      });
    }
  }

  function inputKeydownBind(evt) {
    if (evt.which === 27 && $scope.isOpen) {
      evt.preventDefault();
      evt.stopPropagation();
      $scope.$apply(function() {
        $scope.isOpen = false;
      });
      $element[0].focus();
    } else if (evt.which === 40 && !$scope.isOpen) {
      evt.preventDefault();
      evt.stopPropagation();
      $scope.$apply(function() {
        $scope.isOpen = true;
      });
    }
  }

  function positionPopup() {
    if ($scope.isOpen) {
      var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));
      var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
      var position = $position.positionElements($element, dpElement, placement, appendToBody);
      dpElement.css({top: position.top + 'px', left: position.left + 'px'});
      if (dpElement.hasClass('uib-position-measure')) {
        dpElement.removeClass('uib-position-measure');
      }
    }
  }

  $scope.$on('uib:datepicker.mode', function() {
    $timeout(positionPopup, 0, false);
  });
}])

.directive('uibDatepickerPopup', function() {
  return {
    require: ['ngModel', 'uibDatepickerPopup'],
    controller: 'UibDatepickerPopupController',
    scope: {
      datepickerOptions: '=?',
      isOpen: '=?',
      currentText: '@',
      clearText: '@',
      closeText: '@'
    },
    link: function(scope, element, attrs, ctrls) {
      var ngModel = ctrls[0],
        ctrl = ctrls[1];

      ctrl.init(ngModel);
    }
  };
})

.directive('uibDatepickerPopupWrap', function() {
  return {
    restrict: 'A',
    transclude: true,
    templateUrl: function(element, attrs) {
      return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html';
    }
  };
});
<commit_msg>fix(datepickerPopup): use value instead of viewValue<commit_after>angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position'])

.value('$datepickerPopupLiteralWarning', true)

.constant('uibDatepickerPopupConfig', {
  altInputFormats: [],
  appendToBody: false,
  clearText: 'Clear',
  closeOnDateSelection: true,
  closeText: 'Done',
  currentText: 'Today',
  datepickerPopup: 'yyyy-MM-dd',
  datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html',
  datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
  html5Types: {
    date: 'yyyy-MM-dd',
    'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
    'month': 'yyyy-MM'
  },
  onOpenFocus: true,
  showButtonBar: true,
  placement: 'auto bottom-left'
})

.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning',
function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) {
  var cache = {},
    isHtml5DateInput = false;
  var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
    datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
    ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [];

  this.init = function(_ngModel_) {
    ngModel = _ngModel_;
    ngModelOptions = _ngModel_.$options;
    closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
      $scope.$parent.$eval($attrs.closeOnDateSelection) :
      datepickerPopupConfig.closeOnDateSelection;
    appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?
      $scope.$parent.$eval($attrs.datepickerAppendToBody) :
      datepickerPopupConfig.appendToBody;
    onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?
      $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
    datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?
      $attrs.datepickerPopupTemplateUrl :
      datepickerPopupConfig.datepickerPopupTemplateUrl;
    datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?
      $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
    altInputFormats = angular.isDefined($attrs.altInputFormats) ?
      $scope.$parent.$eval($attrs.altInputFormats) :
      datepickerPopupConfig.altInputFormats;

    $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?
      $scope.$parent.$eval($attrs.showButtonBar) :
      datepickerPopupConfig.showButtonBar;

    if (datepickerPopupConfig.html5Types[$attrs.type]) {
      dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
      isHtml5DateInput = true;
    } else {
      dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
      $attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
        var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
        // Invalidate the $modelValue to ensure that formatters re-run
        // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
        if (newDateFormat !== dateFormat) {
          dateFormat = newDateFormat;
          ngModel.$modelValue = null;

          if (!dateFormat) {
            throw new Error('uibDatepickerPopup must have a date format specified.');
          }
        }
      });
    }

    if (!dateFormat) {
      throw new Error('uibDatepickerPopup must have a date format specified.');
    }

    if (isHtml5DateInput && $attrs.uibDatepickerPopup) {
      throw new Error('HTML5 date input types do not support custom formats.');
    }

    // popup element used to display calendar
    popupEl = angular.element('

');

    popupEl.attr({
      'ng-model': 'date',
      'ng-change': 'dateSelection(date)',
      'template-url': datepickerPopupTemplateUrl
    });

    // datepicker element
    datepickerEl = angular.element(popupEl.children()[0]);
    datepickerEl.attr('template-url', datepickerTemplateUrl);

    if (!$scope.datepickerOptions) {
      $scope.datepickerOptions = {};
    }

    if (isHtml5DateInput) {
      if ($attrs.type === 'month') {
        $scope.datepickerOptions.datepickerMode = 'month';
        $scope.datepickerOptions.minMode = 'month';
      }
    }

    datepickerEl.attr('datepicker-options', 'datepickerOptions');

    if (!isHtml5DateInput) {
      // Internal API to maintain the correct ng-invalid-[key] class
      ngModel.$$parserName = 'date';
      ngModel.$validators.date = validator;
      ngModel.$parsers.unshift(parseDate);
      ngModel.$formatters.push(function(value) {
        if (ngModel.$isEmpty(value)) {
          $scope.date = value;
          return value;
        }

        if (angular.isNumber(value)) {
          value = new Date(value);
        }

        $scope.date = value;

        return dateParser.filter($scope.date, dateFormat);
      });
    } else {
      ngModel.$formatters.push(function(value) {
        $scope.date = value;
        return value;
      });
    }

    // Detect changes in the view from the text box
    ngModel.$viewChangeListeners.push(function() {
      $scope.date = parseDateString(ngModel.$viewValue);
    });

    $element.on('keydown', inputKeydownBind);

    $popup = $compile(popupEl)($scope);
    // Prevent jQuery cache memory leak (template is now redundant after linking)
    popupEl.remove();

    if (appendToBody) {
      $document.find('body').append($popup);
    } else {
      $element.after($popup);
    }

    $scope.$on('$destroy', function() {
      if ($scope.isOpen === true) {
        if (!$rootScope.$$phase) {
          $scope.$apply(function() {
            $scope.isOpen = false;
          });
        }
      }

      $popup.remove();
      $element.off('keydown', inputKeydownBind);
      $document.off('click', documentClickBind);
      if (scrollParentEl) {
        scrollParentEl.off('scroll', positionPopup);
      }
      angular.element($window).off('resize', positionPopup);

      //Clear all watch listeners on destroy
      while (watchListeners.length) {
        watchListeners.shift()();
      }
    });
  };

  $scope.getText = function(key) {
    return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
  };

  $scope.isDisabled = function(date) {
    if (date === 'today') {
      date = new Date();
    }

    var dates = {};
    angular.forEach(['minDate', 'maxDate'], function(key) {
      if (!$scope.datepickerOptions[key]) {
        dates[key] = null;
      } else if (angular.isDate($scope.datepickerOptions[key])) {
        dates[key] = new Date($scope.datepickerOptions[key]);
      } else {
        if ($datepickerPopupLiteralWarning) {
          $log.warn('Literal date support has been deprecated, please switch to date object usage');
        }

        dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium'));
      }
    });

    return $scope.datepickerOptions &&
      dates.minDate && $scope.compare(date, dates.minDate) < 0 ||
      dates.maxDate && $scope.compare(date, dates.maxDate) > 0;
  };

  $scope.compare = function(date1, date2) {
    return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
  };

  // Inner change
  $scope.dateSelection = function(dt) {
    $scope.date = dt;
    var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
    $element.val(date);
    ngModel.$setViewValue(date);

    if (closeOnDateSelection) {
      $scope.isOpen = false;
      $element[0].focus();
    }
  };

  $scope.keydown = function(evt) {
    if (evt.which === 27) {
      evt.stopPropagation();
      $scope.isOpen = false;
      $element[0].focus();
    }
  };

  $scope.select = function(date, evt) {
    evt.stopPropagation();

    if (date === 'today') {
      var today = new Date();
      if (angular.isDate($scope.date)) {
        date = new Date($scope.date);
        date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
      } else {
        date = new Date(today.setHours(0, 0, 0, 0));
      }
    }
    $scope.dateSelection(date);
  };

  $scope.close = function(evt) {
    evt.stopPropagation();

    $scope.isOpen = false;
    $element[0].focus();
  };

  $scope.disabled = angular.isDefined($attrs.disabled) || false;
  if ($attrs.ngDisabled) {
    watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) {
      $scope.disabled = disabled;
    }));
  }

  $scope.$watch('isOpen', function(value) {
    if (value) {
      if (!$scope.disabled) {
        $timeout(function() {
          positionPopup();

          if (onOpenFocus) {
            $scope.$broadcast('uib:datepicker.focus');
          }

          $document.on('click', documentClickBind);

          var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
          if (appendToBody || $position.parsePlacement(placement)[2]) {
            scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));
            if (scrollParentEl) {
              scrollParentEl.on('scroll', positionPopup);
            }
          } else {
            scrollParentEl = null;
          }

          angular.element($window).on('resize', positionPopup);
        }, 0, false);
      } else {
        $scope.isOpen = false;
      }
    } else {
      $document.off('click', documentClickBind);
      if (scrollParentEl) {
        scrollParentEl.off('scroll', positionPopup);
      }
      angular.element($window).off('resize', positionPopup);
    }
  });

  function cameltoDash(string) {
    return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
  }

  function parseDateString(viewValue) {
    var date = dateParser.parse(viewValue, dateFormat, $scope.date);
    if (isNaN(date)) {
      for (var i = 0; i < altInputFormats.length; i++) {
        date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);
        if (!isNaN(date)) {
          return date;
        }
      }
    }
    return date;
  }

  function parseDate(viewValue) {
    if (angular.isNumber(viewValue)) {
      // presumably timestamp to date object
      viewValue = new Date(viewValue);
    }

    if (!viewValue) {
      return null;
    }

    if (angular.isDate(viewValue) && !isNaN(viewValue)) {
      return viewValue;
    }

    if (angular.isString(viewValue)) {
      var date = parseDateString(viewValue);
      if (!isNaN(date)) {
        return date;
      }
    }

    return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
  }

  function validator(modelValue, viewValue) {
    var value = modelValue || viewValue;

    if (!$attrs.ngRequired && !value) {
      return true;
    }

    if (angular.isNumber(value)) {
      value = new Date(value);
    }

    if (!value) {
      return true;
    }

    if (angular.isDate(value) && !isNaN(value)) {
      return true;
    }

    if (angular.isString(value)) {
      return !isNaN(parseDateString(value));
    }

    return false;
  }

  function documentClickBind(event) {
    if (!$scope.isOpen && $scope.disabled) {
      return;
    }

    var popup = $popup[0];
    var dpContainsTarget = $element[0].contains(event.target);
    // The popup node may not be an element node
    // In some browsers (IE) only element nodes have the 'contains' function
    var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
    if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
      $scope.$apply(function() {
        $scope.isOpen = false;
      });
    }
  }

  function inputKeydownBind(evt) {
    if (evt.which === 27 && $scope.isOpen) {
      evt.preventDefault();
      evt.stopPropagation();
      $scope.$apply(function() {
        $scope.isOpen = false;
      });
      $element[0].focus();
    } else if (evt.which === 40 && !$scope.isOpen) {
      evt.preventDefault();
      evt.stopPropagation();
      $scope.$apply(function() {
        $scope.isOpen = true;
      });
    }
  }

  function positionPopup() {
    if ($scope.isOpen) {
      var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));
      var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
      var position = $position.positionElements($element, dpElement, placement, appendToBody);
      dpElement.css({top: position.top + 'px', left: position.left + 'px'});
      if (dpElement.hasClass('uib-position-measure')) {
        dpElement.removeClass('uib-position-measure');
      }
    }
  }

  $scope.$on('uib:datepicker.mode', function() {
    $timeout(positionPopup, 0, false);
  });
}])

.directive('uibDatepickerPopup', function() {
  return {
    require: ['ngModel', 'uibDatepickerPopup'],
    controller: 'UibDatepickerPopupController',
    scope: {
      datepickerOptions: '=?',
      isOpen: '=?',
      currentText: '@',
      clearText: '@',
      closeText: '@'
    },
    link: function(scope, element, attrs, ctrls) {
      var ngModel = ctrls[0],
        ctrl = ctrls[1];

      ctrl.init(ngModel);
    }
  };
})

.directive('uibDatepickerPopupWrap', function() {
  return {
    restrict: 'A',
    transclude: true,
    templateUrl: function(element, attrs) {
      return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html';
    }
  };
});
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const requireEnvs = require('./require-envs');
const request = require('request');
const FB = require('fb');
const logger = require('./logger')(__filename);
const eventCore = require('../core/event-core');

requireEnvs(['FB_APP_ID', 'FB_APP_SECRET']);

const ACCOUNT_TO_FOLLOW = process.env.FB_PAGE_ID || "retrowappu2015";
const REFRESH_INTERVAL = 15 * 60 * 1000; // 15 min
const FB_CFG = {
  appId:     process.env.FB_APP_ID,
  appSecret: process.env.FB_APP_SECRET,
  toFollow:  ACCOUNT_TO_FOLLOW,
  xfbml:     true,
  version:   'v2.5'
};
FB.options(FB_CFG);

// # State
const state = {
  accessToken: null,
  announcements: null,
  eventIds: []
};

function initialize() {
  state.eventIds = eventCore.getEvents()
    .filter(event => !_.isUndefined(event.facebookId))
    .map(event => event.facebookId);

  _getAccessToken()
    .then(() => {
      setInterval(_updateFromFacebook, REFRESH_INTERVAL);

      // Execute update immediately
      _updateFromFacebook();
    });
}

function getAnnouncements() {
  return state.announcements;
}

function _updateFromFacebook() {
    logger.info('Updating FB data');

    FB.setAccessToken(state.accessToken);

    const promises = state.eventIds
      .map(_fetchAttending)
      .concat(_fetchAnnouncements());

    Promise.all(promises)
    .then(function(eventData) {
        logger.info('Facebook update performed');
        _.assign(state, eventData);
    }, function(error) {
        logger.error('Facebook info could not be updated', error);
    });
}

function _fetchAttending(eventId) {
  return new Promise((resolve, reject) => {

    FB.api(`/${ eventId }/?fields=attending_count`,
      function(response) {
        if (response && !response.error) {
          logger.info("Event attending fetched");

          const attendingCount = response.attending_count;
          eventCore.setAttendingCount(eventId, attendingCount);

          resolve(attendingCount);
        } else {
          logger.error("Failed to fetch event attending:", response);
          reject(response && response.error);
        }
      }
    );
  });
}

function _fetchAnnouncements() {
  return new Promise((resolve, reject) => {

    FB.api(`/${ ACCOUNT_TO_FOLLOW }/feed?fields=message,created_time,picture`,
      function(response) {
        if (response && !response.error) {
          logger.info("Announcements fetched");

          state.announcements = response.data
            .filter(x => !_.isUndefined(x.message))
            .map(x => _.pick(x, 'message', 'created_time', 'picture'));

          resolve(response);
        } else {
          logger.error("Failed to fetch announcements:", response);
          reject(response && response.error);
        }
      }
    );
  });
}

function _getAccessToken() {
  return new Promise(function(resolve, reject) {
    request.get('https://graph.facebook.com/oauth/access_token?' +
      'client_id=' + FB_CFG.appId +
      '&client_secret=' + FB_CFG.appSecret +
      '&grant_type=client_credentials',

      function(error, response, body) {
        if (body.error) {
          console.error('Error occured on Facebook Access Token fetch', body.error);
          return reject(body.error);
        }
        else {
          state.accessToken = _parseAccessTokenFromBody(body);
          logger.info('FB Access Token retrieved');
          return resolve(_extendTokenDuration(state.accessToken));
        }
      }
    );
  });
};

function _extendTokenDuration() {
  return new Promise(function(resolve, reject) {
    request.get('https://graph.facebook.com/oauth/access_token?' +
      'client_id=' + FB_CFG.appId +
      '&client_secret=' + FB_CFG.appSecret +
      '&grant_type=client_credentials' +
      '&access_token=' + state.accessToken +
      '&fb_exchange_token=' + state.accessToken +
      '&redirect_uri=https://wappuapp-heroku.com', // unused but required
      function(error, response, body) {
        if (body.error) {
          logger.error('Error occured on Facebook Access Token extension', body.error);
          return reject(body.error);
        }
        else {
          logger.info('FB Access Token extended');
          state.accessToken = _parseAccessTokenFromBody(body);
          return resolve(state.accessToken);
        }
      }
    );
  });
}

function _parseAccessTokenFromBody(body) {
  return body.split('=')[1];
}

export {
  initialize,
  getAnnouncements
};
<commit_msg>Fetch full picture from fb api<commit_after>'use strict';

const _ = require('lodash');
const requireEnvs = require('./require-envs');
const request = require('request');
const FB = require('fb');
const logger = require('./logger')(__filename);
const eventCore = require('../core/event-core');

requireEnvs(['FB_APP_ID', 'FB_APP_SECRET']);

const ACCOUNT_TO_FOLLOW = process.env.FB_PAGE_ID || "retrowappu2015";
const REFRESH_INTERVAL = 15 * 60 * 1000; // 15 min
const FB_CFG = {
  appId:     process.env.FB_APP_ID,
  appSecret: process.env.FB_APP_SECRET,
  toFollow:  ACCOUNT_TO_FOLLOW,
  xfbml:     true,
  version:   'v2.5'
};
FB.options(FB_CFG);

// # State
const state = {
  accessToken: null,
  announcements: null,
  eventIds: []
};

function initialize() {
  state.eventIds = eventCore.getEvents()
    .filter(event => !_.isUndefined(event.facebookId))
    .map(event => event.facebookId);

  _getAccessToken()
    .then(() => {
      setInterval(_updateFromFacebook, REFRESH_INTERVAL);

      // Execute update immediately
      _updateFromFacebook();
    });
}

function getAnnouncements() {
  return state.announcements;
}

function _updateFromFacebook() {
    logger.info('Updating FB data');

    FB.setAccessToken(state.accessToken);

    const promises = state.eventIds
      .map(_fetchAttending)
      .concat(_fetchAnnouncements());

    Promise.all(promises)
    .then(function(eventData) {
        logger.info('Facebook update performed');
        _.assign(state, eventData);
    }, function(error) {
        logger.error('Facebook info could not be updated', error);
    });
}

function _fetchAttending(eventId) {
  return new Promise((resolve, reject) => {

    FB.api(`/${ eventId }/?fields=attending_count`,
      function(response) {
        if (response && !response.error) {
          logger.info("Event attending fetched");

          const attendingCount = response.attending_count;
          eventCore.setAttendingCount(eventId, attendingCount);

          resolve(attendingCount);
        } else {
          logger.error("Failed to fetch event attending:", response);
          reject(response && response.error);
        }
      }
    );
  });
}

function _fetchAnnouncements() {
  return new Promise((resolve, reject) => {
    FB.api(`/${ ACCOUNT_TO_FOLLOW }/feed?fields=message,created_time,full_picture`,
      function(response) {
        if (response && !response.error) {
          logger.info("Announcements fetched");

          state.announcements = response.data
            .filter(x => !_.isUndefined(x.message))
            .map(x => {
              return {
                message: x.message,
                created_time: x.created_time,
                picture: x.full_picture
              };
            });

          resolve(response);
        } else {
          logger.error("Failed to fetch announcements:", response);
          reject(response && response.error);
        }
      }
    );
  });
}

function _getAccessToken() {
  return new Promise(function(resolve, reject) {
    request.get('https://graph.facebook.com/oauth/access_token?' +
      'client_id=' + FB_CFG.appId +
      '&client_secret=' + FB_CFG.appSecret +
      '&grant_type=client_credentials',

      function(error, response, body) {
        if (body.error) {
          console.error('Error occured on Facebook Access Token fetch', body.error);
          return reject(body.error);
        }
        else {
          state.accessToken = _parseAccessTokenFromBody(body);
          logger.info('FB Access Token retrieved');
          return resolve(_extendTokenDuration(state.accessToken));
        }
      }
    );
  });
};

function _extendTokenDuration() {
  return new Promise(function(resolve, reject) {
    request.get('https://graph.facebook.com/oauth/access_token?' +
      'client_id=' + FB_CFG.appId +
      '&client_secret=' + FB_CFG.appSecret +
      '&grant_type=client_credentials' +
      '&access_token=' + state.accessToken +
      '&fb_exchange_token=' + state.accessToken +
      '&redirect_uri=https://wappuapp-heroku.com', // unused but required
      function(error, response, body) {
        if (body.error) {
          logger.error('Error occured on Facebook Access Token extension', body.error);
          return reject(body.error);
        }
        else {
          logger.info('FB Access Token extended');
          state.accessToken = _parseAccessTokenFromBody(body);
          return resolve(state.accessToken);
        }
      }
    );
  });
}

function _parseAccessTokenFromBody(body) {
  return body.split('=')[1];
}

export {
  initialize,
  getAnnouncements
};
<|endoftext|>


<commit_before>/**
 * PowerTip Utility Functions
 *
 * @fileoverview  Private helper functions.
 * @link          http://stevenbenner.github.com/jquery-powertip/
 * @author        Steven Benner (http://stevenbenner.com/)
 * @requires      jQuery 1.7+
 */

/**
 * Determine whether a jQuery object is an SVG element
 * @private
 * @param {jQuery} element The element to check
 * @return {boolean} Whether this is an SVG element
 */
function isSvgElement(element) {
	return window.SVGElement && element[0] instanceof SVGElement;
}

/**
 * Determines if the specified jQuery.Event object has mouse data.
 * @param {jQuery.Event=} event The jQuery.Event object to test.
 * @return {boolean} True if there is mouse data, otherwise false.
 */
function isMouseEvent(event) {
	return !!event && typeof event.pageX === 'number';
}

/**
 * Initializes the viewport dimension cache and hooks up the mouse position
 * tracking and viewport dimension tracking events.
 * Prevents attaching the events more than once.
 * @private
 */
function initTracking() {
	if (!session.mouseTrackingActive) {
		session.mouseTrackingActive = true;

		// grab the current viewport dimensions on load
		getViewportDimensions();
		$(getViewportDimensions);

		// hook mouse move tracking
		$document.on('mousemove' + EVENT_NAMESPACE, trackMouse);

		// hook viewport dimensions tracking
		$window.on('resize' + EVENT_NAMESPACE, trackResize);
		$window.on('scroll' + EVENT_NAMESPACE, trackScroll);
	}
}

/**
 * Updates the viewport dimensions cache.
 * @private
 */
function getViewportDimensions() {
	session.scrollLeft = $window.scrollLeft();
	session.scrollTop = $window.scrollTop();
	session.windowWidth = $window.width();
	session.windowHeight = $window.height();
}

/**
 * Updates the window size info in the viewport dimensions cache.
 * @private
 */
function trackResize() {
	session.windowWidth = $window.width();
	session.windowHeight = $window.height();
}

/**
 * Updates the scroll offset info in the viewport dimensions cache.
 * @private
 */
function trackScroll() {
	var x = $window.scrollLeft(),
		y = $window.scrollTop();
	if (x !== session.scrollLeft) {
		session.currentX += x - session.scrollLeft;
		session.scrollLeft = x;
	}
	if (y !== session.scrollTop) {
		session.currentY += y - session.scrollTop;
		session.scrollTop = y;
	}
}

/**
 * Saves the current mouse coordinates to the session object.
 * @private
 * @param {jQuery.Event} event The mousemove event for the document.
 */
function trackMouse(event) {
	session.currentX = event.pageX;
	session.currentY = event.pageY;
}

/**
 * Tests if the mouse is currently over the specified element.
 * @private
 * @param {jQuery} element The element to check for hover.
 * @return {boolean}
 */
function isMouseOver(element) {
	// use getBoundingClientRect() because jQuery's width() and height()
	// methods do not work with SVG elements
	// compute width/height because those properties do not exist on the object
	// returned by getBoundingClientRect() in older versions of IE
	var elementPosition = element.offset(),
		elementBox = element[0].getBoundingClientRect(),
		elementWidth = elementBox.right - elementBox.left,
		elementHeight = elementBox.bottom - elementBox.top;

	return session.currentX >= elementPosition.left &&
		session.currentX <= elementPosition.left + elementWidth &&
		session.currentY >= elementPosition.top &&
		session.currentY <= elementPosition.top + elementHeight;
}

/**
 * Fetches the tooltip content from the specified element's data attributes.
 * @private
 * @param {jQuery} element The element to get the tooltip content for.
 * @return {(string|jQuery|undefined)} The text/HTML string, jQuery object, or
 *     undefined if there was no tooltip content for the element.
 */
function getTooltipContent(element) {
	var tipText = element.data(DATA_POWERTIP),
		tipObject = element.data(DATA_POWERTIPJQ),
		tipTarget = element.data(DATA_POWERTIPTARGET),
		targetElement,
		content;

	if (tipText) {
		if ($.isFunction(tipText)) {
			tipText = tipText.call(element[0]);
		}
		content = tipText;
	} else if (tipObject) {
		if ($.isFunction(tipObject)) {
			tipObject = tipObject.call(element[0]);
		}
		if (tipObject.length > 0) {
			content = tipObject.clone(true, true);
		}
	} else if (tipTarget) {
		targetElement = $('#' + tipTarget);
		if (targetElement.length > 0) {
			content = targetElement.html();
		}
	}

	return content;
}

/**
 * Finds any viewport collisions that an element (the tooltip) would have if it
 * were absolutely positioned at the specified coordinates.
 * @private
 * @param {CSSCoordinates} coords Coordinates for the element.
 * @param {number} elementWidth Width of the element in pixels.
 * @param {number} elementHeight Height of the element in pixels.
 * @return {number} Value with the collision flags.
 */
function getViewportCollisions(coords, elementWidth, elementHeight) {
	var viewportTop = session.scrollTop,
		viewportLeft =  session.scrollLeft,
		viewportBottom = viewportTop + session.windowHeight,
		viewportRight = viewportLeft + session.windowWidth,
		collisions = Collision.none;

	if (coords.top < viewportTop || Math.abs(coords.bottom - session.windowHeight) - elementHeight < viewportTop) {
		collisions |= Collision.top;
	}
	if (coords.top + elementHeight > viewportBottom || Math.abs(coords.bottom - session.windowHeight) > viewportBottom) {
		collisions |= Collision.bottom;
	}
	if (coords.left < viewportLeft || coords.right + elementWidth > viewportRight) {
		collisions |= Collision.left;
	}
	if (coords.left + elementWidth > viewportRight || coords.right < viewportLeft) {
		collisions |= Collision.right;
	}

	return collisions;
}

/**
 * Counts the number of bits set on a flags value.
 * @param {number} value The flags value.
 * @return {number} The number of bits that have been set.
 */
function countFlags(value) {
	var count = 0;
	while (value) {
		value &= value - 1;
		count++;
	}
	return count;
}
<commit_msg>Added private annotation to isMouseEvent().<commit_after>/**
 * PowerTip Utility Functions
 *
 * @fileoverview  Private helper functions.
 * @link          http://stevenbenner.github.com/jquery-powertip/
 * @author        Steven Benner (http://stevenbenner.com/)
 * @requires      jQuery 1.7+
 */

/**
 * Determine whether a jQuery object is an SVG element
 * @private
 * @param {jQuery} element The element to check
 * @return {boolean} Whether this is an SVG element
 */
function isSvgElement(element) {
	return window.SVGElement && element[0] instanceof SVGElement;
}

/**
 * Determines if the specified jQuery.Event object has mouse data.
 * @private
 * @param {jQuery.Event=} event The jQuery.Event object to test.
 * @return {boolean} True if there is mouse data, otherwise false.
 */
function isMouseEvent(event) {
	return !!event && typeof event.pageX === 'number';
}

/**
 * Initializes the viewport dimension cache and hooks up the mouse position
 * tracking and viewport dimension tracking events.
 * Prevents attaching the events more than once.
 * @private
 */
function initTracking() {
	if (!session.mouseTrackingActive) {
		session.mouseTrackingActive = true;

		// grab the current viewport dimensions on load
		getViewportDimensions();
		$(getViewportDimensions);

		// hook mouse move tracking
		$document.on('mousemove' + EVENT_NAMESPACE, trackMouse);

		// hook viewport dimensions tracking
		$window.on('resize' + EVENT_NAMESPACE, trackResize);
		$window.on('scroll' + EVENT_NAMESPACE, trackScroll);
	}
}

/**
 * Updates the viewport dimensions cache.
 * @private
 */
function getViewportDimensions() {
	session.scrollLeft = $window.scrollLeft();
	session.scrollTop = $window.scrollTop();
	session.windowWidth = $window.width();
	session.windowHeight = $window.height();
}

/**
 * Updates the window size info in the viewport dimensions cache.
 * @private
 */
function trackResize() {
	session.windowWidth = $window.width();
	session.windowHeight = $window.height();
}

/**
 * Updates the scroll offset info in the viewport dimensions cache.
 * @private
 */
function trackScroll() {
	var x = $window.scrollLeft(),
		y = $window.scrollTop();
	if (x !== session.scrollLeft) {
		session.currentX += x - session.scrollLeft;
		session.scrollLeft = x;
	}
	if (y !== session.scrollTop) {
		session.currentY += y - session.scrollTop;
		session.scrollTop = y;
	}
}

/**
 * Saves the current mouse coordinates to the session object.
 * @private
 * @param {jQuery.Event} event The mousemove event for the document.
 */
function trackMouse(event) {
	session.currentX = event.pageX;
	session.currentY = event.pageY;
}

/**
 * Tests if the mouse is currently over the specified element.
 * @private
 * @param {jQuery} element The element to check for hover.
 * @return {boolean}
 */
function isMouseOver(element) {
	// use getBoundingClientRect() because jQuery's width() and height()
	// methods do not work with SVG elements
	// compute width/height because those properties do not exist on the object
	// returned by getBoundingClientRect() in older versions of IE
	var elementPosition = element.offset(),
		elementBox = element[0].getBoundingClientRect(),
		elementWidth = elementBox.right - elementBox.left,
		elementHeight = elementBox.bottom - elementBox.top;

	return session.currentX >= elementPosition.left &&
		session.currentX <= elementPosition.left + elementWidth &&
		session.currentY >= elementPosition.top &&
		session.currentY <= elementPosition.top + elementHeight;
}

/**
 * Fetches the tooltip content from the specified element's data attributes.
 * @private
 * @param {jQuery} element The element to get the tooltip content for.
 * @return {(string|jQuery|undefined)} The text/HTML string, jQuery object, or
 *     undefined if there was no tooltip content for the element.
 */
function getTooltipContent(element) {
	var tipText = element.data(DATA_POWERTIP),
		tipObject = element.data(DATA_POWERTIPJQ),
		tipTarget = element.data(DATA_POWERTIPTARGET),
		targetElement,
		content;

	if (tipText) {
		if ($.isFunction(tipText)) {
			tipText = tipText.call(element[0]);
		}
		content = tipText;
	} else if (tipObject) {
		if ($.isFunction(tipObject)) {
			tipObject = tipObject.call(element[0]);
		}
		if (tipObject.length > 0) {
			content = tipObject.clone(true, true);
		}
	} else if (tipTarget) {
		targetElement = $('#' + tipTarget);
		if (targetElement.length > 0) {
			content = targetElement.html();
		}
	}

	return content;
}

/**
 * Finds any viewport collisions that an element (the tooltip) would have if it
 * were absolutely positioned at the specified coordinates.
 * @private
 * @param {CSSCoordinates} coords Coordinates for the element.
 * @param {number} elementWidth Width of the element in pixels.
 * @param {number} elementHeight Height of the element in pixels.
 * @return {number} Value with the collision flags.
 */
function getViewportCollisions(coords, elementWidth, elementHeight) {
	var viewportTop = session.scrollTop,
		viewportLeft =  session.scrollLeft,
		viewportBottom = viewportTop + session.windowHeight,
		viewportRight = viewportLeft + session.windowWidth,
		collisions = Collision.none;

	if (coords.top < viewportTop || Math.abs(coords.bottom - session.windowHeight) - elementHeight < viewportTop) {
		collisions |= Collision.top;
	}
	if (coords.top + elementHeight > viewportBottom || Math.abs(coords.bottom - session.windowHeight) > viewportBottom) {
		collisions |= Collision.bottom;
	}
	if (coords.left < viewportLeft || coords.right + elementWidth > viewportRight) {
		collisions |= Collision.left;
	}
	if (coords.left + elementWidth > viewportRight || coords.right < viewportLeft) {
		collisions |= Collision.right;
	}

	return collisions;
}

/**
 * Counts the number of bits set on a flags value.
 * @param {number} value The flags value.
 * @return {number} The number of bits that have been set.
 */
function countFlags(value) {
	var count = 0;
	while (value) {
		value &= value - 1;
		count++;
	}
	return count;
}
<|endoftext|>