<commit_before>/* eslint-disable react/forbid-prop-types */
/* eslint-disable react/jsx-one-expression-per-line */
/* eslint-disable react/jsx-no-bind */
/* eslint-disable react/jsx-filename-extension */
/* eslint-disable react/prop-types */

import React, { Component } from "react";
import PropTypes from "prop-types";
import { withStyles } from "@material-ui/core/styles";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import Paper from "@material-ui/core/Paper";
import IconButton from "@material-ui/core/IconButton";
import StyledTableRow from "./StyledTableRow";
import IconPowerSettings from "../../MaterialComponents/Icons/IconPowerSettings";
import IconPowerSettingsDisable from "../../MaterialComponents/Icons/IconPowerSettingsDisable";
import StyledCheckbox from "./StyledCheckbox";
import IconDelete from "../../MaterialComponents/Icons/IconDelete";
import IconLibraryAdd from "../../MaterialComponents/Icons/IconLibraryAdd";
import EnhancedTableHead from "./EnhancedTableHead";
import TABLE_COLUMN_TYPES from "../ColumnTypes";
import TablePaginationActions from "./TablePaginationActions";
import StyledTableCell2 from "./StyledTableCell2";
import TableCellCustom from "./TableCellCustom";
import StyledPagination from "./StyledPagination";
import Tooltip from "../../MaterialComponents/Tooltip";
import InputFieldSelectTableCell from "../../InputField/InputFieldSelectTableCell";
import InputFieldTableCell from "../../InputField/InputFieldTableCell";
import IndicatorsEditor from "./IndicatorsEditorRow";

const styles = () => ({
  root: {
    width: "100%",
    display: "flex",
    height: "calc(100vh - 209px)"
  },
  paper: {
    width: "100%",
    display: "flex",
    flexDirection: "column",
    boxShadow: "none"
  },
  tableWrapper: {
    overflow: "auto",
    boxShadow:
      "0px 1px 3px 0px rgba(0,0,0,0.2), 0px 1px 1px 0px rgba(0,0,0,0.14), 0px 2px 1px -1px rgba(0,0,0,0.12)"
  },
  rowDisabled: {
    backgroundColor: "rgba(0, 0, 0, 0.07) !important"
  }
});

class TableCustom extends Component {
  state = {
    order: "",
    orderBy: ""
  };

  handleRequestSort = (event, property) => {
    const { onSort } = this.props;
    const { orderBy, order } = this.state;
    const isDesc = orderBy === property && order === "desc";
    this.setState(
      {
        order: isDesc ? "asc" : "desc",
        orderBy: property
      },
      () => {
        onSort({
          order: isDesc ? "asc" : "desc",
          orderBy: property
        });
      }
    );
  };

  handleSelectAllClick = event => {
    const {
      onTableSelectionChanged,
      tableData,
      nameIdPaired,
      indicatorsEditor
    } = this.props;
    if (event.target.checked) {
      const newSelecteds = indicatorsEditor
        ? tableData
        : nameIdPaired
        ? tableData.map(n => `${n.id}:${n.name}`)
        : tableData.map(n => n.id);
      onTableSelectionChanged(newSelecteds);
      return;
    }

    onTableSelectionChanged([]);
  };

  handleClick = (event, row, editing) => {
    event.preventDefault();
    event.stopPropagation();
    const {
      onTableSelectionChanged,
      selected,
      nameIdPaired,
      indicatorsEditor
    } = this.props;
    const value = indicatorsEditor
      ? row
      : nameIdPaired
      ? `${row.id}:${row.name}`
      : row.id;
    const selectedIndex = indicatorsEditor
      ? selected
          .map(({ object }) => {
            return object.id;
          })
          .indexOf(value.object.id)
      : selected.indexOf(value);
    let newSelected = [];

    if (editing) {
      newSelected = selected;
      newSelected[selectedIndex] = indicatorsEditor ? row : value;
    } else {
      if (selectedIndex === -1) {
        newSelected = newSelected.concat(
          selected,
          indicatorsEditor ? row : value
        );
      } else if (selectedIndex === 0) {
        newSelected = newSelected.concat(selected.slice(1));
      } else if (selectedIndex === selected.length - 1) {
        newSelected = newSelected.concat(selected.slice(0, -1));
      } else if (selectedIndex > 0) {
        newSelected = newSelected.concat(
          selected.slice(0, selectedIndex),
          selected.slice(selectedIndex + 1)
        );
      }
    }

    onTableSelectionChanged(newSelected);
  };

  rowHovered = (id, value) => {
    this.setState({
      hovered: value ? id : null
    });
  };

  addConditionalRowBackground = (
    row,
    column,
    backgroundClass,
    attribute,
    classes
  ) => {
    return column
      ? {
          [attribute]: !row[column] ? classes[backgroundClass] : ""
        }
      : {};
  };

  render() {
    const {
      columnConfiguration,
      tableData,
      onDelete,
      onPaginate,
      onDuplicate,
      onPaginationLimitChanged,
      limit,
      checkable,
      currentPage,
      classes,
      totalRows,
      onEnable,
      onDisable,
      onRowClick = () => {},
      selected,
      enabledColumn,
      nameIdPaired,
      indicatorsEditor,
      emptyDataMessage,
      impacts,
      paginated = true
    } = this.props;
    const { order, orderBy, hovered } = this.state;

    const isSelected = value => {
      // eslint-disable-next-line
      for (let i = 0; i < selected.length; i++) {
        // eslint-disable-next-line
        if (indicatorsEditor) {
          if (selected[i].object.id == value) {
            return {
              bool: true,
              obj: selected[i]
            };
          }
        } else {
          if (selected[i] == value) {
            return {
              bool: true,
              obj: selected[i]
            };
          }
        }
      }
      return {
        bool: false,
        obj: null
      };
    };

    const emptyRows = limit - Math.min(limit, totalRows - currentPage * limit);

    return (

        <Paper className={classes.paper}>
          {paginated ? (

                      ) : null}

                      {columnConfiguration.map(column => {
                        switch (column.type) {
                          case TABLE_COLUMN_TYPES.number:
                            return (
                              <TableCellCustom
                                align="left"
                                className={classes.tableCellCustom}
                                style={
                                  indicatorsEditor
                                    ? {
                                        padding: "3px 4px"
                                      }
                                    : {}
                                }
                              >
                                {row[column.id] || ""}
                              </TableCellCustom>
                            );
                          case TABLE_COLUMN_TYPES.string:
                            return (
                              <TableCellCustom
                                align="left"
                                className={classes.tableCellCustom}
                                style={
                                  indicatorsEditor
                                    ? {
                                        padding: "3px 4px"
                                      }
                                    : {}
                                }
                              >
                                {column.subkey
                                  ? row[column.subkey][column.id] || ""
                                  : row[column.id] || ""}
                              </TableCellCustom>
                            );
                          case TABLE_COLUMN_TYPES.boolean:
                            return (

                            );
                          case TABLE_COLUMN_TYPES.toggler:
                            return (

                            );
                          case TABLE_COLUMN_TYPES.input:
                            return (

                            );
                          case TABLE_COLUMN_TYPES.select:
                            return (

                            );
                          case TABLE_COLUMN_TYPES.multicolumn:
                            return (
                              <TableCellCustom
                                align="left"
                                className={classes.tableCellCustom}
                                style={
                                  indicatorsEditor
                                    ? {
                                        padding: "3px 4px"
                                      }
                                    : {}
                                }
                              >
                                {column.columns.map(subColumn => (
                                  <React.Fragment>
                                    {subColumn.label} {row[subColumn.id]}
                                    {subColumn.type === "percentage"
                                      ? "%"
                                      : null}
                                    {"   "}
                                  </React.Fragment>
                                ))}
                              </TableCellCustom>
                            );
                          case TABLE_COLUMN_TYPES.hoverActions:
                            return (

                            );
                          default:
                            return null;
                        }
                      })}
                      {indicatorsEditor ? (
                        <IndicatorsEditor
                          row={row}
                          index={index}
                          impacts={impacts}
                          selected={isItemSelected}
                          onImpactEdit={this.handleClick}
                        />
                      ) : null}
                    </StyledTableRow>
                  );
                })}
                {tableData.length < 1 ? (

                ) : null}
              </TableBody>

        </Paper>

    );
  }
}

TableCustom.propTypes = {
  classes: PropTypes.object.isRequired
};

export default withStyles(styles, { withTheme: true })(TableCustom);
<commit_msg>Add image to the table custom.<commit_after>/* eslint-disable react/forbid-prop-types */
/* eslint-disable react/jsx-one-expression-per-line */
/* eslint-disable react/jsx-no-bind */
/* eslint-disable react/jsx-filename-extension */
/* eslint-disable react/prop-types */

import React, { Component } from "react";
import PropTypes from "prop-types";
import { withStyles } from "@material-ui/core/styles";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import Paper from "@material-ui/core/Paper";
import IconButton from "@material-ui/core/IconButton";
import StyledTableRow from "./StyledTableRow";
import IconPowerSettings from "../../MaterialComponents/Icons/IconPowerSettings";
import IconPowerSettingsDisable from "../../MaterialComponents/Icons/IconPowerSettingsDisable";
import StyledCheckbox from "./StyledCheckbox";
import IconDelete from "../../MaterialComponents/Icons/IconDelete";
import IconLibraryAdd from "../../MaterialComponents/Icons/IconLibraryAdd";
import EnhancedTableHead from "./EnhancedTableHead";
import TABLE_COLUMN_TYPES from "../ColumnTypes";
import TablePaginationActions from "./TablePaginationActions";
import StyledTableCell2 from "./StyledTableCell2";
import TableCellCustom from "./TableCellCustom";
import StyledPagination from "./StyledPagination";
import Tooltip from "../../MaterialComponents/Tooltip";
import InputFieldSelectTableCell from "../../InputField/InputFieldSelectTableCell";
import InputFieldTableCell from "../../InputField/InputFieldTableCell";
import IndicatorsEditor from "./IndicatorsEditorRow";

const styles = () => ({
  root: {
    width: "100%",
    display: "flex",
    height: "calc(100vh - 209px)"
  },
  paper: {
    width: "100%",
    display: "flex",
    flexDirection: "column",
    boxShadow: "none"
  },
  tableWrapper: {
    overflow: "auto",
    boxShadow:
      "0px 1px 3px 0px rgba(0,0,0,0.2), 0px 1px 1px 0px rgba(0,0,0,0.14), 0px 2px 1px -1px rgba(0,0,0,0.12)"
  },
  rowDisabled: {
    backgroundColor: "rgba(0, 0, 0, 0.07) !important"
  }
});

class TableCustom extends Component {
  state = {
    order: "",
    orderBy: ""
  };

  handleRequestSort = (event, property) => {
    const { onSort } = this.props;
    const { orderBy, order } = this.state;
    const isDesc = orderBy === property && order === "desc";
    this.setState(
      {
        order: isDesc ? "asc" : "desc",
        orderBy: property
      },
      () => {
        onSort({
          order: isDesc ? "asc" : "desc",
          orderBy: property
        });
      }
    );
  };

  handleSelectAllClick = event => {
    const {
      onTableSelectionChanged,
      tableData,
      nameIdPaired,
      indicatorsEditor
    } = this.props;
    if (event.target.checked) {
      const newSelecteds = indicatorsEditor
        ? tableData
        : nameIdPaired
        ? tableData.map(n => `${n.id}:${n.name}`)
        : tableData.map(n => n.id);
      onTableSelectionChanged(newSelecteds);
      return;
    }

    onTableSelectionChanged([]);
  };

  handleClick = (event, row, editing) => {
    event.preventDefault();
    event.stopPropagation();
    const {
      onTableSelectionChanged,
      selected,
      nameIdPaired,
      indicatorsEditor
    } = this.props;
    const value = indicatorsEditor
      ? row
      : nameIdPaired
      ? `${row.id}:${row.name}`
      : row.id;
    const selectedIndex = indicatorsEditor
      ? selected
          .map(({ object }) => {
            return object.id;
          })
          .indexOf(value.object.id)
      : selected.indexOf(value);
    let newSelected = [];

    if (editing) {
      newSelected = selected;
      newSelected[selectedIndex] = indicatorsEditor ? row : value;
    } else {
      if (selectedIndex === -1) {
        newSelected = newSelected.concat(
          selected,
          indicatorsEditor ? row : value
        );
      } else if (selectedIndex === 0) {
        newSelected = newSelected.concat(selected.slice(1));
      } else if (selectedIndex === selected.length - 1) {
        newSelected = newSelected.concat(selected.slice(0, -1));
      } else if (selectedIndex > 0) {
        newSelected = newSelected.concat(
          selected.slice(0, selectedIndex),
          selected.slice(selectedIndex + 1)
        );
      }
    }

    onTableSelectionChanged(newSelected);
  };

  rowHovered = (id, value) => {
    this.setState({
      hovered: value ? id : null
    });
  };

  addConditionalRowBackground = (
    row,
    column,
    backgroundClass,
    attribute,
    classes
  ) => {
    return column
      ? {
          [attribute]: !row[column] ? classes[backgroundClass] : ""
        }
      : {};
  };

  render() {
    const {
      columnConfiguration,
      tableData,
      onDelete,
      onPaginate,
      onDuplicate,
      onPaginationLimitChanged,
      limit,
      checkable,
      currentPage,
      classes,
      totalRows,
      onEnable,
      onDisable,
      onRowClick = () => {},
      selected,
      enabledColumn,
      nameIdPaired,
      indicatorsEditor,
      emptyDataMessage,
      impacts,
      paginated = true
    } = this.props;
    const { order, orderBy, hovered } = this.state;

    const isSelected = value => {
      // eslint-disable-next-line
      for (let i = 0; i < selected.length; i++) {
        // eslint-disable-next-line
        if (indicatorsEditor) {
          if (selected[i].object.id == value) {
            return {
              bool: true,
              obj: selected[i]
            };
          }
        } else {
          if (selected[i] == value) {
            return {
              bool: true,
              obj: selected[i]
            };
          }
        }
      }
      return {
        bool: false,
        obj: null
      };
    };

    const emptyRows = limit - Math.min(limit, totalRows - currentPage * limit);

    return (

        <Paper className={classes.paper}>
          {paginated ? (

                      ) : null}

                      {columnConfiguration.map(column => {
                        switch (column.type) {
                          case TABLE_COLUMN_TYPES.number:
                            return (
                              <TableCellCustom
                                align="left"
                                className={classes.tableCellCustom}
                                style={
                                  indicatorsEditor
                                    ? {
                                        padding: "3px 4px"
                                      }
                                    : {}
                                }
                              >
                                {row[column.id] || ""}
                              </TableCellCustom>
                            );
                          case TABLE_COLUMN_TYPES.string:
                            return (
                              <TableCellCustom
                                align="left"
                                className={classes.tableCellCustom}
                                style={
                                  indicatorsEditor
                                    ? {
                                        padding: "3px 4px"
                                      }
                                    : {}
                                }
                              >
                                {column.image && (

                                )}
                                {column.subkey
                                  ? row[column.subkey][column.id] || ""
                                  : row[column.id] || ""}
                              </TableCellCustom>
                            );
                          case TABLE_COLUMN_TYPES.boolean:
                            return (

                            );
                          case TABLE_COLUMN_TYPES.toggler:
                            return (

                            );
                          case TABLE_COLUMN_TYPES.input:
                            return (

                            );
                          case TABLE_COLUMN_TYPES.select:
                            return (

                            );
                          case TABLE_COLUMN_TYPES.multicolumn:
                            return (
                              <TableCellCustom
                                align="left"
                                className={classes.tableCellCustom}
                                style={
                                  indicatorsEditor
                                    ? {
                                        padding: "3px 4px"
                                      }
                                    : {}
                                }
                              >
                                {column.columns.map(subColumn => (
                                  <React.Fragment>
                                    {subColumn.label} {row[subColumn.id]}
                                    {subColumn.type === "percentage"
                                      ? "%"
                                      : null}
                                    {"   "}
                                  </React.Fragment>
                                ))}
                              </TableCellCustom>
                            );
                          case TABLE_COLUMN_TYPES.hoverActions:
                            return (

                            );
                          default:
                            return null;
                        }
                      })}
                      {indicatorsEditor ? (
                        <IndicatorsEditor
                          row={row}
                          index={index}
                          impacts={impacts}
                          selected={isItemSelected}
                          onImpactEdit={this.handleClick}
                        />
                      ) : null}
                    </StyledTableRow>
                  );
                })}
                {tableData.length < 1 ? (

                ) : null}
              </TableBody>

        </Paper>

    );
  }
}

TableCustom.propTypes = {
  classes: PropTypes.object.isRequired
};

export default withStyles(styles, { withTheme: true })(TableCustom);
<|endoftext|>


<commit_before>define([
    'angular'
], function(angular) {
    'use strict';
    angular.module('angularProjectApp', [
        'ngRoute',
        'oc.lazyLoad'
    ]).config(function($routeProvider, $ocLazyLoadProvider) {

        $ocLazyLoadProvider.config({
            asyncLoader: require
        });

        $routeProvider
            .when('/home', {
                templateUrl: 'modules/home/home.html',
                controller: 'HomeController',
                resolve: {
                    load: ['$ocLazyLoad',
                        function($ocLazyLoad) {
                            return $ocLazyLoad.load({
                                name: 'home',
                                files: ['modules/home/module.js']
                            });
                        }
                    ]
                }
            })
            .when('/about', {
                templateUrl: 'modules/about/about.html',
                controller: 'AboutController',
                resolve: {
                    load: ['$ocLazyLoad',
                        function($ocLazyLoad) {
                            return $ocLazyLoad.load({
                                name: 'about',
                                files: ['modules/about/module.js']
                            });
                        }
                    ]
                }
            })
            .when('/contact', {
                templateUrl: 'modules/contact/contact.html',
                controller: 'ContactController',
                resolve: {
                    load: ['$ocLazyLoad',
                        function($ocLazyLoad) {
                            return $ocLazyLoad.load({
                                name: 'contact',
                                files: ['modules/contact/module.js']
                            });
                        }
                    ]
                }
            })
            .otherwise({
                redirectTo: '/home'
            });
    });
});<commit_msg>Remove unused files<commit_after><|endoftext|>


<commit_before>var Parser = require('../lib/Parser');
var Actions = require('../lib/Actions');
var Transformations = require('../lib/Transformations');
var PhantomEnvironment = require('../lib/PhantomEnvironment');
var chai = require('chai');
var expect = chai.expect;
var path = require('path');

var env;
var uri = 'file://' + path.join(__dirname, '/parser.html');

before(function () {
    env = new PhantomEnvironment({
        url: uri,
        screen: {
            width: 1080,
            height: 200
        }
    });
});

describe('Parser', function () {
    describe('#parse', function () {
        it('parse single page', function () {
            var parser = new Parser({
                environment: env
            });
            return parser.parse(
                {
                    rules: {
                        scope: '.scrollable > .content > div.scope-test-6-passed',
                        collection: [[
                            {name: 'column1', scope: 'div.scope-test-6-passed-column1'},
                            {
                                name: 'sub-column',
                                scope: 'div:last-child',
                                collection: [
                                    {name: 'column2', scope: 'div.scope-test-6-passed-column2'},
                                    {name: 'column3', scope: 'div.scope-test-6-passed-column3'},
                                    {name: 'column4', scope: 'div.scope-test-6-passed-column4'}
                                ]
                            }
                        ]]
                    }
                }
            ).then(function (found) {
                    expect(found).to.be.instanceOf(Array);
                    expect(found.length).equal(1);

                    var item = found[0];
                    expect(item.column1).equal('column1');
                    for (var i = 2; i <= 4; i++) {
                        var val = 'column' + i;
                        expect(item['sub-column'][val]).equal(val);
                    }
                });
        });

        it('parse page with scroll pagination', function () {
            var parser = new Parser({
                environment: env,
                pagination: {
                    type: 'scroll',
                    interval: 500
                }
            });
            return parser.parse(
                {
                    rules: {
                        scope: '.scrollable > .content > div.scope-test-6-passed',
                        collection: [[
                            {name: 'column1', scope: 'div.scope-test-6-passed-column1'},
                            {
                                name: 'sub-column',
                                scope: 'div:last-child',
                                extract: true,
                                collection: [
                                    {name: 'column2', scope: 'div.scope-test-6-passed-column2'},
                                    {name: 'column3', scope: 'div.scope-test-6-passed-column3'},
                                    {name: 'column4', scope: 'div.scope-test-6-passed-column4'}
                                ]
                            }
                        ]]
                    }
                }
            ).then(function (found) {
                    expect(found).to.be.instanceOf(Array);
                    expect(found.length).equal(11);

                    found.forEach(function (item, i) {
                        expect(item.column1, 'row' + i).equal('column1');
                        for (var i = 2; i <= 4; i++) {
                            var val = 'column' + i;
                            expect(item['sub-column'][val], 'row' + i).equal(val);
                        }
                    }, this);
                });
        });
    });
});

describe('Actions', function () {
    describe('#performForRule', function () {
        it('perform actions from parsing rules', function () {
            return env
                .prepare()
                .then(function () {
                    var actions = new Actions({
                        environment: env
                    });
                    return actions.performForRule({
                            actions: [
                                {
                                    type: 'wait',
                                    scope: 'div.scope-test'
                                },
                                {
                                    type: 'click',
                                    scope: 'div.scope-test'
                                },
                                {
                                    type: 'wait',
                                    scope: 'div.scope-test.clicked'
                                }
                            ]
                        },
                        'body'
                    );
                });
        });
    });
});<commit_msg>added Transformations tests<commit_after>var Parser = require('../lib/Parser');
var Actions = require('../lib/Actions');
var Transformations = require('../lib/Transformations');
var PhantomEnvironment = require('../lib/PhantomEnvironment');
var chai = require('chai');
var expect = chai.expect;
var path = require('path');

var env;
var uri = 'file://' + path.join(__dirname, '/parser.html');

before(function () {
    env = new PhantomEnvironment({
        url: uri,
        screen: {
            width: 1080,
            height: 200
        }
    });
});

describe('Parser', function () {
    describe('#parse', function () {
        it('parse single page', function () {
            var parser = new Parser({
                environment: env
            });
            return parser.parse(
                {
                    rules: {
                        scope: '.scrollable > .content > div.scope-test-6-passed',
                        collection: [[
                            {name: 'column1', scope: 'div.scope-test-6-passed-column1'},
                            {
                                name: 'sub-column',
                                scope: 'div:last-child',
                                collection: [
                                    {name: 'column2', scope: 'div.scope-test-6-passed-column2'},
                                    {name: 'column3', scope: 'div.scope-test-6-passed-column3'},
                                    {name: 'column4', scope: 'div.scope-test-6-passed-column4'}
                                ]
                            }
                        ]]
                    }
                }
            ).then(function (found) {
                    expect(found).to.be.instanceOf(Array);
                    expect(found.length).equal(1);

                    var item = found[0];
                    expect(item.column1).equal('column1');
                    for (var i = 2; i <= 4; i++) {
                        var val = 'column' + i;
                        expect(item['sub-column'][val]).equal(val);
                    }
                });
        });

        it('parse page with scroll pagination', function () {
            var parser = new Parser({
                environment: env,
                pagination: {
                    type: 'scroll',
                    interval: 500
                }
            });
            return parser.parse(
                {
                    rules: {
                        scope: '.scrollable > .content > div.scope-test-6-passed',
                        collection: [[
                            {name: 'column1', scope: 'div.scope-test-6-passed-column1'},
                            {
                                name: 'sub-column',
                                scope: 'div:last-child',
                                extract: true,
                                collection: [
                                    {name: 'column2', scope: 'div.scope-test-6-passed-column2'},
                                    {name: 'column3', scope: 'div.scope-test-6-passed-column3'},
                                    {name: 'column4', scope: 'div.scope-test-6-passed-column4'}
                                ]
                            }
                        ]]
                    }
                }
            ).then(function (found) {
                    expect(found).to.be.instanceOf(Array);
                    expect(found.length).equal(11);

                    found.forEach(function (item, i) {
                        expect(item.column1, 'row' + i).equal('column1');
                        for (var i = 2; i <= 4; i++) {
                            var val = 'column' + i;
                            expect(item['sub-column'][val], 'row' + i).equal(val);
                        }
                    }, this);
                });
        });
    });
});

describe('Actions', function () {
    describe('#performForRule', function () {
        it('perform actions from parsing rules', function () {
            return env
                .prepare()
                .then(function () {
                    var actions = new Actions({
                        environment: env
                    });
                    return actions.performForRule({
                            actions: [
                                {
                                    type: 'wait',
                                    scope: 'div.scope-test'
                                },
                                {
                                    type: 'click',
                                    scope: 'div.scope-test'
                                },
                                {
                                    type: 'wait',
                                    scope: 'div.scope-test.clicked'
                                }
                            ]
                        },
                        'body'
                    );
                });
        });
    });
});

describe('Transformations', function () {
    var transformations = new Transformations();
    describe('#produce', function () {
        it('perform date transform', function () {
            var transformedValue = transformations.produce([
                    {
                        type: 'date',
                        locale: 'en',
                        from: 'HH:mm D MMM YYYY',
                        to: 'YYYY-MM-DD'
                    }
                ],
                '21:10 30 Aug 2016'
            );
            expect(transformedValue).equal('2016-08-30');
        });

        it('perform replace transform', function () {
            var transformedValue = transformations.produce([
                    {
                        type: 'replace',
                        re: ['\\s', 'g'],
                        to: ''
                    }
                ],
                ' t e  s  t'
            );
            expect(transformedValue).equal('test');
        });
    });
});<|endoftext|>


<commit_before>import FilterControls from './FilterControls';
import FilterContainer from './FilterContainer';
import Positions from './Positions';
import {
  debounce,
  intersection,
  isEqual,
  reverse,
  shuffle,
  sortBy,
} from './vendor/lodash.custom';
import {
  merge,
  stringInArray
} from './utils';

class Filterizr {
  constructor(selector = ".filtr-container", options) {
    // make the options a property of the Filterizr instance
    // so that we can later easily modify them
    this.options = options;

    // setup FilterControls and FilterContainer
    const filterControls  = new FilterControls(this);
    const filterContainer = new FilterContainer(selector, options);
    // define props
    this.props = {
      searchTerm: '',
      sort: 'index',
      sortOrder: 'asc',
      FilterContainer: filterContainer,
      FilterItems: filterContainer.props.FilterItems,
      FilteredItems: [],
    }

    // set up events needed by Filterizr
    this.setupEvents();
    // Init Filterizr
    this.filter(this.options.filter);
  }

  // Public API of Filterizr
  filter(category) {
    const {
      searchTerm,
      FilterContainer,
      FilterItems,
    } = this.props;

    // trigger filtering start event
    FilterContainer.trigger('filteringStart');

    // cast category to string or array of strings
    category = Array.isArray(category) ?
      category.map(c => c.toString()) :
      category.toString();

    // filter items and optionally apply search if a search term exists
    const FilteredItems = this.searchFilterItems(this.filterFilterItems(FilterItems, category), searchTerm);
    this.props.FilteredItems = FilteredItems;

    // set up events needed by Filterizr
    // render the items
    this.render(FilteredItems);
  }

  sort(sortAttr = 'index', sortOrder = 'asc') {
    const {
      FilterItems,
    } = this.props;

    // sort main array
    this.props.FilterItems = this.sortFilterItems(FilterItems, sortAttr, sortOrder);
    // apply filters
    const FilteredItems = this.filterFilterItems(this.props.FilterItems, this.options.filter);
    this.props.FilteredItems = FilteredItems;

    // Update and render the sorted items
    this.render(FilteredItems);
  }

  search(searchTerm = this.props.searchTerm) {
    const {
      FilterItems
    } = this.props;

    // filter items and optionally apply search if a search term exists
    const FilteredItems = this.searchFilterItems(this.filterFilterItems(FilterItems, this.options.filter), searchTerm);
    this.props.FilteredItems = FilteredItems;

    // render the items
    this.render(FilteredItems);
  }

  shuffle() {
    const {
      FilteredItems
    } = this.props;

    const ShuffledItems = this.shuffleFilterItems(FilteredItems);
    this.props.FilteredItems = ShuffledItems;

    this.render(ShuffledItems);
  }

  setOptions(newOptions) {
    this.options = merge(newOptions, this.options);
  }

  toggleFilter(toggledFilter) {
    let activeFilters = this.options.filter;

    if (activeFilters === 'all') {
      // if set to all then just set to new category
      activeFilters = toggledFilter;
    }
    else {
      if (Array.isArray(activeFilters)) {
        // if the toggledFilter is already included in the array
        if (stringInArray(activeFilters, toggledFilter)) {
          // then remove it
          activeFilters = activeFilters.filter(f => f !== toggledFilter);
          // and check if there is now only 1 item left in the array
          if (activeFilters.length === 1)
            // in that case flatten it
            activeFilters = activeFilters[0];
        }
        else {
          // if the item is not in the array then simply push it
          activeFilters.push(toggledFilter);
        }
      }
      else {
        // in case the filter is NOT set to "all"
        // check if the toggledFilter is the active one
        if (activeFilters === toggledFilter)
          // in this case revert to all
          activeFilters = "all";
        else
          // otherwise form an array with the two values
          activeFilters = [activeFilters, toggledFilter]
      }
    }

    // update active filter in Filterizr's options
    this.setOptions({
      filter: activeFilters,
    });

    console.log(this.options.filter);

    this.filter(this.options.filter);
  }

  // Helper methods
  filterFilterItems(FilterItems, filters) {
    // Get filtered items
    const FilteredItems = (filters === "all") ?
      // in this case return all items
      FilterItems :
      // otherwise return filtered array
      FilterItems.filter(FilterItem => {
        const categories = FilterItem.getCategories();
        return Array.isArray(filters) ?
          intersection(categories, filters).length :
          stringInArray(categories, filters)
      });

    return FilteredItems;
  }

  sortFilterItems(FilterItems ,sortAttr = 'index', sortOrder = 'asc') {
    // Sort the FilterItems and reverse the array if order is descending
    let SortedItems = sortBy(FilterItems, (FilterItem) => {
      return (sortAttr !== 'index' && sortAttr !== 'sortData') ?
        // if the user has not used one of the two default sort attributes
        // then search for custom data attributes on the filter items to sort
        FilterItem.props.data[sortAttr] :
        // otherwise use the defaults
        FilterItem.props[sortAttr];
    });
    SortedItems = sortOrder === 'asc' ? SortedItems : reverse(SortedItems);

    return SortedItems;
  }

  searchFilterItems(FilterItems, searchTerm = this.props.searchTerm) {
    if (!searchTerm) return FilterItems; // exit case when no search is applied

    const SoughtItems =  FilterItems.filter(FilterItem => {
      const contents = FilterItem.getContentsLowercase();
      return ~contents.lastIndexOf(searchTerm);
    });

    return SoughtItems;
  }

  shuffleFilterItems(FilterItems) {
    let ShuffledItems = shuffle(FilterItems)
    // shuffle items until they are different from the initial FilteredItems
    while (FilterItems.length > 1 && isEqual(FilterItems, ShuffledItems)) {
      ShuffledItems = shuffle(FilterItems)
    }

    return ShuffledItems;
  }

  render(FilterItems) {
    // get items to be filtered out
    const FilteredOutItems = this.props.FilterItems.filter(FilterItem => {
      const categories = FilterItem.getCategories();
      const filters = this.options.filter;
      return Array.isArray(filters) ?
        !intersection(categories, filters).length :
        !stringInArray(categories, filters)
    })
    // filter out old items
    FilteredOutItems.forEach(FilterItem => {
      FilterItem.filterOut(this.options.filterOutCss);
    });

    // Determine target positions for items to be filtered in
    const PositionsArray = Positions(this.options.layout, this);

    // filter in new items
    FilterItems.forEach((FilterItem, index) => {
      FilterItem.filterIn(PositionsArray[index], this.options.filterInCss);
    });
  }

  setupEvents() {
    // set up a window resize event to fire refiltering
    $(window).on('resize', debounce((evt) => {
      // update dimensions of items based on new window size
      this.props.FilterContainer.updateWidth();
      this.props.FilterContainer.updateFilterItemsDimensions();
      // refilter the grid to assume new positions
      this.filter(this.options.filter);
    }, 250));
  }
}

export default Filterizr;
<commit_msg>remove log<commit_after>import FilterControls from './FilterControls';
import FilterContainer from './FilterContainer';
import Positions from './Positions';
import {
  debounce,
  intersection,
  isEqual,
  reverse,
  shuffle,
  sortBy,
} from './vendor/lodash.custom';
import {
  merge,
  stringInArray
} from './utils';

class Filterizr {
  constructor(selector = ".filtr-container", options) {
    // make the options a property of the Filterizr instance
    // so that we can later easily modify them
    this.options = options;

    // setup FilterControls and FilterContainer
    const filterControls  = new FilterControls(this);
    const filterContainer = new FilterContainer(selector, options);
    // define props
    this.props = {
      searchTerm: '',
      sort: 'index',
      sortOrder: 'asc',
      FilterContainer: filterContainer,
      FilterItems: filterContainer.props.FilterItems,
      FilteredItems: [],
    }

    // set up events needed by Filterizr
    this.setupEvents();
    // Init Filterizr
    this.filter(this.options.filter);
  }

  // Public API of Filterizr
  filter(category) {
    const {
      searchTerm,
      FilterContainer,
      FilterItems,
    } = this.props;

    // trigger filtering start event
    FilterContainer.trigger('filteringStart');

    // cast category to string or array of strings
    category = Array.isArray(category) ?
      category.map(c => c.toString()) :
      category.toString();

    // filter items and optionally apply search if a search term exists
    const FilteredItems = this.searchFilterItems(this.filterFilterItems(FilterItems, category), searchTerm);
    this.props.FilteredItems = FilteredItems;

    // set up events needed by Filterizr
    // render the items
    this.render(FilteredItems);
  }

  sort(sortAttr = 'index', sortOrder = 'asc') {
    const {
      FilterItems,
    } = this.props;

    // sort main array
    this.props.FilterItems = this.sortFilterItems(FilterItems, sortAttr, sortOrder);
    // apply filters
    const FilteredItems = this.filterFilterItems(this.props.FilterItems, this.options.filter);
    this.props.FilteredItems = FilteredItems;

    // Update and render the sorted items
    this.render(FilteredItems);
  }

  search(searchTerm = this.props.searchTerm) {
    const {
      FilterItems
    } = this.props;

    // filter items and optionally apply search if a search term exists
    const FilteredItems = this.searchFilterItems(this.filterFilterItems(FilterItems, this.options.filter), searchTerm);
    this.props.FilteredItems = FilteredItems;

    // render the items
    this.render(FilteredItems);
  }

  shuffle() {
    const {
      FilteredItems
    } = this.props;

    const ShuffledItems = this.shuffleFilterItems(FilteredItems);
    this.props.FilteredItems = ShuffledItems;

    this.render(ShuffledItems);
  }

  setOptions(newOptions) {
    this.options = merge(newOptions, this.options);
  }

  toggleFilter(toggledFilter) {
    let activeFilters = this.options.filter;

    if (activeFilters === 'all') {
      // if set to all then just set to new category
      activeFilters = toggledFilter;
    }
    else {
      if (Array.isArray(activeFilters)) {
        // if the toggledFilter is already included in the array
        if (stringInArray(activeFilters, toggledFilter)) {
          // then remove it
          activeFilters = activeFilters.filter(f => f !== toggledFilter);
          // and check if there is now only 1 item left in the array
          if (activeFilters.length === 1)
            // in that case flatten it
            activeFilters = activeFilters[0];
        }
        else {
          // if the item is not in the array then simply push it
          activeFilters.push(toggledFilter);
        }
      }
      else {
        // in case the filter is NOT set to "all"
        // check if the toggledFilter is the active one
        if (activeFilters === toggledFilter)
          // in this case revert to all
          activeFilters = "all";
        else
          // otherwise form an array with the two values
          activeFilters = [activeFilters, toggledFilter]
      }
    }

    // update active filter in Filterizr's options
    this.setOptions({
      filter: activeFilters,
    });

    this.filter(this.options.filter);
  }

  // Helper methods
  filterFilterItems(FilterItems, filters) {
    // Get filtered items
    const FilteredItems = (filters === "all") ?
      // in this case return all items
      FilterItems :
      // otherwise return filtered array
      FilterItems.filter(FilterItem => {
        const categories = FilterItem.getCategories();
        return Array.isArray(filters) ?
          intersection(categories, filters).length :
          stringInArray(categories, filters)
      });

    return FilteredItems;
  }

  sortFilterItems(FilterItems ,sortAttr = 'index', sortOrder = 'asc') {
    // Sort the FilterItems and reverse the array if order is descending
    let SortedItems = sortBy(FilterItems, (FilterItem) => {
      return (sortAttr !== 'index' && sortAttr !== 'sortData') ?
        // if the user has not used one of the two default sort attributes
        // then search for custom data attributes on the filter items to sort
        FilterItem.props.data[sortAttr] :
        // otherwise use the defaults
        FilterItem.props[sortAttr];
    });
    SortedItems = sortOrder === 'asc' ? SortedItems : reverse(SortedItems);

    return SortedItems;
  }

  searchFilterItems(FilterItems, searchTerm = this.props.searchTerm) {
    if (!searchTerm) return FilterItems; // exit case when no search is applied

    const SoughtItems =  FilterItems.filter(FilterItem => {
      const contents = FilterItem.getContentsLowercase();
      return ~contents.lastIndexOf(searchTerm);
    });

    return SoughtItems;
  }

  shuffleFilterItems(FilterItems) {
    let ShuffledItems = shuffle(FilterItems)
    // shuffle items until they are different from the initial FilteredItems
    while (FilterItems.length > 1 && isEqual(FilterItems, ShuffledItems)) {
      ShuffledItems = shuffle(FilterItems)
    }

    return ShuffledItems;
  }

  render(FilterItems) {
    // get items to be filtered out
    const FilteredOutItems = this.props.FilterItems.filter(FilterItem => {
      const categories = FilterItem.getCategories();
      const filters = this.options.filter;
      return Array.isArray(filters) ?
        !intersection(categories, filters).length :
        !stringInArray(categories, filters)
    })
    // filter out old items
    FilteredOutItems.forEach(FilterItem => {
      FilterItem.filterOut(this.options.filterOutCss);
    });

    // Determine target positions for items to be filtered in
    const PositionsArray = Positions(this.options.layout, this);

    // filter in new items
    FilterItems.forEach((FilterItem, index) => {
      FilterItem.filterIn(PositionsArray[index], this.options.filterInCss);
    });
  }

  setupEvents() {
    // set up a window resize event to fire refiltering
    $(window).on('resize', debounce((evt) => {
      // update dimensions of items based on new window size
      this.props.FilterContainer.updateWidth();
      this.props.FilterContainer.updateFilterItemsDimensions();
      // refilter the grid to assume new positions
      this.filter(this.options.filter);
    }, 250));
  }
}

export default Filterizr;
<|endoftext|>


<commit_before>import Ember from 'ember';
import {
  AjaxError,
  UnauthorizedError,
  InvalidError,
  ForbiddenError,
  BadRequestError,
  NotFoundError,
  ServerError,
  isUnauthorizedError,
  isForbiddenError,
  isInvalidError,
  isBadRequestError,
  isNotFoundError,
  isServerError,
  isSuccess
} from './errors';
import parseResponseHeaders from './utils/parse-response-headers';
import { RequestURL } from './utils/url-helpers';

const {
  $,
  RSVP: { Promise },
  get,
  isPresent,
  run
} = Ember;

export default class AjaxRequest {

  request(url, options) {
    const hash = this.options(url, options);
    return new Promise((resolve, reject) => {
      this.raw(url, hash)
        .then(({ response }) => {
          resolve(response);
        })
        .catch(({ response }) => {
          reject(response);
        });
    }, `ember-ajax: ${hash.type} ${hash.url} response`);
  }

  raw(url, options) {
    const hash = this.options(url, options);
    const requestData = {
      type: hash.type,
      url: hash.url
    };
    return new Promise((resolve, reject) => {
      hash.success = (payload, textStatus, jqXHR) => {
        let response = this.handleResponse(
          jqXHR.status,
          parseResponseHeaders(jqXHR.getAllResponseHeaders()),
          payload,
          requestData
        );

        if (hash.dataType === 'jsonp') {
          $('body').trigger('ajaxComplete');
        }

        if (response instanceof AjaxError) {
          run.join(null, reject, { payload, textStatus, jqXHR, response });
        } else {
          run.join(null, resolve, { payload, textStatus, jqXHR, response });
        }
      };

      hash.error = (jqXHR, textStatus, errorThrown) => {
        const payload = this.parseErrorResponse(jqXHR.responseText) || errorThrown;
        const response = this.handleResponse(
           jqXHR.status,
           parseResponseHeaders(jqXHR.getAllResponseHeaders()),
           payload,
           requestData
        );

        if (hash.dataType === 'jsonp') {
          $('body').trigger('ajaxComplete');
        }

        run.join(null, reject, { payload, textStatus, jqXHR, errorThrown, response });
      };

      if (hash.dataType === 'jsonp') {
        $('body').trigger('ajaxSend');
      }

      $.ajax(hash);
    }, `ember-ajax: ${hash.type} ${hash.url}`);
  }

  /**
   * calls `request()` but forces `options.type` to `POST`
   * @public
   */
  post(url, options) {
    return this.request(url, this._addTypeToOptionsFor(options, 'POST'));
  }

  /**
   * calls `request()` but forces `options.type` to `PUT`
   * @public
   */
  put(url, options) {
    return this.request(url, this._addTypeToOptionsFor(options, 'PUT'));
  }

  /**
   * calls `request()` but forces `options.type` to `PATCH`
   * @public
   */
  patch(url, options) {
    return this.request(url, this._addTypeToOptionsFor(options, 'PATCH'));
  }

  /**
   * calls `request()` but forces `options.type` to `DELETE`
   * @public
   */
  del(url, options) {
    return this.request(url, this._addTypeToOptionsFor(options, 'DELETE'));
  }

  // forcibly manipulates the options hash to include the HTTP method on the type key
  _addTypeToOptionsFor(options, method) {
    options = options || {};
    options.type = method;
    return options;
  }

  /**
   * @method options
   * @private
   * @param {String} url
   * @param {Object} options
   * @return {Object}
   */
  options(url, options = {}) {
    options.url = this._buildURL(url, options);
    options.type = options.type || 'GET';
    options.dataType = options.dataType || 'json';
    options.context = this;

    if (this._shouldSendHeaders(options)) {
      const headers = get(this, 'headers');
      if (isPresent(headers)) {
        options.beforeSend = function(xhr) {
          Object.keys(headers).forEach((key) =>  xhr.setRequestHeader(key, headers[key]));
        };
      }
    }

    return options;
  }

  _buildURL(url, options) {
    const host = options.host || get(this, 'host');
    const namespace = get(this, 'namespace');
    const urlObject = new RequestURL(url);

    // If the URL passed is not relative, return the whole URL
    if (urlObject.isAbsolute) {
      return urlObject.href;
    }

    let _url = this._normalizePath(url);
    let _namespace = this._normalizePath(namespace);

    return [ host, _namespace, _url ].join('');
  }

  _normalizePath(path) {
    if (path) {
      // make sure path starts with `/`
      if (path.charAt(0) !== '/') {
        path = `/${path}`;
      }

      // remove end `/`
      if (path.charAt(path.length - 1) === '/') {
        path = path.slice(0, -1);
      }
    }
    return path;
  }

  /**
   * Takes an ajax response, and returns the json payload or an error.
   *
   * By default this hook just returns the json payload passed to it.
   * You might want to override it in two cases:
   *
   * 1. Your API might return useful results in the response headers.
   *    Response headers are passed in as the second argument.
   *
   * 2. Your API might return errors as successful responses with status code
   *    200 and an Errors text or object.
   *
   * @method handleResponse
   * @private
   * @param  {Number} status
   * @param  {Object} headers
   * @param  {Object} payload
   * @param  {Object} requestData the original request information
   * @return {Object | AjaxError} response
   */
  handleResponse(status, headers, payload, requestData) {
    payload = payload || {};
    const errors = this.normalizeErrorResponse(status, headers, payload);

    if (this.isSuccess(status, headers, payload)) {
      return payload;
    } else if (this.isUnauthorizedError(status, headers, payload)) {
      return new UnauthorizedError(errors);
    } else if (this.isForbiddenError(status, headers, payload)) {
      return new ForbiddenError(errors);
    } else if (this.isInvalidError(status, headers, payload)) {
      return new InvalidError(errors);
    } else if (this.isBadRequestError(status, headers, payload)) {
      return new BadRequestError(errors);
    } else if (this.isNotFoundError(status, headers, payload)) {
      return new NotFoundError(errors);
    } else if (this.isServerError(status, headers, payload)) {
      return new ServerError(errors);
    }

    const detailedMessage = this.generateDetailedMessage(status, headers, payload, requestData);
    return new AjaxError(errors, detailedMessage);
  }

  /**
   * Match the host to a provided array of strings or regexes that can match to a host
   *
   * @method matchHosts
   * @public
   * @param {String} host the host you are sending too
   * @param {RegExp | String} matcher a string or regex that you can match the host to.
   * @returns {Boolean} if the host passed the matcher
   */

  matchHosts(host, matcher) {
    if (matcher.constructor === RegExp) {
      return matcher.test(host);
    } else if (typeof matcher === 'string') {
      return matcher === host;
    } else {
      Ember.Logger.warn('allowedHosts only handles strings or regexes.', matcher, 'is neither.');
      return false;
    }
  }

  /**
   * Determine whether the headers should be added for this request
   *
   * This hook is used to help prevent sending headers to every host, regardless
   * of the destination, since this could be a security issue if authentication
   * tokens are accidentally leaked to third parties.
   *
   * To avoid that problem, subclasses should utilize the `headers` computed
   * property to prevent authentication from being sent to third parties, or
   * implement this hook for more fine-grain control over when headers are sent.
   *
   * By default, the headers are sent if the host of the request matches the
   * `host` property designated on the class.
   *
   * @method _shouldSendHeaders
   * @private
   * @property {Object} hash request options hash
   * @returns {Boolean} whether or not headers should be sent
   */
  _shouldSendHeaders({ url, host }) {
    url = url || '';
    host = host || get(this, 'host') || '';

    const urlObject = new RequestURL(url);
    const allowedHosts = get(this, 'allowedHosts') || Ember.A();
    // Add headers on relative URLs

    if (!urlObject.isAbsolute) {
      return true;
    } else if (allowedHosts.find((matcher) => this.matchHosts(urlObject.hostname, matcher))) {
      return true;
    }

    // Add headers on matching host
    const hostObject = new RequestURL(host);
    return urlObject.sameHost(hostObject);
  }

  /**
   * Generates a detailed ("friendly") error message, with plenty
   * of information for debugging (good luck!)
   * @method generateDetailedMessage
   * @private
   * @param  {Number} status
   * @param  {Object} headers
   * @param  {Object} payload
   * @param  {Object} requestData the original request information
   * @return {Object} request information
   */
  generateDetailedMessage(status, headers, payload, requestData) {
    let shortenedPayload;
    const payloadContentType = headers['Content-Type'] || 'Empty Content-Type';

    if (payloadContentType === 'text/html' && payload.length > 250) {
      shortenedPayload = '[Omitted Lengthy HTML]';
    } else {
      shortenedPayload = JSON.stringify(payload);
    }

    const requestDescription = `${requestData.type} ${requestData.url}`;
    const payloadDescription = `Payload (${payloadContentType})`;

    return [
      `Ember Data Request ${requestDescription} returned a ${status}`,
      payloadDescription,
      shortenedPayload
    ].join('\n');
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a an authorized error.
   * @method isUnauthorizedError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isUnauthorizedError(status) {
    return isUnauthorizedError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a forbidden error.
   * @method isForbiddenError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isForbiddenError(status) {
    return isForbiddenError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a an invalid error.
   * @method isInvalidError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isInvalidError(status) {
    return isInvalidError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a bad request error.
   * @method isBadRequestError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isBadRequestError(status) {
    return isBadRequestError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a "not found" error.
   * @method isNotFoundError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isNotFoundError(status) {
    return isNotFoundError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a server error.
   * @method isServerError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isServerError(status) {
    return isServerError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a success.
   * @method isSuccess
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isSuccess(status) {
    return isSuccess(status);
  }

  /**
   * @method parseErrorResponse
   * @private
   * @param {String} responseText
   * @return {Object}
   */
  parseErrorResponse(responseText) {
    let json = responseText;

    try {
      json = Ember.$.parseJSON(responseText);
    } catch (e) {}

    return json;
  }

  /**
   * @method normalizeErrorResponse
   * @private
   * @param  {Number} status
   * @param  {Object} headers
   * @param  {Object} payload
   * @return {Array} errors payload
   */
  normalizeErrorResponse(status, headers, payload) {
    if (payload && typeof payload === 'object' && payload.errors) {
      return payload.errors;
    } else {
      return [
        {
          status: `${status}`,
          title: 'The backend responded with an error',
          detail: payload
        }
      ];
    }
  }
}
<commit_msg>Changed matchHosts to be referenced as a private method<commit_after>import Ember from 'ember';
import {
  AjaxError,
  UnauthorizedError,
  InvalidError,
  ForbiddenError,
  BadRequestError,
  NotFoundError,
  ServerError,
  isUnauthorizedError,
  isForbiddenError,
  isInvalidError,
  isBadRequestError,
  isNotFoundError,
  isServerError,
  isSuccess
} from './errors';
import parseResponseHeaders from './utils/parse-response-headers';
import { RequestURL } from './utils/url-helpers';

const {
  $,
  RSVP: { Promise },
  get,
  isPresent,
  run
} = Ember;

export default class AjaxRequest {

  request(url, options) {
    const hash = this.options(url, options);
    return new Promise((resolve, reject) => {
      this.raw(url, hash)
        .then(({ response }) => {
          resolve(response);
        })
        .catch(({ response }) => {
          reject(response);
        });
    }, `ember-ajax: ${hash.type} ${hash.url} response`);
  }

  raw(url, options) {
    const hash = this.options(url, options);
    const requestData = {
      type: hash.type,
      url: hash.url
    };
    return new Promise((resolve, reject) => {
      hash.success = (payload, textStatus, jqXHR) => {
        let response = this.handleResponse(
          jqXHR.status,
          parseResponseHeaders(jqXHR.getAllResponseHeaders()),
          payload,
          requestData
        );

        if (hash.dataType === 'jsonp') {
          $('body').trigger('ajaxComplete');
        }

        if (response instanceof AjaxError) {
          run.join(null, reject, { payload, textStatus, jqXHR, response });
        } else {
          run.join(null, resolve, { payload, textStatus, jqXHR, response });
        }
      };

      hash.error = (jqXHR, textStatus, errorThrown) => {
        const payload = this.parseErrorResponse(jqXHR.responseText) || errorThrown;
        const response = this.handleResponse(
           jqXHR.status,
           parseResponseHeaders(jqXHR.getAllResponseHeaders()),
           payload,
           requestData
        );

        if (hash.dataType === 'jsonp') {
          $('body').trigger('ajaxComplete');
        }

        run.join(null, reject, { payload, textStatus, jqXHR, errorThrown, response });
      };

      if (hash.dataType === 'jsonp') {
        $('body').trigger('ajaxSend');
      }

      $.ajax(hash);
    }, `ember-ajax: ${hash.type} ${hash.url}`);
  }

  /**
   * calls `request()` but forces `options.type` to `POST`
   * @public
   */
  post(url, options) {
    return this.request(url, this._addTypeToOptionsFor(options, 'POST'));
  }

  /**
   * calls `request()` but forces `options.type` to `PUT`
   * @public
   */
  put(url, options) {
    return this.request(url, this._addTypeToOptionsFor(options, 'PUT'));
  }

  /**
   * calls `request()` but forces `options.type` to `PATCH`
   * @public
   */
  patch(url, options) {
    return this.request(url, this._addTypeToOptionsFor(options, 'PATCH'));
  }

  /**
   * calls `request()` but forces `options.type` to `DELETE`
   * @public
   */
  del(url, options) {
    return this.request(url, this._addTypeToOptionsFor(options, 'DELETE'));
  }

  // forcibly manipulates the options hash to include the HTTP method on the type key
  _addTypeToOptionsFor(options, method) {
    options = options || {};
    options.type = method;
    return options;
  }

  /**
   * @method options
   * @private
   * @param {String} url
   * @param {Object} options
   * @return {Object}
   */
  options(url, options = {}) {
    options.url = this._buildURL(url, options);
    options.type = options.type || 'GET';
    options.dataType = options.dataType || 'json';
    options.context = this;

    if (this._shouldSendHeaders(options)) {
      const headers = get(this, 'headers');
      if (isPresent(headers)) {
        options.beforeSend = function(xhr) {
          Object.keys(headers).forEach((key) =>  xhr.setRequestHeader(key, headers[key]));
        };
      }
    }

    return options;
  }

  _buildURL(url, options) {
    const host = options.host || get(this, 'host');
    const namespace = get(this, 'namespace');
    const urlObject = new RequestURL(url);

    // If the URL passed is not relative, return the whole URL
    if (urlObject.isAbsolute) {
      return urlObject.href;
    }

    let _url = this._normalizePath(url);
    let _namespace = this._normalizePath(namespace);

    return [ host, _namespace, _url ].join('');
  }

  _normalizePath(path) {
    if (path) {
      // make sure path starts with `/`
      if (path.charAt(0) !== '/') {
        path = `/${path}`;
      }

      // remove end `/`
      if (path.charAt(path.length - 1) === '/') {
        path = path.slice(0, -1);
      }
    }
    return path;
  }

  /**
   * Takes an ajax response, and returns the json payload or an error.
   *
   * By default this hook just returns the json payload passed to it.
   * You might want to override it in two cases:
   *
   * 1. Your API might return useful results in the response headers.
   *    Response headers are passed in as the second argument.
   *
   * 2. Your API might return errors as successful responses with status code
   *    200 and an Errors text or object.
   *
   * @method handleResponse
   * @private
   * @param  {Number} status
   * @param  {Object} headers
   * @param  {Object} payload
   * @param  {Object} requestData the original request information
   * @return {Object | AjaxError} response
   */
  handleResponse(status, headers, payload, requestData) {
    payload = payload || {};
    const errors = this.normalizeErrorResponse(status, headers, payload);

    if (this.isSuccess(status, headers, payload)) {
      return payload;
    } else if (this.isUnauthorizedError(status, headers, payload)) {
      return new UnauthorizedError(errors);
    } else if (this.isForbiddenError(status, headers, payload)) {
      return new ForbiddenError(errors);
    } else if (this.isInvalidError(status, headers, payload)) {
      return new InvalidError(errors);
    } else if (this.isBadRequestError(status, headers, payload)) {
      return new BadRequestError(errors);
    } else if (this.isNotFoundError(status, headers, payload)) {
      return new NotFoundError(errors);
    } else if (this.isServerError(status, headers, payload)) {
      return new ServerError(errors);
    }

    const detailedMessage = this.generateDetailedMessage(status, headers, payload, requestData);
    return new AjaxError(errors, detailedMessage);
  }

  /**
   * Match the host to a provided array of strings or regexes that can match to a host
   *
   * @method matchHosts
   * @private
   * @param {String} host the host you are sending too
   * @param {RegExp | String} matcher a string or regex that you can match the host to.
   * @returns {Boolean} if the host passed the matcher
   */

  _matchHosts(host, matcher) {
    if (matcher.constructor === RegExp) {
      return matcher.test(host);
    } else if (typeof matcher === 'string') {
      return matcher === host;
    } else {
      Ember.Logger.warn('allowedHosts only handles strings or regexes.', matcher, 'is neither.');
      return false;
    }
  }

  /**
   * Determine whether the headers should be added for this request
   *
   * This hook is used to help prevent sending headers to every host, regardless
   * of the destination, since this could be a security issue if authentication
   * tokens are accidentally leaked to third parties.
   *
   * To avoid that problem, subclasses should utilize the `headers` computed
   * property to prevent authentication from being sent to third parties, or
   * implement this hook for more fine-grain control over when headers are sent.
   *
   * By default, the headers are sent if the host of the request matches the
   * `host` property designated on the class.
   *
   * @method _shouldSendHeaders
   * @private
   * @property {Object} hash request options hash
   * @returns {Boolean} whether or not headers should be sent
   */
  _shouldSendHeaders({ url, host }) {
    url = url || '';
    host = host || get(this, 'host') || '';

    const urlObject = new RequestURL(url);
    const allowedHosts = get(this, 'allowedHosts') || Ember.A();
    // Add headers on relative URLs

    if (!urlObject.isAbsolute) {
      return true;
    } else if (allowedHosts.find((matcher) => this.matchHosts(urlObject.hostname, matcher))) {
      return true;
    }

    // Add headers on matching host
    const hostObject = new RequestURL(host);
    return urlObject.sameHost(hostObject);
  }

  /**
   * Generates a detailed ("friendly") error message, with plenty
   * of information for debugging (good luck!)
   * @method generateDetailedMessage
   * @private
   * @param  {Number} status
   * @param  {Object} headers
   * @param  {Object} payload
   * @param  {Object} requestData the original request information
   * @return {Object} request information
   */
  generateDetailedMessage(status, headers, payload, requestData) {
    let shortenedPayload;
    const payloadContentType = headers['Content-Type'] || 'Empty Content-Type';

    if (payloadContentType === 'text/html' && payload.length > 250) {
      shortenedPayload = '[Omitted Lengthy HTML]';
    } else {
      shortenedPayload = JSON.stringify(payload);
    }

    const requestDescription = `${requestData.type} ${requestData.url}`;
    const payloadDescription = `Payload (${payloadContentType})`;

    return [
      `Ember Data Request ${requestDescription} returned a ${status}`,
      payloadDescription,
      shortenedPayload
    ].join('\n');
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a an authorized error.
   * @method isUnauthorizedError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isUnauthorizedError(status) {
    return isUnauthorizedError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a forbidden error.
   * @method isForbiddenError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isForbiddenError(status) {
    return isForbiddenError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a an invalid error.
   * @method isInvalidError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isInvalidError(status) {
    return isInvalidError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a bad request error.
   * @method isBadRequestError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isBadRequestError(status) {
    return isBadRequestError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a "not found" error.
   * @method isNotFoundError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isNotFoundError(status) {
    return isNotFoundError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a server error.
   * @method isServerError
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isServerError(status) {
    return isServerError(status);
  }

  /**
   * Default `handleResponse` implementation uses this hook to decide if the
   * response is a success.
   * @method isSuccess
   * @private
   * @param {Number} status
   * @param {Object} headers
   * @param {Object} payload
   * @return {Boolean}
   */
  isSuccess(status) {
    return isSuccess(status);
  }

  /**
   * @method parseErrorResponse
   * @private
   * @param {String} responseText
   * @return {Object}
   */
  parseErrorResponse(responseText) {
    let json = responseText;

    try {
      json = Ember.$.parseJSON(responseText);
    } catch (e) {}

    return json;
  }

  /**
   * @method normalizeErrorResponse
   * @private
   * @param  {Number} status
   * @param  {Object} headers
   * @param  {Object} payload
   * @return {Array} errors payload
   */
  normalizeErrorResponse(status, headers, payload) {
    if (payload && typeof payload === 'object' && payload.errors) {
      return payload.errors;
    } else {
      return [
        {
          status: `${status}`,
          title: 'The backend responded with an error',
          detail: payload
        }
      ];
    }
  }
}
<|endoftext|>


<commit_before>/* Headless server, for use with Pageshot:
 * https://github.com/mozilla-services/pageshot
 * relies on the server module from mozilla testing
 * TODO: add link
 *
 * Accepts requests on the assigned port, from allowed IPs, and runs the given
 * function
 * usage to send a URL:
 * TODO update this
 * curl 'localhost:7777/?http://www.asdf.com'
 *
 * The server takes the following args, more info can be found in server.js:
 *    port:        port to listen on, integer
 *    allowedIPs   list of IPs to allow to talk to the server, space or comma separated string
 *    callback     function to be called on the data passed to the
 *    let blah = new Server(port, allowedIPs, callback)
 */

const { Cu } = require("chrome");
const tabs = require("sdk/tabs");

const { randomString } = require("./randomstring");
const { autoShot, RANDOM_STRING_LENGTH, urlDomainForId } = require("./shooter");
const { nsHttpServer } = Cu.import("file:///home/ben/dev/repos/pageshot/addon/lib/httpd.js");

var backend;
var port = 7777;

exports.init = function init(prefs) {
  console.info("starting headless server");
  // set global backend, processTab needs it
  backend = prefs.backend;
  var server = new nsHttpServer();
  server.start(port);
  server.registerPathHandler('/', handleRequest);
  console.info("headless server listening on PORT".replace('PORT', port));
};

function handleRequest(request, response)
{
  response.setStatusLine("1.1", 200, "OK");

  var url = request._queryString;
  var backendUrl = randomString(RANDOM_STRING_LENGTH) + "/" + urlDomainForId(url);
  console.log('recieved request: URL -> BACK'
                .replace('URL', url)
                .replace('BACK', backendUrl)
              );

  response.write(backendUrl);
  processTab(url, backendUrl);
}

function handleRequest(request, response) {
  // Opens a tab and registers extractTab to the load event
  response.processAsync();
  var url = request._queryString;
  var backendUrl = randomString(RANDOM_STRING_LENGTH) + "/" + urlDomainForId(url);
  console.log('recieved request: URL -> BACK'
                .replace('URL', url).replace('BACK', backendUrl));
  tabs.open({
    "url": url,
    "onLoad": function (tab) {
      autoShot(tab, backend, backendUrl);
      tab.on("close", function (a) {
        console.log('completed processing BACK'.replace('BACK', backendUrl));
        response.setStatusLine("1.1", 200, "OK");
        response.write(contentServerString + backendUrl + '\n');
        response.finish();
      });
    }
  });
}
<commit_msg>added readable request handler<commit_after>/* Headless server, for use with Pageshot:
 * https://github.com/mozilla-services/pageshot
 * relies on the server module from mozilla testing
 * TODO: add link
 *
 * Accepts requests on the assigned port, from allowed IPs, and runs the given
 * function
 * usage to send a URL:
 * TODO update this
 * curl 'localhost:7777/?http://www.asdf.com'
 *
 * The server takes the following args, more info can be found in server.js:
 *    port:        port to listen on, integer
 *    allowedIPs   list of IPs to allow to talk to the server, space or comma separated string
 *    callback     function to be called on the data passed to the
 *    let blah = new Server(port, allowedIPs, callback)
 */

const { Cu } = require("chrome");
const tabs = require("sdk/tabs");

const { randomString } = require("./randomstring");
const { autoShot, RANDOM_STRING_LENGTH, urlDomainForId } = require("./shooter");
const { nsHttpServer } = Cu.import("file:///home/ben/dev/repos/pageshot/addon/lib/httpd.js");

var backend;
var port = 7777;

exports.init = function init(prefs) {
  console.info("starting headless server");
  // set global backend, processTab needs it
  backend = prefs.backend;
  var server = new nsHttpServer();
  server.start(port);
  server.registerPathHandler('/', handleRequest);
  server.registerPathHandler('/readable/', handleReadableRequest);
};

function sleep(milliseconds) {
  var start = new Date().getTime();
  for (var i = 0; i < 1e7; i++) {
    if ((new Date().getTime() - start) > milliseconds){
      break;
    }
  }
}

function handleReadableRequest(request, response) {
  var fired = false;        // Assign local variable fired, because onload gets tricky
  response.processAsync();  // Tell response handler this is going to take a while
  var url = request._queryString; // Grab the passed URL
  var backendUrl = randomString(RANDOM_STRING_LENGTH) + "/" + urlDomainForId(url); // Create backend url, usually happens in shooter.js
  console.log('recieved request: URL -> BACK'   // Log both URLs
                .replace('URL', url).replace('BACK', backendUrl));

  tabs.open({   // Open a tab
    "url": url,
    "onOpen": function (tab) {  // When the tab opens, run this on it
      tab.on("load", function (tab) {
        if (!fired) {           // If it hasn't fired, click on readable button
          fired = true;
          var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
                             .getService(Ci.nsIWindowMediator);
          var browserWindow = wm.getMostRecentWindow("navigator:browser");
          browserWindow.document.getElementById("reader-mode-button").click();
        } else {                // If it has fired, remove the toolbar
          var worker = tab.attach({
            contentScript:
              'var toolbar = document.getElementById("reader-toolbar");\n' +
              'toolbar.parentElement.removeChild(toolbar);\n' +
              'self.port.emit("done");'
          });
          worker.port.on("done", function () { // When the worker emits done, wait a bit then fire autoshot
            console.log('readability should be finished');
            sleep(500000);
            autoShot(tab, backend, backendUrl);
          });
        }
      });

      tab.on("close", function () {     // When the tab is closed we know autoshot is done, so return the URL
        console.log('completed processing BACK'.replace('BACK', backendUrl));
        response.setStatusLine("1.1", 200, "OK");
        response.write(contentServerString + backendUrl + '\n');
        response.finish();
      });
    }
  });
}

function handleRequest(request, response) {
  // Opens a tab and registers extractTab to the load event
  response.processAsync();
  var url = request._queryString;
  var backendUrl = randomString(RANDOM_STRING_LENGTH) + "/" + urlDomainForId(url);
  console.log('recieved request: URL -> BACK'
                .replace('URL', url).replace('BACK', backendUrl));
  tabs.open({
    "url": url,
    "onLoad": function (tab) {
      autoShot(tab, backend, backendUrl);
      tab.on("close", function (a) {
        console.log('completed processing BACK'.replace('BACK', backendUrl));
        response.setStatusLine("1.1", 200, "OK");
        response.write(contentServerString + backendUrl + '\n');
        response.finish();
      });
    }
  });
}
<|endoftext|>


<commit_before>import Ember from 'ember';
import DS from 'ember-data';
import {
  queryParamPropertyName,
  queryIdPropertyName,
  lastWasErrorPropertyName,
  ajaxOptionsPropertyName,
  stickyPropertyName
} from '../property-names';
import {recordHasId} from '../belongs-to-sticky';

var queryId = 0;

/**
 * Mixin for DS.Model extensions.
 */
export default Ember.Mixin.create({
  init() {
    this._super(...arguments);

    // Set sticky properties on init
    this.eachRelationship(key => {
      this._setStickyPropertyForKey(key);
    });
  },

  /**
   * Query a HasMany/BelongsTo relationship link.
   *
   * If you do something like this:
   * ```javascript
   * post.query('comments', { page: 1 });
   * ```
   *
   * The call made to the server will look something like this:
   * ```
   * Started GET "/api/v1/post/1/comments?page=1"
   * ```
   *
   * @param {String} propertyName Relationship property name
   * @param {Object} params Query parameters
   * @returns {Ember.RSVP.Promise}
   */
  query: function (propertyName, params) {
    var self = this;

    //abort the last query request for this property
    var _ajaxOptionsPropertyName = ajaxOptionsPropertyName(propertyName);
    var lastAjaxOptions = this.get(_ajaxOptionsPropertyName);
    if (lastAjaxOptions && lastAjaxOptions.jqXHR) {
      lastAjaxOptions.jqXHR.abort();
    }

    //set the query params as a property on this record
    var _queryParamPropertyName = queryParamPropertyName(propertyName);
    var _queryIdPropertyName = queryIdPropertyName(propertyName);
    var currentQueryId = queryId++;

    const oldParams = this.get(_queryParamPropertyName, params)

    this.set(_queryParamPropertyName, params);
    this.set(_queryIdPropertyName, currentQueryId);

    //get the relationship value, reloading if necessary
    var value = this.reloadRelationship(propertyName, JSON.stringify(params) === JSON.stringify(oldParams));

    //return the promise, clearing the ajax options property
    return value.catch(function (error) {
      if (error instanceof DS.AbortError) {
        //ignore aborted requests
        return;
      }
      throw error;
    }).finally(function () {
      if (self.get(_queryIdPropertyName) !== currentQueryId) {
        //don't clear parameters if they've been set by another request
        return;
      }
      self.set(_ajaxOptionsPropertyName, undefined);
    });
  },

  /**
   * Get the relationship property for the given property name, reloading the async relationship if necessary.
   *
   * @param propertyName Relationship property name
   * @returns {Ember.RSVP.Promise}
   */
  reloadRelationship: function (propertyName, forceReload) {
    //find out what kind of relationship this is
    var relationship = this.relationshipFor(propertyName);
    var isHasMany = relationship && relationship.kind === 'hasMany';

    var self = this;
    var reference = isHasMany ? this.hasMany(propertyName) : this.belongsTo(propertyName);
    return new Ember.RSVP.Promise(function (resolve) {
      //run.next, so that aborted promise gets rejected before starting another
      Ember.run.next(this, function () {
        var isLoaded = reference.value() !== null;
        if (isLoaded || force) {
          resolve(reference.reload());
        } else {
          //isLoaded is false when the last query resulted in an error, so if this load
          //results in an error again, reload the reference to query the server again
          var promise = reference.load().catch(function (error) {
            var _lastWasErrorPropertyName = lastWasErrorPropertyName(propertyName);
            if (self.get(_lastWasErrorPropertyName)) {
              //last access to this property resulted in an error, so reload
              return reference.reload();
            }
            //mark this result as an error for next time the property is queried
            self.set(_lastWasErrorPropertyName, true);
            throw error;
          });
          resolve(promise);
        }
      });
    });
  },

  notifyBelongsToChanged: function (key) {
    //called when a belongsTo property changes
    this._super(...arguments);
    this._setStickyPropertyForKey(key);
  },

  _setStickyPropertyForKey(key) {
    //check if the belongsTo relationship has been marked as sticky
    var meta = this.constructor.metaForProperty(key);
    if (!meta.sticky) {
      return;
    }

    //check if the value is loaded
    var reference = this.belongsTo(key);
    var value = reference && reference.value();
    if (!recordHasId(value) || value.get('isEmpty')) {
      return;
    }

    //if a belongsTo relationship attribute has changed, and the new record has an id,
    //store the record in a property so that the belongsToSticky can return if it required
    this.set(stickyPropertyName(key), value);
  }
});
<commit_msg>Fixes issues surrounding force reloading on different query options<commit_after>import Ember from 'ember';
import DS from 'ember-data';
import {
  queryParamPropertyName,
  queryIdPropertyName,
  lastWasErrorPropertyName,
  ajaxOptionsPropertyName,
  stickyPropertyName
} from '../property-names';
import {recordHasId} from '../belongs-to-sticky';

var queryId = 0;

/**
 * Mixin for DS.Model extensions.
 */
export default Ember.Mixin.create({
  init() {
    this._super(...arguments);

    // Set sticky properties on init
    this.eachRelationship(key => {
      this._setStickyPropertyForKey(key);
    });
  },

  /**
   * Query a HasMany/BelongsTo relationship link.
   *
   * If you do something like this:
   * ```javascript
   * post.query('comments', { page: 1 });
   * ```
   *
   * The call made to the server will look something like this:
   * ```
   * Started GET "/api/v1/post/1/comments?page=1"
   * ```
   *
   * @param {String} propertyName Relationship property name
   * @param {Object} params Query parameters
   * @returns {Ember.RSVP.Promise}
   */
  query: function (propertyName, params) {
    var self = this;

    //abort the last query request for this property
    var _ajaxOptionsPropertyName = ajaxOptionsPropertyName(propertyName);
    var lastAjaxOptions = this.get(_ajaxOptionsPropertyName);
    if (lastAjaxOptions && lastAjaxOptions.jqXHR) {
      lastAjaxOptions.jqXHR.abort();
    }

    //set the query params as a property on this record
    var _queryParamPropertyName = queryParamPropertyName(propertyName);
    var _queryIdPropertyName = queryIdPropertyName(propertyName);
    var currentQueryId = queryId++;

    const oldParams = this.get(_queryParamPropertyName, params)

    this.set(_queryParamPropertyName, params);
    this.set(_queryIdPropertyName, currentQueryId);

    //get the relationship value, reloading if necessary
    var value = this.reloadRelationship(propertyName, JSON.stringify(params) !== JSON.stringify(oldParams));

    //return the promise, clearing the ajax options property
    return value.catch(function (error) {
      if (error instanceof DS.AbortError) {
        //ignore aborted requests
        return;
      }
      throw error;
    }).finally(function () {
      if (self.get(_queryIdPropertyName) !== currentQueryId) {
        //don't clear parameters if they've been set by another request
        return;
      }
      self.set(_ajaxOptionsPropertyName, undefined);
    });
  },

  /**
   * Get the relationship property for the given property name, reloading the async relationship if necessary.
   *
   * @param propertyName Relationship property name
   * @returns {Ember.RSVP.Promise}
   */
  reloadRelationship: function (propertyName, forceReload) {
    //find out what kind of relationship this is
    var relationship = this.relationshipFor(propertyName);
    var isHasMany = relationship && relationship.kind === 'hasMany';

    var self = this;
    var reference = isHasMany ? this.hasMany(propertyName) : this.belongsTo(propertyName);
    return new Ember.RSVP.Promise(function (resolve) {
      //run.next, so that aborted promise gets rejected before starting another
      Ember.run.next(this, function () {
        var isLoaded = reference.value() !== null;
        if (isLoaded || forceReload) {
          resolve(reference.reload());
        } else {
          //isLoaded is false when the last query resulted in an error, so if this load
          //results in an error again, reload the reference to query the server again
          var promise = reference.load().catch(function (error) {
            var _lastWasErrorPropertyName = lastWasErrorPropertyName(propertyName);
            if (self.get(_lastWasErrorPropertyName)) {
              //last access to this property resulted in an error, so reload
              return reference.reload();
            }
            //mark this result as an error for next time the property is queried
            self.set(_lastWasErrorPropertyName, true);
            throw error;
          });
          resolve(promise);
        }
      });
    });
  },

  notifyBelongsToChanged: function (key) {
    //called when a belongsTo property changes
    this._super(...arguments);
    this._setStickyPropertyForKey(key);
  },

  _setStickyPropertyForKey(key) {
    //check if the belongsTo relationship has been marked as sticky
    var meta = this.constructor.metaForProperty(key);
    if (!meta.sticky) {
      return;
    }

    //check if the value is loaded
    var reference = this.belongsTo(key);
    var value = reference && reference.value();
    if (!recordHasId(value) || value.get('isEmpty')) {
      return;
    }

    //if a belongsTo relationship attribute has changed, and the new record has an id,
    //store the record in a property so that the belongsToSticky can return if it required
    this.set(stickyPropertyName(key), value);
  }
});
<|endoftext|>