<commit_before>/* eslint-env browser */
import React, { Component, Fragment } from "react";
import { View } from "react-native";
import Ad, { AdComposer } from "@times-components/ad";
import Button from "@times-components/button";
import ErrorView from "@times-components/error-view";
import { spacing } from "@times-components/styleguide";
import { withTrackScrollDepth } from "@times-components/tracking";
import { normaliseWidthForAssetRequestCache } from "@times-components/utils";
import LazyLoad from "@times-components/lazy-load";
import { scrollUpToPaging } from "./utils/index.web";
import ArticleListError from "./article-list-error";
import ArticleListItem from "./article-list-item";
import ArticleListItemSeparator from "./article-list-item-separator";
import ArticleListPagination from "./article-list-pagination";
import { propTypes, defaultProps } from "./article-list-prop-types";
import ArticleListEmptyState from "./article-list-empty-state";
import styles, { retryButtonStyles } from "./styles/index.web";
import { ListContentContainer } from "./styles/responsive";

class ArticleList extends Component {
  static getImageSize(node) {
    if (typeof window === "undefined") {
      return null;
    }

    return node ? normaliseWidthForAssetRequestCache(node.clientWidth) : null;
  }

  constructor(props) {
    super(props);

    this.advertPosition = 4;
  }

  shouldComponentUpdate(nextProps) {
    const { page } = this.props;
    return page !== nextProps.page;
  }

  render() {
    const {
      adConfig,
      articleListHeader,
      articles,
      articlesLoading,
      count = 0,
      emptyStateMessage,
      error,
      imageRatio,
      onNext,
      onPrev,
      scrollToTop = true,
      page = 0,
      pageSize,
      receiveChildList,
      refetch,
      showImages
    } = this.props;

    const hasAdvertConfig = Object.keys(adConfig).length > 0;

    const paginationComponent = ({
      hideResults = false,
      autoScroll = false
    } = {}) => (
      <ArticleListPagination
        count={count}
        hideResults={hideResults}
        onNext={(...args) => {
          onNext(...args);
          if (autoScroll && typeof window !== "undefined")
            scrollUpToPaging(window);
        }}
        onPrev={(...args) => {
          onPrev(...args);
          if (autoScroll && typeof window !== "undefined")
            scrollUpToPaging(window);
        }}
        page={page}
        pageSize={pageSize}
      />
    );

    const ErrorComponent = (
      <ListContentContainer>
        {paginationComponent()}
        <View style={styles.listContentErrorContainer}>
          <ArticleListError />
          <Button onPress={refetch} style={retryButtonStyles} title="Retry" />
        </View>
      </ListContentContainer>
    );

    const renderAdComponent = ({ key }) => (
      <AdComposer adConfig={adConfig} key={key}>
        <Ad
          isLoading={articlesLoading}
          slotName="inline-ad"
          style={styles.adContainer}
        />
      </AdComposer>
    );

    const data = articlesLoading
      ? Array(pageSize)
          .fill()
          .map((number, index) => ({
            elementId: `empty.${index}`,
            id: index,
            isLoading: true
          }))
      : articles.map((article, index) => ({
          ...article,
          elementId: `${article.id}.${index}`
        }));

    const Contents = ({ clientHasRendered, observed, registerNode }) =>
      data.length === 0 ? (
        <ArticleListEmptyState message={emptyStateMessage} />
      ) : (
        <View>
          <ListContentContainer>
            {paginationComponent({ autoScroll: false, hideResults: false })}
          </ListContentContainer>
          {data &&
            data.map((item, index) => {
              const { elementId } = item;

              const renderAd = () => {
                if (index === this.advertPosition && hasAdvertConfig) {
                  return renderAdComponent({ key: `advert${index}` });
                }

                return null;
              };

              const renderSeparator = () => {
                if (index === 0) {
                  return null;
                }

                return <ArticleListItemSeparator />;
              };

              return (
                <Fragment key={elementId}>
                  <div
                    accessibility-label={elementId}
                    data-testid={`article-list-item-${index}`}
                    id={elementId}
                    ref={node => registerNode(node)}
                  >
                    <ErrorView>
                      {({ hasError }) =>
                        hasError ? null : (
                          <ListContentContainer>
                            {renderSeparator()}
                            <ArticleListItem
                              article={item.isLoading ? null : item}
                              fadeImageIn={clientHasRendered}
                              highResSize={ArticleList.getImageSize(
                                observed.get(elementId)
                              )}
                              imageRatio={imageRatio}
                              index={index}
                              isLoading={item.isLoading === true}
                              length={data.length}
                              lowResSize={100}
                              showImage={showImages}
                            />
                          </ListContentContainer>
                        )
                      }
                    </ErrorView>
                  </div>
                  {renderAd()}
                </Fragment>
              );
            })}
          {paginationComponent({ autoScroll: scrollToTop, hideResults: true })}
        </View>
      );

    if (!articlesLoading) receiveChildList(data);

    return (
      <LazyLoad rootMargin={spacing(10)} threshold={0.5}>
        {({ clientHasRendered, observed, registerNode }) => (
          <View accessibilityRole="main">
            {articleListHeader}
            {error
              ? ErrorComponent
              : Contents({
                  clientHasRendered,
                  observed,
                  registerNode
                })}
          </View>
        )}
      </LazyLoad>
    );
  }
}

ArticleList.propTypes = propTypes;
ArticleList.defaultProps = defaultProps;

export { default as ArticleListPageError } from "./article-list-page-error";
export { ArticleListEmptyState };
export default withTrackScrollDepth(ArticleList);
<commit_msg>fix: article list not updating correctly (#2367)<commit_after>/* eslint-env browser */
import React, { Component, Fragment } from "react";
import { View } from "react-native";
import Ad, { AdComposer } from "@times-components/ad";
import Button from "@times-components/button";
import ErrorView from "@times-components/error-view";
import { spacing } from "@times-components/styleguide";
import { withTrackScrollDepth } from "@times-components/tracking";
import { normaliseWidthForAssetRequestCache } from "@times-components/utils";
import LazyLoad from "@times-components/lazy-load";
import { scrollUpToPaging } from "./utils/index.web";
import ArticleListError from "./article-list-error";
import ArticleListItem from "./article-list-item";
import ArticleListItemSeparator from "./article-list-item-separator";
import ArticleListPagination from "./article-list-pagination";
import { propTypes, defaultProps } from "./article-list-prop-types";
import ArticleListEmptyState from "./article-list-empty-state";
import styles, { retryButtonStyles } from "./styles/index.web";
import { ListContentContainer } from "./styles/responsive";

class ArticleList extends Component {
  static getImageSize(node) {
    if (typeof window === "undefined") {
      return null;
    }

    return node ? normaliseWidthForAssetRequestCache(node.clientWidth) : null;
  }

  constructor(props) {
    super(props);

    this.advertPosition = 4;
  }

  shouldComponentUpdate(nextProps) {
    const { page } = this.props;
    return page === nextProps.page;
  }

  render() {
    const {
      adConfig,
      articleListHeader,
      articles,
      articlesLoading,
      count = 0,
      emptyStateMessage,
      error,
      imageRatio,
      onNext,
      onPrev,
      scrollToTop = true,
      page = 0,
      pageSize,
      receiveChildList,
      refetch,
      showImages
    } = this.props;

    const hasAdvertConfig = Object.keys(adConfig).length > 0;

    const paginationComponent = ({
      hideResults = false,
      autoScroll = false
    } = {}) => (
      <ArticleListPagination
        count={count}
        hideResults={hideResults}
        onNext={(...args) => {
          onNext(...args);
          if (autoScroll && typeof window !== "undefined")
            scrollUpToPaging(window);
        }}
        onPrev={(...args) => {
          onPrev(...args);
          if (autoScroll && typeof window !== "undefined")
            scrollUpToPaging(window);
        }}
        page={page}
        pageSize={pageSize}
      />
    );

    const ErrorComponent = (
      <ListContentContainer>
        {paginationComponent()}
        <View style={styles.listContentErrorContainer}>
          <ArticleListError />
          <Button onPress={refetch} style={retryButtonStyles} title="Retry" />
        </View>
      </ListContentContainer>
    );

    const renderAdComponent = ({ key }) => (
      <AdComposer adConfig={adConfig} key={key}>
        <Ad
          isLoading={articlesLoading}
          slotName="inline-ad"
          style={styles.adContainer}
        />
      </AdComposer>
    );

    const data = articlesLoading
      ? Array(pageSize)
          .fill()
          .map((number, index) => ({
            elementId: `empty.${index}`,
            id: index,
            isLoading: true
          }))
      : articles.map((article, index) => ({
          ...article,
          elementId: `${article.id}.${index}`
        }));

    const Contents = ({ clientHasRendered, observed, registerNode }) =>
      data.length === 0 ? (
        <ArticleListEmptyState message={emptyStateMessage} />
      ) : (
        <View>
          <ListContentContainer>
            {paginationComponent({ autoScroll: false, hideResults: false })}
          </ListContentContainer>
          {data &&
            data.map((item, index) => {
              const { elementId } = item;

              const renderAd = () => {
                if (index === this.advertPosition && hasAdvertConfig) {
                  return renderAdComponent({ key: `advert${index}` });
                }

                return null;
              };

              const renderSeparator = () => {
                if (index === 0) {
                  return null;
                }

                return <ArticleListItemSeparator />;
              };

              return (
                <Fragment key={elementId}>
                  <div
                    accessibility-label={elementId}
                    data-testid={`article-list-item-${index}`}
                    id={elementId}
                    ref={node => registerNode(node)}
                  >
                    <ErrorView>
                      {({ hasError }) =>
                        hasError ? null : (
                          <ListContentContainer>
                            {renderSeparator()}
                            <ArticleListItem
                              article={item.isLoading ? null : item}
                              fadeImageIn={clientHasRendered}
                              highResSize={ArticleList.getImageSize(
                                observed.get(elementId)
                              )}
                              imageRatio={imageRatio}
                              index={index}
                              isLoading={item.isLoading === true}
                              length={data.length}
                              lowResSize={100}
                              showImage={showImages}
                            />
                          </ListContentContainer>
                        )
                      }
                    </ErrorView>
                  </div>
                  {renderAd()}
                </Fragment>
              );
            })}
          {paginationComponent({ autoScroll: scrollToTop, hideResults: true })}
        </View>
      );

    if (!articlesLoading) receiveChildList(data);

    return (
      <LazyLoad rootMargin={spacing(10)} threshold={0.5}>
        {({ clientHasRendered, observed, registerNode }) => (
          <View accessibilityRole="main">
            {articleListHeader}
            {error
              ? ErrorComponent
              : Contents({
                  clientHasRendered,
                  observed,
                  registerNode
                })}
          </View>
        )}
      </LazyLoad>
    );
  }
}

ArticleList.propTypes = propTypes;
ArticleList.defaultProps = defaultProps;

export { default as ArticleListPageError } from "./article-list-page-error";
export { ArticleListEmptyState };
export default withTrackScrollDepth(ArticleList);
<|endoftext|>


<commit_before>var Utils = require('./utils.js');
var eventMethods = require('./events.js');

var Collection = function(array) {
  var items = array || [];

  return Utils.merge(eventMethods, {
    klass: 'Collection',
    listeners: [],

    add: function(item) {
      items.push(item);
      this.emit('add');
      this.emit('change', 'length', this.length());
      this.emit('change');
      return this;
    },

    remove: function(item) {
      items.splice(items.indexOf(item), 1);
      this.emit('remove');
      this.emit('change', 'length', this.length());
      this.emit('change');
      return this;
    },

    at: function(index) {
      return items[index];
    },

    each: function(cb) {
      for(var i in items) {
        cb(items[i], i);
      }
      return this;
    },

    map: function(cb) {
      var out = [];
      this.each(function(item, i) {
        out.push(cb(item, i));
      });
      return Collection(out);
    },

    values: function() {
      return items;
    },

    length: function() {
      return items.length;
    },

    // Return a simple array of all values,
    // but collapse contained model instances to serializable references
    serialize: function() {
      var out = [];
      this.each(function(item) {
        if(item.klass == 'Instance') {
          var ref = item.reference();
          if(ref) {
            out.push(ref);
          }
        } else {
          out.push(item);
        }
      });
      return out;
    },

    unserialize: function(index) {

    },

    clone: function() {
      return this.map(function(item) { return item });
    },

    filter: function(query) {
      return Query(this, query);
    }
  });
};

module.exports = Collection;
<commit_msg>Allow adding multiple items to collections at once<commit_after>var Utils = require('./utils.js');
var eventMethods = require('./events.js');

var Collection = function(array) {
  var items = array || [];

  return Utils.merge(eventMethods, {
    klass: 'Collection',
    listeners: [],

    add: function(item) {
      var itms = Array.isArray(item) ? item : [item];
      Utils.each(itms, function(item) {
        items.push(item);
      });
      this.emit('add');
      this.emit('change', 'length');
      this.emit('change');
      return this;
    },

    remove: function(item) {
      items.splice(items.indexOf(item), 1);
      this.emit('remove');
      this.emit('change', 'length');
      this.emit('change');
      return this;
    },

    at: function(index) {
      return items[index];
    },

    each: function(cb) {
      for(var i in items) {
        cb(items[i], i);
      }
      return this;
    },

    map: function(cb) {
      var out = [];
      this.each(function(item, i) {
        out.push(cb(item, i));
      });
      return Collection(out);
    },

    values: function() {
      return items;
    },

    length: function() {
      return items.length;
    },

    // Return a simple array of all values,
    // but collapse contained model instances to serializable references
    serialize: function() {
      var out = [];
      this.each(function(item) {
        if(item.klass == 'Instance') {
          var ref = item.reference();
          if(ref) {
            out.push(ref);
          }
        } else {
          out.push(item);
        }
      });
      return out;
    },

    unserialize: function(index) {

    },

    clone: function() {
      return this.map(function(item) { return item });
    },

    filter: function(query) {
      return Query(this, query);
    }
  });
};

module.exports = Collection;
<|endoftext|>


<commit_before>/**
 * @license Copyright 2019 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
'use strict';

const fs = require('fs');
const path = require('path');

/** @typedef {(browser: import('puppeteer').Browser, context: {url: string, options: LHCI.CollectCommand.Options}) => void} PuppeteerScript */

class PuppeteerManager {
  /** @param {LHCI.CollectCommand.Options} options */
  constructor(options) {
    this._options = options;

    /** @type {null|import('puppeteer').Browser} */
    this._browser = null;
  }

  /**
   * Returns the puppeteer module. First attempts to require `puppeteer` and then `puppeteer-core`
   * if puppeteer is not available. They are the exact same API but puppeteer-core requires explicit
   * setting of `collect.chromePath`.
   * @return {typeof import('puppeteer')|undefined}
   */
  static _requirePuppeteer() {
    try {
      // eslint-disable-next-line import/no-extraneous-dependencies
      return require('puppeteer');
    } catch (_) {}

    try {
      // @ts-ignore - puppeteer-core is API-compatible with puppeteer
      return require('puppeteer-core');
    } catch (_) {}
  }

  /** @return {Promise<import('puppeteer').Browser>} */
  async _getBrowser() {
    if (!this.isActive()) throw new Error('Should not launch a browser when inactive');
    if (this._browser) return this._browser;

    // Delay require to only run after user requests puppeteer functionality.
    const puppeteer = PuppeteerManager._requirePuppeteer();
    if (!puppeteer) {
      throw new Error(`Unable to require 'puppeteer' for script, have you run 'npm i puppeteer'?`);
    }

    this._browser = await puppeteer.launch({
      ...(this._options.puppeteerLaunchOptions || {}),
      pipe: false,
      devtools: false,
      headless: !this._options.headful,
      // The default value for `chromePath` is determined by yargs using the `getChromiumPath` method.
      executablePath: this._options.chromePath,
    });

    return this._browser;
  }

  /** @return {boolean} */
  isActive() {
    return !!this._options.puppeteerScript;
  }

  /**
   * @param {{puppeteerScript?: string}} options
   * @return {string|undefined}
   */
  static getChromiumPath(options) {
    // If we're not using puppeteer, return undefined.
    if (!options.puppeteerScript) return undefined;

    // Otherwise, check to see if the expected puppeteer download exists.
    const puppeteer = PuppeteerManager._requirePuppeteer();
    const chromiumPath = puppeteer && puppeteer.executablePath();
    return chromiumPath && fs.existsSync(chromiumPath) ? chromiumPath : undefined;
  }

  /** @return {Promise<number>} */
  async getBrowserPort() {
    const browser = await this._getBrowser();
    return Number(new URL(browser.wsEndpoint()).port);
  }

  /**
   * @param {string} url
   * @return {Promise<void>}
   */
  async invokePuppeteerScriptForUrl(url) {
    const scriptPath = this._options.puppeteerScript;
    if (!scriptPath) return;

    const browser = await this._getBrowser();
    /** @type {PuppeteerScript} */
    const script = require(path.join(process.cwd(), scriptPath));
    await script(browser, {url, options: this._options});
  }
}

module.exports = PuppeteerManager;
<commit_msg>fix(cli): resolve puppeteer relative to cwd too<commit_after>/**
 * @license Copyright 2019 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
'use strict';

const fs = require('fs');
const path = require('path');

/** @typedef {(browser: import('puppeteer').Browser, context: {url: string, options: LHCI.CollectCommand.Options}) => void} PuppeteerScript */

class PuppeteerManager {
  /** @param {LHCI.CollectCommand.Options} options */
  constructor(options) {
    this._options = options;

    /** @type {null|import('puppeteer').Browser} */
    this._browser = null;
  }

  /**
   * Returns the puppeteer module. First attempts to require `puppeteer` and then `puppeteer-core`
   * if puppeteer is not available. They are the exact same API but puppeteer-core requires explicit
   * setting of `collect.chromePath`.
   * @return {typeof import('puppeteer')|undefined}
   */
  static _requirePuppeteer() {
    try {
      // eslint-disable-next-line import/no-extraneous-dependencies
      return require('puppeteer');
    } catch (_) {}

    try {
      // @ts-ignore - puppeteer-core is API-compatible with puppeteer
      return require('puppeteer-core');
    } catch (_) {}

    // Try relative to the CWD too in case we're installed globally
    try {
      return require(path.join(process.cwd(), 'node_modules/puppeteer'));
    } catch (_) {}

    try {
      return require(path.join(process.cwd(), 'node_modules/puppeteer-core'));
    } catch (_) {}
  }

  /** @return {Promise<import('puppeteer').Browser>} */
  async _getBrowser() {
    if (!this.isActive()) throw new Error('Should not launch a browser when inactive');
    if (this._browser) return this._browser;

    // Delay require to only run after user requests puppeteer functionality.
    const puppeteer = PuppeteerManager._requirePuppeteer();
    if (!puppeteer) {
      throw new Error(`Unable to require 'puppeteer' for script, have you run 'npm i puppeteer'?`);
    }

    this._browser = await puppeteer.launch({
      ...(this._options.puppeteerLaunchOptions || {}),
      pipe: false,
      devtools: false,
      headless: !this._options.headful,
      // The default value for `chromePath` is determined by yargs using the `getChromiumPath` method.
      executablePath: this._options.chromePath,
    });

    return this._browser;
  }

  /** @return {boolean} */
  isActive() {
    return !!this._options.puppeteerScript;
  }

  /**
   * @param {{puppeteerScript?: string}} options
   * @return {string|undefined}
   */
  static getChromiumPath(options) {
    // If we're not using puppeteer, return undefined.
    if (!options.puppeteerScript) return undefined;

    // Otherwise, check to see if the expected puppeteer download exists.
    const puppeteer = PuppeteerManager._requirePuppeteer();
    const chromiumPath = puppeteer && puppeteer.executablePath();
    return chromiumPath && fs.existsSync(chromiumPath) ? chromiumPath : undefined;
  }

  /** @return {Promise<number>} */
  async getBrowserPort() {
    const browser = await this._getBrowser();
    return Number(new URL(browser.wsEndpoint()).port);
  }

  /**
   * @param {string} url
   * @return {Promise<void>}
   */
  async invokePuppeteerScriptForUrl(url) {
    const scriptPath = this._options.puppeteerScript;
    if (!scriptPath) return;

    const browser = await this._getBrowser();
    /** @type {PuppeteerScript} */
    const script = require(path.join(process.cwd(), scriptPath));
    await script(browser, {url, options: this._options});
  }
}

module.exports = PuppeteerManager;
<|endoftext|>


<commit_before>load("lib/esprima.js");
let Ast = require("ast.js");
let Debug = require("debug.js");

let options = {
  comment: true,
  raw: true,
  loc: true,
  range: true
};

let to_typed_loc = function(loc, fileName) {
  if (loc == null) {
    return null;
  }
  let start = new Ast.Position(loc.start.line, loc.start.column);
  let end   = new Ast.Position(loc.end.line, loc.end.column);
  return new Ast.SourceLocation(fileName, start, end);
};

let to_typed_ast = function(node, fileName) {
  /**
   * Handler for directives (i.e. comments)
   */
  let Directives = {
    _unattached: [], // Line -> arrays of comments finishing at that line
    _first_attached_comment: -1,
    _regexp_extract:  /[ \t*]*(@([a-zA-Z0-9_]*) *{([^}]*)} *([a-zA-Z0-9]*))/,
    _directives: {},
    add_comments: function(comments) {
      for (let i = 0; i < comments.length; ++i) {
        let comment = comments[i];
        let first_char = comment.value[0];
        if (first_char != '*') {
          continue;
        }
        let last_line = comment.lines[1];
        let entry = this._unattached[last_line];
        if (!entry) {
          this._unattached[last_line] = entry = [];
        }
        entry.push(comment);
      }
    },
    /**
     * Get the comments that are attached to the node.
     *
     * Rules are the following:
     * - comments can be attached only to statements and declarations;
     * - to be attached, a comment must appear on the same line as the
     * statement/declaration or on the line immediately before.
     */
    extract_comments_for_node: function(node) {
      if (node.type.search(/Statement|Declaration/) == -1) {
        return null;
      }
      let line_start = node.loc.start.line;
      let result = null;
      if (line_start > 0) {
        if (this._unattached[line_start - 1]) {
          result = this._unattached[line_start - 1];
          delete this._unattached[line_start - 1];
        }
      }
      if (this._unattached[line_start]) {
        if (!result) {
          result = [];
        }
        result.concat(this._unattached[line_start]);
        delete this._unattached[line_start];
      }
      if (!result) {
        return null;
      }
      if (this._first_attached_comment == -1) {
        this._first_attached_comment = line_start;
      }
      return this.parse_comment(result, node);
    },
    get_unattached_comments: function() {
      print("Getting unattached comments");
      let result;
      for(let i = 0; i < this._first_attached_comment; ++i) {
        let current = this._unattached[i];
        if (current) {
          let directives = Directives.parse_comment(current, node);
          if (directives) {
            if (!result) {
              result = [];
            }
            directives.forEach(
              function(v) {
                result.push(v);
              }
            );
          }
        }
      }
      return result;
    },
    parse_comment: function(comments, node) {
      print("Parsing comment "+comments.toSource());
      let result = null;
      for (let i = 0; i < comments.length; ++i) {
        let current = comments[i];
        let matched = current.value.match(Directives._regexp_extract);
        if (!matched) continue;
        let [ignore, string, directive, param, optname] = matched;
        if (!result) {
          result = [];
        }
        string = '* ' + string;
        print("matched");
        print(matched.toSource());
        result.push(new Ast.Directive(comments.lines,
                                      comments.range,
                                      current.type, string,
                                      directive, param, optname));
        let table = Directives._directives[directive];
        if (!table) {
          Directives._directives[directive] = table = [];
        }
        table.push(node);
      }
      return result;
    },
    get_directives: function() {
      return Directives._directives;
    }
  };
  let program;
  let loop = function(node) {
    if (node == null) {
      return null;
    }
    if (Array.isArray(node)) {
      let result = [];
      node.forEach(
        function(current) {
          result.push(loop(current));
        }
      );
      return result;
    }
    print("Translating node "+node.type);
    let loc = to_typed_loc(node.loc, fileName);
    let range = node.range;
    let comments;
    if (node.type != "Program") {
      comments = Directives.extract_comments_for_node(node);
    };
    switch(node.type) {
    case "Program":
      {
        Directives.add_comments(node.comments);

        let elements = loop(node.body);
        return new Ast.Program(loc, range,
                               Directives.get_unattached_comments(),
                               elements,
                               Directives.get_directives());
      }
    case "Function":
      {
        let params = loop(node.params);
        let body = loop(node.body);
        return new Ast.Function(loc, range, comments,
                                node.id,
                                params, body,
                                node.generator, node.expression);
      }
    case "EmptyStatement":
      {
        return new Ast.Statement.Empty(loc, range, comments);
      }
    case "BlockStatement":
      {
        let body = loop(node.body);
        return new Ast.Statement.Block(loc, range, comments, body);
      }
    case "ExpressionStatement":
      {
        let expr = loop(node.expression);
        return new Ast.Statement.Expression(loc, range, comments, expr);
      }
    case "IfStatement":
      {
        let test = loop(node.test);
        let consequent = loop(node.consequent);
        let alternate = loop(node.alternate);
        return new Ast.Statement.If(loc, range, comments,
                                    test, consequent, alternate);
      }
    case "LabeledStatement":
      {
        let body = loop(node.body);
        return new Ast.Statement.Labeled(loc, range, comments,
                                         node.label,
                                         body);
      }
    case "BreakStatement":
      {
        return new Ast.Statement.Break(loc, range, comments,
                                       node.label);
      }
    case "ContinueStatement":
      {
        return new Ast.Statement.Continue(loc, range, comments,
                                          node.label);
      }
    case "WithStatement":
      {
        let object = loop(node.object);
        let body = loop(node.body);
        return new Ast.Statement.With(loc, range, comments,
                                      object, body);
      }
    case "SwitchStatement":
      {
        let discriminant = loop(node.discriminant);
        let cases = loop(node.cases);
        return new Ast.Statement.Switch(loc, range, comments,
                                        discrimant, cases,
                                        node.lexical);
      }
    case "ReturnStatement":
      {
        let argument = loop(node.argument);
        return new Ast.Statement.Return(loc, range, comments,
                                        argument);
      }
    case "ThrowStatement":
      {
        let argument = loop(node.argument);
        return new Ast.Statement.Throw(loc, range, comments,
                                       argument);
      }
    case "TryStatement":
      {
        let block = loop(node.block);
        let handlers = loop(node.handlers);
        let finalizer = loop(node.finalizer);
        return new Ast.Statement.Try(loc, range, comments,
                                     block, handlers, finalizer);
      }
    case "CatchClause":
      {
        let param = loop(node.param);
        let guard = loop(node.guard);
        let body = loop(node.body);
        return new Ast.Clause.Catch(loc, range, comments,
                                    param, guard, body);
      }
      // FIXME: All the other statements
    case "FunctionDeclaration":
      {
        let id = loop(node.id);
        let params = loop(node.params);
        let body = loop(node.body);
        let meta = loop(node.meta);
        return new Ast.Statement.Declaration.Fun(loc, range, comments,
                                                 id, params,
                                                 body, meta);
      }
    case "VariableDeclaration":
      {
        let declarations = loop(node.declarations);
        return new Ast.Statement.Declaration.Var(loc, range, comments,
                                                 declarations,
                                                 node.kind);
      }
    case "VariableDeclarator":
      {
        let id = loop(node.id);
        let init = loop(node.init);
        return new Ast.Declarator(loc, range, comments,
                                  id, init);
      }
    case "BinaryExpression":
      {
        let left = loop(node.left);
        let right= loop(node.right);
        return new Ast.Expression.Bin(loc, range, comments,
                                      node.operator, left, right);
      }
    case "CallExpression":
      {
        let callee = loop(node.callee);
        let args = loop(node.arguments);
        return new Ast.Expression.Call(loc, range, comments,
                                       callee, args);
      }
    case "FunctionExpression":
      {
        let id = loop(node.id);
        let params = loop(node.params);
        let body = loop(node.body);
        return new Ast.Expression.Function(loc, range, comments,
                                           id, params, body,
                                           node.meta);
      }
    case "MemberExpression":
      {
        let object = loop(node.object);
        let property = loop(node.property);
        return new Ast.Expression.Member(loc, range, comments,
                                         object, property,
                                         node.computed);
      }
    case "ThisExpression":
      {
        return new Ast.Expression.This(loc, range, comments);
      }
    case "Identifier":
      {
        return new Ast.Identifier(loc, range, comments, node.name);
      }
    case "Literal":
      {
        return new Ast.Literal(loc, range, comments, node.value);
      }
    default:
      let error = new Error("Node type "+node.type+" not handled yet");
      print(Debug.source_of(node));
      print(error.stack);
      throw error;
    }
  };
  return loop(node);
};

let Parse = {
  fromString: function(source) {
    let untyped_ast = esprima.parse(source, options);
    return to_typed_ast(untyped_ast);
  },
  fromFile: function(fileName) {
    // FIXME: Nicer filename
    let source = read("../../"+fileName);
    let untyped_ast = esprima.parse(source, options);
    print(untyped_ast);
    let typed_ast = to_typed_ast(untyped_ast);
    print(typed_ast.toSource());
    return typed_ast;
  },
  toJS: function(code) {
    return esprima.generate(code);
  }
};

exports = Parse;

<commit_msg>[Feature, Bugfixes] parse.js<commit_after>load("lib/esprima.js");
let Ast = require("ast.js");
let Debug = require("debug.js");

let options = {
  comment: true,
  raw: true,
  loc: true,
  range: true
};

let to_typed_loc = function(loc, fileName) {
  if (loc == null) {
    return null;
  }
  let start = new Ast.Position(loc.start.line, loc.start.column);
  let end   = new Ast.Position(loc.end.line, loc.end.column);
  return new Ast.SourceLocation(fileName, start, end);
};

let to_typed_ast = function(node, fileName) {
  /**
   * Handler for directives (i.e. comments)
   */
  let Directives = {
    _unattached: [], // Line -> arrays of comments finishing at that line
    _first_attached_comment: -1,
    _regexp_extract:  /[ \t*]*(@([a-zA-Z0-9_]*) *{([^}]*)} *([a-zA-Z0-9]*))/,
    _directives: {},
    add_comments: function(comments) {
      for (let i = 0; i < comments.length; ++i) {
        let comment = comments[i];
        let first_char = comment.value[0];
        if (first_char != '*') {
          continue;
        }
        let last_line = comment.lines[1];
        let entry = this._unattached[last_line];
        if (!entry) {
          this._unattached[last_line] = entry = [];
        }
        entry.push(comment);
      }
    },
    /**
     * Get the comments that are attached to the node.
     *
     * Rules are the following:
     * - comments can be attached only to statements and declarations;
     * - to be attached, a comment must appear on the same line as the
     * statement/declaration or on the line immediately before.
     */
    extract_comments_for_node: function(node) {
      if (node.type.search(/Statement|Declaration/) == -1) {
        return null;
      }
      let line_start = node.loc.start.line;
      let result = null;
      if (line_start > 0) {
        if (this._unattached[line_start - 1]) {
          result = this._unattached[line_start - 1];
          delete this._unattached[line_start - 1];
        }
      }
      if (this._unattached[line_start]) {
        if (!result) {
          result = [];
        }
        result.concat(this._unattached[line_start]);
        delete this._unattached[line_start];
      }
      if (!result) {
        return null;
      }
      if (this._first_attached_comment == -1) {
        this._first_attached_comment = line_start;
      }
      return this.parse_comment(result, node);
    },
    get_unattached_comments: function() {
      let result;
      for(let i = 0; i < this._first_attached_comment; ++i) {
        let current = this._unattached[i];
        if (current) {
          let directives = Directives.parse_comment(current, node);
          if (directives) {
            if (!result) {
              result = [];
            }
            directives.forEach(
              function(v) {
                result.push(v);
              }
            );
          }
        }
      }
      return result;
    },
    parse_comment: function(comments, node) {
      let result = null;
      for (let i = 0; i < comments.length; ++i) {
        let current = comments[i];
        let matched = current.value.match(Directives._regexp_extract);
        if (!matched) continue;
        let [ignore, string, directive, param, optname] = matched;
        if (!result) {
          result = [];
        }
        string = '* ' + string;
        result.push(new Ast.Directive(comments.lines,
                                      comments.range,
                                      current.type, string,
                                      directive, param, optname));
        let table = Directives._directives[directive];
        if (!table) {
          Directives._directives[directive] = table = [];
        }
        table.push(node);
      }
      return result;
    },
    get_directives: function() {
      return Directives._directives;
    }
  };
  let program;
  let loop = function(node) {
    if (node == null) {
      return null;
    }
    if (Array.isArray(node)) {
      let result = [];
      node.forEach(
        function(current) {
          result.push(loop(current));
        }
      );
      return result;
    }
    let loc = to_typed_loc(node.loc, fileName);
    let range = node.range;
    let comments;
    if (node.type != "Program") {
      comments = Directives.extract_comments_for_node(node);
    };
    switch(node.type) {
    case "Program":
      {
        Directives.add_comments(node.comments);

        let elements = loop(node.body);
        return new Ast.Program(loc, range,
                               Directives.get_unattached_comments(),
                               elements,
                               Directives.get_directives());
      }
    case "Function":
      {
        let params = loop(node.params);
        let body = loop(node.body);
        return new Ast.Function(loc, range, comments,
                                node.id,
                                params, body,
                                node.generator, node.expression);
      }
    case "EmptyStatement":
      {
        return new Ast.Statement.Empty(loc, range, comments);
      }
    case "BlockStatement":
      {
        let body = loop(node.body);
        return new Ast.Statement.Block(loc, range, comments, body);
      }
    case "ExpressionStatement":
      {
        let expr = loop(node.expression);
        return new Ast.Statement.Expression(loc, range, comments, expr);
      }
    case "IfStatement":
      {
        let test = loop(node.test);
        let consequent = loop(node.consequent);
        let alternate = loop(node.alternate);
        return new Ast.Statement.If(loc, range, comments,
                                    test, consequent, alternate);
      }
    case "LabeledStatement":
      {
        let body = loop(node.body);
        return new Ast.Statement.Labeled(loc, range, comments,
                                         node.label,
                                         body);
      }
    case "BreakStatement":
      {
        return new Ast.Statement.Break(loc, range, comments,
                                       node.label);
      }
    case "ContinueStatement":
      {
        return new Ast.Statement.Continue(loc, range, comments,
                                          node.label);
      }
    case "WithStatement":
      {
        let object = loop(node.object);
        let body = loop(node.body);
        return new Ast.Statement.With(loc, range, comments,
                                      object, body);
      }
    case "SwitchStatement":
      {
        let discriminant = loop(node.discriminant);
        let cases = loop(node.cases);
        return new Ast.Statement.Switch(loc, range, comments,
                                        discrimant, cases,
                                        node.lexical);
      }
    case "ReturnStatement":
      {
        let argument = loop(node.argument);
        return new Ast.Statement.Return(loc, range, comments,
                                        argument);
      }
    case "ThrowStatement":
      {
        let argument = loop(node.argument);
        return new Ast.Statement.Throw(loc, range, comments,
                                       argument);
      }
    case "TryStatement":
      {
        let block = loop(node.block);
        let handlers = loop(node.handlers);
        let finalizer = loop(node.finalizer);
        return new Ast.Statement.Try(loc, range, comments,
                                     block, handlers, finalizer);
      }
    case "CatchClause":
      {
        let param = loop(node.param);
        let guard = loop(node.guard);
        let body = loop(node.body);
        return new Ast.Clause.Catch(loc, range, comments,
                                    param, guard, body);
      }
      // FIXME: All the other statements
    case "FunctionDeclaration":
      {
        let id = loop(node.id);
        let params = loop(node.params);
        let body = loop(node.body);
        let meta = loop(node.meta);
        return new Ast.Statement.Declaration.Fun(loc, range, comments,
                                                 id, params,
                                                 body, meta);
      }
    case "VariableDeclaration":
      {
        let declarators = loop(node.declarations);
        let kind = node.kind;
        declarators.forEach(
          function(decl) {
            decl.kind = kind;
          }
        );
        return new Ast.Statement.Declaration.Var(loc, range, comments,
                                                 declarators,
                                                 kind);
      }
    case "VariableDeclarator":
      {
        let id = loop(node.id);
        let init = loop(node.init);
        return new Ast.Definition(loc, range, comments,
                                  id, init);
      }
    case "AssignmentExpression":
      {
        let left = loop(node.left);
        let right= loop(node.right);
        return new Ast.Expression.Assignment(loc, range, comments,
                                             node.operator, left, right);
      }
    case "BinaryExpression":
      {
        let left = loop(node.left);
        let right= loop(node.right);
        return new Ast.Expression.Bin(loc, range, comments,
                                      node.operator, left, right);
      }
    case "CallExpression":
      {
        let callee = loop(node.callee);
        let args = loop(node.arguments);
        return new Ast.Expression.Call(loc, range, comments,
                                       callee, args);
      }
    case "FunctionExpression":
      {
        let id = loop(node.id);
        let params = loop(node.params);
        let body = loop(node.body);
        return new Ast.Expression.Function(loc, range, comments,
                                           id, params, body,
                                           node.meta);
      }
    case "MemberExpression":
      {
        let object = loop(node.object);
        let property = loop(node.property);
        return new Ast.Expression.Member(loc, range, comments,
                                         object, property,
                                         node.computed);
      }
    case "ThisExpression":
      {
        return new Ast.Expression.This(loc, range, comments);
      }
    case "Identifier":
      {
        return new Ast.Identifier(loc, range, comments, node.name);
      }
    case "Literal":
      {
        return new Ast.Literal(loc, range, comments, node.value);
      }
    default:
      let error = new Error("Node type "+node.type+" not handled yet");
      print(Debug.source_of(node));
      print(error.stack);
      throw error;
    }
  };
  return loop(node);
};

let Parse = {
  fromString: function(source) {
    let untyped_ast = esprima.parse(source, options);
    return to_typed_ast(untyped_ast);
  },
  fromFile: function(fileName) {
    // FIXME: Nicer filename
    let source = read("../../"+fileName);
    let untyped_ast = esprima.parse(source, options);
    print(untyped_ast.toSource());
    let typed_ast = to_typed_ast(untyped_ast, fileName);
    return typed_ast;
  },
  toJS: function(code) {
    return esprima.generate(code);
  }
};

exports = Parse;

<|endoftext|>


<commit_before>/*
 * The contents of this file are subject to the OpenMRS Public License
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and
 * limitations under the License.
 *
 * Copyright (C) OpenHMIS.  All Rights Reserved.
 *
 */

(function() {
	'use strict';

	var app = angular.module('app.cashierBillFunctionsFactory', []);
	app.service('CashierBillFunctions', CashierBillFunctions);

	CashierBillFunctions.$inject = ['EntityFunctions', 'LineItemModel', '$filter'];

	function CashierBillFunctions(EntityFunctions, LineItemModel, $filter) {
		var service;

		service = {
			addMessageLabels: addMessageLabels,
			formatItemPrice: formatItemPrice,
			paymentWarningDialog: paymentWarningDialog,
			adjustBillWarningDialog: adjustBillWarningDialog,
			validateLineItems: validateLineItems,
			populateExistingLineItems: populateExistingLineItems,
			populatePayments: populatePayments,
			reOrderItemPrices: reOrderItemPrices,
			calculateTotalPayableAmount: calculateTotalPayableAmount,
			roundItemPrice: roundItemPrice,
			createPayment: createPayment,
			computeTotalPrice: computeTotalPrice,
		};

		return service;

		/**
		 * All message labels used in the UI are defined here
		 * @returns {{}}
		 */
		function addMessageLabels() {
			var messages = {};
			return messages;
		}

		function formatItemPrice(itemPrice) {
			var price = itemPrice.price;
			if(price !== undefined) {
				price = (Math.round(price * 100) / 100).toFixed(2);
			}
			else {
				price = '';
			}

			if(itemPrice.name === undefined || itemPrice.name === '' || itemPrice.name === null) {
				return price;
			}
			else {
				return price + ' (' + itemPrice.name + ')';
			}
		}

		function paymentWarningDialog($scope) {
			var dialog = emr.setupConfirmationDialog({
				selector: '#payment-warning-dialog',
				actions: {
					confirm: function() {
						$scope.isProcessPayment = true;
						$scope.$apply();
						if($scope.uuid !== undefined) {
							$scope.postPayment();
						} else {
							$scope.saveOrUpdate();
						}
						dialog.close();
					},
					cancel: function() {
						dialog.close();
					}
				}
			});

			dialog.show();

			EntityFunctions.disableBackground();
		}

		function adjustBillWarningDialog($scope) {
			var dialog = emr.setupConfirmationDialog({
				selector: '#adjust-bill-warning-dialog',
				actions: {
					confirm: function() {
						$scope.isAdjustBill = true;
						$scope.$apply();
						$scope.saveOrUpdate();
						dialog.close();
					},
					cancel: function() {
						dialog.close();
					}
				}
			});

			dialog.show();

			EntityFunctions.disableBackground();
		}

		function validateLineItems(lineItems, validatedLineItems) {
			// validate line items
			var count = 0;
			var failed = false;
			for(var i = 0; i < lineItems.length; i++) {
				var lineItem = lineItems[i];
				if(lineItem.selected) {
					if(lineItem.itemStock.name === undefined) {
						var errorMessage =
							emr.message("openhmis.cashier.bill.lineItems.error.invalidItem") + " - " + lineItem.itemStock.toString();
						emr.errorAlert(errorMessage);
						lineItem.invalidEntry = true;
						failed = true;
						continue;
					}
					var requestLineItem = {
						item: lineItem.itemStock.uuid,
						lineItemOrder: count++,
						price: lineItem.itemStockPrice.price,
						priceName: lineItem.itemStockPrice.name || "",
						priceUuid: lineItem.itemStockPrice.uuid,
						quantity: lineItem.itemStockQuantity
					};
					validatedLineItems.push(requestLineItem);
				} else if(lineItem.itemStock !== "") {
					var errorMessage =
						emr.message("openhmis.cashier.bill.lineItems.error.invalidItem") + " - " + lineItem.itemStock.toString();
					emr.errorAlert(errorMessage);
					lineItem.invalidEntry = true;
					failed = true;
				}
			}

			if(validatedLineItems.length == 0 && !failed) {
				emr.errorAlert("openhmis.cashier.bill.chooseItemErrorMessage");
			} else if(validatedLineItems.length > 0 && !failed) {
				return true;
			}

			return false;
		}

		function populateExistingLineItems(lineItems, populatedLineItems, $scope) {
			for(var i = 0; i < lineItems.length; i++) {
				var lineItem = lineItems[i];
				var itemStockPrice = {
					name: lineItem.priceName,
					price: lineItem.price,
					uuid: lineItem.priceUuid
				};
				var lineItemModel = new LineItemModel(lineItem.item, lineItem.quantity, itemStockPrice);
				lineItemModel.setSelected(true);
				lineItemModel.setTotal(lineItem.price * lineItem.quantity);
				populatedLineItems.push(lineItemModel);

				$scope.lineItem = lineItemModel;

				// load item details
				$scope.loadItemDetails(lineItem.item.uuid, lineItemModel);
			}

			$scope.computeTotalPrice();
		}

		function populatePayments(payments, populatedPayments) {
			if(payments !== undefined && payments.length > 0) {
				for(var i = 0; i < payments.length; i++) {
					var payment = {};
					var attributes = [];
					if(payments[i].attributes.length > 0) {
						for(var j = 0; j < payments[i].attributes.length; j++) {
							var attr = {};
							attr.attributeType = payments[i].attributes[j].attributeType.uuid;
							attr.value = payments[i].attributes[j].value;
							attributes.push(attr);
						}
					}
					payment.attributes = attributes;
					payment.amount = payments[i].amount;
					payment.amountTendered = payments[i].amountTendered;
					payment.instanceType = payments[i].instanceType.uuid;
					populatedPayments.push(payment);
				}
			}
		}

		function createPayment(paymentModeAttributes, attributes,
		                       amountTendered, amountDue, paymentModeUuid) {
			var requestPaymentAttributeTypes = [];
			if(EntityFunctions.validateAttributeTypes(
					paymentModeAttributes, attributes, requestPaymentAttributeTypes)) {
				var payment = {};
				payment.attributes = [];
				if(requestPaymentAttributeTypes.length > 0) {
					payment.attributes = requestPaymentAttributeTypes;
				}

				if(amountDue - amountTendered < 0) {
					payment.amount = Math.round(amountDue);
				} else {
					payment.amount = amountTendered;
				}
				payment.amountTendered = amountTendered;
				payment.instanceType = paymentModeUuid;
				return payment;
			}
			return false;
		}

		function reOrderItemPrices(lineItem, itemDetails) {
			var defaultPrice = itemDetails.defaultPrice;
			var index = -1;
			for(var i = 0; i < itemDetails.prices.length; i++) {
				var price = itemDetails.prices[i];
				if(price.uuid === defaultPrice.uuid) {
					index = lineItem.prices.indexOf(price);
				}
			}

			if(index !== -1) {
				lineItem.prices.splice(index, 1);
				lineItem.prices.unshift(defaultPrice);
			}
		}

		function calculateTotalPayableAmount(lineItems, roundingItem) {
			var totalPayableAmount = 0;
			for(var i = 0; i < lineItems.length; i++) {
				var lineItem = lineItems[i];
				if(lineItem.isSelected()) {
					if(roundingItem !== null && roundingItem !== undefined) {
						if(roundingItem.roundingItemUuid !== lineItem.itemStock.uuid) {
							totalPayableAmount += roundItemPrice(
								lineItem.getTotal(), roundingItem.roundToNearest, roundingItem.roundingMode);
						}
					} else {
						totalPayableAmount += lineItem.getTotal();
					}
				}
			}

			return totalPayableAmount;
		}

		function computeTotalPrice($scope) {
			$scope.totalChangeDue = 0;
			$scope.totalAmountDue = 0;

			// calculate amount for current items.
			if($scope.STATUS === 'PENDING') {
				$scope.totalPayableAmount = calculateTotalPayableAmount(
					$scope.lineItems, $scope.roundingItem);
			} else {
				$scope.totalPayableAmount = calculateTotalPayableAmount($scope.lineItems);
				if($scope.STATUS === 'ADJUSTED') {
					$scope.totalPayableAmount = roundItemPrice(
						$scope.totalPayableAmount, $scope.roundingItem.roundToNearest, $scope.roundingItem.roundingMode);
				}
			}

			// sum amount for previous line items.
			$scope.totalPayableAmount += calculateTotalPayableAmount($scope.previousLineItems);

			// calculate tendered amount
			$scope.totalAmountTendered = 0;
			if($scope.currentPayments !== null) {
				for(var i = 0; i < $scope.currentPayments.length; i++) {
					$scope.totalAmountTendered += $scope.currentPayments[i].amountTendered;
				}
			}

			// sum previous payments.
			if($scope.previousPayments !== null) {
				for(var i = 0; i < $scope.previousPayments.length; i++) {
					$scope.totalAmountTendered += $scope.previousPayments[i].amountTendered;
				}
			}

			// calculate change due
			if($scope.totalPayableAmount > 0) {
				$scope.totalChangeDue = $scope.totalAmountTendered - $scope.totalPayableAmount;
			} else if($scope.totalAmountTendered > 0) {
				$scope.totalChangeDue = $scope.totalAmountTendered;
			}

			// calculate amount due
			if($scope.totalChangeDue < 0) {
				$scope.totalAmountDue = $scope.totalChangeDue * -1;
				$scope.totalChangeDue = 0;
			} else {
				$scope.totalAmountDue = 0;
			}

			// auto-fill payment amount.
			if($scope.checkAutofillPaymentAmount && $scope.totalAmountDue !== 0) {
				$scope.amountTendered = $scope.totalAmountDue;
			}

			$scope.totalChangeDue = $filter('number')($scope.totalChangeDue, 2);
			$scope.totalAmountDue = $filter('number')($scope.totalAmountDue, 2);
			$scope.totalAmountTendered = $filter('number')($scope.totalAmountTendered, 2);
			$scope.totalPayableAmount = $filter('number')($scope.totalPayableAmount, 2);
		}

		function roundItemPrice(val, nearest, mode) {
			if(nearest === 0) {
				return val;
			}
			var factor = 1 / nearest;
			switch(mode) {
				case 'FLOOR':
					return Math.floor(val * factor) / factor;
				case 'CEILING':
					return Math.ceil(val * factor) / factor;
				default:
					return Math.round(val * factor) / factor;
			}
		}
	}
})();
<commit_msg>Code review fixes<commit_after>/*
 * The contents of this file are subject to the OpenMRS Public License
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and
 * limitations under the License.
 *
 * Copyright (C) OpenHMIS.  All Rights Reserved.
 *
 */

(function() {
	'use strict';

	var app = angular.module('app.cashierBillFunctionsFactory', []);
	app.service('CashierBillFunctions', CashierBillFunctions);

	CashierBillFunctions.$inject = ['EntityFunctions', 'LineItemModel', '$filter'];

	function CashierBillFunctions(EntityFunctions, LineItemModel, $filter) {
		var service;

		service = {
			addMessageLabels: addMessageLabels,
			formatItemPrice: formatItemPrice,
			paymentWarningDialog: paymentWarningDialog,
			adjustBillWarningDialog: adjustBillWarningDialog,
			validateLineItems: validateLineItems,
			populateExistingLineItems: populateExistingLineItems,
			populatePayments: populatePayments,
			reOrderItemPrices: reOrderItemPrices,
			calculateTotalPayableAmount: calculateTotalPayableAmount,
			roundItemPrice: roundItemPrice,
			createPayment: createPayment,
			computeTotalPrice: computeTotalPrice,
		};

		return service;

		/**
		 * All message labels used in the UI are defined here
		 * @returns {{}}
		 */
		function addMessageLabels() {
			var messages = {};
			return messages;
		}

		function formatItemPrice(itemPrice) {
			var price = itemPrice.price;
			if(price !== undefined) {
				price = (Math.round(price * 100) / 100).toFixed(2);
			} else {
				price = '';
			}

			if(itemPrice.name === undefined || itemPrice.name === '' || itemPrice.name === null) {
				return price;
			} else {
				return price + ' (' + itemPrice.name + ')';
			}
		}

		function paymentWarningDialog($scope) {
			var dialog = emr.setupConfirmationDialog({
				selector: '#payment-warning-dialog',
				actions: {
					confirm: function() {
						$scope.isProcessPayment = true;
						$scope.$apply();
						if($scope.uuid !== undefined) {
							$scope.postPayment();
						} else {
							$scope.saveOrUpdate();
						}
						dialog.close();
					},
					cancel: function() {
						dialog.close();
					}
				}
			});

			dialog.show();

			EntityFunctions.disableBackground();
		}

		function adjustBillWarningDialog($scope) {
			var dialog = emr.setupConfirmationDialog({
				selector: '#adjust-bill-warning-dialog',
				actions: {
					confirm: function() {
						$scope.isAdjustBill = true;
						$scope.$apply();
						$scope.saveOrUpdate();
						dialog.close();
					},
					cancel: function() {
						dialog.close();
					}
				}
			});

			dialog.show();

			EntityFunctions.disableBackground();
		}

		function validateLineItems(lineItems, validatedLineItems) {
			// validate line items
			var count = 0;
			var failed = false;
			for(var i = 0; i < lineItems.length; i++) {
				var lineItem = lineItems[i];
				if(lineItem.selected) {
					if(lineItem.itemStock.name === undefined) {
						var errorMessage =
							emr.message("openhmis.cashier.bill.lineItems.error.invalidItem") + " - " + lineItem.itemStock.toString();
						emr.errorAlert(errorMessage);
						lineItem.invalidEntry = true;
						failed = true;
						continue;
					}
					var requestLineItem = {
						item: lineItem.itemStock.uuid,
						lineItemOrder: count++,
						price: lineItem.itemStockPrice.price,
						priceName: lineItem.itemStockPrice.name || "",
						priceUuid: lineItem.itemStockPrice.uuid,
						quantity: lineItem.itemStockQuantity
					};
					validatedLineItems.push(requestLineItem);
				} else if(lineItem.itemStock !== "") {
					var errorMessage =
						emr.message("openhmis.cashier.bill.lineItems.error.invalidItem") + " - " + lineItem.itemStock.toString();
					emr.errorAlert(errorMessage);
					lineItem.invalidEntry = true;
					failed = true;
				}
			}

			if(validatedLineItems.length == 0 && !failed) {
				emr.errorAlert("openhmis.cashier.bill.chooseItemErrorMessage");
			} else if(validatedLineItems.length > 0 && !failed) {
				return true;
			}

			return false;
		}

		function populateExistingLineItems(lineItems, populatedLineItems, $scope) {
			for(var i = 0; i < lineItems.length; i++) {
				var lineItem = lineItems[i];
				var itemSPrice = {
					name: lineItem.priceName,
					price: lineItem.price,
					uuid: lineItem.priceUuid
				};
				var lineItemModel = new LineItemModel(lineItem.item, lineItem.quantity, itemSPrice);
				lineItemModel.setSelected(true);
				lineItemModel.setTotal(lineItem.price * lineItem.quantity);
				populatedLineItems.push(lineItemModel);

				$scope.lineItem = lineItemModel;

				// load item details
				$scope.loadItemDetails(lineItem.item.uuid, lineItemModel);
			}

			$scope.computeTotalPrice();
		}

		function populatePayments(payments, populatedPayments) {
			if(payments !== undefined && payments.length > 0) {
				for(var i = 0; i < payments.length; i++) {
					var payment = {};
					var attributes = [];
					if(payments[i].attributes.length > 0) {
						for(var j = 0; j < payments[i].attributes.length; j++) {
							var attr = {};
							attr.attributeType = payments[i].attributes[j].attributeType.uuid;
							attr.value = payments[i].attributes[j].value;
							attributes.push(attr);
						}
					}
					payment.attributes = attributes;
					payment.amount = payments[i].amount;
					payment.amountTendered = payments[i].amountTendered;
					payment.instanceType = payments[i].instanceType.uuid;
					populatedPayments.push(payment);
				}
			}
		}

		function createPayment(paymentModeAttributes, attributes,
		                       amountTendered, amountDue, paymentModeUuid) {
			var requestPaymentAttributeTypes = [];
			if(EntityFunctions.validateAttributeTypes(
					paymentModeAttributes, attributes, requestPaymentAttributeTypes)) {
				var payment = {};
				payment.attributes = [];
				if(requestPaymentAttributeTypes.length > 0) {
					payment.attributes = requestPaymentAttributeTypes;
				}

				if(amountDue - amountTendered < 0) {
					payment.amount = Math.round(amountDue);
				} else {
					payment.amount = amountTendered;
				}
				payment.amountTendered = amountTendered;
				payment.instanceType = paymentModeUuid;
				return payment;
			}
			return false;
		}

		function reOrderItemPrices(lineItem, itemDetails) {
			var defaultPrice = itemDetails.defaultPrice;
			var index = -1;
			for(var i = 0; i < itemDetails.prices.length; i++) {
				var price = itemDetails.prices[i];
				if(price.uuid === defaultPrice.uuid) {
					index = lineItem.prices.indexOf(price);
				}
			}

			if(index !== -1) {
				lineItem.prices.splice(index, 1);
				lineItem.prices.unshift(defaultPrice);
			}
		}

		function calculateTotalPayableAmount(lineItems, roundingItem) {
			var totalPayableAmount = 0;
			for(var i = 0; i < lineItems.length; i++) {
				var lineItem = lineItems[i];
				if(lineItem.isSelected()) {
					totalPayableAmount += lineItem.getTotal();
				}
			}

			if(totalPayableAmount > 0 && roundingItem.roundingItemUuid !== lineItem.itemStock.uuid) {
				totalPayableAmount = roundItemPrice(
					totalPayableAmount, roundingItem.roundToNearest, roundingItem.roundingMode);
			}

			return totalPayableAmount;
		}

		function computeTotalPrice($scope) {
			$scope.totalChangeDue = 0;
			$scope.totalAmountDue = 0;

			// calculate amount for current items.
			if($scope.STATUS === 'PENDING') {
				$scope.totalPayableAmount = calculateTotalPayableAmount(
					$scope.lineItems, $scope.roundingItem);
			} else {
				$scope.totalPayableAmount = calculateTotalPayableAmount($scope.lineItems);
				if($scope.STATUS === 'ADJUSTED') {
					$scope.totalPayableAmount = roundItemPrice(
						$scope.totalPayableAmount, $scope.roundingItem.roundToNearest, $scope.roundingItem.roundingMode);
				}
			}

			// sum amount for previous line items.
			$scope.totalPayableAmount += calculateTotalPayableAmount($scope.previousLineItems);

			// calculate tendered amount
			$scope.totalAmountTendered = 0;
			if($scope.currentPayments !== null) {
				for(var i = 0; i < $scope.currentPayments.length; i++) {
					$scope.totalAmountTendered += $scope.currentPayments[i].amountTendered;
				}
			}

			// sum previous payments.
			if($scope.previousPayments !== null) {
				for(var i = 0; i < $scope.previousPayments.length; i++) {
					$scope.totalAmountTendered += $scope.previousPayments[i].amountTendered;
				}
			}

			// calculate change due
			if($scope.totalPayableAmount > 0) {
				$scope.totalChangeDue = $scope.totalAmountTendered - $scope.totalPayableAmount;
			} else if($scope.totalAmountTendered > 0) {
				$scope.totalChangeDue = $scope.totalAmountTendered;
			}

			// calculate amount due
			if($scope.totalChangeDue < 0) {
				$scope.totalAmountDue = $scope.totalChangeDue * -1;
				$scope.totalChangeDue = 0;
			} else {
				$scope.totalAmountDue = 0;
			}

			// auto-fill payment amount.
			if($scope.checkAutofillPaymentAmount && $scope.totalAmountDue !== 0) {
				$scope.amountTendered = $scope.totalAmountDue;
			}

			$scope.totalChangeDue = $filter('number')($scope.totalChangeDue, 2);
			$scope.totalAmountDue = $filter('number')($scope.totalAmountDue, 2);
			$scope.totalAmountTendered = $filter('number')($scope.totalAmountTendered, 2);
			$scope.totalPayableAmount = $filter('number')($scope.totalPayableAmount, 2);
		}

		function roundItemPrice(val, nearest, mode) {
			if(nearest === 0) {
				return val;
			}
			var factor = 1 / nearest;
			switch(mode) {
				case 'FLOOR':
					return Math.floor(val * factor) / factor;
				case 'CEILING':
					return Math.ceil(val * factor) / factor;
				default:
					return Math.round(val * factor) / factor;
			}
		}
	}
})();
<|endoftext|>


<commit_before>// The Grid component allows an element to be located
//  on a grid of tiles
Crafty.c('Grid', {
    init: function () {
        this.attr({
            w: Game.map_grid.tile.width,
            h: Game.map_grid.tile.height
        });
    },

    // Locate this entity at the given position on the grid
    at: function (x, y) {
        if (x === undefined && y === undefined) {
            return {
                x: this.x / Game.map_grid.tile.width,
                y: this.y / Game.map_grid.tile.height
            };
        } else {
            this.attr({
                x: x * Game.map_grid.tile.width,
                y: y * Game.map_grid.tile.height
            });
            return this;
        }
    }
});

// An "Actor" is an entity that is drawn in 2D on canvas
//  via our logical coordinate grid
Crafty.c('Actor', {
    init: function () {
        this.requires('2D, Canvas, Grid');
    },
});

Crafty.c('Sword_H', {
    _extended: false,
    init: function () {
        this.requires('2D, Canvas')
            .fight();
    },

    fight: function() {
        this.z = 0;
        this.bind('KeyDown', function (e) {
            if (e.key == 39) {
                this.move('e', 16);
                this._extended = true;
            } else if (e.key == 37) {
                this.flip('X');
                this.move('w', 16);
                this._extended = true;
            }
        });
        this.bind('KeyUp', function (e) {
            if (e.key == 39) {
                this.move('w', 16);
                this._extended = false;
            } else if (e.key == 37) {
                this.unflip('X');
                this.move('e', 16);
                this._extended = false;
            }
        });
    }
});

Crafty.c('Sword_V', {
    _extended: false,
    init: function () {
        this.requires('2D, Canvas')
            .fight();
    },

    fight: function() {
        this.z = 0;
        this.bind('KeyDown', function (e) {
            if (e.key == 38) {
                this.move('n', 16);
                this._extended = true;
            } else if (e.key == 40) {
                this.flip('Y');
                this.move('s', 16);
                this._extended = true;
            }
        });
        this.bind('KeyUp', function (e) {
            if (e.key == 38) {
                this.move('s', 16);
                this._extended = false;
            } else if (e.key == 40) {
                this.unflip('Y');
                this.move('n', 16);
                this._extended = false;
            }
        });
    }
});

// The Player
Crafty.c('Player', {
    init: function () {
        this.requires('Actor, spr_player, Multiway, Collision')
            .multiway(4, {
                W: -90,
                S: 90,
                D: 0,
                A: 180
            })
            .attach(Crafty.e('Weapon, Sword_H, spr_sword_h'))
            .attach(Crafty.e('Weapon, Sword_V, spr_sword_v'))
            .die();
    },

    die: function () {
        this.z = 1;
        this.onHit('Enemy', function() { this.destroy(); Crafty.scene('Lose'); Game.coin = 0; Game.key = 0; });
        this.onHit('Hedge', this.stopMovement);
        this.onHit('Door', function () {
            if (Game.key == Game.keys) {
                Crafty.scene('Win');
                Game.coin = 0;
                Game.key = 0;
            }
        });
    },

    stopMovement: function () {
        this._speed = 0;
        if (this._movement) {
            this.x -= this._movement.x;
            this.y -= this._movement.y;
        }
    }
});

Crafty.c('Enemy', {
    init: function () {
        this.requires('Actor, spr_enemy, Collision')
            .wrap();
        this.die();
    },

    wrap: function () {
        var _speed = Math.floor(Math.random() * 12);
        this.bind('EnterFrame', function () {
            this.x -= _speed;
            if (this.x < 0) {
                this.x = 50 * 19;
                this.y = Math.floor(Math.random() * 554) + 16;
            }
        });
    },

    die: function () {
        this.onHit('Weapon', function (e) {
            if (e[0].obj._extended) {
                this.destroy();
                Game.score--;
            }
        });
    }
});

Crafty.c('Coin', {
    init: function () {
        this.requires('Actor, spr_coin, Collision')
            .collect();
    },

    collect: function () {
        this.onHit('Player', function () {
            this.destroy();
            Game.coin++;
        });
    }
});

Crafty.c('Key', {
    init: function () {
        this.requires('Actor, spr_key, Collision')
            .collect();
    },

    collect: function () {
        this.onHit('Player', function () {
            this.destroy();
            Game.key++;
        });
    }
});

Crafty.c('Hedge', {
    init: function () {
        this.requires('Actor, spr_hedge');
    }
});

Crafty.c('Door', {
    init: function () {
        this.requires('Actor, spr_door');
    }
});

Crafty.c('Sword', {
    _extended: false,
    init: function () {
        this.requires('Actor, spr_sword, Multiway, Collision');
        console.log(this._parent);
    }
});<commit_msg>Update components.js<commit_after>// The Grid component allows an element to be located
//  on a grid of tiles
Crafty.c('Grid', {
    init: function () {
        this.attr({
            w: Game.map_grid.tile.width,
            h: Game.map_grid.tile.height
        });
    },

    // Locate this entity at the given position on the grid
    at: function (x, y) {
        if (x === undefined && y === undefined) {
            return {
                x: this.x / Game.map_grid.tile.width,
                y: this.y / Game.map_grid.tile.height
            };
        } else {
            this.attr({
                x: x * Game.map_grid.tile.width,
                y: y * Game.map_grid.tile.height
            });
            return this;
        }
    }
});

// An "Actor" is an entity that is drawn in 2D on canvas
//  via our logical coordinate grid
Crafty.c('Actor', {
    init: function () {
        this.requires('2D, Canvas, Grid');
    },
});

Crafty.c('Sword_H', {
    _extended: false,
    init: function () {
        this.requires('2D, Canvas')
            .fight();
    },

    fight: function() {
        this.z = 0;
        this.bind('KeyDown', function (e) {
            if (e.key == 39) {
                this.move('e', 16);
                this._extended = true;
            } else if (e.key == 37) {
                this.flip('X');
                this.move('w', 16);
                this._extended = true;
            }
        });
        this.bind('KeyUp', function (e) {
            if (e.key == 39) {
                this.move('w', 16);
                this._extended = false;
            } else if (e.key == 37) {
                this.unflip('X');
                this.move('e', 16);
                this._extended = false;
            }
        });
    }
});

// The Player
Crafty.c('Player', {
    init: function () {
        this.requires('Actor, spr_player, Multiway, Collision')
            .multiway(4, {
                W: -90,
                S: 90,
                D: 0,
                A: 180
            })
            .attach(Crafty.e('Weapon, Sword_H, spr_sword_h'))
            .die();
    },

    die: function () {
        this.z = 1;
        this.onHit('Enemy', function() { this.destroy(); Crafty.scene('Lose'); Game.coin = 0; Game.key = 0; });
        this.onHit('Hedge', this.stopMovement);
        this.onHit('Door', function () {
            if (Game.key == Game.keys) {
                Crafty.scene('Win');
                Game.coin = 0;
                Game.key = 0;
            }
        });
    },

    stopMovement: function () {
        this._speed = 0;
        if (this._movement) {
            this.x -= this._movement.x;
            this.y -= this._movement.y;
        }
    }
});

Crafty.c('Enemy', {
    init: function () {
        this.requires('Actor, spr_enemy, Collision')
            .wrap();
        this.die();
    },

    wrap: function () {
        var _speed = Math.floor(Math.random() * 12);
        this.bind('EnterFrame', function () {
            this.x -= _speed;
            if (this.x < 0) {
                this.x = 50 * 19;
                this.y = Math.floor(Math.random() * 554) + 16;
            }
        });
    },

    die: function () {
        this.onHit('Weapon', function (e) {
            if (e[0].obj._extended) {
                this.destroy();
                Game.score--;
            }
        });
    }
});

Crafty.c('Coin', {
    init: function () {
        this.requires('Actor, spr_coin, Collision')
            .collect();
    },

    collect: function () {
        this.onHit('Player', function () {
            this.destroy();
            Game.coin++;
        });
    }
});

Crafty.c('Key', {
    init: function () {
        this.requires('Actor, spr_key, Collision')
            .collect();
    },

    collect: function () {
        this.onHit('Player', function () {
            this.destroy();
            Game.key++;
        });
    }
});

Crafty.c('Hedge', {
    init: function () {
        this.requires('Actor, spr_hedge');
    }
});

Crafty.c('Door', {
    init: function () {
        this.requires('Actor, spr_door');
    }
});

Crafty.c('Sword', {
    _extended: false,
    init: function () {
        this.requires('Actor, spr_sword, Multiway, Collision');
        console.log(this._parent);
    }
});
<|endoftext|>


<commit_before>// see also components/ScreenSize for another way to consume this data
import React from 'react'
import PropTypes from 'prop-types'
import MediaQuery from 'react-responsive'
import { connect } from 'react-redux'
import { breakPoints } from './defaults'

export const MediaQueryWrapper = (props = {}) => {
  // eslint-disable-next-line no-unused-vars
  const {
    dispatch, fakeWidth, children, ...other
  } = props
  const values = { deviceWidth: fakeWidth, width: fakeWidth }
  return (
    <MediaQuery {...other} {...{ values }}>
      {children}
    </MediaQuery>
  )
}

MediaQueryWrapper.propTypes = {
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
  component: PropTypes.oneOfType([
    PropTypes.node,
    PropTypes.func,
    PropTypes.string,
  ]),
  dispatch: PropTypes.func.isRequired,
  fakeWidth: PropTypes.number.isRequired,
}

MediaQueryWrapper.defaultProps = {
  children: null,
  component: 'div',
}

export const responsiveWrapper = (props = {}) => connect(state => ({
  fakeWidth: state.responsive.fakeWidth, ...props,
}))(MediaQueryWrapper)

export const XsScreen = responsiveWrapper({ maxWidth: breakPoints.sm - 1 })
export const SmScreen = responsiveWrapper({
  query: `(min-width: ${breakPoints.sm}px) and (max-width: ${breakPoints.md
    - 1}px)`,
})
export const MdScreen = responsiveWrapper({
  query: `(min-width: ${breakPoints.md}px) and (max-width: ${breakPoints.lg
    - 1}px)`,
})
export const LgScreen = responsiveWrapper({
  query: `(min-width: ${breakPoints.lg}px)`,
})

export const XsScreenHidden = responsiveWrapper({ minWidth: breakPoints.sm })
export const SmScreenHidden = responsiveWrapper({
  query: `(max-width: ${breakPoints.sm - 1}px), (min-width: ${
    breakPoints.md
  }px)`,
})
export const MdScreenHidden = responsiveWrapper({
  query: `(max-width: ${breakPoints.md - 1}px), (min-width: ${
    breakPoints.lg
  }px)`,
})
export const LgScreenHidden = responsiveWrapper({
  maxWidth: breakPoints.lg - 1,
})

export { XsScreen as PhoneScreen }
export { SmScreen as TabletScreen }
export const DesktopScreen = responsiveWrapper({ minWidth: breakPoints.md })
export const MobileScreen = responsiveWrapper({ maxWidth: breakPoints.md - 1 })

export { XsScreenHidden as PhoneScreenHidden }
export { SmScreenHidden as TabletScreenHidden }
export { MobileScreen as DesktopScreenHidden }
export { DesktopScreen as MobileScreenHidden }
<commit_msg>Added PropType.symbol to MediaQueryWrapper's component prop type.<commit_after>// see also components/ScreenSize for another way to consume this data
import React from 'react'
import PropTypes from 'prop-types'
import MediaQuery from 'react-responsive'
import { connect } from 'react-redux'
import { breakPoints } from './defaults'

export const MediaQueryWrapper = (props = {}) => {
  // eslint-disable-next-line no-unused-vars
  const {
    dispatch, fakeWidth, children, ...other
  } = props
  const values = { deviceWidth: fakeWidth, width: fakeWidth }
  return (
    <MediaQuery {...other} {...{ values }}>
      {children}
    </MediaQuery>
  )
}

MediaQueryWrapper.propTypes = {
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
  component: PropTypes.oneOfType([
    PropTypes.node,
    PropTypes.func,
    PropTypes.string,
    PropTypes.symbol,
  ]),
  dispatch: PropTypes.func.isRequired,
  fakeWidth: PropTypes.number.isRequired,
}

MediaQueryWrapper.defaultProps = {
  children: null,
  component: 'div',
}

export const responsiveWrapper = (props = {}) => connect(state => ({
  fakeWidth: state.responsive.fakeWidth, ...props,
}))(MediaQueryWrapper)

export const XsScreen = responsiveWrapper({ maxWidth: breakPoints.sm - 1 })
export const SmScreen = responsiveWrapper({
  query: `(min-width: ${breakPoints.sm}px) and (max-width: ${breakPoints.md
    - 1}px)`,
})
export const MdScreen = responsiveWrapper({
  query: `(min-width: ${breakPoints.md}px) and (max-width: ${breakPoints.lg
    - 1}px)`,
})
export const LgScreen = responsiveWrapper({
  query: `(min-width: ${breakPoints.lg}px)`,
})

export const XsScreenHidden = responsiveWrapper({ minWidth: breakPoints.sm })
export const SmScreenHidden = responsiveWrapper({
  query: `(max-width: ${breakPoints.sm - 1}px), (min-width: ${
    breakPoints.md
  }px)`,
})
export const MdScreenHidden = responsiveWrapper({
  query: `(max-width: ${breakPoints.md - 1}px), (min-width: ${
    breakPoints.lg
  }px)`,
})
export const LgScreenHidden = responsiveWrapper({
  maxWidth: breakPoints.lg - 1,
})

export { XsScreen as PhoneScreen }
export { SmScreen as TabletScreen }
export const DesktopScreen = responsiveWrapper({ minWidth: breakPoints.md })
export const MobileScreen = responsiveWrapper({ maxWidth: breakPoints.md - 1 })

export { XsScreenHidden as PhoneScreenHidden }
export { SmScreenHidden as TabletScreenHidden }
export { MobileScreen as DesktopScreenHidden }
export { DesktopScreen as MobileScreenHidden }
<|endoftext|>


<commit_before>/*******************************************************************************
 * Copyright (c) 2012-2014 University of Stuttgart.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and the Apache License 2.0 which both accompany this distribution,
 * and are available at http://www.eclipse.org/legal/epl-v10.html
 * and http://www.apache.org/licenses/LICENSE-2.0
 *
 * Contributors:
 *    Oliver Kopp - initial API and implementation and/or initial documentation
 *******************************************************************************/

/**
 * This file contains supporting functions for the topoplogy modeler
 */
define(
	// although XMLWriter ist not an AMD module, requirejs does not complain when loading it
	["winery-support-common", "XMLWriter"],
	function (w) {
		// has to be consistent with {@link org.eclipse.winery.common.constants.Namespaces}
		var TOSCA_NAMESPACE = "http://docs.oasis-open.org/tosca/ns/2011/12";
		var TOSCA_WINERY_EXTENSIONS_NAMESPACE ="http://www.opentosca.org/winery/extensions/tosca/2013/02/12";

		var topologyTemplateURL;

		var module = {
			save: save,
			setTopologyTemplateURL: function(url) {
				topologyTemplateURL = url;
			},
			getTopologyTemplateAsXML: getTopologyTemplateAsXML,

			TOSCA_NAMESPACE: TOSCA_NAMESPACE,
			TOSCA_WINERY_EXTENSIONS_NAMESPACE: TOSCA_WINERY_EXTENSIONS_NAMESPACE
		};
		return module;

		function writeReqOrCaps(elements, xmlw, globalWrapperElementName, singleElementWrapperName) {
			if (elements.length != 0) {
				xmlw.writeStartElement(globalWrapperElementName);

				$.each(elements, function(i,e) {
					xmlw.writeStartElement(singleElementWrapperName);
					e = $(e);
					xmlw.writeAttributeString("id",   e.children(".id").text());
					xmlw.writeAttributeString("name", e.children(".name").text());
					writeType(xmlw, e.children(".type").children("a").data("qname"));
					savePropertiesFromDivToXMLWriter(e.children("div.propertiesContainer"), xmlw);
					xmlw.writeEndElement();
				});

				xmlw.writeEndElement();
			}

		}

		/**
		 * "doSave"
		 */
		function save() {
			$("#saveBtn").button('loading');

			$.ajax({
				url: topologyTemplateURL,
				type: "PUT",
				contentType: 'text/xml',
				data: getTopologyTemplateAsXML(false),
				success: function(data, textStatus, jqXHR) {
					$("#saveBtn").button('reset');
					vShowSuccess("successfully saved.");
				},
				error: function(jqXHR, textStatus, errorThrown) {
					$("#saveBtn").button('reset');
					vShowAJAXError("Could not save", jqXHR, errorThrown);
				}
			});
		}

		/**
		 * Creates an XML String of the modelled topology template.
		 */
		function getTopologyTemplateAsXML(needsDefinitionsTag) {

			var xmlw = new XMLWriter("utf-8");
			xmlw.writeStartDocument();

			if (needsDefinitionsTag) {
				xmlw.writeStartElement("Definitions");
				xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE);
				xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE);

				xmlw.writeStartElement("ServiceTemplate");
				xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE);
				xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE);
			}
			xmlw.writeStartElement("TopologyTemplate");
			xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE);
			xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE);
			$("div.NodeTemplateShape").not(".hidden").each (function() {
				xmlw.writeStartElement("NodeTemplate");

				var id = $(this).attr("id");

				var headerContainer = $(this).children("div.headerContainer");
				var name = headerContainer.children("div.name").text();
				var typeQNameStr = headerContainer.children("span.typeQName").text();
				var minmaxdiv = headerContainer.children("div.minMaxInstances");
				var min = minmaxdiv.children("span.minInstances").text();
				var max = minmaxdiv.children("span.maxInstances").text();
				if (max == "∞") {
					max = "unbounded";
				}
				var x = $(this).css("left");
				x = x.substring(0, x.indexOf("px"));
				var y = $(this).css("top");
				y = y.substring(0, y.indexOf("px"));

				xmlw.writeAttributeString("id", id);
				if (name != "") {
					xmlw.writeAttributeString("name", name);
				}
				writeType(xmlw, typeQNameStr);
				if (min != "") {
					xmlw.writeAttributeString("minInstances", min);
				}
				if (max != "") {
					xmlw.writeAttributeString("maxInstances", max);
				}
				xmlw.writeAttributeString("winery:x", x);
				xmlw.writeAttributeString("winery:y", y);

				/** Properties **/
				savePropertiesFromDivToXMLWriter($(this).children("div.propertiesContainer"), xmlw);

				/** Requirements **/
				writeReqOrCaps(
					$(this).children("div.requirementsContainer").children("div.content").children("div.reqorcap"),
					xmlw,
					"Requirements",
					"Requirement");

				/** Capabilities **/
				writeReqOrCaps(
					$(this).children("div.capabilitiesContainer").children("div.content").children("div.reqorcap"),
					xmlw,
					"Capabilities",
					"Capability");

				/** Policies **/
				w.writeCollectionDefinedByATextArea(xmlw,
						$(this).children("div.policiesContainer").children("div.content").children("div.policy"),
						"Policies");

				/** Deployment Artifacts **/
				// DAs do not store all data in the HTML, a global JavaScript variable is also used
				var das = $(this).children("div.deploymentArtifactsContainer").children("div.content").children("div.deploymentArtifact");
				if (das.length != 0) {
					xmlw.writeStartElement("DeploymentArtifacts");
					das.each(function(i,e) {
						// the textarea contains a valid deployment artifact xml
						var xml = $(e).children("textarea").val();
						xmlw.writeXML(xml);
					});
					xmlw.writeEndElement();
				}

				// End: Nodetemplate
				xmlw.writeEndElement();
			});
			jsPlumb.select().each(function(connection) {
				xmlw.writeStartElement("RelationshipTemplate");
				var id = connection.id;
				var typeQNameStr = connection.getType()[0];

				var connData = winery.connections[id];
				if (!connData) {
					vShowError("Error in the internal data structure: Id " + id + " not found");
					return;
				}

				xmlw.writeAttributeString("id", connData.id);
				if (connData.name != "") {
					xmlw.writeAttributeString("name", connData.name);
				}
				writeType(xmlw, typeQNameStr);

				if (typeof connData.propertiesContainer !== "undefined") {
					savePropertiesFromDivToXMLWriter(connData.propertiesContainer, xmlw);
				}

				xmlw.writeStartElement("SourceElement");
				if (connData.req) {
					// conn starts at a requirement
					xmlw.writeAttributeString("ref", connData.req);
				} else {
					// conn starts at a node template
					xmlw.writeAttributeString("ref", connection.sourceId);
				}
				xmlw.writeEndElement();
				xmlw.writeStartElement("TargetElement");
				if (connData.cap) {
					// conn ends at a capability
					xmlw.writeAttributeString("ref", connData.cap);
				} else {
					// conn ends at a node template
					xmlw.writeAttributeString("ref", connection.targetId);
				}
				xmlw.writeEndElement();

				xmlw.writeEndElement();
			});

			if (needsDefinitionsTag) {
				xmlw.writeEndElement();
				xmlw.writeEndElement();
			}

			xmlw.writeEndDocument();

			return xmlw.flush();
		}

		function writeQNameAttribute(w, nsPrefix, qnameStr) {
			var qname = getQName(qnameStr);
			w.writeAttributeString("xmlns:" + nsPrefix, qname.namespace);
			w.writeAttributeString("type", nsPrefix + ":" + qname.localName);
		}

		function writeType(w, typeQNameStr) {
			writeQNameAttribute(w, "ty", typeQNameStr);
		}

	}
);

<commit_msg>Fix comment<commit_after>/*******************************************************************************
 * Copyright (c) 2012-2015 University of Stuttgart.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and the Apache License 2.0 which both accompany this distribution,
 * and are available at http://www.eclipse.org/legal/epl-v10.html
 * and http://www.apache.org/licenses/LICENSE-2.0
 *
 * Contributors:
 *    Oliver Kopp - initial API and implementation and/or initial documentation
 *******************************************************************************/

/**
 * This file contains supporting functions for the topoplogy modeler
 */
define(
	// although XMLWriter ist not an AMD module, requirejs does not complain when loading it
	["winery-support-common", "XMLWriter"],
	function (w) {
		// has to be consistent with {@link org.eclipse.winery.common.constants.Namespaces}
		var TOSCA_NAMESPACE = "http://docs.oasis-open.org/tosca/ns/2011/12";
		var TOSCA_WINERY_EXTENSIONS_NAMESPACE ="http://www.opentosca.org/winery/extensions/tosca/2013/02/12";

		var topologyTemplateURL;

		var module = {
			save: save,
			setTopologyTemplateURL: function(url) {
				topologyTemplateURL = url;
			},
			getTopologyTemplateAsXML: getTopologyTemplateAsXML,

			TOSCA_NAMESPACE: TOSCA_NAMESPACE,
			TOSCA_WINERY_EXTENSIONS_NAMESPACE: TOSCA_WINERY_EXTENSIONS_NAMESPACE
		};
		return module;

		function writeReqOrCaps(elements, xmlw, globalWrapperElementName, singleElementWrapperName) {
			if (elements.length != 0) {
				xmlw.writeStartElement(globalWrapperElementName);

				$.each(elements, function(i,e) {
					xmlw.writeStartElement(singleElementWrapperName);
					e = $(e);
					xmlw.writeAttributeString("id",   e.children(".id").text());
					xmlw.writeAttributeString("name", e.children(".name").text());
					writeType(xmlw, e.children(".type").children("a").data("qname"));
					savePropertiesFromDivToXMLWriter(e.children("div.propertiesContainer"), xmlw);
					xmlw.writeEndElement();
				});

				xmlw.writeEndElement();
			}

		}

		/**
		 * "doSave"
		 */
		function save() {
			$("#saveBtn").button('loading');

			$.ajax({
				url: topologyTemplateURL,
				type: "PUT",
				contentType: 'text/xml',
				data: getTopologyTemplateAsXML(false),
				success: function(data, textStatus, jqXHR) {
					$("#saveBtn").button('reset');
					vShowSuccess("successfully saved.");
				},
				error: function(jqXHR, textStatus, errorThrown) {
					$("#saveBtn").button('reset');
					vShowAJAXError("Could not save", jqXHR, errorThrown);
				}
			});
		}

		/**
		 * Creates an XML String of the modelled topology template.
		 */
		function getTopologyTemplateAsXML(needsDefinitionsTag) {

			var xmlw = new XMLWriter("utf-8");
			xmlw.writeStartDocument();

			if (needsDefinitionsTag) {
				xmlw.writeStartElement("Definitions");
				xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE);
				xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE);

				xmlw.writeStartElement("ServiceTemplate");
				xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE);
				xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE);
			}
			xmlw.writeStartElement("TopologyTemplate");
			xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE);
			xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE);
			$("div.NodeTemplateShape").not(".hidden").each (function() {
				xmlw.writeStartElement("NodeTemplate");

				var id = $(this).attr("id");

				var headerContainer = $(this).children("div.headerContainer");
				var name = headerContainer.children("div.name").text();
				var typeQNameStr = headerContainer.children("span.typeQName").text();
				var minmaxdiv = headerContainer.children("div.minMaxInstances");
				var min = minmaxdiv.children("span.minInstances").text();
				var max = minmaxdiv.children("span.maxInstances").text();
				if (max == "∞") {
					max = "unbounded";
				}
				var x = $(this).css("left");
				x = x.substring(0, x.indexOf("px"));
				var y = $(this).css("top");
				y = y.substring(0, y.indexOf("px"));

				xmlw.writeAttributeString("id", id);
				if (name != "") {
					xmlw.writeAttributeString("name", name);
				}
				writeType(xmlw, typeQNameStr);
				if (min != "") {
					xmlw.writeAttributeString("minInstances", min);
				}
				if (max != "") {
					xmlw.writeAttributeString("maxInstances", max);
				}
				xmlw.writeAttributeString("winery:x", x);
				xmlw.writeAttributeString("winery:y", y);

				/** Properties **/
				savePropertiesFromDivToXMLWriter($(this).children("div.propertiesContainer"), xmlw);

				/** Requirements **/
				writeReqOrCaps(
					$(this).children("div.requirementsContainer").children("div.content").children("div.reqorcap"),
					xmlw,
					"Requirements",
					"Requirement");

				/** Capabilities **/
				writeReqOrCaps(
					$(this).children("div.capabilitiesContainer").children("div.content").children("div.reqorcap"),
					xmlw,
					"Capabilities",
					"Capability");

				/** Policies **/
				w.writeCollectionDefinedByATextArea(xmlw,
						$(this).children("div.policiesContainer").children("div.content").children("div.policy"),
						"Policies");

				/** Deployment Artifacts **/
				var das = $(this).children("div.deploymentArtifactsContainer").children("div.content").children("div.deploymentArtifact");
				if (das.length != 0) {
					xmlw.writeStartElement("DeploymentArtifacts");
					das.each(function(i,e) {
						// the textarea contains a valid deployment artifact xml
						var xml = $(e).children("textarea").val();
						xmlw.writeXML(xml);
					});
					xmlw.writeEndElement();
				}

				// End: Nodetemplate
				xmlw.writeEndElement();
			});
			jsPlumb.select().each(function(connection) {
				xmlw.writeStartElement("RelationshipTemplate");
				var id = connection.id;
				var typeQNameStr = connection.getType()[0];

				var connData = winery.connections[id];
				if (!connData) {
					vShowError("Error in the internal data structure: Id " + id + " not found");
					return;
				}

				xmlw.writeAttributeString("id", connData.id);
				if (connData.name != "") {
					xmlw.writeAttributeString("name", connData.name);
				}
				writeType(xmlw, typeQNameStr);

				if (typeof connData.propertiesContainer !== "undefined") {
					savePropertiesFromDivToXMLWriter(connData.propertiesContainer, xmlw);
				}

				xmlw.writeStartElement("SourceElement");
				if (connData.req) {
					// conn starts at a requirement
					xmlw.writeAttributeString("ref", connData.req);
				} else {
					// conn starts at a node template
					xmlw.writeAttributeString("ref", connection.sourceId);
				}
				xmlw.writeEndElement();
				xmlw.writeStartElement("TargetElement");
				if (connData.cap) {
					// conn ends at a capability
					xmlw.writeAttributeString("ref", connData.cap);
				} else {
					// conn ends at a node template
					xmlw.writeAttributeString("ref", connection.targetId);
				}
				xmlw.writeEndElement();

				xmlw.writeEndElement();
			});

			if (needsDefinitionsTag) {
				xmlw.writeEndElement();
				xmlw.writeEndElement();
			}

			xmlw.writeEndDocument();

			return xmlw.flush();
		}

		function writeQNameAttribute(w, nsPrefix, qnameStr) {
			var qname = getQName(qnameStr);
			w.writeAttributeString("xmlns:" + nsPrefix, qname.namespace);
			w.writeAttributeString("type", nsPrefix + ":" + qname.localName);
		}

		function writeType(w, typeQNameStr) {
			writeQNameAttribute(w, "ty", typeQNameStr);
		}

	}
);

<|endoftext|>