<commit_before>import gql from 'util/GraphQL';
import { loadFixtures, unloadFixtures } from 'util/fixtures';
import client from 'util/client';
import MockDate from 'mockdate';
import fixtures from '../__fixtures__/CreateMediaArticle';
import { getReplyRequestId } from '../CreateOrUpdateReplyRequest';

jest.mock('mediaManager', () => ({
  insert: jest.fn(),
}));

describe('creation', () => {
  beforeAll(() => loadFixtures(fixtures));
  beforeEach(() => {
    fetch.mockImplementation(() =>
      Promise.resolve({
        status: 200,
        body: {},
        buffer: jest.fn(),
      })
    );
    imageHash.mockImplementation((file, bits, method, callback) =>
      callback(undefined, 'mock_image_hash')
    );
    uploadToGCS.mockImplementation(() =>
      Promise.resolve('http://foo.com/output_image.jpeg')
    );
  });
  afterAll(() => unloadFixtures(fixtures));

  it('creates a media article and a reply request', async () => {
    MockDate.set(1485593157011);
    const userId = 'test';
    const appId = 'foo';

    const { data, errors } = await gql`
      mutation(
        $mediaUrl: String!
        $articleType: ArticleTypeEnum!
        $reference: ArticleReferenceInput!
      ) {
        CreateMediaArticle(
          mediaUrl: $mediaUrl
          articleType: $articleType
          reference: $reference
          reason: "気になります"
        ) {
          id
        }
      }
    `(
      {
        mediaUrl: 'http://foo.com/input_image.jpeg',
        articleType: 'IMAGE',
        reference: { type: 'LINE' },
      },
      { user: { id: userId, appId } }
    );
    MockDate.reset();

    expect(errors).toBeUndefined();

    const {
      body: { _source: article },
    } = await client.get({
      index: 'articles',
      type: 'doc',
      id: data.CreateMediaArticle.id,
    });

    expect(article.replyRequestCount).toBe(1);
    expect(article).toMatchInlineSnapshot(`
      Object {
        "appId": "foo",
        "articleCategories": Array [],
        "articleReplies": Array [],
        "articleType": "IMAGE",
        "attachmentHash": "mock_image_hash",
        "attachmentUrl": "http://foo.com/output_image.jpeg",
        "createdAt": "2017-01-28T08:45:57.011Z",
        "hyperlinks": Array [],
        "lastRequestedAt": "2017-01-28T08:45:57.011Z",
        "normalArticleCategoryCount": 0,
        "normalArticleReplyCount": 0,
        "references": Array [
          Object {
            "appId": "foo",
            "createdAt": "2017-01-28T08:45:57.011Z",
            "type": "LINE",
            "userId": "test",
          },
        ],
        "replyRequestCount": 1,
        "tags": Array [],
        "text": "",
        "updatedAt": "2017-01-28T08:45:57.011Z",
        "userId": "test",
      }
    `);

    const replyRequestId = getReplyRequestId({
      articleId: data.CreateMediaArticle.id,
      userId,
      appId,
    });

    const {
      body: { _source: replyRequest },
    } = await client.get({
      index: 'replyrequests',
      type: 'doc',
      id: replyRequestId,
    });

    delete replyRequest.articleId; // articleId is random
    expect(replyRequest).toMatchInlineSnapshot(`
      Object {
        "appId": "foo",
        "createdAt": "2017-01-28T08:45:57.011Z",
        "feedbacks": Array [],
        "negativeFeedbackCount": 0,
        "positiveFeedbackCount": 0,
        "reason": "気になります",
        "status": "NORMAL",
        "updatedAt": "2017-01-28T08:45:57.011Z",
        "userId": "test",
      }
    `);

    // // Cleanup
    await client.delete({
      index: 'articles',
      type: 'doc',
      id: data.CreateMediaArticle.id,
    });

    await client.delete({
      index: 'replyrequests',
      type: 'doc',
      id: replyRequestId,
    });
  });

  it('avoids creating duplicated media articles and adds replyRequests automatically', async () => {
    MockDate.set(1485593157011);
    const userId = 'test';
    const appId = 'foo';
    imageHash.mockImplementation((file, bits, method, callback) =>
      callback(undefined, 'ffff8000')
    );

    const { data, errors } = await gql`
      mutation(
        $mediaUrl: String!
        $articleType: ArticleTypeEnum!
        $reference: ArticleReferenceInput!
      ) {
        CreateMediaArticle(
          mediaUrl: $mediaUrl
          articleType: $articleType
          reference: $reference
          reason: "気になります"
        ) {
          id
        }
      }
    `(
      {
        mediaUrl: 'http://foo.com/input_image.jpeg',
        articleType: 'IMAGE',
        reference: { type: 'LINE' },
      },
      { user: { id: userId, appId } }
    );
    MockDate.reset();
    expect(errors).toBeUndefined();

    // Expects no new article is created,
    // and it returns the existing ID
    expect(data.CreateMediaArticle.id).toBe('image1');

    const articleId = data.CreateMediaArticle.id;
    const {
      body: { _source: article },
    } = await client.get({
      index: 'articles',
      type: 'doc',
      id: articleId,
    });

    // Expects lastRequestedAt, references are updated
    expect(article).toMatchInlineSnapshot(`
      Object {
        "attachmentHash": "ffff8000",
        "attachmentUrl": "http://foo/image.jpeg",
        "lastRequestedAt": "2017-01-28T08:45:57.011Z",
        "references": Array [
          Object {
            "type": "LINE",
          },
        ],
        "replyRequestCount": 2,
        "text": "",
      }
    `);

    // Expects new replyRequest is generated
    const replyRequestId = getReplyRequestId({ articleId, appId, userId });
    const {
      body: { _source: replyRequest },
    } = await client.get({
      index: 'replyrequests',
      type: 'doc',
      id: replyRequestId,
    });

    expect(replyRequest).toMatchInlineSnapshot(`
      Object {
        "appId": "foo",
        "articleId": "image1",
        "createdAt": "2017-01-28T08:45:57.011Z",
        "feedbacks": Array [],
        "negativeFeedbackCount": 0,
        "positiveFeedbackCount": 0,
        "reason": "気になります",
        "status": "NORMAL",
        "updatedAt": "2017-01-28T08:45:57.011Z",
        "userId": "test",
      }
    `);

    // Cleanup
    await client.delete({
      index: 'replyrequests',
      type: 'doc',
      id: replyRequestId,
    });
  });

  it('should pass correct filename and mime type to uploadToGCS', async () => {
    uploadToGCS.mockClear();
    const OLD_ENV = process.env;
    process.env = { ...OLD_ENV }; // Make a copy
    delete process.env.GCS_IMAGE_FOLDER;
    uploadFile({}, 'mock_image', 'IMAGE');

    expect(uploadToGCS.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {},
          "mock_image.jpeg",
          "image/jpeg",
        ],
      ]
    `);

    process.env.GCS_IMAGE_FOLDER = 'images';
    uploadToGCS.mockClear();
    uploadFile({}, 'mock_image', 'IMAGE');

    expect(uploadToGCS.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {},
          "images/mock_image.jpeg",
          "image/jpeg",
        ],
      ]
    `);

    uploadToGCS.mockClear();
    uploadFile({}, 'mock_video', 'VIDEO');

    expect(uploadToGCS.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {},
          undefined,
          "*/*",
        ],
      ]
    `);

    // Restore old environment
    process.env = OLD_ENV;
  });
});

describe('error', () => {
  beforeEach(() => {
    fetch.mockImplementation(() =>
      Promise.resolve({
        status: 200,
        body: {},
        buffer: jest.fn(),
      })
    );
    imageHash.mockImplementation((file, bits, method, callback) =>
      callback(undefined, 'mock_image_hash')
    );
    uploadToGCS.mockImplementation(() =>
      Promise.resolve('http://foo.com/output_image.jpeg')
    );
  });

  it('throws type error', async () => {
    MockDate.set(1485593157011);
    const userId = 'test';
    const appId = 'foo';

    const { errors } = await gql`
      mutation(
        $mediaUrl: String!
        $articleType: ArticleTypeEnum!
        $reference: ArticleReferenceInput!
      ) {
        CreateMediaArticle(
          mediaUrl: $mediaUrl
          articleType: $articleType
          reference: $reference
          reason: "気になります"
        ) {
          id
        }
      }
    `(
      {
        mediaUrl: 'http://foo.com/input_image.jpeg',
        articleType: 'AUDIO',
        reference: { type: 'LINE' },
      },
      { user: { id: userId, appId } }
    );
    MockDate.reset();

    expect(errors).toMatchInlineSnapshot(`
      Array [
        [GraphQLError: Type AUDIO is not yet supported.],
      ]
    `);
  });

  it('throws imageHash error', async () => {
    MockDate.set(1485593157011);
    const userId = 'test';
    const appId = 'foo';
    imageHash.mockImplementation((file, bits, method, callback) =>
      callback('ImageHash error', 'mock_image_hash')
    );

    const { errors } = await gql`
      mutation(
        $mediaUrl: String!
        $articleType: ArticleTypeEnum!
        $reference: ArticleReferenceInput!
      ) {
        CreateMediaArticle(
          mediaUrl: $mediaUrl
          articleType: $articleType
          reference: $reference
          reason: "気になります"
        ) {
          id
        }
      }
    `(
      {
        mediaUrl: 'http://foo.com/input_image.jpeg',
        articleType: 'IMAGE',
        reference: { type: 'LINE' },
      },
      { user: { id: userId, appId } }
    );
    MockDate.reset();

    expect(errors).toMatchInlineSnapshot(`
      Array [
        [GraphQLError: Unexpected error value: "ImageHash error"],
      ]
    `);
  });

  it('throws uploadToGCS error', async () => {
    MockDate.set(1485593157011);
    const userId = 'test';
    const appId = 'foo';
    uploadToGCS.mockImplementation(() => Promise.reject('UploadToGCS error'));

    const { errors } = await gql`
      mutation(
        $mediaUrl: String!
        $articleType: ArticleTypeEnum!
        $reference: ArticleReferenceInput!
      ) {
        CreateMediaArticle(
          mediaUrl: $mediaUrl
          articleType: $articleType
          reference: $reference
          reason: "気になります"
        ) {
          id
        }
      }
    `(
      {
        mediaUrl: 'http://foo.com/input_image.jpeg',
        articleType: 'IMAGE',
        reference: { type: 'LINE' },
      },
      { user: { id: userId, appId } }
    );
    MockDate.reset();

    expect(errors).toMatchInlineSnapshot(`
      Array [
        [GraphQLError: Unexpected error value: "UploadToGCS error"],
      ]
    `);
  });
});
<commit_msg>Fix first test for CreateMediaArticle by introducing mock correctly<commit_after>import gql from 'util/GraphQL';
import { loadFixtures, unloadFixtures } from 'util/fixtures';
import client from 'util/client';
import MockDate from 'mockdate';
import fixtures from '../__fixtures__/CreateMediaArticle';
import { getReplyRequestId } from '../CreateOrUpdateReplyRequest';
import mediaManager from 'util/mediaManager';

jest.mock('util/mediaManager');

describe('creation', () => {
  beforeAll(() => loadFixtures(fixtures));
  beforeEach(() => {
    mediaManager.insert.mockClear();
  });
  // beforeEach(() => {
  //   fetch.mockImplementation(() =>
  //     Promise.resolve({
  //       status: 200,
  //       body: {},
  //       buffer: jest.fn(),
  //     })
  //   );
  //   imageHash.mockImplementation((file, bits, method, callback) =>
  //     callback(undefined, 'mock_image_hash')
  //   );
  //   uploadToGCS.mockImplementation(() =>
  //     Promise.resolve('http://foo.com/output_image.jpeg')
  //   );
  // });
  afterAll(() => unloadFixtures(fixtures));

  it('creates a media article and a reply request', async () => {
    MockDate.set(1485593157011);
    const userId = 'test';
    const appId = 'foo';

    mediaManager.insert.mockImplementationOnce(async () => {
      return {
        id: 'mock_image_hash',
        url: 'http://foo.com/output_image.jpeg',
        type: 'image',
      };
    });

    const { data, errors } = await gql`
      mutation(
        $mediaUrl: String!
        $articleType: ArticleTypeEnum!
        $reference: ArticleReferenceInput!
      ) {
        CreateMediaArticle(
          mediaUrl: $mediaUrl
          articleType: $articleType
          reference: $reference
          reason: "気になります"
        ) {
          id
        }
      }
    `(
      {
        mediaUrl: 'http://foo.com/input_image.jpeg',
        articleType: 'IMAGE',
        reference: { type: 'LINE' },
      },
      { user: { id: userId, appId } }
    );
    MockDate.reset();

    expect(errors).toBeUndefined();

    // Expect calls to insert() to match snapshot
    expect(mediaManager.insert.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "url": "http://foo.com/input_image.jpeg",
          },
        ],
      ]
    `);

    const {
      body: { _source: article },
    } = await client.get({
      index: 'articles',
      type: 'doc',
      id: data.CreateMediaArticle.id,
    });

    expect(article.replyRequestCount).toBe(1);
    expect(article).toMatchInlineSnapshot(`
      Object {
        "appId": "foo",
        "articleCategories": Array [],
        "articleReplies": Array [],
        "articleType": "IMAGE",
        "attachmentHash": "mock_image_hash",
        "createdAt": "2017-01-28T08:45:57.011Z",
        "hyperlinks": Array [],
        "lastRequestedAt": "2017-01-28T08:45:57.011Z",
        "normalArticleCategoryCount": 0,
        "normalArticleReplyCount": 0,
        "references": Array [
          Object {
            "appId": "foo",
            "createdAt": "2017-01-28T08:45:57.011Z",
            "type": "LINE",
            "userId": "test",
          },
        ],
        "replyRequestCount": 1,
        "tags": Array [],
        "text": "",
        "updatedAt": "2017-01-28T08:45:57.011Z",
        "userId": "test",
      }
    `);

    const replyRequestId = getReplyRequestId({
      articleId: data.CreateMediaArticle.id,
      userId,
      appId,
    });

    const {
      body: { _source: replyRequest },
    } = await client.get({
      index: 'replyrequests',
      type: 'doc',
      id: replyRequestId,
    });

    delete replyRequest.articleId; // articleId is random
    expect(replyRequest).toMatchInlineSnapshot(`
      Object {
        "appId": "foo",
        "createdAt": "2017-01-28T08:45:57.011Z",
        "feedbacks": Array [],
        "negativeFeedbackCount": 0,
        "positiveFeedbackCount": 0,
        "reason": "気になります",
        "status": "NORMAL",
        "updatedAt": "2017-01-28T08:45:57.011Z",
        "userId": "test",
      }
    `);

    // // Cleanup
    await client.delete({
      index: 'articles',
      type: 'doc',
      id: data.CreateMediaArticle.id,
    });

    await client.delete({
      index: 'replyrequests',
      type: 'doc',
      id: replyRequestId,
    });
  });

  it('avoids creating duplicated media articles and adds replyRequests automatically', async () => {
    MockDate.set(1485593157011);
    const userId = 'test';
    const appId = 'foo';
    imageHash.mockImplementation((file, bits, method, callback) =>
      callback(undefined, 'ffff8000')
    );

    const { data, errors } = await gql`
      mutation(
        $mediaUrl: String!
        $articleType: ArticleTypeEnum!
        $reference: ArticleReferenceInput!
      ) {
        CreateMediaArticle(
          mediaUrl: $mediaUrl
          articleType: $articleType
          reference: $reference
          reason: "気になります"
        ) {
          id
        }
      }
    `(
      {
        mediaUrl: 'http://foo.com/input_image.jpeg',
        articleType: 'IMAGE',
        reference: { type: 'LINE' },
      },
      { user: { id: userId, appId } }
    );
    MockDate.reset();
    expect(errors).toBeUndefined();

    // Expects no new article is created,
    // and it returns the existing ID
    expect(data.CreateMediaArticle.id).toBe('image1');

    const articleId = data.CreateMediaArticle.id;
    const {
      body: { _source: article },
    } = await client.get({
      index: 'articles',
      type: 'doc',
      id: articleId,
    });

    // Expects lastRequestedAt, references are updated
    expect(article).toMatchInlineSnapshot(`
      Object {
        "attachmentHash": "ffff8000",
        "attachmentUrl": "http://foo/image.jpeg",
        "lastRequestedAt": "2017-01-28T08:45:57.011Z",
        "references": Array [
          Object {
            "type": "LINE",
          },
        ],
        "replyRequestCount": 2,
        "text": "",
      }
    `);

    // Expects new replyRequest is generated
    const replyRequestId = getReplyRequestId({ articleId, appId, userId });
    const {
      body: { _source: replyRequest },
    } = await client.get({
      index: 'replyrequests',
      type: 'doc',
      id: replyRequestId,
    });

    expect(replyRequest).toMatchInlineSnapshot(`
      Object {
        "appId": "foo",
        "articleId": "image1",
        "createdAt": "2017-01-28T08:45:57.011Z",
        "feedbacks": Array [],
        "negativeFeedbackCount": 0,
        "positiveFeedbackCount": 0,
        "reason": "気になります",
        "status": "NORMAL",
        "updatedAt": "2017-01-28T08:45:57.011Z",
        "userId": "test",
      }
    `);

    // Cleanup
    await client.delete({
      index: 'replyrequests',
      type: 'doc',
      id: replyRequestId,
    });
  });

  it('should pass correct filename and mime type to uploadToGCS', async () => {
    uploadToGCS.mockClear();
    const OLD_ENV = process.env;
    process.env = { ...OLD_ENV }; // Make a copy
    delete process.env.GCS_IMAGE_FOLDER;
    uploadFile({}, 'mock_image', 'IMAGE');

    expect(uploadToGCS.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {},
          "mock_image.jpeg",
          "image/jpeg",
        ],
      ]
    `);

    process.env.GCS_IMAGE_FOLDER = 'images';
    uploadToGCS.mockClear();
    uploadFile({}, 'mock_image', 'IMAGE');

    expect(uploadToGCS.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {},
          "images/mock_image.jpeg",
          "image/jpeg",
        ],
      ]
    `);

    uploadToGCS.mockClear();
    uploadFile({}, 'mock_video', 'VIDEO');

    expect(uploadToGCS.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {},
          undefined,
          "*/*",
        ],
      ]
    `);

    // Restore old environment
    process.env = OLD_ENV;
  });
});

describe('error', () => {
  beforeEach(() => {
    fetch.mockImplementation(() =>
      Promise.resolve({
        status: 200,
        body: {},
        buffer: jest.fn(),
      })
    );
    imageHash.mockImplementation((file, bits, method, callback) =>
      callback(undefined, 'mock_image_hash')
    );
    uploadToGCS.mockImplementation(() =>
      Promise.resolve('http://foo.com/output_image.jpeg')
    );
  });

  it('throws type error', async () => {
    MockDate.set(1485593157011);
    const userId = 'test';
    const appId = 'foo';

    const { errors } = await gql`
      mutation(
        $mediaUrl: String!
        $articleType: ArticleTypeEnum!
        $reference: ArticleReferenceInput!
      ) {
        CreateMediaArticle(
          mediaUrl: $mediaUrl
          articleType: $articleType
          reference: $reference
          reason: "気になります"
        ) {
          id
        }
      }
    `(
      {
        mediaUrl: 'http://foo.com/input_image.jpeg',
        articleType: 'AUDIO',
        reference: { type: 'LINE' },
      },
      { user: { id: userId, appId } }
    );
    MockDate.reset();

    expect(errors).toMatchInlineSnapshot(`
      Array [
        [GraphQLError: Type AUDIO is not yet supported.],
      ]
    `);
  });

  it('throws imageHash error', async () => {
    MockDate.set(1485593157011);
    const userId = 'test';
    const appId = 'foo';
    imageHash.mockImplementation((file, bits, method, callback) =>
      callback('ImageHash error', 'mock_image_hash')
    );

    const { errors } = await gql`
      mutation(
        $mediaUrl: String!
        $articleType: ArticleTypeEnum!
        $reference: ArticleReferenceInput!
      ) {
        CreateMediaArticle(
          mediaUrl: $mediaUrl
          articleType: $articleType
          reference: $reference
          reason: "気になります"
        ) {
          id
        }
      }
    `(
      {
        mediaUrl: 'http://foo.com/input_image.jpeg',
        articleType: 'IMAGE',
        reference: { type: 'LINE' },
      },
      { user: { id: userId, appId } }
    );
    MockDate.reset();

    expect(errors).toMatchInlineSnapshot(`
      Array [
        [GraphQLError: Unexpected error value: "ImageHash error"],
      ]
    `);
  });

  it('throws uploadToGCS error', async () => {
    MockDate.set(1485593157011);
    const userId = 'test';
    const appId = 'foo';
    uploadToGCS.mockImplementation(() => Promise.reject('UploadToGCS error'));

    const { errors } = await gql`
      mutation(
        $mediaUrl: String!
        $articleType: ArticleTypeEnum!
        $reference: ArticleReferenceInput!
      ) {
        CreateMediaArticle(
          mediaUrl: $mediaUrl
          articleType: $articleType
          reference: $reference
          reason: "気になります"
        ) {
          id
        }
      }
    `(
      {
        mediaUrl: 'http://foo.com/input_image.jpeg',
        articleType: 'IMAGE',
        reference: { type: 'LINE' },
      },
      { user: { id: userId, appId } }
    );
    MockDate.reset();

    expect(errors).toMatchInlineSnapshot(`
      Array [
        [GraphQLError: Unexpected error value: "UploadToGCS error"],
      ]
    `);
  });
});
<|endoftext|>


<commit_before>/* globals gettext */

import Backbone from 'backbone';

class UnenrollView extends Backbone.View {

  constructor(options) {
    const defaults = {
      el: '.unenroll-modal',
    };
    super(Object.assign({}, defaults, options));
  }

  handleTrigger(triggerElement) {
    this.$previouslyFocusedElement = triggerElement;
  }

  switchToSlideOne() {
    // Randomize survey option order
    const survey = document.querySelector('.options');
    for (let i = survey.children.length - 1; i >= 0; i -= 1) {
      survey.appendChild(survey.children[Math.trunc(Math.random() * i)]);
    }
    this.$('.inner-wrapper header').hide();
    this.$('#unenroll_form').hide();
    this.$('.slide1').removeClass('hidden');

    if (window.trapFocusForAccessibleModal) {
      window.trapFocusForAccessibleModal(
        this.$previouslyFocusedElement,
        window.focusableElementsString,
        this.closeButtonSelector,
        this.modalId,
        this.mainPageSelector,
      );
    }
  }

  switchToSlideTwo() {
    let reason = this.$(".reasons_survey input[name='reason']:checked").attr('val');
    if (reason === 'Other') {
      reason = this.$('.other_text').val();
    }
    if (reason) {
      window.analytics.track('unenrollment_reason.selected', {
        category: 'user-engagement',
        label: reason,
        displayName: 'v1',
      });
    }
    this.$('.slide1').addClass('hidden');
    this.$('.survey_course_name').text(this.$('#unenroll_course_name').text());
    this.$('.slide2').removeClass('hidden');
    this.$('.reasons_survey .return_to_dashboard').attr('href', this.urls.dashboard);
    this.$('.reasons_survey .browse_courses').attr('href', this.urls.browseCourses);

    if (window.trapFocusForAccessibleModal) {
      window.trapFocusForAccessibleModal(
        this.$previouslyFocusedElement,
        window.focusableElementsString,
        this.closeButtonSelector,
        this.modalId,
        this.mainPageSelector,
      );
    }
  }

  unenrollComplete(event, xhr) {
    if (xhr.status === 200) {
      if (!this.isEdx) {
        location.href = this.urls.dashboard;
      } else {
        this.switchToSlideOne();
        this.$('.reasons_survey:first .submit_reasons').click(this.switchToSlideTwo.bind(this));
      }
    } else if (xhr.status === 403) {
      location.href = `${this.urls.signInUser}?course_id=${
        encodeURIComponent($('#unenroll_course_id').val())}&enrollment_action=unenroll`;
    } else {
      $('#unenroll_error').text(
        gettext('Unable to determine whether we should give you a refund because' +
                ' of System Error. Please try again later.'),
      ).stop()
       .css('display', 'block');
    }
  }

  startSubmit() {
    this.$('.submit').prop('disabled', true);
  }

  initialize(options) {
    const view = this;

    this.urls = options.urls;
    this.isEdx = options.isEdx;

    this.closeButtonSelector = '.unenroll-modal .close-modal';
    this.$closeButton = $(this.closeButtonSelector);
    this.modalId = `#${this.$el.attr('id')}`;
    this.mainPageSelector = '#dashboard-main';

    this.triggerSelector = '.action-unenroll';
    $(this.triggerSelector).each((index, element) => {
      $(element).on('click', view.handleTrigger.bind($(element)));
    });

    this.$('.submit .submit-button').on('click', this.startSubmit.bind(this));
    $('#unenroll_form').on('ajax:complete', this.unenrollComplete.bind(this));
  }
}

export default UnenrollView;
<commit_msg>Re-add course key to segment unenroll tracker (#25516)<commit_after>/* globals gettext */

import Backbone from 'backbone';

class UnenrollView extends Backbone.View {

  constructor(options) {
    const defaults = {
      el: '.unenroll-modal',
    };
    super(Object.assign({}, defaults, options));
  }

  handleTrigger(triggerElement) {
    this.$previouslyFocusedElement = triggerElement;
  }

  switchToSlideOne() {
    // Randomize survey option order
    const survey = document.querySelector('.options');
    for (let i = survey.children.length - 1; i >= 0; i -= 1) {
      survey.appendChild(survey.children[Math.trunc(Math.random() * i)]);
    }
    this.$('.inner-wrapper header').hide();
    this.$('#unenroll_form').hide();
    this.$('.slide1').removeClass('hidden');

    if (window.trapFocusForAccessibleModal) {
      window.trapFocusForAccessibleModal(
        this.$previouslyFocusedElement,
        window.focusableElementsString,
        this.closeButtonSelector,
        this.modalId,
        this.mainPageSelector,
      );
    }
  }

  switchToSlideTwo() {
    let reason = this.$(".reasons_survey input[name='reason']:checked").attr('val');
    const courserunKey = $('#unenroll_course_id').val() + $('#unenroll_course_number').val();
    if (reason === 'Other') {
      reason = this.$('.other_text').val();
    }
    if (reason) {
      window.analytics.track('unenrollment_reason.selected', {
        category: 'user-engagement',
        label: reason,
        displayName: 'v1',
        courserunKey,
      });
    }
    this.$('.slide1').addClass('hidden');
    this.$('.survey_course_name').text(this.$('#unenroll_course_name').text());
    this.$('.slide2').removeClass('hidden');
    this.$('.reasons_survey .return_to_dashboard').attr('href', this.urls.dashboard);
    this.$('.reasons_survey .browse_courses').attr('href', this.urls.browseCourses);

    if (window.trapFocusForAccessibleModal) {
      window.trapFocusForAccessibleModal(
        this.$previouslyFocusedElement,
        window.focusableElementsString,
        this.closeButtonSelector,
        this.modalId,
        this.mainPageSelector,
      );
    }
  }

  unenrollComplete(event, xhr) {
    if (xhr.status === 200) {
      if (!this.isEdx) {
        location.href = this.urls.dashboard;
      } else {
        this.switchToSlideOne();
        this.$('.reasons_survey:first .submit_reasons').click(this.switchToSlideTwo.bind(this));
      }
    } else if (xhr.status === 403) {
      location.href = `${this.urls.signInUser}?course_id=${
        encodeURIComponent($('#unenroll_course_id').val())}&enrollment_action=unenroll`;
    } else {
      $('#unenroll_error').text(
        gettext('Unable to determine whether we should give you a refund because' +
                ' of System Error. Please try again later.'),
      ).stop()
       .css('display', 'block');
    }
  }

  startSubmit() {
    this.$('.submit').prop('disabled', true);
  }

  initialize(options) {
    const view = this;
    this.urls = options.urls;
    this.isEdx = options.isEdx;

    this.closeButtonSelector = '.unenroll-modal .close-modal';
    this.$closeButton = $(this.closeButtonSelector);
    this.modalId = `#${this.$el.attr('id')}`;
    this.mainPageSelector = '#dashboard-main';

    this.triggerSelector = '.action-unenroll';
    $(this.triggerSelector).each((index, element) => {
      $(element).on('click', view.handleTrigger.bind($(element)));
    });

    this.$('.submit .submit-button').on('click', this.startSubmit.bind(this));
    $('#unenroll_form').on('ajax:complete', this.unenrollComplete.bind(this));
  }
}

export default UnenrollView;
<|endoftext|>


<commit_before>
/* This file contains code to generate the configuration for different types of
 * levels. */

const RG = require('../src/rg');
RG.Names = require('./name-gen');
const WorldConf = require('../src/world.creator');

const LevelGen = {};

const getNumLevels = function(name) {
    switch (name) {
        case 'Cave': return 1;
        case 'Crypt': return 2;
        case 'Dungeon': return 3;
        case 'Labyrinth': return 1;
        default: return 3;
    }
};

/* Returns generation constraints based on the level name. */
const getConstraint = function(name) {
    switch (name) {
        case 'Cave': return {
            actor: actor => actor.type === 'animal' || actor.type === 'goblin'
        };
        case 'Crypt': return {
            actor: actor => actor.type === 'undead'
        };
        default: return null;
    }
};

const convertToImplemented = function(name) {
    switch (name) {
        case 'Grotto': return 'Cave';
        case 'Cavern': return 'Cave';
        case 'Catacombs': return 'Crypt';
        case 'Tombs': return 'Crypt';
        case 'Cells': return 'Dungeon';
        default: return name;
    }
};

//---------------------------------------------------------------------------
// DUNGEON GENERATION
//---------------------------------------------------------------------------

LevelGen.getDungeonConf = dungeonName => {
    const usedName = convertToImplemented(dungeonName);
    // const brName = RG.Names.getGenericPlaceName('branch');
    const nLevels = getNumLevels(usedName);
    const constraint = getConstraint(usedName);
    const obj = {
        name: dungeonName,
        dungeonType: usedName.toLowerCase(),
        nBranches: 1,
        branch: [
            {name: dungeonName, nLevels, entranceLevel: 0}
        ]
    };

    if (constraint) {
        obj.constraint = constraint;
    }

    return obj;
};

//---------------------------------------------------------------------------
// CITY GENERATION
//---------------------------------------------------------------------------

const getNumQuarters = (cityType) => {
    switch (cityType) {
        case 'Hamlet': return 2;
        case 'Village': return 1;
        case 'Town': return 2;
        case 'Fort': return 2;
        case 'Stronhold': return RG.RAND.getUniformInt(2, 4);
        case 'Capital': return RG.RAND.getUniformInt(3, 5);
        default: return 1;
    }
};

const getRandomShopType = () => RG.RAND.arrayGetRand(RG.SHOP_TYPES);

/* Adds shop generation constraints for the quarter. */
const addShopConstraints = (qConf, conf) => {
    const maxValue = conf.maxValue || 100;
    const shopTypeConf = conf.shopType || 'random';
    const qName = conf.name;

    if (qName === 'Market' || qName === 'Bazaar') {
        const nShops = RG.RAND.getUniformInt(1, 3);
        qConf.nShops = nShops;
        qConf.shop = [];
        for (let i = 0; i < nShops; i++) {
            let shopType = getRandomShopType();

            // Optionally allow first shopType to be given
            if (shopTypeConf !== 'random' && i === 0) {
                shopType = shopTypeConf;
            }
            const shopFunc = (item) => (
                item.type === shopType && item.value <= maxValue
            );
            qConf.push(shopFunc);
        }
    }
};

/* Returns the configuration for city quarters. */
const getQuarterConf = (nQuarters, conf) => {
    const quarters = [];
    for (let i = 0; i < nQuarters; i++) {
        const qName = RG.Names.getGenericPlaceName('quarter');
        const qConf = {
            name: qName,
            nLevels: 1
        };
        if (i === 0) {
            qConf.entranceLevel = 0;
        }

        addShopConstraints(qConf, conf);
        // TODO add any other special features based on the type
        quarters.push(qConf);
    }
    return quarters;
};

LevelGen.getCityConf = (cityName, conf) => {
    let cityType = RG.Names.getGenericPlaceName('city');
    if (conf.type === 'fort') {
        cityType = 'Fort';
    }
    else if (conf.capital) {
        cityType = 'Capital';
    }
    else if (conf.type === 'stronghold') {
        cityType = 'Stronghold';
    }
    else if (conf.type === 'village') {
        cityType = RG.Names.getVillageType();
    }
    const nQuarters = getNumQuarters(cityType, conf);
    const quarters = getQuarterConf(nQuarters, conf);
    const connect = WorldConf.createQuarterConnections(quarters);
    const obj = {
        name: cityName,
        nQuarters,
        quarter: quarters
    };

    if (connect) {obj.connect = connect;}
    return obj;
};

module.exports = LevelGen;
<commit_msg>Changed default nShops 0 -> 1.<commit_after>
/* This file contains code to generate the configuration for different types of
 * levels. */

const RG = require('../src/rg');
RG.Names = require('./name-gen');
const WorldConf = require('../src/world.creator');

const LevelGen = {};

const getNumLevels = function(name) {
    switch (name) {
        case 'Cave': return 1;
        case 'Crypt': return 2;
        case 'Dungeon': return 3;
        case 'Labyrinth': return 1;
        default: return 3;
    }
};

/* Returns generation constraints based on the level name. */
const getConstraint = function(name) {
    switch (name) {
        case 'Cave': return {
            actor: actor => actor.type === 'animal' || actor.type === 'goblin'
        };
        case 'Crypt': return {
            actor: actor => actor.type === 'undead'
        };
        default: return null;
    }
};

const convertToImplemented = function(name) {
    switch (name) {
        case 'Grotto': return 'Cave';
        case 'Cavern': return 'Cave';
        case 'Catacombs': return 'Crypt';
        case 'Tombs': return 'Crypt';
        case 'Cells': return 'Dungeon';
        default: return name;
    }
};

//---------------------------------------------------------------------------
// DUNGEON GENERATION
//---------------------------------------------------------------------------

LevelGen.getDungeonConf = dungeonName => {
    const usedName = convertToImplemented(dungeonName);
    // const brName = RG.Names.getGenericPlaceName('branch');
    const nLevels = getNumLevels(usedName);
    const constraint = getConstraint(usedName);
    const obj = {
        name: dungeonName,
        dungeonType: usedName.toLowerCase(),
        nBranches: 1,
        branch: [
            {name: dungeonName, nLevels, entranceLevel: 0}
        ]
    };

    if (constraint) {
        obj.constraint = constraint;
    }

    return obj;
};

//---------------------------------------------------------------------------
// CITY GENERATION
//---------------------------------------------------------------------------

const getNumQuarters = (cityType) => {
    switch (cityType) {
        case 'Hamlet': return 2;
        case 'Village': return 1;
        case 'Town': return 2;
        case 'Fort': return 2;
        case 'Stronhold': return RG.RAND.getUniformInt(2, 4);
        case 'Capital': return RG.RAND.getUniformInt(3, 5);
        default: return 1;
    }
};

const getRandomShopType = () => RG.RAND.arrayGetRand(RG.SHOP_TYPES);

/* Adds shop generation constraints for the quarter. */
const addShopConstraints = (qConf, conf) => {
    const maxValue = conf.maxValue || 100;
    const shopTypeConf = conf.shopType || 'random';
    const qName = conf.name;

    if (qName === 'Market' || qName === 'Bazaar') {
        const nShops = RG.RAND.getUniformInt(1, 3);
        qConf.nShops = nShops;
        qConf.shop = [];
        for (let i = 0; i < nShops; i++) {
            let shopType = getRandomShopType();

            // Optionally allow first shopType to be given
            if (shopTypeConf !== 'random' && i === 0) {
                shopType = shopTypeConf;
            }
            const shopFunc = item => (
                item.type === shopType && item.value <= maxValue
            );
            qConf.shop.push(shopFunc);
        }
    }
    else {
        qConf.nShops = 1;
    }
};

/* Returns the configuration for city quarters. */
const getQuarterConf = (nQuarters, conf) => {
    const quarters = [];
    for (let i = 0; i < nQuarters; i++) {
        const qName = RG.Names.getGenericPlaceName('quarter');
        const qConf = {
            name: qName,
            nLevels: 1
        };
        if (i === 0) {
            qConf.entranceLevel = 0;
        }

        addShopConstraints(qConf, conf);
        // TODO add any other special features based on the type
        quarters.push(qConf);
    }
    return quarters;
};

LevelGen.getCityConf = (cityName, conf) => {
    let cityType = RG.Names.getGenericPlaceName('city');
    if (conf.type === 'fort') {
        cityType = 'Fort';
    }
    else if (conf.capital) {
        cityType = 'Capital';
    }
    else if (conf.type === 'stronghold') {
        cityType = 'Stronghold';
    }
    else if (conf.type === 'village') {
        cityType = RG.Names.getVillageType();
    }
    const nQuarters = getNumQuarters(cityType);
    const quarters = getQuarterConf(nQuarters, conf);
    const connect = WorldConf.createQuarterConnections(quarters);
    const obj = {
        name: cityName,
        nQuarters,
        quarter: quarters
    };

    if (connect) {obj.connect = connect;}
    return obj;
};

module.exports = LevelGen;
<|endoftext|>


<commit_before>Errors = new Meteor.Collection(null);

throwError = function(message) {
  Errors.insert({
    message: message
  });
};

Template.errors.helpers({
  errors: function() {
    return Errors.find();
  }
});<commit_msg>Commit 9-3 Effacer les erreurs après 3 secondes.<commit_after>Errors = new Meteor.Collection(null);

// function utilisée pour récupérer les messages d'erreurs
throwError = function(message) {
  Errors.insert({
    message: message
  });
};

// Helper du template d'affichage
// On notera que errors.html comporte deux template
// _la boucle d'erreurs
// _le template d'une erreur
Template.errors.helpers({
  errors: function() {
    return Errors.find();
  }
});

// Suppresion de l'alert
Template.error.onRendered(function() {
  var error = this.data;
  Meteor.setTimeout(function() {
    Errors.remove(error._id);
  }, 3000);
});<|endoftext|>


<commit_before>Router.configure({
    autoRender: false,
    notFoundTemplate: 'notFound'
});

Router.map(function() {
    this.route('home', {
	path: '/'
    });
    this.route('info', {});
    this.route('instructions', {});

    this.route('browseParts', {
	path: '/parts/browse/:page?/:_id?',
	before: [function(){
	    Session.set('currentPart',this.params._id);
	    Session.set('currentPage',this.params.page);
	}]
    });

    this.route('designPart', {
	path: '/parts/design/:_id?',
	before: [function(){
	    if(!Meteor.user()){
		this.render("mustBeLoggedIn");
		this.stop();
	    }
	    Session.set('currentPart',this.params._id);
	}]
    });

    this.route('browseShips', {
	path: '/ships/browse/:page?/:_id?',
	before: [function(){
	    Session.set('currentShip',this.params._id);
	    Session.set('currentPage',this.params.page);
	}]
    });

    this.route('designShip', {
	path: '/ships/design/:_id?',
	before: [function(){
	    if(!Meteor.user()){
		this.render("mustBeLoggedIn");
		this.stop();
	    }
	    Session.set('currentShip',this.params._id);
	}]
    });

    this.route('launchShip', {
	path: '/ships/launch/:_id?',
	before: [function(){
	    if(this.params._id) Session.set('currentShip',this.params._id);
	    else{
		var id = Session.get('currentShip');
		if(!id){
		    this.render("mustSelectShip");
		    this.stop();
		}
	    }
	    if(!(Ships.findOne(id))){
		    this.render("mustSelectShip");
		    this.stop();
	    }

	}]
    });
});
<commit_msg>fixed bug on launch<commit_after>Router.configure({
    autoRender: false,
    notFoundTemplate: 'notFound'
});

Router.map(function() {
    this.route('home', {
	path: '/'
    });
    this.route('info', {});
    this.route('instructions', {});

    this.route('browseParts', {
	path: '/parts/browse/:page?/:_id?',
	before: [function(){
	    Session.set('currentPart',this.params._id);
	    Session.set('currentPage',this.params.page);
	}]
    });

    this.route('designPart', {
	path: '/parts/design/:_id?',
	before: [function(){
	    if(!Meteor.user()){
		this.render("mustBeLoggedIn");
		this.stop();
	    }
	    Session.set('currentPart',this.params._id);
	}]
    });

    this.route('browseShips', {
	path: '/ships/browse/:page?/:_id?',
	before: [function(){
	    Session.set('currentShip',this.params._id);
	    Session.set('currentPage',this.params.page);
	}]
    });

    this.route('designShip', {
	path: '/ships/design/:_id?',
	before: [function(){
	    if(!Meteor.user()){
		this.render("mustBeLoggedIn");
		this.stop();
	    }
	    Session.set('currentShip',this.params._id);
	}]
    });

    this.route('launchShip', {
	path: '/ships/launch/:_id?',
	before: [function(){
	    if(this.params._id) Session.set('currentShip',this.params._id);
	    if(!(Session.get('currentShip') || !(Ships.findOne(Session.get('currentShip'))))){
		this.render("mustSelectShip");
		this.stop();
	    }

	}]
    });
});
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc directive
 * @name ng.directive:ngClick
 *
 * @description
 * The ngClick allows you to specify custom behavior when
 * element is clicked.
 *
 * @element ANY
 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
 * click. (Event object is available as `$event`)
 *
 * @example
   <doc:example>
     <doc:source>
      <button ng-click="count = count + 1" ng-init="count=0">
        Increment
      </button>
      count: {{count}}
     </doc:source>
     <doc:scenario>
       it('should check ng-click', function() {
         expect(binding('count')).toBe('0');
         element('.doc-example-live :button').click();
         expect(binding('count')).toBe('1');
       });
     </doc:scenario>
   </doc:example>
 */
/*
 * A directive that allows creation of custom onclick handlers that are defined as angular
 * expressions and are compiled and executed within the current scope.
 *
 * Events that are handled via these handler are always configured not to propagate further.
 */
var ngEventDirectives = {};
forEach(
  'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress'.split(' '),
  function(name) {
    var directiveName = directiveNormalize('ng-' + name);
    ngEventDirectives[directiveName] = ['$parse', function($parse) {
      return function(scope, element, attr) {
        var fn = $parse(attr[directiveName]);
        element.bind(lowercase(name), function(event) {
          scope.$apply(function() {
            fn(scope, {$event:event});
          });
        });
      };
    }];
  }
);

/**
 * @ngdoc directive
 * @name ng.directive:ngDblclick
 *
 * @description
 * The `ngDblclick` directive allows you to specify custom behavior on dblclick event.
 *
 * @element ANY
 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
 * dblclick. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMousedown
 *
 * @description
 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
 *
 * @element ANY
 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
 * mousedown. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMouseup
 *
 * @description
 * Specify custom behavior on mouseup event.
 *
 * @element ANY
 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
 * mouseup. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMouseover
 *
 * @description
 * Specify custom behavior on mouseover event.
 *
 * @element ANY
 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
 * mouseover. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMouseenter
 *
 * @description
 * Specify custom behavior on mouseenter event.
 *
 * @element ANY
 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
 * mouseenter. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMouseleave
 *
 * @description
 * Specify custom behavior on mouseleave event.
 *
 * @element ANY
 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
 * mouseleave. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMousemove
 *
 * @description
 * Specify custom behavior on mousemove event.
 *
 * @element ANY
 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
 * mousemove. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngKeydown
 *
 * @description
 * Specify custom behavior on keydown event.
 *
 * @element ANY
 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngKeyup
 *
 * @description
 * Specify custom behavior on keyup event.
 *
 * @element ANY
 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngKeypress
 *
 * @description
 * Specify custom behavior on keypress event.
 *
 * @element ANY
 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
 * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngSubmit
 *
 * @description
 * Enables binding angular expressions to onsubmit events.
 *
 * Additionally it prevents the default action (which for form means sending the request to the
 * server and reloading the current page).
 *
 * @element form
 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
 *
 * @example
   <doc:example>
     <doc:source>

      <form ng-submit="submit()" ng-controller="Ctrl">
        Enter text and hit enter:
        <input type="text" ng-model="text" name="text" />
        <input type="submit" id="submit" value="Submit" />

list={{list}}

      </form>
     </doc:source>
     <doc:scenario>
       it('should check ng-submit', function() {
         expect(binding('list')).toBe('[]');
         element('.doc-example-live #submit').click();
         expect(binding('list')).toBe('["hello"]');
         expect(input('text').val()).toBe('');
       });
       it('should ignore empty strings', function() {
         expect(binding('list')).toBe('[]');
         element('.doc-example-live #submit').click();
         element('.doc-example-live #submit').click();
         expect(binding('list')).toBe('["hello"]');
       });
     </doc:scenario>
   </doc:example>
 */
var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
  element.bind('submit', function() {
    scope.$apply(attrs.ngSubmit);
  });
});
<commit_msg>docs(ngSubmit): clarify that there must be no `action` attribute<commit_after>'use strict';

/**
 * @ngdoc directive
 * @name ng.directive:ngClick
 *
 * @description
 * The ngClick allows you to specify custom behavior when
 * element is clicked.
 *
 * @element ANY
 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
 * click. (Event object is available as `$event`)
 *
 * @example
   <doc:example>
     <doc:source>
      <button ng-click="count = count + 1" ng-init="count=0">
        Increment
      </button>
      count: {{count}}
     </doc:source>
     <doc:scenario>
       it('should check ng-click', function() {
         expect(binding('count')).toBe('0');
         element('.doc-example-live :button').click();
         expect(binding('count')).toBe('1');
       });
     </doc:scenario>
   </doc:example>
 */
/*
 * A directive that allows creation of custom onclick handlers that are defined as angular
 * expressions and are compiled and executed within the current scope.
 *
 * Events that are handled via these handler are always configured not to propagate further.
 */
var ngEventDirectives = {};
forEach(
  'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress'.split(' '),
  function(name) {
    var directiveName = directiveNormalize('ng-' + name);
    ngEventDirectives[directiveName] = ['$parse', function($parse) {
      return function(scope, element, attr) {
        var fn = $parse(attr[directiveName]);
        element.bind(lowercase(name), function(event) {
          scope.$apply(function() {
            fn(scope, {$event:event});
          });
        });
      };
    }];
  }
);

/**
 * @ngdoc directive
 * @name ng.directive:ngDblclick
 *
 * @description
 * The `ngDblclick` directive allows you to specify custom behavior on dblclick event.
 *
 * @element ANY
 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
 * dblclick. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMousedown
 *
 * @description
 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
 *
 * @element ANY
 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
 * mousedown. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMouseup
 *
 * @description
 * Specify custom behavior on mouseup event.
 *
 * @element ANY
 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
 * mouseup. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMouseover
 *
 * @description
 * Specify custom behavior on mouseover event.
 *
 * @element ANY
 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
 * mouseover. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMouseenter
 *
 * @description
 * Specify custom behavior on mouseenter event.
 *
 * @element ANY
 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
 * mouseenter. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMouseleave
 *
 * @description
 * Specify custom behavior on mouseleave event.
 *
 * @element ANY
 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
 * mouseleave. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngMousemove
 *
 * @description
 * Specify custom behavior on mousemove event.
 *
 * @element ANY
 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
 * mousemove. (Event object is available as `$event`)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngKeydown
 *
 * @description
 * Specify custom behavior on keydown event.
 *
 * @element ANY
 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngKeyup
 *
 * @description
 * Specify custom behavior on keyup event.
 *
 * @element ANY
 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngKeypress
 *
 * @description
 * Specify custom behavior on keypress event.
 *
 * @element ANY
 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
 * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ng.directive:ngSubmit
 *
 * @description
 * Enables binding angular expressions to onsubmit events.
 *
 * Additionally it prevents the default action (which for form means sending the request to the
 * server and reloading the current page) **but only if the form does not contain an `action`
 * attribute**.
 *
 * @element form
 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
 *
 * @example
   <doc:example>
     <doc:source>

      <form ng-submit="submit()" ng-controller="Ctrl">
        Enter text and hit enter:
        <input type="text" ng-model="text" name="text" />
        <input type="submit" id="submit" value="Submit" />

list={{list}}

      </form>
     </doc:source>
     <doc:scenario>
       it('should check ng-submit', function() {
         expect(binding('list')).toBe('[]');
         element('.doc-example-live #submit').click();
         expect(binding('list')).toBe('["hello"]');
         expect(input('text').val()).toBe('');
       });
       it('should ignore empty strings', function() {
         expect(binding('list')).toBe('[]');
         element('.doc-example-live #submit').click();
         element('.doc-example-live #submit').click();
         expect(binding('list')).toBe('["hello"]');
       });
     </doc:scenario>
   </doc:example>
 */
var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
  element.bind('submit', function() {
    scope.$apply(attrs.ngSubmit);
  });
});
<|endoftext|>


<commit_before>angular.module('rv').controller('IndexController', [
    '$scope', '$location', 'RepoListResource',
    function ($scope, $location, RepoListResource) {
        'use strict';

        $scope.repos = [];

        RepoListResource.getRepos().$promise.then(function (data) {
            $scope.repos = data;
        });

        $scope.isActiveRepo = function (repoName) {
            return $location.url() === '//video/' + repoName;
        };
    }
]);<commit_msg>Fixed active menu element feature<commit_after>angular.module('rv').controller('IndexController', [
    '$scope', '$location', 'RepoListResource',
    function ($scope, $location, RepoListResource) {
        'use strict';

        $scope.repos = [];

        RepoListResource.getRepos().$promise.then(function (data) {
            $scope.repos = data;
        });

        $scope.isActiveRepo = function (repoName) {
            return $location.url() === '/video/' + repoName;
        };
    }
]);<|endoftext|>


<commit_before>'use strict';

/* Controllers */

angular.module('angular-client-side-auth')
.controller('NavCtrl', ['$rootScope', '$scope', '$location', 'Auth', function($rootScope, $scope, $location, Auth) {
    $scope.user = Auth.user;
    $scope.userRoles = Auth.userRoles;
    $scope.accessLevels = Auth.accessLevels;

    $scope.logout = function() {
        Auth.logout(function() {
            $location.path('/login');
        }, function() {
            $rootScope.error = "Failed to logout";
        });
    };
}]);

angular.module('angular-client-side-auth')
.controller('DataCtrl', ['$rootScope', '$scope', '$location', 'Auth', function($rootScope, $scope, $location, Auth) {
    $scope.user = Auth.user;
    $scope.userRoles = Auth.userRoles;
    $scope.accessLevels = Auth.accessLevels;

    $scope.getData = function() {
        /*Auth.getData(function(res) {
            $rootScope.success = "Retrieved data";
            $scope.data = res;
        }, function() {
            $rootScope.error = "Failed to retrieve data";
        });
        */
        $scope.data = [{"payer": "bobby", "payee" : "Bob"}, {"payer": "test", "payee" : "test2"}];
        $scope.test = JSON.stringify($scope.data);
        $rootScope.success = JSON.stringify($scope.data);
    };
}]);

angular.module('angular-client-side-auth')
.controller('TransCtrl', ['$rootScope', '$scope', '$location', 'Auth', function($rootScope, $scope, $location, Auth) {
    $scope.user = Auth.user;
    $scope.userRoles = Auth.userRoles;
    $scope.accessLevels = Auth.accessLevels;

    $scope.newTransaction = function() {
        Auth.newTransaction( {
            payer: $scope.payer,
            payee: $scope.payee,
            amount: $scope.amount,
            reason: $scope.reason
        }, function() { //Success
            $rootScope.success = "Created transaction";
            $location.path('/transactionLog');
        }, function() {
            $rootScope.error = "Failed to create transaction";
        });
    };
}]);

angular.module('angular-client-side-auth')
.controller('LoginCtrl',
['$rootScope', '$scope', '$location', '$window', 'Auth', function($rootScope, $scope, $location, $window, Auth) {

    $scope.rememberme = true;
    $scope.login = function() {
        Auth.login({
                username: $scope.username,
                password: $scope.password,
                rememberme: $scope.rememberme
            },
            function(res) {
                $location.path('/');
            },
            function(err) {
                $rootScope.error = "Failed to login";
            });
    };

    $scope.loginOauth = function(provider) {
        $window.location.href = '/auth/' + provider;
    };
}]);

angular.module('angular-client-side-auth')
.controller('RegisterCtrl',
['$rootScope', '$scope', '$location', 'Auth', function($rootScope, $scope, $location, Auth) {
    $scope.role = Auth.userRoles.user;
    $scope.userRoles = Auth.userRoles;

    $scope.register = function() {
        Auth.register({
                username: $scope.username,
                password: $scope.password,
                role: $scope.role
            },
            function() {
                $location.path('/');
            },
            function(err) {
                $rootScope.error = err;
            });
    };
}]);

angular.module('angular-client-side-auth')
.controller('AdminCtrl',
['$rootScope', '$scope', 'Users', 'Auth', function($rootScope, $scope, Users, Auth) {
    $scope.loading = true;
    $scope.userRoles = Auth.userRoles;

    Users.getAll(function(res) {
        $scope.users = res;
        $scope.loading = false;
    }, function(err) {
        $rootScope.error = "Failed to fetch users.";
        $scope.loading = false;
    });

}]);

<commit_msg>Passing data to jade<commit_after>'use strict';

/* Controllers */

angular.module('angular-client-side-auth')
.controller('NavCtrl', ['$rootScope', '$scope', '$location', 'Auth', function($rootScope, $scope, $location, Auth) {
    $scope.user = Auth.user;
    $scope.userRoles = Auth.userRoles;
    $scope.accessLevels = Auth.accessLevels;

    $scope.logout = function() {
        Auth.logout(function() {
            $location.path('/login');
        }, function() {
            $rootScope.error = "Failed to logout";
        });
    };
}]);

angular.module('angular-client-side-auth')
.controller('DataCtrl', ['$rootScope', '$scope', '$location', 'Auth', function($rootScope, $scope, $location, Auth) {
    $scope.user = Auth.user;
    $scope.userRoles = Auth.userRoles;
    $scope.accessLevels = Auth.accessLevels;

        /*Auth.getData(function(res) {
            $rootScope.success = "Retrieved data";
            $scope.data = res;
        }, function() {
            $rootScope.error = "Failed to retrieve data";
        });
        */
    $scope.data = [{"payer": "bobby", "payee" : "Bob"}, {"payer": "test", "payee" : "test2"}];
    $scope.test = JSON.stringify($scope.data);
    $rootScope.success = JSON.stringify($scope.data);
}]);

angular.module('angular-client-side-auth')
.controller('TransCtrl', ['$rootScope', '$scope', '$location', 'Auth', function($rootScope, $scope, $location, Auth) {
    $scope.user = Auth.user;
    $scope.userRoles = Auth.userRoles;
    $scope.accessLevels = Auth.accessLevels;

    $scope.newTransaction = function() {
        Auth.newTransaction( {
            payer: $scope.payer,
            payee: $scope.payee,
            amount: $scope.amount,
            reason: $scope.reason
        }, function() { //Success
            $rootScope.success = "Created transaction";
            $location.path('/transactionLog');
        }, function() {
            $rootScope.error = "Failed to create transaction";
        });
    };
}]);

angular.module('angular-client-side-auth')
.controller('LoginCtrl',
['$rootScope', '$scope', '$location', '$window', 'Auth', function($rootScope, $scope, $location, $window, Auth) {

    $scope.rememberme = true;
    $scope.login = function() {
        Auth.login({
                username: $scope.username,
                password: $scope.password,
                rememberme: $scope.rememberme
            },
            function(res) {
                $location.path('/');
            },
            function(err) {
                $rootScope.error = "Failed to login";
            });
    };

    $scope.loginOauth = function(provider) {
        $window.location.href = '/auth/' + provider;
    };
}]);

angular.module('angular-client-side-auth')
.controller('RegisterCtrl',
['$rootScope', '$scope', '$location', 'Auth', function($rootScope, $scope, $location, Auth) {
    $scope.role = Auth.userRoles.user;
    $scope.userRoles = Auth.userRoles;

    $scope.register = function() {
        Auth.register({
                username: $scope.username,
                password: $scope.password,
                role: $scope.role
            },
            function() {
                $location.path('/');
            },
            function(err) {
                $rootScope.error = err;
            });
    };
}]);

angular.module('angular-client-side-auth')
.controller('AdminCtrl',
['$rootScope', '$scope', 'Users', 'Auth', function($rootScope, $scope, Users, Auth) {
    $scope.loading = true;
    $scope.userRoles = Auth.userRoles;

    Users.getAll(function(res) {
        $scope.users = res;
        $scope.loading = false;
    }, function(err) {
        $rootScope.error = "Failed to fetch users.";
        $scope.loading = false;
    });

}]);

<|endoftext|>


<commit_before>import React, { Fragment } from "react";

import { removeEnumerator } from "../utils/surveyUtils";

export class SurveyCollection extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            currentNodeIndex: 0,
            topLevelNodeIds: [],
        };
    }

    componentDidMount() {
        const topLevelNodeIds = this.props.surveyQuestions
            .filter(sq => sq.parentQuestion === -1)
            .sort((a, b) => a.id - b.id)
            .map(sq => sq.id);
        this.setState({
            topLevelNodeIds: topLevelNodeIds,
        });
    }

    componentDidUpdate(prevProps, prevState) {
        if (this.state.currentNodeIndex !== prevState.currentNodeIndex) {
            this.props.setSelectedQuestion(this.getNodeById(this.state.topLevelNodeIds[this.state.currentNodeIndex]));

        } else if (this.props.selectedQuestion.id !== prevProps.selectedQuestion.id
            && this.props.selectedQuestion.parentQuestion === -1) {

            this.setState({ currentNodeIndex: this.state.topLevelNodeIds.indexOf(this.props.selectedQuestion.id) });
        }
    }

    prevSurveyQuestionTree = () => {
        if (this.state.currentNodeIndex > 0) {
            this.setState({ currentNodeIndex: this.state.currentNodeIndex - 1 });
        } else {
            alert("There are no previous questions.");
        }
    };

    nextSurveyQuestionTree = () => {
        if (this.state.currentNodeIndex < this.state.topLevelNodeIds.length - 1) {
            this.setState({ currentNodeIndex: this.state.currentNodeIndex + 1 });
        } else {
            alert("There are no more questions.");
        }
    };

    setSurveyQuestionTree = (index) => this.setState({ currentNodeIndex: index });

    checkAllSubAnswers = (currentQuestionId) => {
        const { surveyQuestions } = this.props;
        const { visible, answered } = surveyQuestions.find(sq => sq.id === currentQuestionId);
        const childQuestions = surveyQuestions.filter(sq => sq.parentQuestion === currentQuestionId);

        return visible.length === answered.length
                    && (childQuestions.length === 0
                    || childQuestions.every(cq => this.checkAllSubAnswers(cq.id)));
    };

    getTopColor = (node) => this.checkAllSubAnswers(node.id)
                                ? "0px 0px 15px 4px green inset"
                                : node.answered.length > 0
                                    ? "0px 0px 15px 4px yellow inset"
                                    : "0px 0px 15px 4px red inset";

    getNodeById = (id) => this.props.surveyQuestions.find(sq => sq.id === id);

    render() {
        return (
            <fieldset className="mb-3 justify-content-center text-center">

Survey Questions

                {this.props.surveyQuestions.length > 0
                ?

                            <button
                                type="button"
                                id="prev-survey-question"
                                className="btn btn-outline-lightgreen m-2"
                                onClick={this.prevSurveyQuestionTree}
                                disabled={this.state.currentNodeIndex === 0}
                                style={{ opacity: this.state.currentNodeIndex === 0 ? "0.25" : "1.0" }}
                            >
                                {"<"}
                            </button>
                            {this.state.topLevelNodeIds.map((node, i) =>
                                <button
                                    type="button"
                                    id="top-select"
                                    key={i}
                                    className="btn btn-outline-lightgreen m-2"
                                    title={removeEnumerator(this.getNodeById(node).question)}
                                    onClick={() => this.setSurveyQuestionTree(i)}
                                    style={{
                                        boxShadow: `${(i === this.state.currentNodeIndex)
                                                        ? "0px 0px 2px 2px black inset,"
                                                        : ""}
                                                    ${this.getTopColor(this.getNodeById(node))}
                                                    `,
                                    }}
                                >
                                    {i + 1}
                                </button>
                            )}
                            <button
                                type="button"
                                id="next-survey-question"
                                className="btn btn-outline-lightgreen"
                                onClick={this.nextSurveyQuestionTree}
                                disabled={this.state.currentNodeIndex === this.state.topLevelNodeIds.length - 1}
                                style={{ opacity: this.state.currentNodeIndex === this.state.topLevelNodeIds.length - 1 ? "0.25" : "1.0" }}
                            >
                                {">"}
                            </button>

                        {this.state.topLevelNodeIds.length > 0 &&
                            <SurveyQuestionTree
                                hierarchyLabel=""
                                surveyNode={this.getNodeById(this.state.topLevelNodeIds[this.state.currentNodeIndex])}
                                surveyQuestions={this.props.surveyQuestions}
                                surveyRules={this.props.surveyRules}
                                setCurrentValue={this.props.setCurrentValue}
                                selectedQuestion={this.props.selectedQuestion}
                                selectedSampleId={this.props.selectedSampleId}
                                setSelectedQuestion={this.props.setSelectedQuestion}
                            />
                        }

                :

This project is missing survey questions!

                }
            </fieldset>
        );
    }
}

class SurveyQuestionTree extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            showAnswers: true,
        };
    }

    toggleShowAnswers = () => this.setState({ showAnswers: !this.state.showAnswers });

    render() {
        const childNodes = this.props.surveyQuestions.filter(surveyNode => surveyNode.parentQuestion === this.props.surveyNode.id);

        const shadowColor = this.props.surveyNode.answered.length === 0
                            ? "0px 0px 15px 4px red inset"
                            : this.props.surveyNode.answered.length === this.props.surveyNode.visible.length
                                ? "0px 0px 15px 4px green inset"
                                : "0px 0px 15px 4px yellow inset";
        return (
            <fieldset className={"mb-1 justify-content-center text-center"}>

                    <button
                        type="button"
                        id={this.props.surveyNode.question + "_" + this.props.surveyNode.id}
                        className="text-center btn btn-outline-lightgreen btn-sm text-bold px-3 py-2 mr-1"
                        onClick={this.toggleShowAnswers}
                    >
                        {this.state.showAnswers ? - : +}
                    </button>
                    <button
                        type="button"
                        id={this.props.surveyNode.question + "_" + this.props.surveyNode.id}
                        className="text-center btn btn-outline-lightgreen btn-sm col overflow-hidden text-truncate"
                        title={removeEnumerator(this.props.surveyNode.question)}
                        style={{
                            boxShadow: `${(this.props.surveyNode.id === this.props.selectedQuestion.id)
                                    ? "0px 0px 2px 2px black inset,"
                                    : ""}
                                    ${shadowColor}`,
                        }}
                        onClick={() => this.props.setSelectedQuestion(this.props.surveyNode)}
                    >
                        {this.props.hierarchyLabel + removeEnumerator(this.props.surveyNode.question)}
                    </button>

                {this.state.showAnswers &&
                    <SurveyAnswers
                        surveyNode={this.props.surveyNode}
                        selectedSampleId={this.props.selectedSampleId}
                        setCurrentValue={this.props.setCurrentValue}
                        surveyRules={this.props.surveyRules}
                        surveyQuestions={this.props.surveyQuestions}
                    />
                }
                {
                    childNodes.map((childNode, uid) =>
                        <Fragment key={uid}>
                            {this.props.surveyQuestions.find(sq => sq.id === childNode.id).visible.length > 0 &&
                                <SurveyQuestionTree
                                    key={uid}
                                    surveyNode={childNode}
                                    surveyQuestions={this.props.surveyQuestions}
                                    setCurrentValue={this.props.setCurrentValue}
                                    selectedQuestion={this.props.selectedQuestion}
                                    selectedSampleId={this.props.selectedSampleId}
                                    setSelectedQuestion={this.props.setSelectedQuestion}
                                    hierarchyLabel={this.props.hierarchyLabel + "- "}
                                />
                            }
                        </Fragment>
                    )
                }
            </fieldset>
        );
    }
}

function AnswerButton({ surveyNode, surveyNode: { answers, answered }, selectedSampleId, setCurrentValue }) {
    return

        {answers.map((ans, uid) =>

                <button
                    type="button"
                    className="btn btn-outline-darkgray btn-sm btn-block pl-1 overflow-hidden text-truncate"
                    title={ans.answer}
                    id={ans.answer + "_" + ans.id}
                    name={ans.answer + "_" + ans.id}
                    style={{
                        boxShadow: answered.some(a => a.answerId === ans.id && a.sampleId === selectedSampleId)
                            ? "0px 0px 8px 3px black inset"
                            : answered.some(a => a.answerId === ans.id)
                                ? "0px 0px 8px 3px grey inset"
                                : "initial",
                    }}
                    onClick={() => setCurrentValue(surveyNode, ans.id, ans.answer)}
                >

                    {ans.answer}
                </button>

        )}

;
}

function AnswerRadioButton({ surveyNode, surveyNode: { answers, answered }, selectedSampleId, setCurrentValue }) {
    return

        {answers.map((ans, uid) =>

                <button
                    type="button"
                    className="btn btn-outline-darkgray btn-sm btn-block pl-1 overflow-hidden text-truncate"
                    title={ans.answer}
                    id={ans.answer + "_" + ans.id}
                    name={ans.answer + "_" + ans.id}
                    onClick={() => setCurrentValue(surveyNode, ans.id, ans.answer)}
                >

 a.answerId === ans.id && a.sampleId === selectedSampleId)
                                                ? "black"
                                                : answered.some(a => a.answerId === ans.id)
                                                    ? "#e8e8e8"
                                                    : "white",
                        }}
                    />
                    {ans.answer}
                </button>

        )}

;
}

class AnswerInput extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            newInput: "",
        };
    }

    componentDidMount() {
        this.resetInputText();
    }

    componentDidUpdate(prevProps) {
        if (this.props.surveyNode.id !== prevProps.surveyNode.id) {
            this.setState({ newInput: "" });
        }
        if (this.props.selectedSampleId !== prevProps.selectedSampleId) {
            this.resetInputText();
        }
    }

    resetInputText = () => {
        const matchingNode = this.props.surveyNode.answered
            .find(a => a.answerId === this.props.surveyNode.answers[0].id
                  && a.sampleId === this.props.selectedSampleId);

        this.setState({
            newInput: matchingNode ? matchingNode.answerText : "",
        });
    }

    updateInputValue = (value) => this.setState({newInput: value});

    render() {
        const { surveyNode, surveyNode: { answers, dataType }, setCurrentValue } = this.props;
        return (

                <input
                    type={dataType}
                    className="form-control mr-2"
                    placeholder={answers[0].answer}
                    id={answers[0].answer + "_" + answers[0].id}
                    name={answers[0].answer + "_" + answers[0].id}
                    value={this.state.newInput}
                    onChange={e => this.updateInputValue(e.target.value)}
                />
                <input
                    id="save-input"
                    className="text-center btn btn-outline-lightgreen btn-sm"
                    type="button"
                    name="save-input"
                    value="Save"
                    onClick={() => setCurrentValue(surveyNode, answers[0].id, this.state.newInput)}
                />

        );
    }
}

class AnswerDropDown extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            showDropdown: false,
        };
    }

    componentDidUpdate (prevProps) {
        if (this.props.surveyNode !== prevProps.surveyNode) {
            this.setState({ showDropdown: false });
        }
    }

    toggleDropDown = () => this.setState({ showDropdown: !this.state.showDropdown });

    render () {
        const { surveyNode, surveyNode: { answers, answered }, selectedSampleId, setCurrentValue } = this.props;
        const options = answers.map((ans, uid) =>

 setCurrentValue(surveyNode, ans.id, ans.answer)}
                className="d-inline-flex py-2 border-bottom"
                style={{ backgroundColor: answered.some(a => a.answerId === ans.id) ? "#e8e8e8" : "#f1f1f1" }}
            >

                    {ans.answer}

        );

        return (

                        {answers.map(ans =>
                            answered.some(a => a.answerId === ans.id && a.sampleId === selectedSampleId) &&
                            <Fragment>

                                    <span
                                        className="dot"
                                        style={{
                                            height: "15px",
                                            width: "15px",
                                            backgroundColor: ans.color,
                                            borderRadius: "50%",
                                            display: "inline-block",
                                        }}
                                    />

                                    {ans.answer}

                            </Fragment>
                        )}

                    <button
                        type="button"
                        onClick={this.toggleDropDown}
                        onBlur={() => this.setState({ showDropdown: false })}
                        className="dropbtn"
                        style={{
                            backgroundColor: "#31BAB0",
                            color: "white",
                            padding: "8px 16px",
                            border: "none",
                            cursor: "pointer",
                        }}
                    >

                    </button>

                    <div
                        id="myDropdown"
                        className="dropdown-content flex-column container"
                        style={{
                            display: this.state.showDropdown ? "flex" : "none",
                            position: "absolute",
                            backgroundColor: "#f1f1f1",
                            overflow: "auto",
                            boxShadow: "0px 8px 16px 0px rgba(0,0,0,0.2)",
                            zIndex: "10",
                            cursor: "pointer",
                        }}
                    >
                        {options}

        );
    }
}

function SurveyAnswers(props) {
    if (props.surveyNode.componentType.toLowerCase() === "radiobutton") {
        return (<AnswerRadioButton {...props} />);
    } else if (props.surveyNode.componentType.toLowerCase() === "input") {
        return (<AnswerInput {...props} />);
    } else if (props.surveyNode.componentType.toLowerCase() === "dropdown") {
        return (<AnswerDropDown {...props} />);
    } else {
        return (<AnswerButton {...props} />);
    }
}
<commit_msg>input fields store values<commit_after>import React, { Fragment } from "react";

import { removeEnumerator } from "../utils/surveyUtils";

export class SurveyCollection extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            currentNodeIndex: 0,
            topLevelNodeIds: [],
        };
    }

    componentDidMount() {
        const topLevelNodeIds = this.props.surveyQuestions
            .filter(sq => sq.parentQuestion === -1)
            .sort((a, b) => a.id - b.id)
            .map(sq => sq.id);
        this.setState({
            topLevelNodeIds: topLevelNodeIds,
        });
    }

    componentDidUpdate(prevProps, prevState) {
        if (this.state.currentNodeIndex !== prevState.currentNodeIndex) {
            this.props.setSelectedQuestion(this.getNodeById(this.state.topLevelNodeIds[this.state.currentNodeIndex]));

        } else if (this.props.selectedQuestion.id !== prevProps.selectedQuestion.id
            && this.props.selectedQuestion.parentQuestion === -1) {

            this.setState({ currentNodeIndex: this.state.topLevelNodeIds.indexOf(this.props.selectedQuestion.id) });
        }
    }

    prevSurveyQuestionTree = () => {
        if (this.state.currentNodeIndex > 0) {
            this.setState({ currentNodeIndex: this.state.currentNodeIndex - 1 });
        } else {
            alert("There are no previous questions.");
        }
    };

    nextSurveyQuestionTree = () => {
        if (this.state.currentNodeIndex < this.state.topLevelNodeIds.length - 1) {
            this.setState({ currentNodeIndex: this.state.currentNodeIndex + 1 });
        } else {
            alert("There are no more questions.");
        }
    };

    setSurveyQuestionTree = (index) => this.setState({ currentNodeIndex: index });

    checkAllSubAnswers = (currentQuestionId) => {
        const { surveyQuestions } = this.props;
        const { visible, answered } = surveyQuestions.find(sq => sq.id === currentQuestionId);
        const childQuestions = surveyQuestions.filter(sq => sq.parentQuestion === currentQuestionId);

        return visible.length === answered.length
                    && (childQuestions.length === 0
                    || childQuestions.every(cq => this.checkAllSubAnswers(cq.id)));
    };

    getTopColor = (node) => this.checkAllSubAnswers(node.id)
                                ? "0px 0px 15px 4px green inset"
                                : node.answered.length > 0
                                    ? "0px 0px 15px 4px yellow inset"
                                    : "0px 0px 15px 4px red inset";

    getNodeById = (id) => this.props.surveyQuestions.find(sq => sq.id === id);

    render() {
        return (
            <fieldset className="mb-3 justify-content-center text-center">

Survey Questions

                {this.props.surveyQuestions.length > 0
                ?

                            <button
                                type="button"
                                id="prev-survey-question"
                                className="btn btn-outline-lightgreen m-2"
                                onClick={this.prevSurveyQuestionTree}
                                disabled={this.state.currentNodeIndex === 0}
                                style={{ opacity: this.state.currentNodeIndex === 0 ? "0.25" : "1.0" }}
                            >
                                {"<"}
                            </button>
                            {this.state.topLevelNodeIds.map((node, i) =>
                                <button
                                    type="button"
                                    id="top-select"
                                    key={i}
                                    className="btn btn-outline-lightgreen m-2"
                                    title={removeEnumerator(this.getNodeById(node).question)}
                                    onClick={() => this.setSurveyQuestionTree(i)}
                                    style={{
                                        boxShadow: `${(i === this.state.currentNodeIndex)
                                                        ? "0px 0px 2px 2px black inset,"
                                                        : ""}
                                                    ${this.getTopColor(this.getNodeById(node))}
                                                    `,
                                    }}
                                >
                                    {i + 1}
                                </button>
                            )}
                            <button
                                type="button"
                                id="next-survey-question"
                                className="btn btn-outline-lightgreen"
                                onClick={this.nextSurveyQuestionTree}
                                disabled={this.state.currentNodeIndex === this.state.topLevelNodeIds.length - 1}
                                style={{ opacity: this.state.currentNodeIndex === this.state.topLevelNodeIds.length - 1 ? "0.25" : "1.0" }}
                            >
                                {">"}
                            </button>

                        {this.state.topLevelNodeIds.length > 0 &&
                            <SurveyQuestionTree
                                hierarchyLabel=""
                                surveyNode={this.getNodeById(this.state.topLevelNodeIds[this.state.currentNodeIndex])}
                                surveyQuestions={this.props.surveyQuestions}
                                surveyRules={this.props.surveyRules}
                                setCurrentValue={this.props.setCurrentValue}
                                selectedQuestion={this.props.selectedQuestion}
                                selectedSampleId={this.props.selectedSampleId}
                                setSelectedQuestion={this.props.setSelectedQuestion}
                            />
                        }

                :

This project is missing survey questions!

                }
            </fieldset>
        );
    }
}

class SurveyQuestionTree extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            showAnswers: true,
        };
    }

    toggleShowAnswers = () => this.setState({ showAnswers: !this.state.showAnswers });

    render() {
        const childNodes = this.props.surveyQuestions.filter(surveyNode => surveyNode.parentQuestion === this.props.surveyNode.id);

        const shadowColor = this.props.surveyNode.answered.length === 0
                            ? "0px 0px 15px 4px red inset"
                            : this.props.surveyNode.answered.length === this.props.surveyNode.visible.length
                                ? "0px 0px 15px 4px green inset"
                                : "0px 0px 15px 4px yellow inset";
        return (
            <fieldset className={"mb-1 justify-content-center text-center"}>

                    <button
                        type="button"
                        id={this.props.surveyNode.question + "_" + this.props.surveyNode.id}
                        className="text-center btn btn-outline-lightgreen btn-sm text-bold px-3 py-2 mr-1"
                        onClick={this.toggleShowAnswers}
                    >
                        {this.state.showAnswers ? - : +}
                    </button>
                    <button
                        type="button"
                        id={this.props.surveyNode.question + "_" + this.props.surveyNode.id}
                        className="text-center btn btn-outline-lightgreen btn-sm col overflow-hidden text-truncate"
                        title={removeEnumerator(this.props.surveyNode.question)}
                        style={{
                            boxShadow: `${(this.props.surveyNode.id === this.props.selectedQuestion.id)
                                    ? "0px 0px 2px 2px black inset,"
                                    : ""}
                                    ${shadowColor}`,
                        }}
                        onClick={() => this.props.setSelectedQuestion(this.props.surveyNode)}
                    >
                        {this.props.hierarchyLabel + removeEnumerator(this.props.surveyNode.question)}
                    </button>

                {this.state.showAnswers &&
                    <SurveyAnswers
                        surveyNode={this.props.surveyNode}
                        selectedSampleId={this.props.selectedSampleId}
                        setCurrentValue={this.props.setCurrentValue}
                        surveyRules={this.props.surveyRules}
                        surveyQuestions={this.props.surveyQuestions}
                    />
                }
                {
                    childNodes.map((childNode, uid) =>
                        <Fragment key={uid}>
                            {this.props.surveyQuestions.find(sq => sq.id === childNode.id).visible.length > 0 &&
                                <SurveyQuestionTree
                                    key={uid}
                                    surveyNode={childNode}
                                    surveyQuestions={this.props.surveyQuestions}
                                    setCurrentValue={this.props.setCurrentValue}
                                    selectedQuestion={this.props.selectedQuestion}
                                    selectedSampleId={this.props.selectedSampleId}
                                    setSelectedQuestion={this.props.setSelectedQuestion}
                                    hierarchyLabel={this.props.hierarchyLabel + "- "}
                                />
                            }
                        </Fragment>
                    )
                }
            </fieldset>
        );
    }
}

function AnswerButton({ surveyNode, surveyNode: { answers, answered }, selectedSampleId, setCurrentValue }) {
    return

        {answers.map((ans, uid) =>

                <button
                    type="button"
                    className="btn btn-outline-darkgray btn-sm btn-block pl-1 overflow-hidden text-truncate"
                    title={ans.answer}
                    id={ans.answer + "_" + ans.id}
                    name={ans.answer + "_" + ans.id}
                    style={{
                        boxShadow: answered.some(a => a.answerId === ans.id && a.sampleId === selectedSampleId)
                            ? "0px 0px 8px 3px black inset"
                            : answered.some(a => a.answerId === ans.id)
                                ? "0px 0px 8px 3px grey inset"
                                : "initial",
                    }}
                    onClick={() => setCurrentValue(surveyNode, ans.id, ans.answer)}
                >

                    {ans.answer}
                </button>

        )}

;
}

function AnswerRadioButton({ surveyNode, surveyNode: { answers, answered }, selectedSampleId, setCurrentValue }) {
    return

        {answers.map((ans, uid) =>

                <button
                    type="button"
                    className="btn btn-outline-darkgray btn-sm btn-block pl-1 overflow-hidden text-truncate"
                    title={ans.answer}
                    id={ans.answer + "_" + ans.id}
                    name={ans.answer + "_" + ans.id}
                    onClick={() => setCurrentValue(surveyNode, ans.id, ans.answer)}
                >

 a.answerId === ans.id && a.sampleId === selectedSampleId)
                                                ? "black"
                                                : answered.some(a => a.answerId === ans.id)
                                                    ? "#e8e8e8"
                                                    : "white",
                        }}
                    />
                    {ans.answer}
                </button>

        )}

;
}

class AnswerInput extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            newInput: "",
        };
    }

    componentDidMount() {
        this.resetInputText();
    }

    componentDidUpdate(prevProps) {
        if (this.props.surveyNode.id !== prevProps.surveyNode.id) {
            const matchingNode = this.props.surveyNode.answered
                .find(a => a.answerId === this.props.surveyNode.answers[0].id
                    && a.sampleId === this.props.selectedSampleId);
            this.setState({ newInput: matchingNode ? matchingNode.answerText : ""});
        }
        if (this.props.selectedSampleId !== prevProps.selectedSampleId) {
            this.resetInputText();
        }
    }

    resetInputText = () => {
        const matchingNode = this.props.surveyNode.answered
            .find(a => a.answerId === this.props.surveyNode.answers[0].id
                  && a.sampleId === this.props.selectedSampleId);

        this.setState({
            newInput: matchingNode ? matchingNode.answerText : "",
        });
    }

    updateInputValue = (value) => this.setState({newInput: value});

    render() {
        const { surveyNode, surveyNode: { answers, dataType }, setCurrentValue } = this.props;
        return (

                <input
                    type={dataType}
                    className="form-control mr-2"
                    placeholder={answers[0].answer}
                    id={answers[0].answer + "_" + answers[0].id}
                    name={answers[0].answer + "_" + answers[0].id}
                    value={this.state.newInput}
                    onChange={e => this.updateInputValue(e.target.value)}
                />
                <input
                    id="save-input"
                    className="text-center btn btn-outline-lightgreen btn-sm"
                    type="button"
                    name="save-input"
                    value="Save"
                    onClick={() => setCurrentValue(surveyNode, answers[0].id, this.state.newInput)}
                />

        );
    }
}

class AnswerDropDown extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            showDropdown: false,
        };
    }

    componentDidUpdate (prevProps) {
        if (this.props.surveyNode !== prevProps.surveyNode) {
            this.setState({ showDropdown: false });
        }
    }

    toggleDropDown = () => this.setState({ showDropdown: !this.state.showDropdown });

    render () {
        const { surveyNode, surveyNode: { answers, answered }, selectedSampleId, setCurrentValue } = this.props;
        const options = answers.map((ans, uid) =>

 setCurrentValue(surveyNode, ans.id, ans.answer)}
                className="d-inline-flex py-2 border-bottom"
                style={{ backgroundColor: answered.some(a => a.answerId === ans.id) ? "#e8e8e8" : "#f1f1f1" }}
            >

                    {ans.answer}

        );

        return (

                        {answers.map(ans =>
                            answered.some(a => a.answerId === ans.id && a.sampleId === selectedSampleId) &&
                            <Fragment>

                                    <span
                                        className="dot"
                                        style={{
                                            height: "15px",
                                            width: "15px",
                                            backgroundColor: ans.color,
                                            borderRadius: "50%",
                                            display: "inline-block",
                                        }}
                                    />

                                    {ans.answer}

                            </Fragment>
                        )}

                    <button
                        type="button"
                        onClick={this.toggleDropDown}
                        onBlur={() => this.setState({ showDropdown: false })}
                        className="dropbtn"
                        style={{
                            backgroundColor: "#31BAB0",
                            color: "white",
                            padding: "8px 16px",
                            border: "none",
                            cursor: "pointer",
                        }}
                    >

                    </button>

                    <div
                        id="myDropdown"
                        className="dropdown-content flex-column container"
                        style={{
                            display: this.state.showDropdown ? "flex" : "none",
                            position: "absolute",
                            backgroundColor: "#f1f1f1",
                            overflow: "auto",
                            boxShadow: "0px 8px 16px 0px rgba(0,0,0,0.2)",
                            zIndex: "10",
                            cursor: "pointer",
                        }}
                    >
                        {options}

        );
    }
}

function SurveyAnswers(props) {
    if (props.surveyNode.componentType.toLowerCase() === "radiobutton") {
        return (<AnswerRadioButton {...props} />);
    } else if (props.surveyNode.componentType.toLowerCase() === "input") {
        return (<AnswerInput {...props} />);
    } else if (props.surveyNode.componentType.toLowerCase() === "dropdown") {
        return (<AnswerDropDown {...props} />);
    } else {
        return (<AnswerButton {...props} />);
    }
}
<|endoftext|>