<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'MemoModal',
  extends: 'foam.u2.View',

  documentation: 'View for attaching a memo to an object',

  imports: [
    'notify'
  ],

  requires: [
    'foam.u2.ControllerMode'
  ],

  css: `
    ^ {
      border-radius: 3px;
      background-color: #fff;
      /* Don't let the modal exceed the screen size, minus some margin. */
      max-width: calc(100vw - 48px);
      max-height: calc(100vh - 116px);
      overflow-y: scroll;
      /* The line below accounts for the top nav bar. */
    }
    ^title {
      padding: 32px 0px;
      font-size: 1.5em;
      font-weight: bold;
    }
    ^main {
      padding: 0px 32px;
    }
    ^ .buttons {
      padding: 32px;
      box-sizing: border-box;
      display: flex;
      justify-content: flex-end;
    }
  `,

  messages: [
    { name: 'CONFIRM_DELETE_1', message: 'Are you sure you want to delete' },
    { name: 'SUCCESS_MSG', message: ' deleted.' },
    { name: 'FAIL_MSG', message: 'Failed to delete' }
  ],

  properties: [
    {
      class: 'Function',
      name: 'onExecute'
    },
    {
      class: 'String',
      name: 'title',
      expression: isMemoRequired => {
        if (isMemoRequired)
          return "Please provide a note (required)";
        return "Please provide a note (optional)";
      }
    },
    {
      class: 'String',
      name: 'memo',
      view: {
        class: 'foam.u2.tag.TextArea',
        rows: '8px',
        onKey: true
      }
    },
    {
      class: 'Boolean',
      name: 'isMemoRequired'
    }
  ],

  methods: [
    function initE() {
      this.SUPER();
      this
        .addClass(this.myClass())
        .start()
          .addClass(this.myClass('main'))
          .start()
            .addClass(this.myClass("title"))
            .add(this.title)
          .end()
          .startContext({ data: this, controllerMode: this.ControllerMode.EDIT })
            .tag(this.MEMO)
          .endContext()
        .end()
        .start()
          .addClass('buttons')
          .startContext({ data: this })
            .tag(this.CANCEL, { buttonStyle: 'SECONDARY' })
            .tag(this.OK)
          .endContext()
        .end();
    }
  ],

  actions: [
    {
      name: 'ok',
      isEnabled: (isMemoRequired, memo) => {
        if ( ! isMemoRequired ) return true;

        return memo != "";
      },
      code: function(X) {
        this.onExecute(this.memo);

        X.closeDialog();
      }
    },
    {
      name: 'cancel',
      code: function(X) {
        X.closeDialog();
      }
    }
  ]
});
<commit_msg>simple fix that was causing system confusion on approvals (#3556)<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'MemoModal',
  extends: 'foam.u2.View',

  documentation: 'View for attaching a memo to an object',

  imports: [
    'notify'
  ],

  requires: [
    'foam.u2.ControllerMode'
  ],

  css: `
    ^ {
      border-radius: 3px;
      background-color: #fff;
      /* Don't let the modal exceed the screen size, minus some margin. */
      max-width: calc(100vw - 48px);
      max-height: calc(100vh - 116px);
      overflow-y: scroll;
      /* The line below accounts for the top nav bar. */
    }
    ^title {
      padding: 32px 0px;
      font-size: 1.5em;
      font-weight: bold;
    }
    ^main {
      padding: 0px 32px;
    }
    ^ .buttons {
      padding: 32px;
      box-sizing: border-box;
      display: flex;
      justify-content: flex-end;
    }
  `,

  messages: [
    { name: 'CONFIRM_DELETE_1', message: 'Are you sure you want to delete' },
    { name: 'SUCCESS_MSG', message: ' deleted.' },
    { name: 'FAIL_MSG', message: 'Failed to delete' }
  ],

  properties: [
    {
      class: 'Function',
      name: 'onExecute'
    },
    {
      class: 'String',
      name: 'title',
      expression: function(isMemoRequired) {
        if ( isMemoRequired ) return 'Please provide a note (required)';
        return 'Please provide a note (optional)';
      }
    },
    {
      class: 'String',
      name: 'memo',
      view: {
        class: 'foam.u2.tag.TextArea',
        rows: '8px',
        onKey: true
      }
    },
    {
      class: 'Boolean',
      name: 'isMemoRequired'
    }
  ],

  methods: [
    function initE() {
      this.SUPER();
      this
        .addClass(this.myClass())
        .start()
          .addClass(this.myClass('main'))
          .start()
            .addClass(this.myClass('title'))
            .add(this.title)
          .end()
          .startContext({ data: this, controllerMode: this.ControllerMode.EDIT })
            .tag(this.MEMO)
          .endContext()
        .end()
        .start()
          .addClass('buttons')
          .startContext({ data: this })
            .tag(this.CANCEL, { buttonStyle: 'SECONDARY' })
            .tag(this.OK)
          .endContext()
        .end();
    }
  ],

  actions: [
    {
      name: 'ok',
      isEnabled: (isMemoRequired, memo) => {
        if ( ! isMemoRequired ) return true;
        return memo != '';
      },
      code: function(X) {
        this.onExecute(this.memo);
        X.closeDialog();
      }
    },
    {
      name: 'cancel',
      code: function(X) {
        X.closeDialog();
      }
    }
  ]
});
<|endoftext|>


<commit_before>import React from 'react';
import { Form, Divider, Header, Tab } from 'semantic-ui-react';

// PROJECT COMPONENTS
import { FormPartsContainer, IntervalColumnHeadings, CashFlowRow } from './formHelpers';
import { BenefitsTable } from './BenefitsTable';
import { StackedBarGraph } from './output/StackedBarGraph';
import {
  GraphHolder,
  GrossGraph,
  BenefitGraph,
} from './ResultsGraph';

// COMPONENT HELPER FUNCTIONS
import { getTimeSetter } from '../utils/getTimeSetter';

// ========================================
// COMPONENTS
// ========================================
/** @todo description
*
* @function
* @param {object} props Values described below
* @property {object} props.future Client future/predictive data.
* @property {string} props.time Used in class names. Meant to make
*     this more easily decoupled in future.
* @property {function} props.setClientProperty Update client state
*     values.
*
* @returns {class} Component
*/
const IncomeForm = function ({ future, time, setClientProperty }) {

  var type = 'income';

  /**
  * As per Project Hope input, for the first prototype we're only
  * including the ability to change earned income.
  */
  return (

      <IntervalColumnHeadings type={ type } />
      <CashFlowRow
        timeState={ future }
				  type={ type }
				  time={ time }
				  setClientProperty={ setClientProperty }
				  generic='earned'
				  labelInfo='(Weekly income = hourly wage times average number of work hours per week)'>
          How much money would you get paid in the future? (You can try different amounts)
      </CashFlowRow>

  );
};  // End IncomeForm() Component

const TabbedVisualizations = ({ client }) => {
  return (
  // Benefit Courses, Tracks, Routes, Traces, Progressions, Progress, Trajectories, Changes
    <Tab
      menu={{ color: 'teal',  attached: true, tabular: true }}
      panes={ [
        { menuItem: 'Changes', render: () => {return <Tab.Pane><BenefitsTable client={ client } /></Tab.Pane>;} },
        { menuItem: 'Changes Chart', render: () => {return <Tab.Pane><StackedBarGraph client={ client } /></Tab.Pane>;} },
        {
          menuItem: 'Stacked Incomes',
          render:   () => {
            return (
              <Tab.Pane>
                <GraphHolder
                  client={ client }
                  Graph={ GrossGraph } />
              </Tab.Pane>
            );
          },
        },
        {
          menuItem: 'Benefit Programs',
          render:   () => {
            return (
              <Tab.Pane>
                <GraphHolder
                  client={ client }
                  Graph={ BenefitGraph } />
              </Tab.Pane>
            );
          },
        },
      ] } />
  );
};

/** @todo Abstract all the step components?
 *
 * @function
 * @param {object} props See below.
 * @property {function} props.changeClient Updates state upstream.
 * @property {function} props.translate Uses user chosen language-specific
 *    snippets.
 * @property {object} props.client JSON object with future and current values.
 * @property {function} props.nextStep Go to next form section.
 * @property {function} props.previousStep Go to previous form section.
 *
 * @returns {object} Component
 */
const PredictionsStep = function (props) {

  const setTimeProp = getTimeSetter('future', props.changeClient);

  /** @todo Are these titles accurate now? */
  return (
    <Form className = 'income-form flex-item flex-column'>
      <FormPartsContainer
        title     = 'What Might Happen?'
        clarifier = { null }
        left      = {{ name: 'Previous', func: props.previousStep }}
        right     = {{ name: 'New Client', func: props.resetClient }}>
        <IncomeForm
          setClientProperty ={ setTimeProp }
          future            ={ props.client.future }
          time              ={ 'future' } />
        <Divider className='ui section divider hidden' />

            With the new pay, how could your benefits change?

        <TabbedVisualizations client={ props.client } />
      </FormPartsContainer>
    </Form>
  );
};  // End FutureIncomeStep() Component

export { PredictionsStep };
                                              <commit_msg>Lints<commit_after>import React from 'react';
import { Form, Divider, Header, Tab } from 'semantic-ui-react';

// PROJECT COMPONENTS
import { FormPartsContainer, IntervalColumnHeadings, CashFlowRow } from './formHelpers';
import { BenefitsTable } from './BenefitsTable';
import { StackedBarGraph } from './output/StackedBarGraph';
import {
  GraphHolder,
  GrossGraph,
  BenefitGraph,
} from './ResultsGraph';

// COMPONENT HELPER FUNCTIONS
import { getTimeSetter } from '../utils/getTimeSetter';

// ========================================
// COMPONENTS
// ========================================
/** @todo description
*
* @function
* @param {object} props Values described below
* @property {object} props.future Client future/predictive data.
* @property {string} props.time Used in class names. Meant to make
*     this more easily decoupled in future.
* @property {function} props.setClientProperty Update client state
*     values.
*
* @returns {class} Component
*/
const IncomeForm = function ({ future, time, setClientProperty }) {

  var type = 'income';

  /**
  * As per Project Hope input, for the first prototype we're only
  * including the ability to change earned income.
  */
  return (

      <IntervalColumnHeadings type={ type } />
      <CashFlowRow
        timeState={ future }
				  type={ type }
				  time={ time }
				  setClientProperty={ setClientProperty }
				  generic='earned'
				  labelInfo='(Weekly income = hourly wage times average number of work hours per week)'>
          How much money would you get paid in the future? (You can try different amounts)
      </CashFlowRow>

  );
};  // End IncomeForm() Component

const TabbedVisualizations = ({ client }) => {
  return (
  // Benefit Courses, Tracks, Routes, Traces, Progressions, Progress, Trajectories, Changes
    <Tab
      menu={{ color: 'teal',  attached: true, tabular: true }}
      panes={ [
        { menuItem: 'Changes', render: () => {return <Tab.Pane><BenefitsTable client={ client } /></Tab.Pane>;} },
        { menuItem: 'Changes Chart', render: () => {return <Tab.Pane><StackedBarGraph client={ client } /></Tab.Pane>;} },
        {
          menuItem: 'Stacked Incomes',
          render:   () => {
            return (
              <Tab.Pane>
                <GraphHolder
                  client={ client }
                  Graph={ GrossGraph } />
              </Tab.Pane>
            );
          },
        },
        {
          menuItem: 'Benefit Programs',
          render:   () => {
            return (
              <Tab.Pane>
                <GraphHolder
                  client={ client }
                  Graph={ BenefitGraph } />
              </Tab.Pane>
            );
          },
        },
      ] } />
  );
};

/** @todo Abstract all the step components?
 *
 * @function
 * @param {object} props See below.
 * @property {function} props.changeClient Updates state upstream.
 * @property {function} props.translate Uses user chosen language-specific
 *    snippets.
 * @property {object} props.client JSON object with future and current values.
 * @property {function} props.nextStep Go to next form section.
 * @property {function} props.previousStep Go to previous form section.
 *
 * @returns {object} Component
 */
const PredictionsStep = function (props) {

  const setTimeProp = getTimeSetter('future', props.changeClient);

  /** @todo Are these titles accurate now? */
  return (
    <Form className = 'income-form flex-item flex-column'>
      <FormPartsContainer
        title     = 'What Might Happen?'
        clarifier = { null }
        left      = {{ name: 'Previous', func: props.previousStep }}
        right     = {{ name: 'New Client', func: props.resetClient }}>
        <IncomeForm
          setClientProperty ={ setTimeProp }
          future            ={ props.client.future }
          time              ={ 'future' } />
        <Divider className='ui section divider hidden' />

            With the new pay, how could your benefits change?

        <TabbedVisualizations client={ props.client } />
      </FormPartsContainer>
    </Form>
  );
};  // End FutureIncomeStep() Component

export { PredictionsStep };

<|endoftext|>


<commit_before>// REACT COMPONENTS
import React from 'react';
import {
  // Generic Form stuff
	Button,
  Grid,
  Header,
  Segment,
  Divider,
  Form,
  Input,
  Checkbox
  // Money columns
} from 'semantic-ui-react';

// UTILITIES
import { roundMoney, limit, toMonthlyAmount } from '../helpers/math';

// ========================================
// GENERIC COMPONENTS
// ========================================

/** Returns a Grid Column containing a button of the style used
* to navigate backwards and forwards through steps of the form.
*
* @function
* @param {object} props
* @property {object} props.func - A function that is run when the
* button is clicked.
* @property {string} props.name - The text to be displayed on
* the button.
*
* @returns Component
*/
const BottomButton = function(props){
  return (
    <Grid.Column className='large-bottom-button' width={3}>
      <Button color='teal' fluid size='large' onClick={props.func}>
        { props.children }
      </Button>
    </Grid.Column>
  );
};  // End BottomButton() Component

/** The row containing the 'Previous' and 'Next' buttons at the
* bottom of each form page.
*
* @function
* @param {object} propsContainer - Containing the `props` object in
* another object seemed to be the only way to pass on `props`.
* @property {object} propsContainer.props - Properties needed
* for the functional aspects of the Component. @todo Move the
* functions `previousStep()` and `nextStep()` into here if possible.
* @property {object} propsContainer.props.previousStep() - Function
* that goes back to the previous form element.
* @property {object} propsContainer.props.nextStep() - Function
* that goes forward to the next form element.
*
* @todo Use this somehow for the first page and last page too,
* but then hide the 'previous' and the 'next' respectively? Or
* split those into their own Components?
*
* @returns Component
*/
const BottomButtons = function(props){
  return (
    <Grid textAlign='center' verticalAlign='middle'>
      <Grid.Row>
        { !props.left
          ? <Grid.Column className='large-bottom-button' width={3}/>
          : <BottomButton func={props.left.func}>{ props.left.name }</BottomButton>
        }
        <Grid.Column width={10} />
        { !props.right
          ? <Grid.Column className='large-bottom-button' width={3}/>
          : <BottomButton func={props.right.func}>{ props.right.name }</BottomButton>

        }
      </Grid.Row>
    </Grid>
  );
};  // End BottomButtons() Component

/** Constructor for all the stuff that's supposed to go inside
* the Form Component. Does not include the `<Form>` Component
* as a container because it looks like that needs to be unique
* (the 'CurrentBenefitsStep' gives it `size='massive'`).
*
* @function
* @param {object} props
* @property {string} props.title - Text to go in the `h1` element.
* @property {string} props.clarifier - Text to go in the `h3`
* element, giving some description, instructions, or clarifications.
* @property {string} props.children - Component(s) to be inserted
* into the middle - a custom form section containing inputs, etc.
* @property {Object} props.next - the 'next form section' function
* @property {Object} props.prev - the 'previous form section' function
*
* @returns Component
*/
const FormPartsContainer = function(props) {
  return (
    <Segment padded='very' style={{ minHeight: '600px' }}>
      <Segment style={{ minHeight: '500px' }} basic={true}>

          { props.title }

        { !props.clarifier
          ? null
          :
              { props.clarifier }

        }

        { props.children }

      </Segment>
      <Divider />
      <BottomButtons left={props.left} right={props.right} next={props.next} prev={props.prev} />

    </Segment>
  );
};  // End FormPartsContainer() Component

/** Toggle with size='massive'
*
* @function
* @param {object} props - sent from attributes of previous element
* @property {string} props.label - Text displayed next to toggle
* @property {string} props.value - Should reflect state change and
* will be displayed as the value. Flow goes:
* user input > state > value > visual feedback
* @property {object} props.storeChecked - Function that changes page
* state (changes the "source of truth")
* @property {string} props.name - The key of the client property that
* will be saved and will be used to fill in the field's value.
*
* @returns Component
*/
const MassiveToggle = function (props) {

  var time = props.time || '';

  /** @todo Switch props.storeChecked to props.onChange everywhere */
  return (
    <Form.Field className='massive-toggle'
      toggle
      label={ props.label }
      checked={ props.checked || props.value }
      onChange={ props.onChange || props.storeChecked }
      name={ props.name || props.name }
      control={ Checkbox }
      size='massive'/>
  );

};  // End MassiveToggle{} Component

/** A clearer way than a ternary operator to have a possible
* subheader. Also, clearer for separate styling
*
* @function
* @param {object} props
* @property {object} props.children - Contents of this element
*
* @returns Component
*/
const FormSubheading = function ( props ) {

  if ( !props.children ) { return null; }

  return (
    <wrapper className = { 'form-subheading' }
      style={{ display: 'block', textAlign: 'left' }}>
        { props.children }
    </wrapper>
  );

};  // End FormSubheading{} Component

/** @todo description
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
const FormHeading = function ({ subheading, children }) {

  if ( !children ) { return null; }

  return (
    <wrapper className={'form-heading'} >

 {/** div here to make sure header margin doesn\'t collapse */}

        { children }

      <FormSubheading>{subheading}</FormSubheading>

    </wrapper>
  );

};  // End FormHeading{} Component

/** @todo description
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
const InlineLabelInfo = function ( props ) {

  var labelInfoDisplay = ' hidden'; // Will be '' in future

  // return (
  //   <wrapper className = { 'label-info info-revealer' + labelInfoDisplay } style = {{
  //     position: 'relative', top: '-0.5em',
  //     marginLeft: '1em', padding: '0.1em 0.2em',
  //     textAlign: 'left', verticalAlign: 'middle',
  //     border: '1px solid black'
  //   }}>
  //     <wrapper className='info-indicator'>i</wrapper>
  //     <wrapper className='info-tooltip'>{props.children}</wrapper>
  //   </wrapper>
  // );

  return (
    <wrapper className = { 'label-info' + labelInfoDisplay }
      style = {{ marginLeft: '1em' }}>
        { props.children }
    </wrapper>
  );

};  // End InlineLabelInfo{} Component

// Possible tooltip version of labels:
// (could be made official in the Row creator with conditionals)
// Earned Income
//
//

//
i

//

//       Weekly income = hourly wage times average number of work hours per week
//

//

//

// ========================================
// MONEY ON INTERVALS COLUMNS COMPONENTS
// ========================================

// Ideas of how to handle a different styling situation (if the designers switch columns)

// If we want more control over placement, we may look into this:
// <Grid textAlign='center' verticalAlign='middle'>
//   <Grid.Row className='inputs-in-right-column'>
//     <Grid.Column className='left-label'>
//       Earned Income
//     </Grid.Column>
//     <Grid.Column className='right-input'>
//       <Input type='number'/>
//     </Grid.Column>
//   </Grid.Row>
// </Grid>

// <Form.Field inline>
//   Income Source
//

//     Income Source
//     Income Source
//     Income Source
//

//   <Input
//     type='number'
//     onChange={props.storeComplex}
//     className='right-column'
//     name='Earned Income' placeholder='Earned Income'
//   />

/** @todo description
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
const ColumnHeading = function ({ type, colName, style, children }) {
  var classes = type + '-column header ' + colName;
  return (
    {children}
  );
};  // End ColumnHeading()

/** @todo description
*
* @function
* @param {object} props
* @property {object} __ - explanation
*
* @returns Component
*/
const IntervalColumnHeadings = function ({ type }) {

  var baseStyles  = {
        marginTop: '0.7em', marginBottom: '0.7em',
        display: 'inline-block', fontSize: '14px'
      },
      inputStyles  = { ...baseStyles, width: '7em', textAlign: 'center' },
      lefterStyles = { ...inputStyles, marginRight: '0.2em' },
      rightStyles  = { ...inputStyles, marginRight: '0.9em' };

  return (
    <wrapper style={{ display: 'inline-block' }}>
      <ColumnHeading type={type} colName='weekly'  style={lefterStyles}>Weekly</ColumnHeading>
      <ColumnHeading type={type} colName='monthly' style={lefterStyles}>Monthly</ColumnHeading>
      <ColumnHeading type={type} colName='yearly'  style={rightStyles}>Yearly</ColumnHeading>
      <ColumnHeading type={type} colName={type} style={baseStyles}>Expense Type</ColumnHeading>
    </wrapper>
  );

};  // End IntervalColumnHeadings{} Component

/** @todo description
*
* @todo Make this more generic and make the caller
* handle more of the specifics.
*
* @function
* @param {object} props
* @property {object} __ - explanation
*
* @returns Component
*/
const CashFlowInput = function ({ interval, generic, time, type, store, value, style, id, name }) {

  var handleChange = function ( evnt, inputProps ) {

    var name    = time + generic + 'Monthly',
        monthly = toMonthlyAmount[ interval ]( evnt, evnt.target.value ),
        obj     = { name: name , value: monthly };

    store( evnt, obj );

  };  // End handleChange()

  /** @todo Different class for something current that has a previous value that isn't 0 */

  return (
    <Input
      className = { type + ' cashflow-column ' + interval }
      onChange  = { handleChange }
      value     = { value }
      style     = { style }
      name      = { name }
      type      = { 'number' } step = { '0.01' } min = { '0' }
    />
  );

};  // End CashFlowInput{} Component

/** @todo description
*
* @function
* @param {object} props
* @property {object} __ - explanation
*
* @returns Component
*/
const CashFlowRow = function ({ generic, client, storeComplex, children, labelInfo, type, time }) {

  var lefter  = { width: '7em', marginRight: '.2em' },
      righter = { width: '7em', marginRight: '.9em' },
      time    = time,
      sharedProps = {
        type: type, time: time,
        store: storeComplex, generic: generic
      };

  /** baseVal
  * Get the time ('current' or 'previous') monthly value unless there is
  * none, in which case, get the previous monthly cash flow value (to
  * prefill current values with previous ones if needed).
  *
  * @var
  *
  * @todo Add some kind of UI indication when it's the same as the previous
  * value. What if some of the row's values are the same and some are
  * different?
  */
  var generic     = generic,
      intervalID  = generic + 'Monthly',
      baseVal     = client[ time + intervalID ];

  if ( !baseVal ) { baseVal = client[ 'previous' + intervalID ] || ''; }

  // Could use `capitalizeWord()` in CashFlowInput to use `type`
  // to get id, but doesn't seem worth it at the moment.
  // Maybe put some of these in an object?
  return (
    <Form.Field inline>
      <CashFlowInput
        type     = { type }
        time     = { time }
        interval = { 'weekly' }
        value    = { roundMoney( baseVal / 4.33 ) || '' }
        store    = { storeComplex }
        generic  = { generic }
        name     = { time + generic + 'Weekly' }
        style    = { lefter }
      />
      <CashFlowInput
        type     = { type }
        time     = { time }
        interval = { 'monthly' }
        value    = { roundMoney( baseVal ) || '' }
        store    = { storeComplex }
        generic  = { generic }
        name     = { time + generic + 'Monthly' }
        style    = { lefter }
      />
      <CashFlowInput
        type     = { type }
        time     = { time }
        interval = { 'yearly' }
        value    = { roundMoney( baseVal * 12 ) || '' }
        store    = { storeComplex }
        generic  = { generic }
        name     = { time + generic + 'Yearly' }
        style    = { righter }
      />
      <wrapper>
        { children }
        <InlineLabelInfo>{ labelInfo }</InlineLabelInfo>
      </wrapper>
    </Form.Field>
  );

};  // End CashFlowRow{} Component

/** @todo Separate into different files? */
export {
  BottomButtons, FormPartsContainer, BottomButton,
  MassiveToggle, FormSubheading, FormHeading,
  InlineLabelInfo,
  IntervalColumnHeadings, ColumnHeading, CashFlowInput,
  CashFlowRow
};
<commit_msg>Edit to remove warnings<commit_after>// REACT COMPONENTS
import React from 'react';
import {
  // Generic Form stuff
	Button,
  Grid,
  Header,
  Segment,
  Divider,
  Form,
  Input,
  Checkbox
  // Money columns
} from 'semantic-ui-react';

// UTILITIES
import { roundMoney, toMonthlyAmount } from '../helpers/math';

// ========================================
// GENERIC COMPONENTS
// ========================================

/** Returns a Grid Column containing a button of the style used
* to navigate backwards and forwards through steps of the form.
*
* @function
* @param {object} props
* @property {object} props.func - A function that is run when the
* button is clicked.
* @property {string} props.name - The text to be displayed on
* the button.
*
* @returns Component
*/
const BottomButton = function(props){
  return (
    <Grid.Column className='large-bottom-button' width={3}>
      <Button color='teal' fluid size='large' onClick={props.func}>
        { props.children }
      </Button>
    </Grid.Column>
  );
};  // End BottomButton() Component

/** The row containing the 'Previous' and 'Next' buttons at the
* bottom of each form page.
*
* @function
* @param {object} propsContainer - Containing the `props` object in
* another object seemed to be the only way to pass on `props`.
* @property {object} propsContainer.props - Properties needed
* for the functional aspects of the Component. @todo Move the
* functions `previousStep()` and `nextStep()` into here if possible.
* @property {object} propsContainer.props.previousStep() - Function
* that goes back to the previous form element.
* @property {object} propsContainer.props.nextStep() - Function
* that goes forward to the next form element.
*
* @todo Use this somehow for the first page and last page too,
* but then hide the 'previous' and the 'next' respectively? Or
* split those into their own Components?
*
* @returns Component
*/
const BottomButtons = function(props){
  return (
    <Grid textAlign='center' verticalAlign='middle'>
      <Grid.Row>
        { !props.left
          ? <Grid.Column className='large-bottom-button' width={3}/>
          : <BottomButton func={props.left.func}>{ props.left.name }</BottomButton>
        }
        <Grid.Column width={10} />
        { !props.right
          ? <Grid.Column className='large-bottom-button' width={3}/>
          : <BottomButton func={props.right.func}>{ props.right.name }</BottomButton>

        }
      </Grid.Row>
    </Grid>
  );
};  // End BottomButtons() Component

/** Constructor for all the stuff that's supposed to go inside
* the Form Component. Does not include the `<Form>` Component
* as a container because it looks like that needs to be unique
* (the 'CurrentBenefitsStep' gives it `size='massive'`).
*
* @function
* @param {object} props
* @property {string} props.title - Text to go in the `h1` element.
* @property {string} props.clarifier - Text to go in the `h3`
* element, giving some description, instructions, or clarifications.
* @property {string} props.children - Component(s) to be inserted
* into the middle - a custom form section containing inputs, etc.
* @property {Object} props.next - the 'next form section' function
* @property {Object} props.prev - the 'previous form section' function
*
* @returns Component
*/
const FormPartsContainer = function(props) {
  return (
    <Segment padded='very' style={{ minHeight: '600px' }}>
      <Segment style={{ minHeight: '500px' }} basic={true}>

          { props.title }

        { !props.clarifier
          ? null
          :
              { props.clarifier }

        }

        { props.children }

      </Segment>
      <Divider />
      <BottomButtons left={props.left} right={props.right} next={props.next} prev={props.prev} />

    </Segment>
  );
};  // End FormPartsContainer() Component

/** Toggle with size='massive'
*
* @function
* @param {object} props - sent from attributes of previous element
* @property {string} props.label - Text displayed next to toggle
* @property {string} props.value - Should reflect state change and
* will be displayed as the value. Flow goes:
* user input > state > value > visual feedback
* @property {object} props.storeChecked - Function that changes page
* state (changes the "source of truth")
* @property {string} props.name - The key of the client property that
* will be saved and will be used to fill in the field's value.
*
* @returns Component
*/
const MassiveToggle = function (props) {

  /** @todo Switch props.storeChecked to props.onChange everywhere */
  return (
    <Form.Field className='massive-toggle'
      toggle
      label={ props.label }
      checked={ props.checked || props.value }
      onChange={ props.onChange || props.storeChecked }
      name={ props.name || props.name }
      control={ Checkbox }
      size='massive'/>
  );

};  // End MassiveToggle{} Component

/** A clearer way than a ternary operator to have a possible
* subheader. Also, clearer for separate styling
*
* @function
* @param {object} props
* @property {object} props.children - Contents of this element
*
* @returns Component
*/
const FormSubheading = function ( props ) {

  if ( !props.children ) { return null; }

  return (
    <wrapper className = { 'form-subheading' }
      style={{ display: 'block', textAlign: 'left' }}>
        { props.children }
    </wrapper>
  );

};  // End FormSubheading{} Component

/** @todo description
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
const FormHeading = function ({ subheading, children }) {

  if ( !children ) { return null; }

  return (
    <wrapper className={'form-heading'} >

 {/** div here to make sure header margin doesn\'t collapse */}

        { children }

      <FormSubheading>{subheading}</FormSubheading>

    </wrapper>
  );

};  // End FormHeading{} Component

/** @todo description
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
const InlineLabelInfo = function ( props ) {

  var labelInfoDisplay = ' hidden'; // Will be '' in future

  // return (
  //   <wrapper className = { 'label-info info-revealer' + labelInfoDisplay } style = {{
  //     position: 'relative', top: '-0.5em',
  //     marginLeft: '1em', padding: '0.1em 0.2em',
  //     textAlign: 'left', verticalAlign: 'middle',
  //     border: '1px solid black'
  //   }}>
  //     <wrapper className='info-indicator'>i</wrapper>
  //     <wrapper className='info-tooltip'>{props.children}</wrapper>
  //   </wrapper>
  // );

  return (
    <wrapper className = { 'label-info' + labelInfoDisplay }
      style = {{ marginLeft: '1em' }}>
        { props.children }
    </wrapper>
  );

};  // End InlineLabelInfo{} Component

// Possible tooltip version of labels:
// (could be made official in the Row creator with conditionals)
// Earned Income
//
//

//
i

//

//       Weekly income = hourly wage times average number of work hours per week
//

//

//

// ========================================
// MONEY ON INTERVALS COLUMNS COMPONENTS
// ========================================

// Ideas of how to handle a different styling situation (if the designers switch columns)

// If we want more control over placement, we may look into this:
// <Grid textAlign='center' verticalAlign='middle'>
//   <Grid.Row className='inputs-in-right-column'>
//     <Grid.Column className='left-label'>
//       Earned Income
//     </Grid.Column>
//     <Grid.Column className='right-input'>
//       <Input type='number'/>
//     </Grid.Column>
//   </Grid.Row>
// </Grid>

// <Form.Field inline>
//   Income Source
//

//     Income Source
//     Income Source
//     Income Source
//

//   <Input
//     type='number'
//     onChange={props.storeComplex}
//     className='right-column'
//     name='Earned Income' placeholder='Earned Income'
//   />

/** @todo description
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
const ColumnHeading = function ({ type, colName, style, children }) {
  var classes = type + '-column header ' + colName;
  return (
    {children}
  );
};  // End ColumnHeading()

/** @todo description
*
* @function
* @param {object} props
* @property {object} __ - explanation
*
* @returns Component
*/
const IntervalColumnHeadings = function ({ type }) {

  var baseStyles  = {
        marginTop: '0.7em', marginBottom: '0.7em',
        display: 'inline-block', fontSize: '14px'
      },
      inputStyles  = { ...baseStyles, width: '7em', textAlign: 'center' },
      lefterStyles = { ...inputStyles, marginRight: '0.2em' },
      rightStyles  = { ...inputStyles, marginRight: '0.9em' };

  return (
    <wrapper style={{ display: 'inline-block' }}>
      <ColumnHeading type={type} colName='weekly'  style={lefterStyles}>Weekly</ColumnHeading>
      <ColumnHeading type={type} colName='monthly' style={lefterStyles}>Monthly</ColumnHeading>
      <ColumnHeading type={type} colName='yearly'  style={rightStyles}>Yearly</ColumnHeading>
      <ColumnHeading type={type} colName={type} style={baseStyles}>Expense Type</ColumnHeading>
    </wrapper>
  );

};  // End IntervalColumnHeadings{} Component

/** @todo description
*
* @todo Make this more generic and make the caller
* handle more of the specifics.
*
* @function
* @param {object} props
* @property {object} __ - explanation
*
* @returns Component
*/
const CashFlowInput = function ({ interval, generic, time, type, store, value, style, id, name }) {

  var handleChange = function ( evnt, inputProps ) {

    var name    = time + generic + 'Monthly',
        monthly = toMonthlyAmount[ interval ]( evnt, evnt.target.value ),
        obj     = { name: name , value: monthly };

    store( evnt, obj );

  };  // End handleChange()

  /** @todo Different class for something current that has a previous value that isn't 0 */

  return (
    <Input
      className = { type + ' cashflow-column ' + interval }
      onChange  = { handleChange }
      value     = { value }
      style     = { style }
      name      = { name }
      type      = { 'number' } step = { '0.01' } min = { '0' }
    />
  );

};  // End CashFlowInput{} Component

/** @todo description
*
* @function
* @param {object} props
* @property {object} __ - explanation
*
* @returns Component
*/
const CashFlowRow = function ({ generic, client, storeComplex, children, labelInfo, type, time }) {

  var lefter  = { width: '7em', marginRight: '.2em' },
      righter = { width: '7em', marginRight: '.9em' };

  /** baseVal
  * Get the time ('current' or 'previous') monthly value unless there is
  * none, in which case, get the previous monthly cash flow value (to
  * prefill current values with previous ones if needed).
  *
  * @var
  *
  * @todo Add some kind of UI indication when it's the same as the previous
  * value. What if some of the row's values are the same and some are
  * different?
  */
  var intervalID  = generic + 'Monthly',
      baseVal     = client[ time + intervalID ];

  if ( !baseVal ) { baseVal = client[ 'previous' + intervalID ] || ''; }

  // Could use `capitalizeWord()` in CashFlowInput to use `type`
  // to get id, but doesn't seem worth it at the moment.
  // Maybe put some of these in an object?
  return (
    <Form.Field inline>
      <CashFlowInput
        type     = { type }
        time     = { time }
        interval = { 'weekly' }
        value    = { roundMoney( baseVal / 4.33 ) || '' }
        store    = { storeComplex }
        generic  = { generic }
        name     = { time + generic + 'Weekly' }
        style    = { lefter }
      />
      <CashFlowInput
        type     = { type }
        time     = { time }
        interval = { 'monthly' }
        value    = { roundMoney( baseVal ) || '' }
        store    = { storeComplex }
        generic  = { generic }
        name     = { time + generic + 'Monthly' }
        style    = { lefter }
      />
      <CashFlowInput
        type     = { type }
        time     = { time }
        interval = { 'yearly' }
        value    = { roundMoney( baseVal * 12 ) || '' }
        store    = { storeComplex }
        generic  = { generic }
        name     = { time + generic + 'Yearly' }
        style    = { righter }
      />
      <wrapper>
        { children }
        <InlineLabelInfo>{ labelInfo }</InlineLabelInfo>
      </wrapper>
    </Form.Field>
  );

};  // End CashFlowRow{} Component

/** @todo Separate into different files? */
export {
  BottomButtons, FormPartsContainer, BottomButton,
  MassiveToggle, FormSubheading, FormHeading,
  InlineLabelInfo,
  IntervalColumnHeadings, ColumnHeading, CashFlowInput,
  CashFlowRow
};
<|endoftext|>


<commit_before>/**
 * Copyright 2014 Solinea, Inc.
 *
 * Licensed under the Solinea Software License Agreement (goldstone),
 * Version 1.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *     http://www.solinea.com/goldstone/LICENSE.pdf
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Author: Alex Jacobs
 */

// extends UtilizationCpuView
var LogAnalysisView = UtilizationCpuView.extend({
    defaults: {
        margin: {
            top: 20,
            right: 40,
            bottom: 35,
            left: 63
        },

        // populated dynamically by
        // returned levels param of data
        filter: {
            error: true,
            warning: true,
            audit: true,
            info: true,
            debug: true
        },

        isZoomed: false

    },

    processOptions: function() {

        LogAnalysisView.__super__.processOptions.apply(this, arguments);

        var ns = this.defaults;
        ns.yAxisLabel = 'Log Events';
        ns.urlRoot = this.options.urlRoot;
    },

    processMargins: function() {
        var ns = this.defaults;
        ns.mw = ns.width - ns.margin.left - ns.margin.right;
        ns.mh = ns.height - ns.margin.top - ns.margin.bottom;
    },

    constructUrl: function() {
        var self = this;
        var ns = this.defaults;

        var seconds = (ns.end - ns.start) / 1000;
        var interval = Math.max(1, Math.floor((seconds / (ns.width / 10))));

        this.collection.url = ns.urlRoot + 'start_time=' + Math.floor(ns.start / 1000) + '&end_time=' + Math.floor(ns.end / 1000) + '&interval=' + interval + 's';
    },

    startEndToGlobalLookback: function() {
        var self = this;
        var ns = this.defaults;

        var globalLookback = $('#global-lookback-range').val();

        ns.end = +new Date();
        ns.start = ns.end - (globalLookback * 60 * 1000);
    },

    triggerSearchTable: function() {

        drawSearchTable('#log-search-table', this.defaults.start, this.defaults.end);
    },

    processListeners: function() {
        var ns = this.defaults;
        var self = this;

        this.collection.on('sync', function() {
            self.update();
        });

        this.collection.on('error', this.dataErrorMessage, this);

        this.on('refreshReached', function(params) {

            if (ns.isZoomed === true) {
                return;
            }

            ns.start = params[0];
            ns.end = params[1];

            $(this.el).find('#spinner').show();
            this.constructUrl();
            this.collection.fetchWithRemoval();

        });

        this.on('selectorChanged', function(params) {
            $(this.el).find('#spinner').show();
            ns.isZoomed = false;
            ns.start = params[0];
            ns.end = params[1];
            this.constructUrl();
            this.collection.fetchWithRemoval();
        });
    },

    standardInit: function() {
        LogAnalysisView.__super__.standardInit.apply(this, arguments);

        var ns = this.defaults;

        ns.xAxis = d3.svg.axis()
            .scale(ns.x)
            .orient("bottom")
            .ticks(7);

        this.startEndToGlobalLookback();
        this.triggerSearchTable();
        this.constructUrl();
        this.collection.fetchWithRemoval();

    },

    specialInit: function() {
        var ns = this.defaults;
        var self = this;

        // ZOOM IN
        this.$el.find('.fa-search-plus').on('click', function() {
            ns.isZoomed = true;
            $('.global-refresh-selector select').val(-1);
            console.log('clicked plus');
            self.paintNewChart([ns.width * 0.8, 0], 4);

        });

        // ZOOM IN MORE
        this.$el.find('.fa-forward').on('click', function() {
            ns.isZoomed = true;
            $('.global-refresh-selector select').val(-1);
            console.log('clicked forward');
            self.paintNewChart([ns.width * 0.8, 0], 12);

        });

        // ZOOM OUT
        this.$el.find('.fa-search-minus').on('click', function() {
            ns.isZoomed = true;
            $('.global-refresh-selector select').val(-1);
            console.log('clicked minus');
            $(self.el).find('#spinner').show();
            self.paintNewChart([ns.width * 0.6, 0], 0.45);

        });

        // ZOOM OUT MORE
        this.$el.find('.fa-backward').on('click', function() {
            ns.isZoomed = true;
            $('.global-refresh-selector select').val(-1);
            console.log('clicked fa-backward');
            $(self.el).find('#spinner').show();
            self.paintNewChart([ns.width * 0.55, 0], 0.25);
        });
    },

    paintNewChart: function(coordinates, mult) {
        console.log('coordinates', coordinates);
        $(this.el).find('#spinner').show();

        var ns = this.defaults;
        var self = this;
        var zoomedStart;
        var zoomedEnd;

        var leftMarginX = 64;
        var rightMarginX = 42;

        var adjustedClick = Math.max(0, Math.min(coordinates[0] - leftMarginX, (ns.width - leftMarginX - rightMarginX)));

        var fullDomain = [+ns.x.domain()[0], +ns.x.domain()[1]];

        var domainDiff = fullDomain[1] - fullDomain[0];

        var clickSpot = +ns.x.invert(adjustedClick);

        var zoomMult = mult || 4;

        if (zoomMult >= 1) {
            zoomedStart = clickSpot - (domainDiff / zoomMult);
            zoomedEnd = clickSpot + (domainDiff / zoomMult);
        } else {
            zoomedStart = clickSpot - (domainDiff / zoomMult);
            zoomedEnd = clickSpot + (domainDiff / zoomMult);
        }

        ns.start = zoomedStart;
        ns.end = Math.min(+new Date(), zoomedEnd);

        if (ns.end - ns.start < 1000) {
            ns.start -= 500;
            ns.end += 500;
        }

        this.constructUrl();

        this.collection.fetchWithRemoval();
        return null;
    },

    dblclicked: function(coordinates) {
        this.paintNewChart(coordinates);
    },

    collectionPrep: function() {
        var ns = this.defaults;
        var self = this;

        var data = this.collection.toJSON();

        finalData = [];

        _.each(data, function(item) {

            finalData.push({
                debug: item.debug || 0,
                audit: item.audit || 0,
                info: item.info || 0,
                warning: item.warning || 0,
                error: item.error || 0,
                date: item.time,
            });

        });

        return finalData;

    },

    sums: function(datum) {
        var ns = this.defaults;

        // Return the sums for the filters that are on
        return d3.sum(ns.color.domain().map(function(k) {

            if (ns.filter[k]) {
                return datum[k];
            } else {
                return 0;
            }
        }));
    },

    update: function() {
        LogAnalysisView.__super__.update.apply(this, arguments);

        this.hideSpinner();

        var ns = this.defaults;
        var self = this;

        // populate the modal based on the event types.
        // clear out the modal and reapply based on the unique events
        if ($(this.el).find('#populateEventFilters').length) {
            $(this.el).find('#populateEventFilters').empty();
        }

        _.each(_.keys(ns.filter), function(item) {

            if (item === 'none') {
                return null;
            }

            var addCheckIfActive = function(item) {
                if (ns.filter[item]) {
                    return 'checked';
                } else {
                    return '';
                }
            };

            var checkMark = addCheckIfActive(item);

            $(self.el).find('#populateEventFilters').
            append(

                '
' +
                '
' +
                '
' +
                '' +
                '<input id="' + item + '" type="checkbox" ' + checkMark + '>' +
                '' +
                '' + item + '' +
                '
' +
                '
' +
                '
'
            );
        });

        $(this.el).find('#populateEventFilters :checkbox').on('click', function() {
            var checkboxId = this.id;
            ns.filter[checkboxId] = !ns.filter[checkboxId];
            self.update();
        });

        this.refreshSearchTable(ns.start, ns.end, ns.filter);
        this.redraw();

    },

    refreshSearchTable: function(start, end, levels) {
        var ns = this.defaults;
        var self = this;

        var oTable,
            startTs = Math.floor(start / 1000),
            endTs = Math.floor(end / 1000),
            uri = '/intelligence/log/search/data'.concat(
                "?start_time=", startTs,
                "&end_time=", endTs);

        levels = ns.filter || {};
        for (var k in levels) {
            uri = uri.concat("&", k, "=", levels[k]);
        }

        if ($.fn.dataTable.isDataTable("#log-search-table")) {
            oTable = $("#log-search-table").DataTable();
            oTable.ajax.url(uri);
            oTable.ajax.reload();
        }
    },

    redraw: function() {

        var ns = this.defaults;
        var self = this;

        ns.y.domain([
            0,
            d3.max(ns.data.map(function(d) {
                return self.sums(d);
            }))
        ]);

        d3.select(this.el).select('.x.axis')
            .transition()
            .duration(500)
            .call(ns.xAxis.scale(ns.x));

        d3.select(this.el).select('.y.axis')
            .transition()
            .duration(500)
            .call(ns.yAxis.scale(ns.y));

        // drawSearchTable('#log-search-table', ns.start, ns.end);

    },

    template: _.template(
        '

'),

    modal2: _.template(
        // event filter modal
        '<div class="modal fade" id="modal-filter-<%= this.el.slice(1) %>' +
        '" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">' +
        '
' +
        '
' +

        // header
        '
' +
        '<button type="button" class="close" data-dismiss="modal">×Close</button>' +
        '
Log Severity Filters
' +
        '
' +

        // body
        '
' +
        '
Uncheck log-type to hide from display

' +
        '

' +
        '
' +

        // footer
        '
' +
        '<button type="button" id="eventFilterUpdateButton-<%= this.el.slice(1) %>' +
        '" class="btn btn-primary" data-dismiss="modal">Exit</button>' +
        '
' +

        '
' +
        '
' +
        '
'
    ),

    render: function() {
        this.$el.append(this.template());
        $(this.el).find('.special-icon-post').append('');
        $(this.el).find('.special-icon-pre').append('');
        $(this.el).find('.special-icon-pre').append('');
        $(this.el).find('.special-icon-pre').append('');
        $(this.el).find('.special-icon-pre').append('');
        this.$el.append(this.modal2());
        return this;
    }

});
<commit_msg>Updates relative center point when using zoom buttons.<commit_after>/**
 * Copyright 2014 Solinea, Inc.
 *
 * Licensed under the Solinea Software License Agreement (goldstone),
 * Version 1.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *     http://www.solinea.com/goldstone/LICENSE.pdf
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Author: Alex Jacobs
 */

// extends UtilizationCpuView
var LogAnalysisView = UtilizationCpuView.extend({
    defaults: {
        margin: {
            top: 20,
            right: 40,
            bottom: 35,
            left: 63
        },

        // populated dynamically by
        // returned levels param of data
        filter: {
            error: true,
            warning: true,
            audit: true,
            info: true,
            debug: true
        },

        isZoomed: false

    },

    processOptions: function() {

        LogAnalysisView.__super__.processOptions.apply(this, arguments);

        var ns = this.defaults;
        ns.yAxisLabel = 'Log Events';
        ns.urlRoot = this.options.urlRoot;
    },

    processMargins: function() {
        var ns = this.defaults;
        ns.mw = ns.width - ns.margin.left - ns.margin.right;
        ns.mh = ns.height - ns.margin.top - ns.margin.bottom;
    },

    constructUrl: function() {
        var self = this;
        var ns = this.defaults;

        var seconds = (ns.end - ns.start) / 1000;
        var interval = Math.max(1, Math.floor((seconds / (ns.width / 10))));

        this.collection.url = ns.urlRoot + 'start_time=' + Math.floor(ns.start / 1000) + '&end_time=' + Math.floor(ns.end / 1000) + '&interval=' + interval + 's';
    },

    startEndToGlobalLookback: function() {
        var self = this;
        var ns = this.defaults;

        var globalLookback = $('#global-lookback-range').val();

        ns.end = +new Date();
        ns.start = ns.end - (globalLookback * 60 * 1000);
    },

    triggerSearchTable: function() {

        drawSearchTable('#log-search-table', this.defaults.start, this.defaults.end);
    },

    processListeners: function() {
        var ns = this.defaults;
        var self = this;

        this.collection.on('sync', function() {
            self.update();
        });

        this.collection.on('error', this.dataErrorMessage, this);

        this.on('refreshReached', function(params) {

            if (ns.isZoomed === true) {
                return;
            }

            ns.start = params[0];
            ns.end = params[1];

            $(this.el).find('#spinner').show();
            this.constructUrl();
            this.collection.fetchWithRemoval();

        });

        this.on('selectorChanged', function(params) {
            $(this.el).find('#spinner').show();
            ns.isZoomed = false;
            ns.start = params[0];
            ns.end = params[1];
            this.constructUrl();
            this.collection.fetchWithRemoval();
        });
    },

    standardInit: function() {
        LogAnalysisView.__super__.standardInit.apply(this, arguments);

        var ns = this.defaults;

        ns.xAxis = d3.svg.axis()
            .scale(ns.x)
            .orient("bottom")
            .ticks(7);

        this.startEndToGlobalLookback();
        this.triggerSearchTable();
        this.constructUrl();
        this.collection.fetchWithRemoval();

    },

    specialInit: function() {
        var ns = this.defaults;
        var self = this;

        // ZOOM IN
        this.$el.find('.fa-search-plus').on('click', function() {
            ns.isZoomed = true;
            $('.global-refresh-selector select').val(-1);
            console.log('clicked plus');
            self.paintNewChart([ns.width, 0], 4);

        });

        // ZOOM IN MORE
        this.$el.find('.fa-forward').on('click', function() {
            ns.isZoomed = true;
            $('.global-refresh-selector select').val(-1);
            console.log('clicked forward');
            self.paintNewChart([ns.width, 0], 12);

        });

        // ZOOM OUT
        this.$el.find('.fa-search-minus').on('click', function() {
            ns.isZoomed = true;
            $('.global-refresh-selector select').val(-1);
            console.log('clicked minus');
            $(self.el).find('#spinner').show();
            self.paintNewChart([ns.width * 0.7, 0], 0.45);

        });

        // ZOOM OUT MORE
        this.$el.find('.fa-backward').on('click', function() {
            ns.isZoomed = true;
            $('.global-refresh-selector select').val(-1);
            console.log('clicked fa-backward');
            $(self.el).find('#spinner').show();
            self.paintNewChart([ns.width * 0.7, 0], 0.25);
        });
    },

    paintNewChart: function(coordinates, mult) {
        console.log('coordinates', coordinates);
        $(this.el).find('#spinner').show();

        var ns = this.defaults;
        var self = this;
        var zoomedStart;
        var zoomedEnd;

        var leftMarginX = 64;
        var rightMarginX = 42;

        var adjustedClick = Math.max(0, Math.min(coordinates[0] - leftMarginX, (ns.width - leftMarginX - rightMarginX)));

        var fullDomain = [+ns.x.domain()[0], +ns.x.domain()[1]];

        var domainDiff = fullDomain[1] - fullDomain[0];

        var clickSpot = +ns.x.invert(adjustedClick);

        var zoomMult = mult || 4;

        // if (zoomMult >= 1) {
        zoomedStart = Math.floor(clickSpot - (domainDiff / zoomMult));
        zoomedEnd = Math.floor(clickSpot + (domainDiff / zoomMult));
        // } else {
        // zoomedStart = Math.floor(clickSpot - (domainDiff / zoomMult));
        // zoomedEnd = Math.floor(clickSpot + (domainDiff / zoomMult));
        // }

        // debugger;
        ns.start = zoomedStart;
        ns.end = Math.min(+new Date(), zoomedEnd);

        if (ns.end - ns.start < 2000) {
            ns.start -= 1000;
            ns.end += 1000;
        }

        this.constructUrl();

        this.collection.fetchWithRemoval();
        return null;
    },

    dblclicked: function(coordinates) {
        this.paintNewChart(coordinates);
    },

    collectionPrep: function() {
        var ns = this.defaults;
        var self = this;

        var data = this.collection.toJSON();

        finalData = [];

        _.each(data, function(item) {

            finalData.push({
                debug: item.debug || 0,
                audit: item.audit || 0,
                info: item.info || 0,
                warning: item.warning || 0,
                error: item.error || 0,
                date: item.time,
            });

        });

        return finalData;

    },

    sums: function(datum) {
        var ns = this.defaults;

        // Return the sums for the filters that are on
        return d3.sum(ns.color.domain().map(function(k) {

            if (ns.filter[k]) {
                return datum[k];
            } else {
                return 0;
            }
        }));
    },

    update: function() {
        LogAnalysisView.__super__.update.apply(this, arguments);

        this.hideSpinner();

        var ns = this.defaults;
        var self = this;

        // populate the modal based on the event types.
        // clear out the modal and reapply based on the unique events
        if ($(this.el).find('#populateEventFilters').length) {
            $(this.el).find('#populateEventFilters').empty();
        }

        _.each(_.keys(ns.filter), function(item) {

            if (item === 'none') {
                return null;
            }

            var addCheckIfActive = function(item) {
                if (ns.filter[item]) {
                    return 'checked';
                } else {
                    return '';
                }
            };

            var checkMark = addCheckIfActive(item);

            $(self.el).find('#populateEventFilters').
            append(

                '
' +
                '
' +
                '
' +
                '' +
                '<input id="' + item + '" type="checkbox" ' + checkMark + '>' +
                '' +
                '' + item + '' +
                '
' +
                '
' +
                '
'
            );
        });

        $(this.el).find('#populateEventFilters :checkbox').on('click', function() {
            var checkboxId = this.id;
            ns.filter[checkboxId] = !ns.filter[checkboxId];
            self.update();
        });

        this.refreshSearchTable(ns.start, ns.end, ns.filter);
        this.redraw();

    },

    refreshSearchTable: function(start, end, levels) {
        var ns = this.defaults;
        var self = this;

        var oTable,
            startTs = Math.floor(start / 1000),
            endTs = Math.floor(end / 1000),
            uri = '/intelligence/log/search/data'.concat(
                "?start_time=", startTs,
                "&end_time=", endTs);

        levels = ns.filter || {};
        for (var k in levels) {
            uri = uri.concat("&", k, "=", levels[k]);
        }

        if ($.fn.dataTable.isDataTable("#log-search-table")) {
            oTable = $("#log-search-table").DataTable();
            oTable.ajax.url(uri);
            oTable.ajax.reload();
        }
    },

    redraw: function() {

        var ns = this.defaults;
        var self = this;

        ns.y.domain([
            0,
            d3.max(ns.data.map(function(d) {
                return self.sums(d);
            }))
        ]);

        d3.select(this.el).select('.x.axis')
            .transition()
            .duration(500)
            .call(ns.xAxis.scale(ns.x));

        d3.select(this.el).select('.y.axis')
            .transition()
            .duration(500)
            .call(ns.yAxis.scale(ns.y));

        // drawSearchTable('#log-search-table', ns.start, ns.end);

    },

    template: _.template(
        '

'),

    modal2: _.template(
        // event filter modal
        '<div class="modal fade" id="modal-filter-<%= this.el.slice(1) %>' +
        '" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">' +
        '
' +
        '
' +

        // header
        '
' +
        '<button type="button" class="close" data-dismiss="modal">×Close</button>' +
        '
Log Severity Filters
' +
        '
' +

        // body
        '
' +
        '
Uncheck log-type to hide from display

' +
        '

' +
        '
' +

        // footer
        '
' +
        '<button type="button" id="eventFilterUpdateButton-<%= this.el.slice(1) %>' +
        '" class="btn btn-primary" data-dismiss="modal">Exit</button>' +
        '
' +

        '
' +
        '
' +
        '
'
    ),

    render: function() {
        this.$el.append(this.template());
        $(this.el).find('.special-icon-post').append('');
        $(this.el).find('.special-icon-pre').append('');
        $(this.el).find('.special-icon-pre').append('');
        $(this.el).find('.special-icon-pre').append('');
        $(this.el).find('.special-icon-pre').append('');
        this.$el.append(this.modal2());
        return this;
    }

});
<|endoftext|>


<commit_before>/*

	jquery-markd
	--------

	@file	   	jquery-markd.js
	@version	0.1.0
	@date	   	30.08.12
	@author	 	Tom-Marius Olsen <tmol@dagbladet.no>

	Copyright (c) 2012 DB Medialab AS <http://www.dbmedialab.no/>

*/

(function($) {

	$.fn.markd = function(options){

		// Extend our default options with those provided.
		var opts = $.extend({}, $.fn.markd.defaults, options);

		return this.each(function(){

			$(this).addClass('markd mousetrap');

			var _element  = $(this);
			var _parser = window.marked;
			var _is_fullscreen = false;
			var preview = {};
			var offset = _element.offset();

			//Set options for marked
			_parser.setOptions({
				pedantic: opts.parser.pedantic,
				sanitize: opts.parser.sanitize
			});

			/**
			 * Get user input
			 * @param	{element}
			 * @return	{string}
			 **/
			function _getContent(el){
				if(el.is('textarea')){
					return el.val();
				} else {
					if(document.getElementById(el.attr('id')).innerText){
						var content = document.getElementById(el.attr('id')).innerText;
					} else {
						var content = document.getElementById(el.attr('id')).textContent;
					};
					//Get this, 2 spaces in a content editable actually converts to:
					//0020 00a0, meaning, "space no-break space". So, manually convert
					//no-break spaces to spaces again before handing to marked.
					//Thanks to Oscar Godson for that little tidbit.
					content = content.replace(/\u00a0/g, ' ').replace(/ /g, ' ');
					return content;
				};
			};

			/**
			 * Update user input-field
			 * @param	{element}
			 * @param	{string}
			 * @return	{void}
			 **/
			function _setContent(el, content){
				if(el.is('textarea')){
					el.val(content);
				} else {
					content = content.replace(/\n/g, '
');
					el.html(content);
				};
			};

			/**
			 * Place the cursor at a certain position
			 * @param	{element}
			 * @param	{int}
			 * @return	{void}
			 **/
			function _setCursor(el, position){
				el.selection(position, position);
			};

			/**
			 * Get content of iframe depending on browser
			 * @param	{element}
			 * @return	{element}
			 **/
			function _getIframeInnards(el) {
				return el.contentDocument || el.contentWindow.document;
			}

			/**
			 * Create a new iframe at the top of the document
			 * @param	{none}
			 * @return	{object} reference to the iframe
			 **/
			function createPreview(){
				var p = {};
				//Create a new iframe-element
				p.el = $('');
				//Add the element to the top of the document
				$('body').prepend(p.el);
				p.innards = _getIframeInnards(p.el[0]);
				//Need something for... you guessed it, Firefox
				//I have no idea why the open, write, close statements are needed in Firefox,
				//but for some reason it works once thy have been called.
				p.innards.open();
				p.innards.write('');
				p.innards.close();
				//Create aliases for the head and body inside the iframe for easy access
				p.head = $('head', p.innards);
				p.body = $('body', p.innards);
				//Add the preview-theme to the iframe
				p.head.append('<link rel="stylesheet" href="'+opts.theme+'" />');
				return p;
			};

			/**
			 * Remove and delete a iframe created using createPreview()
			 * @param	{object} object created using createPreview()
			 * @return	{object} empty object
			 **/
			function deletePreview(p){
				if(p.el != undefined) p.el.remove();
				return p = {};
			}

			/**
			 * Insert a string into a string
			 * @param	{string} original string
			 * @param	{int} 	 where to insert the string
			 * @param	{string} string to insert
			 * @return	{string} the new string
			 **/
			function insert(string, index, insert){
				if (index > 0){
					return string.substring(0, index) + insert + string.substring(index, string.length);
				} else {
					return insert + string;
				};
			};

			/**
			 * Return the selection for a element.
			 * @param	{element}
			 * @return	{object} obj.start and obj.end
			 **/
			function _getSelection(el){
				var selection = el.selection();
				if(!el.is('textarea')){
					//If we are not using a textarea newlines does not count in the selection-range,
					//so we need to add these manually.
					var t = _getContent(el).substr(0, (selection.end));
					var i = 0;
					var newlines = t.match(/\n/g);
					if( newlines != null ){
						i = t.substr(0, (selection.end+1)).match(/\n/g).length;
						selection.start += i;
						selection.end += i;
					};

				};
				return selection;
			};

			// !Bind keyboard commands when the textfield recives focus.
			_element.bind('focus', function(event){

				// !Bind tab to fake-enable tabbing
				Mousetrap.bind('tab', function(){
					var s = _getSelection(_element);
					var t = _getContent(_element);
					t = insert(t, s.end, '	');
					_setContent(_element, t);
					return false;
				});

				// !Bind ctrl-k to links
				Mousetrap.bind(opts.keyboardShortcuts.link, function(){
					var s = _getSelection(_element);
					var t = _getContent(_element);
					t = insert(t, s.end, ']()');
					t = insert(t, s.start, '[');
					_setContent(_element, t);
					return false;
				});

				// !Bind ctrl-b to bold
				Mousetrap.bind(opts.keyboardShortcuts.bold, function(){
					var s = _getSelection(_element);
					var t = _getContent(_element);
					t = insert(t, s.end, '__');
					t = insert(t, s.start, '__');
					_setContent(_element, t);
					return false;
				});

				// !Bind ctrl-i to emphasis/italic
				Mousetrap.bind(opts.keyboardShortcuts.italic, function(){
					var s = _getSelection(_element);
					var t = _getContent(_element);
					t = insert(t, s.end, '_');
					t = insert(t, s.start, '_');
					_setContent(_element, t);
					return false;
				});

				// !Bind ctrl-h as in 'help'. Opens a new window showing the markdown syntax.
				Mousetrap.bind(opts.keyboardShortcuts.help, function(){
					window.open('http://daringfireball.net/projects/markdown/syntax', '_blank');
					return false;
				});

				// !Bind ctrl-p to toggle preview text
				Mousetrap.bind(opts.keyboardShortcuts.preview, function(){
					if(!_is_fullscreen){
						if(preview.el == undefined){
							//Create a new iframe to preview the text
							preview = createPreview();
							//Place the iframe directly above the textarea
							preview.el.css({
								top: offset.top,
								left: offset.left,
								width: _element.outerWidth(),
								height: _element.outerHeight()
							});
							//Render the text into the iframe
							preview.body.html( _parser(_getContent(_element)) );
							// Bind esc to close preview
							Mousetrap.bind('esc', function(){
								//Remove and delete the preview
								preview = deletePreview(preview);
								//Unbind esc
								Mousetrap.unbind('esc');
							});
						} else {
							//Remove and delete the preview
							preview = deletePreview(preview);
							//Unbind esc
							Mousetrap.unbind('esc');
						};
					};
					//Return false to prevent default browser behavior.
					return false;
				});

				// !Bind ctrl-f as a toggle for fullscreen-mode.
				Mousetrap.bind(opts.keyboardShortcuts.fullscreen, function(){
					//Enter fullscreen
					if(!_is_fullscreen){
						//Flag fullscreen as true
						_is_fullscreen = true;
						//Give editor fullscreen styles
						_element.addClass('fullscreen');
						//Create a new iframe to preview the text
						preview = createPreview();
						//Give the preview fullscreen styles
						preview.el.addClass('fullscreen');
						//Update the preview on each keyup
						_element.bind('keyup', function(event){
							preview.body.html( _parser( _getContent(_element) ) );
						});
						// Bind esc to close fullscreen view
						Mousetrap.bind('esc', function(){
							//Flag fullscreen as false
							_is_fullscreen = false;
							//Remove fullscreen styles from editor
							_element.removeClass('fullscreen');
							//Unbind preview-update on each keyup
							_element.unbind('keyup');
							//Unbind esc
							Mousetrap.unbind('esc');
							//Remove and delete the preview
							preview = deletePreview(preview);
						});
					//Exit fullscreen
					} else {
						//Flag fullscreen as false
						_is_fullscreen = false;
						//Remove fullscreen styles from editor
						_element.removeClass('fullscreen');
						//Unbind preview-update on each keyup
						_element.unbind('keyup');
						//Unbind esc
						Mousetrap.unbind('esc');
						//Remove and delete the preview
						preview = deletePreview(preview);
					};
					//Return false to prevent default browser behavior.
					return false;
				});

			});

			// !Unbind keyboard commands and clean-up when textarea looses focus.
			_element.bind('blur', function(event){
				// Unbind the keyboard commands
				Mousetrap.unbind('tab');
				Mousetrap.unbind(opts.keyboardShortcuts.bold);
				Mousetrap.unbind(opts.keyboardShortcuts.italic);
				Mousetrap.unbind(opts.keyboardShortcuts.link);
				Mousetrap.unbind(opts.keyboardShortcuts.help);
				Mousetrap.unbind(opts.keyboardShortcuts.preview);
				Mousetrap.unbind(opts.keyboardShortcuts.fullscreen);
				// If we are not in fullscreen-mode and the preview is visible, we need to kill the preview.
				if(!_is_fullscreen) preview = deletePreview(preview);
			});

		});

	};

	// !Default options
	$.fn.markd.defaults = {
		theme: 'preview.css',
		keyboardShortcuts: {
			bold: 		'ctrl+b',
			italic: 	'ctrl+i',
			link: 		'ctrl+k',
			help:		'ctrl+h',
			preview:	'ctrl+p',
			fullscreen: 'ctrl+f'
		},
		parser: {
			pedantic: false,
			sanitize: false
		}
	};

}(jQuery));<commit_msg>Fixed issues with text selection when using contenteditable.<commit_after>/*

	jquery-markd
	--------

	@file	   	jquery-markd.js
	@version	0.1.0
	@date	   	30.08.12
	@author	 	Tom-Marius Olsen <tmol@dagbladet.no>

	Copyright (c) 2012 DB Medialab AS <http://www.dbmedialab.no/>

*/

(function($) {

	$.fn.markd = function(options){

		// Extend our default options with those provided.
		var opts = $.extend({}, $.fn.markd.defaults, options);

		return this.each(function(){

			$(this).addClass('markd mousetrap');

			var _element  = $(this);
			var _parser = window.marked;
			var _is_fullscreen = false;
			var preview = {};
			var offset = _element.offset();

			//Set options for marked
			_parser.setOptions({
				pedantic: opts.parser.pedantic,
				sanitize: opts.parser.sanitize
			});

			/**
			 * Get user input
			 * @param	{element}
			 * @return	{string}
			 **/
			function _getContent(el){
				if(el.is('textarea')){
					return el.val();
				} else {
					if(document.getElementById(el.attr('id')).innerText){
						var content = document.getElementById(el.attr('id')).innerText;
					} else {
						var content = document.getElementById(el.attr('id')).textContent;
					};
					//Get this, 2 spaces in a content editable actually converts to:
					//0020 00a0, meaning, "space no-break space". So, manually convert
					//no-break spaces to spaces again before handing to marked.
					//Thanks to Oscar Godson for that little tidbit.
					content = content.replace(/\u00a0/g, ' ').replace(/ /g, ' ');
					return content;
				};
			};

			/**
			 * Update user input-field
			 * @param	{element}
			 * @param	{string}
			 * @return	{void}
			 **/
			function _setContent(el, content){
				if(el.is('textarea')){
					el.val(content);
				} else {
					content = content.replace(/(\r\n|\n|\r)/gm, '
');
					el.html(content);
				};
			};

			/**
			 * Place the cursor at a certain position
			 * @param	{element}
			 * @param	{int}
			 * @return	{void}
			 **/
			function _setCursor(el, position){
				if(!el.is('textarea')){
					//If we are not using a textarea remove newlines
					//as these do not count.
					var t = _getContent(el).substr(0, position);
					var newlines = t.match(/(\r\n|\n|\r)/gm);
					if( newlines != null ){
						position -= newlines.length;
					};
				};
				el.selection(position, position);
			};

			/**
			 * Get content of iframe depending on browser
			 * @param	{element}
			 * @return	{element}
			 **/
			function _getIframeInnards(el) {
				return el.contentDocument || el.contentWindow.document;
			};

			/**
			 * Create a new iframe at the top of the document
			 * @param	{none}
			 * @return	{object} reference to the iframe
			 **/
			function createPreview(){
				var p = {};
				//Create a new iframe-element
				p.el = $('');
				//Add the element to the top of the document
				$('body').prepend(p.el);
				p.innards = _getIframeInnards(p.el[0]);
				//Need something for... you guessed it, Firefox
				//I have no idea why the open, write, close statements are needed in Firefox,
				//but for some reason it works once thy have been called.
				p.innards.open();
				p.innards.write('');
				p.innards.close();
				//Create aliases for the head and body inside the iframe for easy access
				p.head = $('head', p.innards);
				p.body = $('body', p.innards);
				//Add the preview-theme to the iframe
				p.head.append('<link rel="stylesheet" href="'+opts.theme+'" />');
				return p;
			};

			/**
			 * Remove and delete a iframe created using createPreview()
			 * @param	{object} object created using createPreview()
			 * @return	{object} empty object
			 **/
			function deletePreview(p){
				if(p.el != undefined) p.el.remove();
				return p = {};
			};

			/**
			 * Insert a string into a string
			 * @param	{string} original string
			 * @param	{int} 	 where to insert the string
			 * @param	{string} string to insert
			 * @return	{string} the new string
			 **/
			function insert(string, index, insert){
				if (index > 0){
					return string.substring(0, index) + insert + string.substring(index, string.length);
				} else {
					return insert + string;
				};
			};

			/**
			 * Return the selection for a element.
			 * @param	{element}
			 * @return	{object} obj.start and obj.end
			 **/
			function _getSelection(el){
				var selection = el.selection();
				if(!el.is('textarea')){
					//If we are not using a textarea newlines does not count in the selection-range,
					//so we need to add these manually.
					var t = _getContent(el).substr(0, selection.end);
					var newlines = t.match(/(\r\n|\n|\r)/gm);
					if( newlines != null ){
						selection.start += newlines.length;
						selection.end += newlines.length;
					};
				};
				return selection;
			};

			//If we are not using a textarea we need to remove all linebreaks
			//or these will be counted twice.
			if(!_element.is('textarea')){
				var t = _element.html();
				_setContent(_element, t.replace(/(\r\n|\n|\r)/gm, ''));
			};

			// !Bind keyboard commands when the textfield recives focus.
			_element.bind('focus', function(event){

				// !Bind tab to fake-enable tabbing
				Mousetrap.bind('tab', function(){
					var s = _getSelection(_element);
					var t = _getContent(_element);
					if(_element.is('textarea')){
						t = insert(t, s.end, '    ');
					} else {
						t = insert(t, s.end, '    ');
					};
					_setContent(_element, t);
					_setCursor(_element, s.end+4);
					return false;
				});

				// !Bind ctrl-k to links
				Mousetrap.bind(opts.keyboardShortcuts.link, function(){
					var s = _getSelection(_element);
					var t = _getContent(_element);
					t = insert(t, s.end, ']()');
					t = insert(t, s.start, '[');
					_setContent(_element, t);
					_setCursor(_element, s.end+3);
					return false;
				});

				// !Bind ctrl-b to bold
				Mousetrap.bind(opts.keyboardShortcuts.bold, function(){
					var s = _getSelection(_element);
					var t = _getContent(_element);
					console.log('selection end: '+ s.end);
					t = insert(t, s.end, '__');
					t = insert(t, s.start, '__');
					_setContent(_element, t);
					_setCursor(_element, s.end+4);
					return false;
				});

				// !Bind ctrl-i to emphasis/italic
				Mousetrap.bind(opts.keyboardShortcuts.italic, function(){
					var s = _getSelection(_element);
					var t = _getContent(_element);
					t = insert(t, s.end, '_');
					t = insert(t, s.start, '_');
					_setContent(_element, t);
					_setCursor(_element, s.end+2);
					return false;
				});

				// !Bind ctrl-h as in 'help'. Opens a new window showing the markdown syntax.
				Mousetrap.bind(opts.keyboardShortcuts.help, function(){
					window.open('http://daringfireball.net/projects/markdown/syntax', '_blank');
					return false;
				});

				// !Bind ctrl-p to toggle preview text
				Mousetrap.bind(opts.keyboardShortcuts.preview, function(){
					if(!_is_fullscreen){
						if(preview.el == undefined){
							//Create a new iframe to preview the text
							preview = createPreview();
							//Place the iframe directly above the textarea
							preview.el.css({
								top: offset.top,
								left: offset.left,
								width: _element.outerWidth(),
								height: _element.outerHeight()
							});
							//Render the text into the iframe
							preview.body.html( _parser(_getContent(_element)) );
							// Bind esc to close preview
							Mousetrap.bind('esc', function(){
								//Remove and delete the preview
								preview = deletePreview(preview);
								//Unbind esc
								Mousetrap.unbind('esc');
							});
						} else {
							//Remove and delete the preview
							preview = deletePreview(preview);
							//Unbind esc
							Mousetrap.unbind('esc');
						};
					};
					//Return false to prevent default browser behavior.
					return false;
				});

				// !Bind ctrl-f as a toggle for fullscreen-mode.
				Mousetrap.bind(opts.keyboardShortcuts.fullscreen, function(){
					//Enter fullscreen
					if(!_is_fullscreen){
						//Flag fullscreen as true
						_is_fullscreen = true;
						//Give editor fullscreen styles
						_element.addClass('fullscreen');
						//Create a new iframe to preview the text
						preview = createPreview();
						//Give the preview fullscreen styles
						preview.el.addClass('fullscreen');
						//Update the preview on each keyup
						_element.bind('keyup', function(event){
							preview.body.html( _parser( _getContent(_element) ) );
						});
						// Bind esc to close fullscreen view
						Mousetrap.bind('esc', function(){
							//Flag fullscreen as false
							_is_fullscreen = false;
							//Remove fullscreen styles from editor
							_element.removeClass('fullscreen');
							//Unbind preview-update on each keyup
							_element.unbind('keyup');
							//Unbind esc
							Mousetrap.unbind('esc');
							//Remove and delete the preview
							preview = deletePreview(preview);
						});
					//Exit fullscreen
					} else {
						//Flag fullscreen as false
						_is_fullscreen = false;
						//Remove fullscreen styles from editor
						_element.removeClass('fullscreen');
						//Unbind preview-update on each keyup
						_element.unbind('keyup');
						//Unbind esc
						Mousetrap.unbind('esc');
						//Remove and delete the preview
						preview = deletePreview(preview);
					};
					//Return false to prevent default browser behavior.
					return false;
				});

			});

			// !Unbind keyboard commands and clean-up when textarea looses focus.
			_element.bind('blur', function(event){
				// Unbind the keyboard commands
				Mousetrap.unbind('tab');
				Mousetrap.unbind(opts.keyboardShortcuts.bold);
				Mousetrap.unbind(opts.keyboardShortcuts.italic);
				Mousetrap.unbind(opts.keyboardShortcuts.link);
				Mousetrap.unbind(opts.keyboardShortcuts.help);
				Mousetrap.unbind(opts.keyboardShortcuts.preview);
				Mousetrap.unbind(opts.keyboardShortcuts.fullscreen);
				// If we are not in fullscreen-mode and the preview is visible, we need to kill the preview.
				if(!_is_fullscreen) preview = deletePreview(preview);
			});

		});

	};

	// !Default options
	$.fn.markd.defaults = {
		theme: 'preview.css',
		keyboardShortcuts: {
			bold: 		'ctrl+b',
			italic: 	'ctrl+i',
			link: 		'ctrl+k',
			help:		'ctrl+h',
			preview:	'ctrl+p',
			fullscreen: 'ctrl+f'
		},
		parser: {
			pedantic: false,
			sanitize: false
		}
	};

}(jQuery));<|endoftext|>