<commit_before>import MockDate from 'mockdate';
import initState from '../handlers/initState';
import choosingArticle from '../handlers/choosingArticle';
import choosingReply from '../handlers/choosingReply';
import askingReplyFeedback from '../handlers/askingReplyFeedback';
import askingArticleSubmissionConsent from '../handlers/askingArticleSubmissionConsent';
import askingReplyRequestReason from '../handlers/askingReplyRequestReason';
import { ManipulationError } from '../handlers/utils';
import handleInput from '../handleInput';

import {
  REASON_PREFIX,
  DOWNVOTE_PREFIX,
  UPVOTE_PREFIX,
  VIEW_ARTICLE_PREFIX,
  getArticleURL,
} from 'src/lib/sharedUtils';

jest.mock('../handlers/initState');
jest.mock('../handlers/choosingArticle');
jest.mock('../handlers/choosingReply');
jest.mock('../handlers/askingReplyFeedback');
jest.mock('../handlers/askingArticleSubmissionConsent');
jest.mock('../handlers/askingReplyRequestReason');

// Original session ID in context
const FIXED_DATE = 612964800000;

// If session is renewed, sessionId will become this value
const NOW = 1561982400000;

beforeEach(() => {
  initState.mockClear();
  choosingArticle.mockClear();
  choosingReply.mockClear();
  askingReplyFeedback.mockClear();
  askingArticleSubmissionConsent.mockClear();
  askingReplyRequestReason.mockClear();
  MockDate.set(NOW);
});

afterEach(() => {
  MockDate.reset();
});

it('rejects undefined input', () => {
  const data = {};
  const event = {};

  return expect(handleInput(data, event)).rejects.toMatchInlineSnapshot(
    `[Error: input undefined]`
  );
});

it('invokes state handler specified by event.postbackHandlerState', async () => {
  const context = {
    state: 'ASKING_REPLY_FEEDBACK',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'postback',
    postbackHandlerState: 'CHOOSING_REPLY',
    input: 'reply-id',
  };

  choosingReply.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: 'ASKING_REPLY_FEEDBACK',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": "ASKING_REPLY_FEEDBACK",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyFeedback).not.toHaveBeenCalled();
  expect(choosingReply).toHaveBeenCalledTimes(1);
});

it('shows article list when VIEW_ARTICLE_PREFIX is sent', async () => {
  const context = {
    state: 'ASKING_REPLY_FEEDBACK',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: `${VIEW_ARTICLE_PREFIX}${getArticleURL('article-id')}`,
  };

  choosingArticle.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: 'CHOOSING_REPLY',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "searchedText": "",
                    "sessionId": 1561982400000,
                  },
                  "state": "CHOOSING_REPLY",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyFeedback).not.toHaveBeenCalled();
  expect(choosingArticle).toHaveBeenCalledTimes(1);
});

it('Resets session on free-form input, triggers fast-forward', async () => {
  const context = {
    state: 'ASKING_REPLY_FEEDBACK',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: 'Newly forwarded message',
  };

  initState.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: true,
      state: 'CHOOSING_ARTICLE',
    })
  );

  choosingArticle.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: 'CHOOSING_REPLY',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 1561982400000,
                  },
                  "state": "CHOOSING_REPLY",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyFeedback).not.toHaveBeenCalled();
  expect(initState).toHaveBeenCalledTimes(1);
  expect(choosingArticle).toHaveBeenCalledTimes(1);
});

it('processes upvote', async () => {
  const context = {
    state: 'ASKING_REPLY_FEEDBACK',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: `${UPVOTE_PREFIX}My upvote reason`,
  };

  askingReplyFeedback.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: 'ASKING_REPLY_FEEDBACK',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": "ASKING_REPLY_FEEDBACK",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyFeedback).toHaveBeenCalledTimes(1);
});

it('processes downvote', async () => {
  const context = {
    state: 'ASKING_REPLY_FEEDBACK',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: `${DOWNVOTE_PREFIX}My downvote reason`,
  };

  askingReplyFeedback.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: 'ASKING_REPLY_FEEDBACK',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": "ASKING_REPLY_FEEDBACK",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyFeedback).toHaveBeenCalledTimes(1);
});

it('processes first article reason submission', async () => {
  const context = {
    state: 'ASKING_ARTICLE_SUBMISSION_CONSENT',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: `${REASON_PREFIX}My reason sending it to DB`,
  };

  askingArticleSubmissionConsent.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: '__INIT__',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": "__INIT__",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingArticleSubmissionConsent).toHaveBeenCalledTimes(1);
});

it('processes not replied yet reply request submission', async () => {
  const context = {
    state: 'ASKING_REPLY_REQUEST_REASON',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: `${REASON_PREFIX}My reason adding reply request`,
  };

  askingReplyRequestReason.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: '__INIT__',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": "__INIT__",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyRequestReason).toHaveBeenCalledTimes(1);
});

it('handles unimplemented state using defaultState', async () => {
  const context = {
    state: '__INIT__',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'postback',
    postbackHandlerState: 'NOT_IMPLEMENTED_YET',
    input: 'foo',
  };

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
          Object {
            "context": Object {
              "data": Object {
                "sessionId": 612964800000,
              },
              "state": "__INIT__",
            },
            "replies": Array [
              Object {
                "text": "我們看不懂 QQ
          大俠請重新來過。",
                "type": "text",
              },
            ],
          }
        `);

  expect(initState).not.toHaveBeenCalled();
});

it('handles ManipulationError fired in handlers', async () => {
  const context = {
    state: 'CHOOSING_ARTICLE',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'postback',
    postbackHandlerState: 'CHOOSING_ARTICLE',
    input: `article-id`,
  };

  choosingArticle.mockImplementationOnce(() =>
    Promise.reject(new ManipulationError('Foo error'))
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": "CHOOSING_ARTICLE",
                },
                "replies": Array [
                  Object {
                    "altText": "Error: Foo error",
                    "contents": Object {
                      "body": Object {
                        "contents": Array [
                          Object {
                            "text": "Foo error",
                            "type": "text",
                            "wrap": true,
                          },
                        ],
                        "layout": "vertical",
                        "type": "box",
                      },
                      "header": Object {
                        "contents": Array [
                          Object {
                            "color": "#ffb600",
                            "text": "⚠ Wrong usage",
                            "type": "text",
                            "weight": "bold",
                          },
                        ],
                        "layout": "vertical",
                        "type": "box",
                      },
                      "styles": Object {
                        "body": Object {
                          "separator": true,
                        },
                      },
                      "type": "bubble",
                    },
                    "type": "flex",
                  },
                ],
              }
          `);
});

it('throws on unknown error', async () => {
  const context = {
    state: 'CHOOSING_ARTICLE',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'postback',
    postbackHandlerState: 'CHOOSING_ARTICLE',
    input: `article-id`,
  };

  choosingArticle.mockImplementationOnce(() =>
    Promise.reject(new Error('Unknown error'))
  );

  await expect(handleInput(context, event)).rejects.toMatchInlineSnapshot(
    `[Error: Unknown error]`
  );
});
<commit_msg>Update test, askingReplyFeedBack won't return state anymore<commit_after>import MockDate from 'mockdate';
import initState from '../handlers/initState';
import choosingArticle from '../handlers/choosingArticle';
import choosingReply from '../handlers/choosingReply';
import askingReplyFeedback from '../handlers/askingReplyFeedback';
import askingArticleSubmissionConsent from '../handlers/askingArticleSubmissionConsent';
import askingReplyRequestReason from '../handlers/askingReplyRequestReason';
import { ManipulationError } from '../handlers/utils';
import handleInput from '../handleInput';

import {
  REASON_PREFIX,
  DOWNVOTE_PREFIX,
  UPVOTE_PREFIX,
  VIEW_ARTICLE_PREFIX,
  getArticleURL,
} from 'src/lib/sharedUtils';

jest.mock('../handlers/initState');
jest.mock('../handlers/choosingArticle');
jest.mock('../handlers/choosingReply');
jest.mock('../handlers/askingReplyFeedback');
jest.mock('../handlers/askingArticleSubmissionConsent');
jest.mock('../handlers/askingReplyRequestReason');

// Original session ID in context
const FIXED_DATE = 612964800000;

// If session is renewed, sessionId will become this value
const NOW = 1561982400000;

beforeEach(() => {
  initState.mockClear();
  choosingArticle.mockClear();
  choosingReply.mockClear();
  askingReplyFeedback.mockClear();
  askingArticleSubmissionConsent.mockClear();
  askingReplyRequestReason.mockClear();
  MockDate.set(NOW);
});

afterEach(() => {
  MockDate.reset();
});

it('rejects undefined input', () => {
  const data = {};
  const event = {};

  return expect(handleInput(data, event)).rejects.toMatchInlineSnapshot(
    `[Error: input undefined]`
  );
});

it('invokes state handler specified by event.postbackHandlerState', async () => {
  const context = {
    state: 'ASKING_REPLY_FEEDBACK',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'postback',
    postbackHandlerState: 'CHOOSING_REPLY',
    input: 'reply-id',
  };

  choosingReply.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: 'ASKING_REPLY_FEEDBACK',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": "ASKING_REPLY_FEEDBACK",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyFeedback).not.toHaveBeenCalled();
  expect(choosingReply).toHaveBeenCalledTimes(1);
});

it('shows article list when VIEW_ARTICLE_PREFIX is sent', async () => {
  const context = {
    state: 'ASKING_REPLY_FEEDBACK',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: `${VIEW_ARTICLE_PREFIX}${getArticleURL('article-id')}`,
  };

  choosingArticle.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: 'CHOOSING_REPLY',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "searchedText": "",
                    "sessionId": 1561982400000,
                  },
                  "state": "CHOOSING_REPLY",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyFeedback).not.toHaveBeenCalled();
  expect(choosingArticle).toHaveBeenCalledTimes(1);
});

it('Resets session on free-form input, triggers fast-forward', async () => {
  const context = {
    state: 'ASKING_REPLY_FEEDBACK',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: 'Newly forwarded message',
  };

  initState.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: true,
      state: 'CHOOSING_ARTICLE',
    })
  );

  choosingArticle.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: 'CHOOSING_REPLY',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 1561982400000,
                  },
                  "state": "CHOOSING_REPLY",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyFeedback).not.toHaveBeenCalled();
  expect(initState).toHaveBeenCalledTimes(1);
  expect(choosingArticle).toHaveBeenCalledTimes(1);
});

it('processes upvote', async () => {
  const context = {
    state: 'ASKING_REPLY_FEEDBACK',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: `${UPVOTE_PREFIX}My upvote reason`,
  };

  askingReplyFeedback.mockImplementationOnce(params => {
    // askingReplyFeedback doesn't return `state`, discard it
    // eslint-disable-next-line no-unused-vars
    const { state, ...restParams } = params;
    return Promise.resolve({
      ...restParams,
      isSkipUser: false,
      replies: 'Foo replies',
    });
  });

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": undefined,
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyFeedback).toHaveBeenCalledTimes(1);
});

it('processes downvote', async () => {
  const context = {
    state: 'ASKING_REPLY_FEEDBACK',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: `${DOWNVOTE_PREFIX}My downvote reason`,
  };

  askingReplyFeedback.mockImplementationOnce(params => {
    // askingReplyFeedback doesn't return `state`, discard it
    // eslint-disable-next-line no-unused-vars
    const { state, ...restParams } = params;
    return Promise.resolve({
      ...restParams,
      isSkipUser: false,
      replies: 'Foo replies',
    });
  });

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": undefined,
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyFeedback).toHaveBeenCalledTimes(1);
});

it('processes first article reason submission', async () => {
  const context = {
    state: 'ASKING_ARTICLE_SUBMISSION_CONSENT',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: `${REASON_PREFIX}My reason sending it to DB`,
  };

  askingArticleSubmissionConsent.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: '__INIT__',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": "__INIT__",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingArticleSubmissionConsent).toHaveBeenCalledTimes(1);
});

it('processes not replied yet reply request submission', async () => {
  const context = {
    state: 'ASKING_REPLY_REQUEST_REASON',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'message',
    input: `${REASON_PREFIX}My reason adding reply request`,
  };

  askingReplyRequestReason.mockImplementationOnce(params =>
    Promise.resolve({
      ...params,
      isSkipUser: false,
      state: '__INIT__',
      replies: 'Foo replies',
    })
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": "__INIT__",
                },
                "replies": "Foo replies",
              }
          `);

  expect(askingReplyRequestReason).toHaveBeenCalledTimes(1);
});

it('handles unimplemented state using defaultState', async () => {
  const context = {
    state: '__INIT__',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'postback',
    postbackHandlerState: 'NOT_IMPLEMENTED_YET',
    input: 'foo',
  };

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
          Object {
            "context": Object {
              "data": Object {
                "sessionId": 612964800000,
              },
              "state": "__INIT__",
            },
            "replies": Array [
              Object {
                "text": "我們看不懂 QQ
          大俠請重新來過。",
                "type": "text",
              },
            ],
          }
        `);

  expect(initState).not.toHaveBeenCalled();
});

it('handles ManipulationError fired in handlers', async () => {
  const context = {
    state: 'CHOOSING_ARTICLE',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'postback',
    postbackHandlerState: 'CHOOSING_ARTICLE',
    input: `article-id`,
  };

  choosingArticle.mockImplementationOnce(() =>
    Promise.reject(new ManipulationError('Foo error'))
  );

  await expect(handleInput(context, event)).resolves.toMatchInlineSnapshot(`
              Object {
                "context": Object {
                  "data": Object {
                    "sessionId": 612964800000,
                  },
                  "state": "CHOOSING_ARTICLE",
                },
                "replies": Array [
                  Object {
                    "altText": "Error: Foo error",
                    "contents": Object {
                      "body": Object {
                        "contents": Array [
                          Object {
                            "text": "Foo error",
                            "type": "text",
                            "wrap": true,
                          },
                        ],
                        "layout": "vertical",
                        "type": "box",
                      },
                      "header": Object {
                        "contents": Array [
                          Object {
                            "color": "#ffb600",
                            "text": "⚠ Wrong usage",
                            "type": "text",
                            "weight": "bold",
                          },
                        ],
                        "layout": "vertical",
                        "type": "box",
                      },
                      "styles": Object {
                        "body": Object {
                          "separator": true,
                        },
                      },
                      "type": "bubble",
                    },
                    "type": "flex",
                  },
                ],
              }
          `);
});

it('throws on unknown error', async () => {
  const context = {
    state: 'CHOOSING_ARTICLE',
    data: { sessionId: FIXED_DATE },
  };
  const event = {
    type: 'postback',
    postbackHandlerState: 'CHOOSING_ARTICLE',
    input: `article-id`,
  };

  choosingArticle.mockImplementationOnce(() =>
    Promise.reject(new Error('Unknown error'))
  );

  await expect(handleInput(context, event)).rejects.toMatchInlineSnapshot(
    `[Error: Unknown error]`
  );
});
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-native-modalbox';
import { TouchableOpacity, StyleSheet, View, Text } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import { GenericPage } from '../../pages/GenericPage';
import { Button, PageButton, ExpiryTextInput, PageInfo, GenericChoiceList } from '..';
import { PageContentModal } from './PageContentModal';

import {
  programStrings,
  tableStrings,
  buttonStrings,
  modalStrings,
  pageInfoStrings,
} from '../../localization';
import { parsePositiveInteger } from '../../utilities';
import globalStyles, {
  WARM_GREY,
  SUSSOL_ORANGE,
  DARK_GREY,
  dataTableStyles,
  expansionPageStyles,
} from '../../globalStyles';

const MODAL_KEYS = {
  REASON_EDIT: 'reasonEdit',
};
export class StocktakeBatchModal extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      currentBatch: null,
      reasons: [],
      isModalOpen: false,
      modalKey: null,
    };
  }

  componentDidMount = () => {
    const { database } = this.props;
    const queryString = 'type == $0 && isActive == true';
    const reasons = database.objects('Options').filtered(queryString, 'stocktakeLineAdjustment');
    this.setState({ reasons });
  };

  openModal = (key, currentBatch) => {
    this.setState({ modalKey: key, isModalOpen: true, currentBatch });
  };

  closeModal = () => {
    this.setState({ isModalOpen: false });
  };

  reasonModalConfirm = ({ item: option }) => {
    if (option) {
      const { database } = this.props;
      const { currentBatch } = this.state;
      const { id } = currentBatch;
      database.write(() => database.update('StocktakeBatch', { id, option }));
    }
    this.closeModal();
  };

  /**
   * Opens the reason modal for applying a reason to a stocktakeBatch
   * if the snapshot quantity and counted total quantity differ.
   * Otherwise, removes the reason from the stocktake items batches.
   * @param {Object} stocktakeBatch
   */
  assignReason = stocktakeBatch => {
    const { REASON_EDIT } = MODAL_KEYS;
    const { database } = this.props;
    const { id, option, shouldHaveReason } = stocktakeBatch;
    if (shouldHaveReason) {
      if (!option) this.openModal(REASON_EDIT, stocktakeBatch);
    } else database.write(() => database.update('StocktakeBatch', { id, option: null }));
  };

  onEndEditing = (key, stocktakeBatch, newValue) => {
    const { reasons, isModalOpen } = this.state;
    const { database } = this.props;
    const { id } = stocktakeBatch;

    if (!newValue) return;
    // If the reason modal is open just ignore any change to the current line
    if (isModalOpen) return;
    switch (key) {
      case 'countedTotalQuantity': {
        const quantity = parsePositiveInteger(newValue);
        if (quantity === null) return;
        database.write(() => {
          stocktakeBatch.countedTotalQuantity = quantity;
        });
        database.save(stocktakeBatch);
        if (reasons.length > 0) this.assignReason(stocktakeBatch);
        break;
      }
      case 'batch': {
        let newBatchName = '';
        if (newValue !== `(${tableStrings.no_batch_name})`) newBatchName = newValue;
        database.write(() => database.update('StocktakeBatch', { id, batch: newBatchName }));
        break;
      }
      case 'expiryDate':
        database.write(() => database.update('StocktakeBatch', { id, expiryDate: newValue }));
        break;
      default:
        break;
    }
  };

  refreshData = () => {
    const { stocktakeItem } = this.props;
    this.setState({ data: stocktakeItem.batches });
  };

  renderCell = (key, stocktakeBatch) => {
    const { stocktakeItem } = this.props;
    const { stocktake } = stocktakeItem;
    const isEditable = !stocktake.isFinalised;
    const { option } = stocktakeBatch;
    const { REASON_EDIT } = MODAL_KEYS;
    switch (key) {
      default:
        return {
          cellContents: stocktakeBatch[key],
        };
      case 'batch':
        return {
          type: isEditable ? 'editable' : 'text',
          cellContents:
            stocktakeBatch[key] && stocktakeBatch[key] !== ''
              ? stocktakeBatch[key]
              : `(${tableStrings.no_batch_name})`,
          keyboardType: 'default',
        };
      case 'countedTotalQuantity': {
        const emptyCellContents = isEditable ? '' : tableStrings.not_counted;
        return {
          type: isEditable ? 'editable' : 'text',
          cellContents: stocktakeBatch.hasBeenCounted
            ? stocktakeBatch.countedTotalQuantity
            : emptyCellContents,
          placeholder: tableStrings.not_counted,
        };
      }
      case 'expiryDate': {
        return (
          <ExpiryTextInput
            key={stocktakeBatch.id}
            isEditable={isEditable}
            onEndEditing={newValue => {
              this.onEndEditing(key, stocktakeBatch, newValue);
              this.refreshData();
            }}
            text={stocktakeBatch[key]}
            style={dataTableStyles.text}
          />
        );
      }
      case 'difference': {
        const { difference } = stocktakeBatch;
        const prefix = difference > 0 ? '+' : '';
        return { cellContents: `${prefix}${difference}` };
      }
      case 'reason': {
        const onPress = this.openModal.bind(this, REASON_EDIT, stocktakeBatch);
        const editable = option && isEditable;
        return (
          <TouchableOpacity
            key={stocktakeBatch.id}
            onPress={editable ? onPress : null}
            style={localStyles.reasonCell}
          >
            {editable && <Icon name="external-link" size={14} color={SUSSOL_ORANGE} />}
            <Text style={{ width: '80%' }} numberOfLines={1} ellipsizeMode="tail">
              {stocktakeBatch.option ? stocktakeBatch.option.title : programStrings.not_applicable}
            </Text>
          </TouchableOpacity>
        );
      }
    }
  };

  renderAddBatchButton = () => {
    const { database, stocktakeItem } = this.props;

    const addNewBatch = () => {
      database.write(() => {
        stocktakeItem.createNewBatch(database);
      });
      this.refreshData();
    };

    return (
      <PageButton
        text={buttonStrings.add_batch}
        onPress={addNewBatch}
        isDisabled={stocktakeItem.stocktake.isFinalised}
        style={localStyles.addBatchButton}
      />
    );
  };

  renderPageInfo = () => {
    const { stocktakeItem } = this.props;
    const { itemName } = stocktakeItem;

    const infoColumns = [
      [
        {
          title: pageInfoStrings.by_batch,
          info: itemName,
        },
      ],
    ];
    return <PageInfo columns={infoColumns} />;
  };

  renderFooter = () => {
    const { onConfirm } = this.props;
    return (
      <View style={localStyles.footer}>
        <Button
          text={buttonStrings.done}
          disabledColor={WARM_GREY}
          style={[globalStyles.button, localStyles.OKButton]}
          textStyle={[globalStyles.buttonText, localStyles.OKButtonText]}
          onPress={onConfirm}
        />
      </View>
    );
  };

  getModalTitle = () => {
    const { modalKey } = this.state;
    const { REASON_EDIT } = MODAL_KEYS;
    switch (modalKey) {
      default:
        return '';
      case REASON_EDIT:
        return programStrings.select_a_reason;
    }
  };

  renderModalContent = () => {
    const { modalKey, currentBatch, reasons } = this.state;
    const { option } = currentBatch;
    const highlightValue = option && option.title;
    const { REASON_EDIT } = MODAL_KEYS;
    switch (modalKey) {
      default: {
        return null;
      }
      case REASON_EDIT: {
        return (
          <GenericChoiceList
            data={reasons}
            highlightValue={highlightValue}
            keyToDisplay="title"
            onPress={this.reasonModalConfirm}
            title={modalStrings.select_a_reason}
          />
        );
      }
    }
  };

  getColumns = () => {
    const { reasons } = this.state;
    const columns = [
      {
        key: 'batch',
        width: 2,
        title: tableStrings.batch_name,
        alignText: 'center',
      },
      {
        key: 'expiryDate',
        width: 1,
        title: tableStrings.expiry,
        alignText: 'center',
      },
      {
        key: 'snapshotTotalQuantity',
        width: 1,
        title: unwrapText(tableStrings.snapshot_quantity),
        alignText: 'right',
      },
      {
        key: 'countedTotalQuantity',
        width: 1,
        title: unwrapText(tableStrings.actual_quantity),
        alignText: 'right',
      },
      {
        key: 'difference',
        width: 1,
        title: tableStrings.difference,
        alignText: 'right',
      },
    ];
    if (reasons.length > 0) {
      columns.push({
        key: 'reason',
        width: 1,
        title: tableStrings.reason,
        alignText: 'right',
      });
    }
    return columns;
  };

  render() {
    const { database, genericTablePageStyles, isOpen } = this.props;
    const { data, isModalOpen, modalKey, currentBatch } = this.state;
    const { REASON_EDIT } = MODAL_KEYS;

    return (
      <Modal
        isOpen={isOpen}
        style={[localStyles.modal]}
        backdropOpacity={0.33}
        position="top"
        backdropPressToClose={false}
        swipeToClose={false}
      >
        <GenericPage
          data={data}
          renderCell={this.renderCell}
          refreshData={this.refreshData}
          hideSearchBar={true}
          dontRenderSearchBar={true}
          onEndEditing={this.onEndEditing}
          renderTopLeftComponent={this.renderPageInfo}
          renderTopRightComponent={this.renderAddBatchButton}
          columns={this.getColumns()}
          dataTypesLinked={['StocktakeBatch', 'Stocktake']}
          database={database}
          pageStyles={expansionPageStyles}
          {...genericTablePageStyles}
        />
        {isModalOpen && (
          <PageContentModal
            isOpen={isModalOpen}
            onClose={currentBatch && currentBatch.enforceReason ? null : this.closeModal}
            title={this.getModalTitle()}
            coverScreen={modalKey === REASON_EDIT}
          >
            {this.renderModalContent()}
          </PageContentModal>
        )}
        {this.renderFooter()}
      </Modal>
    );
  }
}

StocktakeBatchModal.defaultProps = {
  genericTablePageStyles: {},
};

StocktakeBatchModal.propTypes = {
  database: PropTypes.object.isRequired,
  genericTablePageStyles: PropTypes.object,
  stocktakeItem: PropTypes.object.isRequired,
  isOpen: PropTypes.bool.isRequired,
  onConfirm: PropTypes.func.isRequired,
};

export default StocktakeBatchModal;

const unwrapText = text => text.replace(/\n/g, ' ');
const localStyles = StyleSheet.create({
  addBatchButton: {
    height: 30,
    width: 90,
  },
  modal: {
    height: '100%',
    width: '100%',
    padding: 20,
    backgroundColor: DARK_GREY,
  },
  footer: {
    flex: 1,
    maxHeight: 50,
    alignItems: 'flex-end',
    justifyContent: 'flex-end',
  },
  OKButton: {
    backgroundColor: SUSSOL_ORANGE,
  },
  OKButtonText: {
    color: 'white',
    fontSize: 16,
  },
  reasonCell: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingLeft: 10,
    paddingRight: 10,
  },
});
<commit_msg>Add shift of default case in switch statement<commit_after>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-native-modalbox';
import { TouchableOpacity, StyleSheet, View, Text } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import { GenericPage } from '../../pages/GenericPage';
import { Button, PageButton, ExpiryTextInput, PageInfo, GenericChoiceList } from '..';
import { PageContentModal } from './PageContentModal';

import {
  programStrings,
  tableStrings,
  buttonStrings,
  modalStrings,
  pageInfoStrings,
} from '../../localization';
import { parsePositiveInteger } from '../../utilities';
import globalStyles, {
  WARM_GREY,
  SUSSOL_ORANGE,
  DARK_GREY,
  dataTableStyles,
  expansionPageStyles,
} from '../../globalStyles';

const MODAL_KEYS = {
  REASON_EDIT: 'reasonEdit',
};
export class StocktakeBatchModal extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      currentBatch: null,
      reasons: [],
      isModalOpen: false,
      modalKey: null,
    };
  }

  componentDidMount = () => {
    const { database } = this.props;
    const queryString = 'type == $0 && isActive == true';
    const reasons = database.objects('Options').filtered(queryString, 'stocktakeLineAdjustment');
    this.setState({ reasons });
  };

  openModal = (key, currentBatch) => {
    this.setState({ modalKey: key, isModalOpen: true, currentBatch });
  };

  closeModal = () => {
    this.setState({ isModalOpen: false });
  };

  reasonModalConfirm = ({ item: option }) => {
    if (option) {
      const { database } = this.props;
      const { currentBatch } = this.state;
      const { id } = currentBatch;
      database.write(() => database.update('StocktakeBatch', { id, option }));
    }
    this.closeModal();
  };

  /**
   * Opens the reason modal for applying a reason to a stocktakeBatch
   * if the snapshot quantity and counted total quantity differ.
   * Otherwise, removes the reason from the stocktake items batches.
   * @param {Object} stocktakeBatch
   */
  assignReason = stocktakeBatch => {
    const { REASON_EDIT } = MODAL_KEYS;
    const { database } = this.props;
    const { id, option, shouldHaveReason } = stocktakeBatch;
    if (shouldHaveReason) {
      if (!option) this.openModal(REASON_EDIT, stocktakeBatch);
    } else database.write(() => database.update('StocktakeBatch', { id, option: null }));
  };

  onEndEditing = (key, stocktakeBatch, newValue) => {
    const { reasons, isModalOpen } = this.state;
    const { database } = this.props;
    const { id } = stocktakeBatch;

    if (!newValue) return;
    // If the reason modal is open just ignore any change to the current line
    if (isModalOpen) return;
    switch (key) {
      case 'countedTotalQuantity': {
        const quantity = parsePositiveInteger(newValue);
        if (quantity === null) return;
        database.write(() => {
          stocktakeBatch.countedTotalQuantity = quantity;
        });
        database.save(stocktakeBatch);
        if (reasons.length > 0) this.assignReason(stocktakeBatch);
        break;
      }
      case 'batch': {
        let newBatchName = '';
        if (newValue !== `(${tableStrings.no_batch_name})`) newBatchName = newValue;
        database.write(() => database.update('StocktakeBatch', { id, batch: newBatchName }));
        break;
      }
      case 'expiryDate':
        database.write(() => database.update('StocktakeBatch', { id, expiryDate: newValue }));
        break;
      default:
        break;
    }
  };

  refreshData = () => {
    const { stocktakeItem } = this.props;
    this.setState({ data: stocktakeItem.batches });
  };

  renderCell = (key, stocktakeBatch) => {
    const { stocktakeItem } = this.props;
    const { stocktake } = stocktakeItem;
    const isEditable = !stocktake.isFinalised;
    const { option } = stocktakeBatch;
    const { REASON_EDIT } = MODAL_KEYS;
    switch (key) {
      case 'batch':
        return {
          type: isEditable ? 'editable' : 'text',
          cellContents:
            stocktakeBatch[key] && stocktakeBatch[key] !== ''
              ? stocktakeBatch[key]
              : `(${tableStrings.no_batch_name})`,
          keyboardType: 'default',
        };
      case 'countedTotalQuantity': {
        const emptyCellContents = isEditable ? '' : tableStrings.not_counted;
        return {
          type: isEditable ? 'editable' : 'text',
          cellContents: stocktakeBatch.hasBeenCounted
            ? stocktakeBatch.countedTotalQuantity
            : emptyCellContents,
          placeholder: tableStrings.not_counted,
        };
      }
      case 'expiryDate': {
        return (
          <ExpiryTextInput
            key={stocktakeBatch.id}
            isEditable={isEditable}
            onEndEditing={newValue => {
              this.onEndEditing(key, stocktakeBatch, newValue);
              this.refreshData();
            }}
            text={stocktakeBatch[key]}
            style={dataTableStyles.text}
          />
        );
      }
      case 'difference': {
        const { difference } = stocktakeBatch;
        const prefix = difference > 0 ? '+' : '';
        return { cellContents: `${prefix}${difference}` };
      }
      case 'reason': {
        const onPress = this.openModal.bind(this, REASON_EDIT, stocktakeBatch);
        const editable = option && isEditable;
        return (
          <TouchableOpacity
            key={stocktakeBatch.id}
            onPress={editable ? onPress : null}
            style={localStyles.reasonCell}
          >
            {editable && <Icon name="external-link" size={14} color={SUSSOL_ORANGE} />}
            <Text style={{ width: '80%' }} numberOfLines={1} ellipsizeMode="tail">
              {stocktakeBatch.option ? stocktakeBatch.option.title : programStrings.not_applicable}
            </Text>
          </TouchableOpacity>
        );
      }
      default:
        return {
          cellContents: stocktakeBatch[key],
        };
    }
  };

  renderAddBatchButton = () => {
    const { database, stocktakeItem } = this.props;

    const addNewBatch = () => {
      database.write(() => {
        stocktakeItem.createNewBatch(database);
      });
      this.refreshData();
    };

    return (
      <PageButton
        text={buttonStrings.add_batch}
        onPress={addNewBatch}
        isDisabled={stocktakeItem.stocktake.isFinalised}
        style={localStyles.addBatchButton}
      />
    );
  };

  renderPageInfo = () => {
    const { stocktakeItem } = this.props;
    const { itemName } = stocktakeItem;

    const infoColumns = [
      [
        {
          title: pageInfoStrings.by_batch,
          info: itemName,
        },
      ],
    ];
    return <PageInfo columns={infoColumns} />;
  };

  renderFooter = () => {
    const { onConfirm } = this.props;
    return (
      <View style={localStyles.footer}>
        <Button
          text={buttonStrings.done}
          disabledColor={WARM_GREY}
          style={[globalStyles.button, localStyles.OKButton]}
          textStyle={[globalStyles.buttonText, localStyles.OKButtonText]}
          onPress={onConfirm}
        />
      </View>
    );
  };

  getModalTitle = () => {
    const { modalKey } = this.state;
    const { REASON_EDIT } = MODAL_KEYS;
    switch (modalKey) {
      default:
        return '';
      case REASON_EDIT:
        return programStrings.select_a_reason;
    }
  };

  renderModalContent = () => {
    const { modalKey, currentBatch, reasons } = this.state;
    const { option } = currentBatch;
    const highlightValue = option && option.title;
    const { REASON_EDIT } = MODAL_KEYS;
    switch (modalKey) {
      default: {
        return null;
      }
      case REASON_EDIT: {
        return (
          <GenericChoiceList
            data={reasons}
            highlightValue={highlightValue}
            keyToDisplay="title"
            onPress={this.reasonModalConfirm}
            title={modalStrings.select_a_reason}
          />
        );
      }
    }
  };

  getColumns = () => {
    const { reasons } = this.state;
    const columns = [
      {
        key: 'batch',
        width: 2,
        title: tableStrings.batch_name,
        alignText: 'center',
      },
      {
        key: 'expiryDate',
        width: 1,
        title: tableStrings.expiry,
        alignText: 'center',
      },
      {
        key: 'snapshotTotalQuantity',
        width: 1,
        title: unwrapText(tableStrings.snapshot_quantity),
        alignText: 'right',
      },
      {
        key: 'countedTotalQuantity',
        width: 1,
        title: unwrapText(tableStrings.actual_quantity),
        alignText: 'right',
      },
      {
        key: 'difference',
        width: 1,
        title: tableStrings.difference,
        alignText: 'right',
      },
    ];
    if (reasons.length > 0) {
      columns.push({
        key: 'reason',
        width: 1,
        title: tableStrings.reason,
        alignText: 'right',
      });
    }
    return columns;
  };

  render() {
    const { database, genericTablePageStyles, isOpen } = this.props;
    const { data, isModalOpen, modalKey, currentBatch } = this.state;
    const { REASON_EDIT } = MODAL_KEYS;

    return (
      <Modal
        isOpen={isOpen}
        style={[localStyles.modal]}
        backdropOpacity={0.33}
        position="top"
        backdropPressToClose={false}
        swipeToClose={false}
      >
        <GenericPage
          data={data}
          renderCell={this.renderCell}
          refreshData={this.refreshData}
          hideSearchBar={true}
          dontRenderSearchBar={true}
          onEndEditing={this.onEndEditing}
          renderTopLeftComponent={this.renderPageInfo}
          renderTopRightComponent={this.renderAddBatchButton}
          columns={this.getColumns()}
          dataTypesLinked={['StocktakeBatch', 'Stocktake']}
          database={database}
          pageStyles={expansionPageStyles}
          {...genericTablePageStyles}
        />
        {isModalOpen && (
          <PageContentModal
            isOpen={isModalOpen}
            onClose={currentBatch && currentBatch.enforceReason ? null : this.closeModal}
            title={this.getModalTitle()}
            coverScreen={modalKey === REASON_EDIT}
          >
            {this.renderModalContent()}
          </PageContentModal>
        )}
        {this.renderFooter()}
      </Modal>
    );
  }
}

StocktakeBatchModal.defaultProps = {
  genericTablePageStyles: {},
};

StocktakeBatchModal.propTypes = {
  database: PropTypes.object.isRequired,
  genericTablePageStyles: PropTypes.object,
  stocktakeItem: PropTypes.object.isRequired,
  isOpen: PropTypes.bool.isRequired,
  onConfirm: PropTypes.func.isRequired,
};

export default StocktakeBatchModal;

const unwrapText = text => text.replace(/\n/g, ' ');
const localStyles = StyleSheet.create({
  addBatchButton: {
    height: 30,
    width: 90,
  },
  modal: {
    height: '100%',
    width: '100%',
    padding: 20,
    backgroundColor: DARK_GREY,
  },
  footer: {
    flex: 1,
    maxHeight: 50,
    alignItems: 'flex-end',
    justifyContent: 'flex-end',
  },
  OKButton: {
    backgroundColor: SUSSOL_ORANGE,
  },
  OKButtonText: {
    color: 'white',
    fontSize: 16,
  },
  reasonCell: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingLeft: 10,
    paddingRight: 10,
  },
});
<|endoftext|>


<commit_before>/*
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

/**
  Export common window/document services through the Context.

  Rather than using window or document directly, objects should import: the
  services that foam.core.Window exports:, and then access them as this.name,
  rather than as console.name or document.name.

  All FObjects already import: [ 'assert', 'error', 'log', 'warn' ], meaning
  that these do not need to be explicitly imported.

  This is done to remove dependency on the globals 'document' and 'window',
  which makes it easier to write code which works with multiple windows.

  It also allows for common services to be decorated, trapped, or replaced
  in sub-contexts (for example, to replace console.error and console.warn when
  running test).

  A foam.core.Window is installed by FOAM on starup for the default
  window/document, but if user code opens a new Window, it should create
  and install a new foam.core.Window explicitly.
 */
foam.CLASS({
  package: 'foam.core',
  name: 'Window',

  // documentation: 'Encapsulates top-level window/document features.',

  exports: [
    'getElementsByClassName',
    'getElementById',
    'assert',
    'async',
    'cancelAnimationFrame',
    'clearInterval',
    'clearTimeout',
    'console',
    'delayed',
    'document',
    'error',
    'framed',
    'info',
    'installCSS',
    'log',
    'merged',
    'requestAnimationFrame',
    'setInterval',
    'setTimeout',
    'warn',
    'window'
  ],

  properties: [
    [ 'name', 'window' ],
    'window',
    {
      name: 'document',
      factory: function() { return this.window.document; }
    },
    {
      name: 'console',
      factory: function() { return this.window.console; }
    }
  ],

  methods: [
    function getElementById(id) {
      return this.document.getElementById(id);
    },

    function getElementsByClassName(cls) {
      return this.document.getElementsByClassName(cls);
    },

    /**
      Like console.assert(), but faster and context-dependent.
      Returns the first argument, so that it can be nested as a sub-expression.
     */
    function assert(b /*, args */) {
      /* Like console.assert() except that it takes more than one argument. */
      if ( ! b ) {
        this.console.assert(false, Array.prototype.slice.call(arguments, 1).join(' '));
      }
      return b;
    },

    function error() {
      this.console.error.apply(this.console, arguments);
    },

    function info() {
      this.console.info.apply(this.console, arguments);
    },

    function log() {
      this.console.log.apply(this.console, arguments);
    },

    function warn() {
      this.console.warn.apply(this.console, arguments);
    },

    function async(l) {
      /* Decorate a listener so that the event is delivered asynchronously. */
      return this.delayed(l, 0);
    },

    function delayed(l, delay) {
      /* Decorate a listener so that events are delivered 'delay' ms later. */
      return foam.Function.bind(function() {
        this.setTimeout(
          function() { l.apply(this, arguments); },
          delay);
      }, this);
    },

    function merged(l, opt_delay) {
      var delay = opt_delay || 16;
      var ctx     = this;

      return foam.Function.setName(function() {
        var triggered = false;
        var lastArgs  = null;
        function mergedListener() {
          triggered = false;
          var args = Array.from(lastArgs);
          lastArgs = null;
          l.apply(this, args);
        }

        var f = function() {
          lastArgs = arguments;

          if ( ! triggered ) {
            triggered = true;
            ctx.setTimeout(mergedListener, delay);
          }
        };

        return f;
      }(), 'merged(' + l.name + ')');
    },

    function framed(l) {
      var ctx = this;

      return foam.Function.setName(function() {
        var triggered = false;
        var lastArgs  = null;
        function frameFired() {
          triggered = false;
          var args = lastArgs;
          lastArgs = null;
          l.apply(this, args);
        }

        var f = function framed() {
          lastArgs = arguments;

          if ( ! triggered ) {
            triggered = true;
            ctx.requestAnimationFrame(frameFired);
          }
        };

        return f;
      }(), 'framed(' + l.name + ')');
    },

    function setTimeout(f, t) {
      return this.window.setTimeout.apply(this.window, arguments);
    },
    function clearTimeout(id) {
      this.window.clearTimeout(id);
    },

    function setInterval(f, t) {
      return this.window.setInterval.apply(this.window, arguments);
    },
    function clearInterval(id) {
      this.window.clearInterval(id);
    },

    function requestAnimationFrame(f) {
      return this.window.requestAnimationFrame(f);
    },
    function cancelAnimationFrame(id) {
      this.window.cancelAnimationFrame(id);
    },
    function installCSS(text) {
      /* Create a new <style> tag containing the given CSS code. */
      this.document.head.insertAdjacentHTML('beforeend',
          '<style>' + text + '</style>');
    }
  ]
});

/*
 * requestAnimationFrame is not available on nodejs,
 * so swap out with calls to setTimeout.
 */
if ( foam.isServer ) {
  foam.CLASS({
    refines: 'foam.core.Window',
    methods: [
      function requestAnimationFrame(f) {
        return this.setTimeout(f, 16);
      },
      function cancelAnimationFrame(id) {
        this.clearTimeout(id);
      }
    ]
  });
}

// Replace top-level Context with one which includes Window's exports.
foam.__context__ = foam.core.Window.create(
  { window: global },
  foam.__context__
).__subContext__;
<commit_msg>Slightly simplified Window code.<commit_after>/*
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

/**
  Export common window/document services through the Context.

  Rather than using window or document directly, objects should import: the
  services that foam.core.Window exports:, and then access them as this.name,
  rather than as console.name or document.name.

  All FObjects already import: [ 'assert', 'error', 'log', 'warn' ], meaning
  that these do not need to be explicitly imported.

  This is done to remove dependency on the globals 'document' and 'window',
  which makes it easier to write code which works with multiple windows.

  It also allows for common services to be decorated, trapped, or replaced
  in sub-contexts (for example, to replace console.error and console.warn when
  running test).

  A foam.core.Window is installed by FOAM on starup for the default
  window/document, but if user code opens a new Window, it should create
  and install a new foam.core.Window explicitly.
 */
foam.CLASS({
  package: 'foam.core',
  name: 'Window',

  // documentation: 'Encapsulates top-level window/document features.',

  exports: [
    'getElementsByClassName',
    'getElementById',
    'assert',
    'async',
    'cancelAnimationFrame',
    'clearInterval',
    'clearTimeout',
    'console',
    'delayed',
    'document',
    'error',
    'framed',
    'info',
    'installCSS',
    'log',
    'merged',
    'requestAnimationFrame',
    'setInterval',
    'setTimeout',
    'warn',
    'window'
  ],

  properties: [
    [ 'name', 'window' ],
    'window',
    {
      name: 'document',
      factory: function() { return this.window.document; }
    },
    {
      name: 'console',
      factory: function() { return this.window.console; }
    }
  ],

  methods: [
    function getElementById(id) {
      return this.document.getElementById(id);
    },

    function getElementsByClassName(cls) {
      return this.document.getElementsByClassName(cls);
    },

    /**
      Like console.assert(), but faster and context-dependent.
      Returns the first argument, so that it can be nested as a sub-expression.
     */
    function assert(b /*, args */) {
      /* Like console.assert() except that it takes more than one argument. */
      if ( ! b ) {
        this.console.assert(false, Array.prototype.slice.call(arguments, 1).join(' '));
      }
      return b;
    },

    function error() {
      this.console.error.apply(this.console, arguments);
    },

    function info() {
      this.console.info.apply(this.console, arguments);
    },

    function log() {
      this.console.log.apply(this.console, arguments);
    },

    function warn() {
      this.console.warn.apply(this.console, arguments);
    },

    function async(l) {
      /* Decorate a listener so that the event is delivered asynchronously. */
      return this.delayed(l, 0);
    },

    function delayed(l, delay) {
      /* Decorate a listener so that events are delivered 'delay' ms later. */
      return foam.Function.bind(function() {
        this.setTimeout(
          function() { l.apply(this, arguments); },
          delay);
      }, this);
    },

    function merged(l, opt_delay) {
      var delay = opt_delay || 16;
      var ctx     = this;

      return foam.Function.setName(function() {
        var triggered = false;
        var lastArgs  = null;
        function mergedListener() {
          triggered = false;
          var args = Array.from(lastArgs);
          lastArgs = null;
          l.apply(this, args);
        }

        var f = function() {
          lastArgs = arguments;

          if ( ! triggered ) {
            triggered = true;
            ctx.setTimeout(mergedListener, delay);
          }
        };

        return f;
      }(), 'merged(' + l.name + ')');
    },

    function framed(l) {
      var ctx = this;

      return foam.Function.setName(function() {
        var triggered = false;
        var lastArgs  = null;
        function frameFired() {
          triggered = false;
          var args = lastArgs;
          lastArgs = null;
          l.apply(this, args);
        }

        var f = function framed() {
          lastArgs = arguments;

          if ( ! triggered ) {
            triggered = true;
            ctx.requestAnimationFrame(frameFired);
          }
        };

        return f;
      }(), 'framed(' + l.name + ')');
    },

    function setTimeout(f, t) {
      return this.window.setTimeout(f, t);
    },
    function clearTimeout(id) {
      this.window.clearTimeout(id);
    },

    function setInterval(f, t) {
      return this.window.setInterval(f, t);
    },
    function clearInterval(id) {
      this.window.clearInterval(id);
    },

    function requestAnimationFrame(f) {
      return this.window.requestAnimationFrame(f);
    },
    function cancelAnimationFrame(id) {
      this.window.cancelAnimationFrame(id);
    },
    function installCSS(text) {
      /* Create a new <style> tag containing the given CSS code. */
      this.document.head.insertAdjacentHTML('beforeend',
          '<style>' + text + '</style>');
    }
  ]
});

/*
 * requestAnimationFrame is not available on nodejs,
 * so swap out with calls to setTimeout.
 */
if ( foam.isServer ) {
  foam.CLASS({
    refines: 'foam.core.Window',
    methods: [
      function requestAnimationFrame(f) {
        return this.setTimeout(f, 16);
      },
      function cancelAnimationFrame(id) {
        this.clearTimeout(id);
      }
    ]
  });
}

// Replace top-level Context with one which includes Window's exports.
foam.__context__ = foam.core.Window.create(
  { window: global },
  foam.__context__
).__subContext__;
<|endoftext|>


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

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfTimeSpan',
  extends: 'foam.mlang.AbstractExpr',
  implements: [ 'foam.core.Serializable' ],
  properties: [
    {
      class: 'foam.mlang.ExprProperty',
      name: 'delegate'
    },
    {
      class: 'Long',
      name: 'timeSpanMs'
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        var ms = this.timeSpanMs;
        return new Date(Math.floor(ts.getTime() / ms) * ms + ms - 1);
      },
      javaCode: `
        java.util.Date ts = (java.util.Date) getDelegate().f(obj);
        long ms = getTimeSpanMs();
        return new java.util.Date((ts.getTime() / ms) * ms + ms - 1);
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfDay',
  extends: 'foam.mlang.AbstractExpr',
  implements: [ 'foam.core.Serializable' ],
  properties: [
    {
      class: 'foam.mlang.ExprProperty',
      name: 'delegate'
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
      },
      javaCode: `
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTimeInMillis(((java.util.Date) getDelegate().f(obj)).getTime());
c.set(java.util.Calendar.HOUR_OF_DAY, 23);
c.set(java.util.Calendar.MINUTE, 59);
c.set(java.util.Calendar.SECOND, 59);
c.set(java.util.Calendar.MILLISECOND, 999);
return c.getTime();
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfWeek',
  properties: [
    {
      name: 'delegate'
    },
    {
      class: 'Int',
      name: 'startOfWeek',
      documentation: 'Value between 0 - Sunday and 6 - Saturday inclusive.  Indicates which day is considered the first day of a new week.',
      min: 0,
      max: 6,
      value: 6
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setDate(ts.getDate() + 5 + this.startOfWeek - ts.getDay());
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);

        return ts;
        return ts.getTime() > Date.now() ? new Date() : ts;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfMonth',
  properties: [
    {
      name: 'delegate'
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setMonth(ts.getMonth() + 1);
        ts.setDate(0);
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
        return ts.getTime() > Date.now() ? new Date() : ts;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfQuarter',
  properties: [
    {
      name: 'delegate',
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        // N % 3 is how many months into the quarter we are 0 1 or 2
        // 2 - ( N % 3 ) is how many months left till EOQ.
        // N + ( 2 - ( N % 3 ) ) is the last month of this quarter
        // N + ( 2 - ( N % 3 ) ) + 1 is the first month of next quarter
        // which reduces to N + 3 - ( N % 3 ).
        ts.setMonth(ts.getMonth() + 3 - ( ts.getMonth() % 3 ));
        ts.setDate(0);
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
        return ts.getTime() > Date.now() ? new Date() : ts;
      }
    }
  ]
});
<commit_msg>Added java code to glang using java.time library<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfTimeSpan',
  extends: 'foam.mlang.AbstractExpr',
  implements: [ 'foam.core.Serializable' ],
  properties: [
    {
      class: 'foam.mlang.ExprProperty',
      name: 'delegate'
    },
    {
      class: 'Long',
      name: 'timeSpanMs'
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        var ms = this.timeSpanMs;
        return new Date(Math.floor(ts.getTime() / ms) * ms + ms - 1);
      },
      javaCode: `
        java.util.Date ts = (java.util.Date) getDelegate().f(obj);
        long ms = getTimeSpanMs();
        return new java.util.Date((ts.getTime() / ms) * ms + ms - 1);
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfDay',
  extends: 'foam.mlang.AbstractExpr',
  implements: [ 'foam.core.Serializable' ],
  properties: [
    {
      class: 'foam.mlang.ExprProperty',
      name: 'delegate'
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
java.util.date eod = java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());

return eod;
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfWeek',
  properties: [
    {
      name: 'delegate'
    },
    {
      class: 'Int',
      name: 'startOfWeek',
      documentation: 'Value between 0 - Sunday and 6 - Saturday inclusive.  Indicates which day is considered the first day of a new week.',
      min: 0,
      max: 6,
      value: 6
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setDate(ts.getDate() + 5 + this.startOfWeek - ts.getDay());
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);

        return ts;
        return ts.getTime() > Date.now() ? new Date() : ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Set to end of week
localDate = localDate.plusDays(6 - localDate.getDayOfWeek().getValue());

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
java.util.date eod = java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());

return eod;
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfMonth',
  properties: [
    {
      name: 'delegate'
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        ts.setMonth(ts.getMonth() + 1);
        ts.setDate(0);
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
        return ts.getTime() > Date.now() ? new Date() : ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Set to end of month
localDate = localDate.plusDays(localDate.lengthOfMonth() - localDate.getDayOfMonth());

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
java.util.date eod = java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());

return eod;
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.glang',
  name: 'EndOfQuarter',
  properties: [
    {
      name: 'delegate',
    }
  ],
  methods: [
    {
      name: 'f',
      code: function(obj) {
        var ts = new Date(this.delegate.f(obj));
        // N % 3 is how many months into the quarter we are 0 1 or 2
        // 2 - ( N % 3 ) is how many months left till EOQ.
        // N + ( 2 - ( N % 3 ) ) is the last month of this quarter
        // N + ( 2 - ( N % 3 ) ) + 1 is the first month of next quarter
        // which reduces to N + 3 - ( N % 3 ).
        ts.setMonth(ts.getMonth() + 3 - ( ts.getMonth() % 3 ));
        ts.setDate(0);
        ts.setHours(23, 59, 59);
        ts.setMilliseconds(999);
        return ts;
        return ts.getTime() > Date.now() ? new Date() : ts;
      },
      javaCode: `
// Convert to LocalDate
java.util.Date date = (java.util.Date) getDelegate().f(obj);
java.time.LocalDate localDate = java.time.Instant.ofEpochMilli(date.getTime()).atZone(java.time.ZoneId.systemDefault()).toLocalDate();

// Set month to end of quarter
localDate = localDate.plusMonths(2 - (localDate.getMonthValue() - 1) % 3);

// Set to end of month
localDate = localDate.plusDays(localDate.lengthOfMonth() - localDate.getDayOfMonth());

// Convert to LocalDateTime set to End of Day
java.time.LocalDateTime localDateTime = localDate.atTime(java.time.LocalTime.MAX);

// Convert to Date using LocalDateTime
java.util.date eod = java.util.Date.from(localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());

return eod;
      `
    }
  ]
});
<|endoftext|>


<commit_before><commit_msg>chore(www): Simplify code highlighting things (#13801)<commit_after><|endoftext|>


<commit_before>$(function (){
	var rich_text_area = $('.rich_text_area');
	var parentElement = rich_text_area.parent().parent().parent().parent();
	var helpBox = parentElement.find('.object-help');
	if (helpBox.html()) {
		helpBox.addClass('charcount');
	} else {
		var helpBoxExists = true
		var elem = parentElement.append("

");
		$('.charcount').hide();
	}
	var whiteSpace = /\s\s+/gm;
	var wordsRegex = /\s+/gi;
	var charCountElemText = $('.charcount').text();

	var maxChars = rich_text_area.find('textarea').attr('maxlength');
	$('.richtext').bind('hallomodified', function(event) {
		var text = event.currentTarget.innerText;
		var textNoWhitespace = text.replace(whiteSpace, ' ');
		var textWordCount = textNoWhitespace.trim().replace(wordsRegex, ' ').split(' ').length;
		var textCharCount = textNoWhitespace.length - 1;
		var backendCount = $('.richtext').html().length;
		if (charCountElemText.length > 1) {
			$('.charcount').css({opacity :1});
			$('.charcount').html(
				charCountElemText + '

' +
				"Visible characters: " + textCharCount +
				"
Words: " + textWordCount +
				"
Max Chars: " + maxChars +
				"
Backend Count: " + backendCount + '');
			if (backendCount > maxChars) {
				$('.backend-count').css({color: 'red'})
			} else if (backendCount + 20  > maxChars) {
				$('.backend-count').css({color: 'orange'})
			}
		} else {
			$('.charcount').show();
			$('.charcount').html(
				"Visible characters: " + textCharCount +
				"
Words: " + textWordCount +
				"
Max Chars: " + maxChars +
				"
Backend Count: " + backendCount + '');
			if (backendCount  > maxChars) {
				$('.backend-count').css({color: 'red'})
			} else if (backendCount + 20  > maxChars) {
				$('.backend-count').css({color: 'orange'})
			}
		}
	});
});
<commit_msg>Support for multipl richtextareas<commit_after>$(function (){
	var rich_text_areas = $('.rich_text_area');
	rich_text_areas.each(function(){
		var specificRichText = $(this)
		var parentElement = specificRichText.parent().parent().parent().parent();
		var helpBox = parentElement.find('.object-help');
		if (helpBox.html()) {
			helpBox.addClass('charcount');
		} else {
			var helpBoxExists = true
			var elem = parentElement.append("

");
			var helpBox = parentElement.find('.charcount');
			helpBox.hide();
		}
		var whiteSpace = /\s\s+/gm;
		var wordsRegex = /\s+/gi;
		var charCountElemText = helpBox.text();
		var maxChars = specificRichText.find('textarea').attr('maxlength');

		$(specificRichText.find('.richtext')).bind('hallomodified', function(event) {
			var text = event.currentTarget.innerText;
			var textNoWhitespace = text.replace(whiteSpace, ' ');
			var textWordCount = textNoWhitespace.trim().replace(wordsRegex, ' ').split(' ').length;
			var textCharCount = textNoWhitespace.length - 1;
			var backendCount = $(specificRichText).find('.richtext').html().length;
			if (charCountElemText.length > 1) {
				helpBox.css({opacity :1});
				helpBox.html(
					charCountElemText + '

' +
					"Visible characters: " + textCharCount +
					"
Words: " + textWordCount +
					"
Max Chars: " + maxChars +
					"
Backend Count: " + backendCount + '');
				if (backendCount > maxChars) {
					$('.backend-count').css({color: 'red'})
				} else if (backendCount + 20  > maxChars) {
					$('.backend-count').css({color: 'orange'})
				}
			} else {
				helpBox.show();
				helpBox.html(
					"Visible characters: " + textCharCount +
					"
Words: " + textWordCount +
					"
Max Chars: " + maxChars +
					"
Backend Count: " + backendCount + '');
				if (backendCount  > maxChars) {
					$('.backend-count').css({color: 'red'})
				} else if (backendCount + 20  > maxChars) {
					$('.backend-count').css({color: 'orange'})
				}
			}
		});
	})

});
<|endoftext|>


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

foam.CLASS({
  package: 'foam.u2',
  name: 'EnumView',
  extends: 'foam.u2.view.ChoiceView',

  implements: [
    'foam.mlang.Expressions'
  ],

  requires: [
    'foam.nanos.auth.Permission'
  ],

  imports: [
    'auth'
  ],

  properties: [
    {
      class: 'Class',
      name: 'of',
      required: true
    },
    {
       class: 'Boolean',
       name: 'permissioned',
       value: false
    },
    {
      name: 'permissionResults',
      expression: async function(of, permissioned) {
        if ( of ) {
          if ( permissioned ) {
            var results = [];
            var model = of.name.toLowerCase();
            for ( var i = 0; i < of.VALUES.length; i++ ) {
              var readPermission = model + '.read.' + of.VALUES[i].label.toLowerCase();
              var permResult = await this.auth.check(null, readPermission);
              results.push([of.VALUES[i].label, permResult]);
            }
            return results;
          } else {
            return of.VALUES.map((v) => {
              return [v, v.label];
            });
          }
        } else {
          return [];
        }
      }
    }
  ],

  methods: [
    function initE() {
      this.SUPER();
      this.permissionedChoices();
    },

    function fromProperty(p) {
      this.SUPER(p);
      if ( ! this.of ) this.of = p.of;
    },

    function permissionedChoices() {
      this.permissionResults.then((array) => {
        var values = [];
        if ( this.of ) {
          if ( this.permissioned ) {
            var hash = {};
            for ( var i = 0; i < array.length; i+=1 ) {
              hash[array[i]] = i;
            }
            this.of.VALUES.map((v) => {
              var value = [v.label, true];
              if ( hash.hasOwnProperty(value) ) {
                values.push([v, v.label]);
              }
            });
            return values;
          } else {
            return array;
          }
        }
      }).then((array) => {
        this.choices = array;
      });
    }
  ]
});
<commit_msg>Removed unused requires and imports in EnumView<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'EnumView',
  extends: 'foam.u2.view.ChoiceView',

  imports: [
    'auth'
  ],

  properties: [
    {
      class: 'Class',
      name: 'of',
      required: true
    },
    {
       class: 'Boolean',
       name: 'permissioned',
       value: false
    },
    {
      name: 'permissionResults',
      expression: async function(of, permissioned) {
        if ( of ) {
          if ( permissioned ) {
            var results = [];
            var model = of.name.toLowerCase();
            for ( var i = 0; i < of.VALUES.length; i++ ) {
              var readPermission = model + '.read.' + of.VALUES[i].label.toLowerCase();
              var permResult = await this.auth.check(null, readPermission);
              results.push([of.VALUES[i].label, permResult]);
            }
            return results;
          } else {
            return of.VALUES.map((v) => {
              return [v, v.label];
            });
          }
        } else {
          return [];
        }
      }
    }
  ],

  methods: [
    function initE() {
      this.SUPER();
      this.permissionedChoices();
    },

    function fromProperty(p) {
      this.SUPER(p);
      if ( ! this.of ) this.of = p.of;
    },

    function permissionedChoices() {
      this.permissionResults.then((array) => {
        var values = [];
        if ( this.of ) {
          if ( this.permissioned ) {
            var hash = {};
            for ( var i = 0; i < array.length; i+=1 ) {
              hash[array[i]] = i;
            }
            this.of.VALUES.map((v) => {
              var value = [v.label, true];
              if ( hash.hasOwnProperty(value) ) {
                values.push([v, v.label]);
              }
            });
            return values;
          } else {
            return array;
          }
        }
      }).then((array) => {
        this.choices = array;
      });
    }
  ]
});
<|endoftext|>


<commit_before>
class MyToggle extends React.Component {

	constructor(props) {
		super(props);
		this.handleToggleClick = this.handleToggleClick.bind(this);
		this.state = {
			isToggleOn: false
		};
	}

	handleToggleClick(e) {

		this.setState(
			function(prevState) {
				return ({isToggleOn: !prevState.isToggleOn})
			}
		);
	}

	render() {
		let button = null;

		if(this.props.hide) {
			return null; // Return null stops the component from being rendered.
		}

		if(this.props.showAsButton){
			button = <button onClick={this.handleToggleClick} >
					     {this.state.isToggleOn ? 'State: ON' : 'State: OFF'}
				     </button>;

		} else {

			button =
					     {this.state.isToggleOn ? 'State: ON' : 'State: OFF'}
				     ;

		}

		return (
 {button}
);

	}
}

//MyActionHandler Written using a function
var MyActionHandler = () => {

    //Function to handle the click event
	const handleClick = (e) => {
    	e.preventDefault();
    	console.log('The link was clicked. THIS: '+ this);
    };

	return (
		Click Me
	);
};

//Written using a class. Note the this.handleClick call
class MyActionHandlerClass extends React.Component {
	constructor(props) {
		super(props);
		this.handleClick1 = this.handleClick1.bind(this); //To ensure this get bound with handleclick1
	}

	handleClick1(e) {
    	e.preventDefault();
    	console.log('The link CLICK ME HERE was clicked. THIS: '+ this);
    }

    render() {
		return (
			Click Me Here
		);
    }
}

class MyClock extends React.Component {
	constructor(props) {
		super(props);
		this.state = {date: new Date()};
	}

	render() {
		return (

 CLOCK TIME: {this.state.date.toLocaleTimeString()}

			);
	}

	componentDidMount() {
		setInterval (
			() => {this.tick()},
			1000);
	}

	tick() {
	    this.setState (
		    {
	    	  date: new Date()
	    	}
    	);
  	}

}

class MyList extends React.Component {
	constructor(props) {
		super(props);
	}

	render() {

		const numbers = this.props.numbers;
		const listItems = numbers.map(
							(x) =>

 Value: {x}

						   );

		return (

				{listItems}

			);
	}

}

var MyHelloTag = () => {
	return(

Hello Clock

			<MyClock/>
			<MyActionHandler/>

			<MyActionHandlerClass/>

			<MyToggle showAsButton={true} hide={false}/>
			<MyList numbers={[1,2,3,4,5]}/>

		);
};

ReactDOM.render(
	<MyHelloTag/>,
	document.getElementById('root')
);
<commit_msg>Changed list of numbers to list of strings<commit_after>
class MyToggle extends React.Component {

	constructor(props) {
		super(props);
		this.handleToggleClick = this.handleToggleClick.bind(this);
		this.state = {
			isToggleOn: false
		};
	}

	handleToggleClick(e) {

		this.setState(
			function(prevState) {
				return ({isToggleOn: !prevState.isToggleOn})
			}
		);
	}

	render() {
		let button = null;

		if(this.props.hide) {
			return null; // Return null stops the component from being rendered.
		}

		if(this.props.showAsButton){
			button = <button onClick={this.handleToggleClick} >
					     {this.state.isToggleOn ? 'State: ON' : 'State: OFF'}
				     </button>;

		} else {

			button =
					     {this.state.isToggleOn ? 'State: ON' : 'State: OFF'}
				     ;

		}

		return (
 {button}
);

	}
}

//MyActionHandler Written using a function
var MyActionHandler = () => {

    //Function to handle the click event
	const handleClick = (e) => {
    	e.preventDefault();
    	console.log('The link was clicked. THIS: '+ this);
    };

	return (
		Click Me
	);
};

//Written using a class. Note the this.handleClick call
class MyActionHandlerClass extends React.Component {
	constructor(props) {
		super(props);
		this.handleClick1 = this.handleClick1.bind(this); //To ensure this get bound with handleclick1
	}

	handleClick1(e) {
    	e.preventDefault();
    	console.log('The link CLICK ME HERE was clicked. THIS: '+ this);
    }

    render() {
		return (
			Click Me Here
		);
    }
}

class MyClock extends React.Component {
	constructor(props) {
		super(props);
		this.state = {date: new Date()};
	}

	render() {
		return (

 CLOCK TIME: {this.state.date.toLocaleTimeString()}

			);
	}

	componentDidMount() {
		setInterval (
			() => {this.tick()},
			1000);
	}

	tick() {
	    this.setState (
		    {
	    	  date: new Date()
	    	}
    	);
  	}

}

class MyList extends React.Component {
	constructor(props) {
		super(props);
	}

	render() {

		const numbers = this.props.numbers;
		const listItems = numbers.map(
							(x) =>

 Value: {x}

						   );

		return (

				{listItems}

			);
	}

}

var MyHelloTag = () => {
	return(

Hello Clock

			<MyClock/>
			<MyActionHandler/>

			<MyActionHandlerClass/>

			<MyToggle showAsButton={true} hide={false}/>
			<MyList numbers={["First","Second","Third","Fourth","Fifth"]}/>

		);
};

ReactDOM.render(
	<MyHelloTag/>,
	document.getElementById('root')
);
<|endoftext|>


<commit_before>
class MyToggle extends React.Component {

	constructor(props) {
		super(props);
		this.handleToggleClick = this.handleToggleClick.bind(this);
		this.state = {
			isToggleOn: false
		};
	}

	handleToggleClick(e) {

		this.setState(
			function(prevState) {
				return ({isToggleOn: !prevState.isToggleOn})
			}
		);
	}

	render() {
		let button = null;

		if(this.props.hide) {
			return null; // Return null stops the component from being rendered.
		}

		if(this.props.showAsButton){
			button = <button onClick={this.handleToggleClick} >
					     {this.state.isToggleOn ? 'State: ON' : 'State: OFF'}
				     </button>;

		} else {

			button =
					     {this.state.isToggleOn ? 'State: ON' : 'State: OFF'}
				     ;

		}

		return (
 {button}
);

	}
}

//MyActionHandler Written using a function
var MyActionHandler = () => {

    //Function to handle the click event
	const handleClick = (e) => {
    	e.preventDefault();
    	console.log('The link was clicked. THIS: '+ this);
    };

	return (
		Click Me
	);
};

//Written using a class. Note the this.handleClick call
class MyActionHandlerClass extends React.Component {
	constructor(props) {
		super(props);
		this.handleClick1 = this.handleClick1.bind(this); //To ensure this get bound with handleclick1
	}

	handleClick1(e) {
    	e.preventDefault();
    	console.log('The link CLICK ME HERE was clicked. THIS: '+ this);
    }

    render() {
		return (
			Click Me Here
		);
    }
}

class MyClock extends React.Component {
	constructor(props) {
		super(props);
		this.state = {date: new Date()};
	}

	render() {
		return (

 CLOCK TIME: {this.state.date.toLocaleTimeString()}

			);
	}

	componentDidMount() {
		setInterval (
			() => {this.tick()},
			1000);
	}

	tick() {
	    this.setState (
		    {
	    	  date: new Date()
	    	}
    	);
  	}

}

class MyList extends React.Component {
	constructor(props) {
		super(props);
	}

	render() {

		const numbers = this.props.numbers;
		const listItems = numbers.map(
							(x) =>

 Value: {x}

						   );

		return (

				{listItems}

			);
	}

}

class MyCuteLittleForm extends React.Component {

	constructor(props) {
		super(props);
		this.state = {
			nameOfPerson: 'Enter name here',
			address: 'Enter address here',
			state: '2'
		};

		this.handleOnChangeValue = this.handleOnChangeValue.bind(this);

		this.handleSubmit = this.handleSubmit.bind(this);
	}

	handleSubmit(e) {
		alert("Form has been submitted, with name: "+ this.state.nameOfPerson + " address: "+ this.state.address);
		e.preventDefault();

	}

	handleOnChangeValue(e) {
		const controlName = e.target.name;
		this.setState({[controlName]: e.target.value});
	}

	render() {
		const formView =
			<form onSubmit={this.handleSubmit}>
				<fieldset>
					<legend> Enter details </legend>
					Name:

					<input name="nameOfPerson" type="text" value = {this.state.nameOfPerson} onChange = {this.handleOnChangeValue} />

					Address:

					<textarea name="address" value = {this.state.address} onChange = {this.handleOnChangeValue} />

					State:

					<select name="state" value = {this.state.state} onChange = {this.handleOnChangeValue}>
						<option value="1">Maharashtra</option>
						<option value="2">UP</option>
						<option value="3">Tamil Nadu</option>
					</select>

				<input type="submit" value = "SUBMIT" />
				</fieldset>

				<fieldset>
					<legend> DETAILS </legend>

					Name: {this.state.nameOfPerson}

					Address: {this.state.address}

					State: {this.state.state}

				</fieldset>

			</form>

		return (

				{formView}

			);
	}

}

var MyHelloTag = () => {
	return(

Hello Clock

			<MyClock/>
			<MyActionHandler/>

			<MyActionHandlerClass/>

			<MyToggle showAsButton={true} hide={false}/>
			<MyList numbers={["First","Second","Third","Fourth","Fifth"]}/>
			<MyCuteLittleForm/>

		);
};

ReactDOM.render(
	<MyHelloTag/>,
	document.getElementById('root')
);
<commit_msg>Added a temp widget which shows C and F. The state is managed in the parent widget, and the values are propagated as props downwards to each child component.<commit_after>
class MyToggle extends React.Component {

	constructor(props) {
		super(props);
		this.handleToggleClick = this.handleToggleClick.bind(this);
		this.state = {
			isToggleOn: false
		};
	}

	handleToggleClick(e) {

		this.setState(
			function(prevState) {
				return ({isToggleOn: !prevState.isToggleOn})
			}
		);
	}

	render() {
		let button = null;

		if(this.props.hide) {
			return null; // Return null stops the component from being rendered.
		}

		if(this.props.showAsButton){
			button = <button onClick={this.handleToggleClick} >
					     {this.state.isToggleOn ? 'State: ON' : 'State: OFF'}
				     </button>;

		} else {

			button =
					     {this.state.isToggleOn ? 'State: ON' : 'State: OFF'}
				     ;

		}

		return (
 {button}
);

	}
}

//MyActionHandler Written using a function
var MyActionHandler = () => {

    //Function to handle the click event
	const handleClick = (e) => {
    	e.preventDefault();
    	console.log('The link was clicked. THIS: '+ this);
    };

	return (
		Click Me
	);
};

//Written using a class. Note the this.handleClick call
class MyActionHandlerClass extends React.Component {
	constructor(props) {
		super(props);
		this.handleClick1 = this.handleClick1.bind(this); //To ensure this get bound with handleclick1
	}

	handleClick1(e) {
    	e.preventDefault();
    	console.log('The link CLICK ME HERE was clicked. THIS: '+ this);
    }

    render() {
		return (
			Click Me Here
		);
    }
}

class MyClock extends React.Component {
	constructor(props) {
		super(props);
		this.state = {date: new Date()};
	}

	render() {
		return (

 CLOCK TIME: {this.state.date.toLocaleTimeString()}

			);
	}

	componentDidMount() {
		setInterval (
			() => {this.tick()},
			1000);
	}

	tick() {
	    this.setState (
		    {
	    	  date: new Date()
	    	}
    	);
  	}

}

class MyList extends React.Component {
	constructor(props) {
		super(props);
	}

	render() {

		const numbers = this.props.numbers;
		const listItems = numbers.map(
							(x) =>

 Value: {x}

						   );

		return (

				{listItems}

			);
	}

}

class MyCuteLittleForm extends React.Component {

	constructor(props) {
		super(props);
		this.state = {
			nameOfPerson: 'Enter name here',
			address: 'Enter address here',
			state: '2'
		};

		this.handleOnChangeValue = this.handleOnChangeValue.bind(this);

		this.handleSubmit = this.handleSubmit.bind(this);
	}

	handleSubmit(e) {
		alert("Form has been submitted, with name: "+ this.state.nameOfPerson + " address: "+ this.state.address);
		e.preventDefault();

	}

	handleOnChangeValue(e) {
		const controlName = e.target.name;
		this.setState({[controlName]: e.target.value});
	}

	render() {
		const formView =
			<form onSubmit={this.handleSubmit}>
				<fieldset>
					<legend> Enter details </legend>
					Name:

					<input name="nameOfPerson" type="text" value = {this.state.nameOfPerson} onChange = {this.handleOnChangeValue} />

					Address:

					<textarea name="address" value = {this.state.address} onChange = {this.handleOnChangeValue} />

					State:

					<select name="state" value = {this.state.state} onChange = {this.handleOnChangeValue}>
						<option value="1">Maharashtra</option>
						<option value="2">UP</option>
						<option value="3">Tamil Nadu</option>
					</select>

				<input type="submit" value = "SUBMIT" />
				</fieldset>

				<fieldset>
					<legend> DETAILS </legend>

					Name: {this.state.nameOfPerson}

					Address: {this.state.address}

					State: {this.state.state}

				</fieldset>

			</form>

		return (

				{formView}

			);
	}

}

class MyTemperatureEnteringComponent extends React.Component {

	constructor(props) {
		super(props);
	}

	render() {

		const name = this.props.type;

		let control =
			<form>
				<fieldset>
					<legend> Enter temperature in {name} </legend>
					<input type="number" name={name} value={this.props.temperatureValue} onChange={this.props.onChangeFunction}/>
				</fieldset>
			</form>;

		return (
 {control}
	);
	}

}

class MyTemperatureEnteringWidget extends React.Component {

	constructor(props) {
		super(props);

		this.state = {
			c_temp : 0,
			f_temp : this.convertCtoF(0)
		};

		this.handleOnChangeValue = this.handleOnChangeValue.bind(this);

	}

	convertCtoF(tempInC){
		return (tempInC * 9 / 5) + 32.0;
	}

	convertFtoC(tempInF){
		return (tempInF - 32) * 5 / 9;
	}

	handleOnChangeValue(e) {
		const controlName = e.target.name;
		const temperature = e.target.value;
		let f_temp = -1000, c_temp = -1000; //some arbitary outside value

		if(controlName === "celcius"){
			console.log('temperature entered in celcius control. recvd value = '+ temperature);
			c_temp = temperature;
			f_temp = this.convertCtoF(temperature);

		} else if (controlName === 'farenheit') {
			console.log('temperature entered in farenheit control. recvd value = '+ temperature);
			f_temp = temperature;
			c_temp = this.convertFtoC(temperature);
		} else {
			console.log('WTF. Which control did this come from? Control Name: '+ controlName);
		}

		this.setState({
			f_temp : [f_temp],
			c_temp : [c_temp]
		});

	}

	render() {

		return (

				<fieldset>
					<legend> TEMP CONTROL </legend>
					<MyTemperatureEnteringComponent type='celcius' temperatureValue={this.state.c_temp} onChangeFunction={this.handleOnChangeValue}/>
					<MyTemperatureEnteringComponent type='farenheit' temperatureValue={this.state.f_temp} onChangeFunction={this.handleOnChangeValue}/>

				</fieldset>

			);
	}

}

var MyHelloTag = () => {
	return(

Hello Clock

			<MyClock/>
			<MyActionHandler/>

			<MyActionHandlerClass/>

			<MyToggle showAsButton={true} hide={false}/>

			<MyList numbers={["First","Second","Third","Fourth","Fifth"]}/>

			<MyCuteLittleForm/>

			<MyTemperatureEnteringWidget/>

		);
};

ReactDOM.render(
	<MyHelloTag/>,
	document.getElementById('root')
);
<|endoftext|>