<commit_before>'use strict';
var RedisClient = require('../lib/RedisClient');
var _ = require('lodash');
var sinon = require('sinon');
var Socket = require('../test/helpers/Socket');

function tcreateConnection(t, host, hostname) {
  return function (_host, _hostname, cb) {
    if (t) {
      t.equal(_host, host);
      t.equal(_hostname, hostname);
    }
    cb();

    return new Socket();
  };
}

function stubRedsminEndpoint(fn) {
  return fn || function () {};
}

// Quiet console output
RedisClient.log = sinon.stub(_.clone(console));

var R = null;
var endpoint = null;
var HOST = '127.0.1.1';
var PORT = 6378;

exports['RedisClient'] = {
  setUp: function (done) {
    // setup here
    endpoint = stubRedsminEndpoint();
    R = new RedisClient(endpoint);
    done();
  },
  tearDown: function (callback) {
    // clean up
    //RedisClient.log = console;
    callback();
  },

  global: function (t) {
    t.expect(2);
    t.equal(typeof RedisClient, 'function', 'should be a function.');
    t.equal(typeof RedisClient.net, 'object', '.net should be a object.');
    t.done();
  },

  constructor: function (t) {
    t.done();
  },

  connect: function (t) {
    t.expect(5);

    RedisClient.net.createConnection = tcreateConnection(t, PORT, HOST);

    R.on('connect', function () {
      t.ok(true, 'connected');
      t.equal(R.connected, true);
      t.done();
    });

    t.equal(R.connected, false);
    R.connect('redis://' + HOST + ':' + PORT);
  },

  'updatePortAndHostname with a connection string without redis://': function (t) {
    R.updatePortAndHostname(HOST + ':' + PORT);
    t.equal(R.port, PORT);
    t.equal(R.hostname, HOST);
    t.done();
  },

  /**
   * onData from redis
   * @param  {[type]} t [description]
   * @return {[type]}   [description]
   */
  'onData': function (t) {
    t.expect(1);

    R = new RedisClient(stubRedsminEndpoint(function fnWrite(data) {
      t.equal(data, 'test data');
      t.done();
    }));

    R.onData('test data');
  },

  /**
   * write data to redis
   */
  'write': function (t) {
    t.expect(1);

    RedisClient.net.createConnection = tcreateConnection();

    R.connect('redis://127.0.0.1:6378');

    var spy = sinon.spy(R.socket, 'write');

    R.write('KEYS *');

    t.ok(spy.calledWith('KEYS *'), "write to redis");
    t.done();
  },

  'onClose': function (t) {
    t.expect(1);

    RedisClient.net.createConnection = tcreateConnection();

    R.on('close', function () {
      t.equal(R.connected, false);
      t.done();
    });

    R.on('connect', _.once(function () {
      R.onClose(new Error('close'));
    }));

    R.connect('redis://127.0.0.1:6378');
  },

  'onClose (reconnect)': function (t) {
    t.expect(2);

    RedisClient.net.createConnection = tcreateConnection();

    R = new RedisClient(stubRedsminEndpoint(), {
      initialTimeout: 1,
      maxTimeout: 10
    });

    var spy = sinon.spy(R, "onConnected");

    R.on('connect', function () {

      t.ok(true, "connect called");

      if (spy.callCount === 2) {
        R._connect = function () {};
        t.done();
        return;
      }

      process.nextTick(_.bind(R.onClose, R, new Error('close')));
    });

    R.connect('redis://127.0.0.1:6378');
  },

  'Reconnect': function (t) {
    t.expect(1);
    // Simulate that the backoff will always directly hit reconnect
    R.backoff.backoff = function () {
      R.reconnect();
    };

    R._connect = function () {
      t.equal(R.connected, false);
      t.done();
    };

    R.onClose();
  },

  'onError': function (t) {
    var R = new RedisClient(stubRedsminEndpoint());
    t.expect(1);
    t.doesNotThrow(function () {
      R.onError();
    });
    t.done();
  }
};
<commit_msg>fix(test): move RedisClient to mocha<commit_after>'use strict';
var RedisClient = require('../lib/RedisClient');
var _ = require('lodash');
var sinon = require('sinon');
var Socket = require('../test/helpers/Socket');
var t = require('chai').assert;

function tcreateConnection(t, host, hostname) {
  return function (_host, _hostname, cb) {
    if (t) {
      t.equal(_host, host);
      t.equal(_hostname, hostname);
    }
    cb();

    return new Socket();
  };
}

function stubRedsminEndpoint(fn) {
  return fn || function () {};
}

// Quiet console output
RedisClient.log = sinon.stub(_.clone(console));

describe('RedisClient', function () {
  var R, endpoint;
  var HOST = '127.0.1.1';
  var PORT = 6378;

  beforeEach(function () {
    endpoint = stubRedsminEndpoint();
    R = new RedisClient(endpoint);
  });

  afterEach(function (done) {
    done();
  });

  it('should global', function (done) {
    t.equal(typeof RedisClient, 'function', 'should be a function.');
    t.equal(typeof RedisClient.net, 'object', '.net should be a object.');
    done();
  });

  it('should constructor', function (done) {
    done();
  });

  it('should connect', function (done) {
    RedisClient.net.createConnection = tcreateConnection(t, PORT, HOST);

    R.on('connect', function () {
      t.ok(true, 'connected');
      t.equal(R.connected, true);
      done();
    });

    t.equal(R.connected, false);
    R.connect('redis://' + HOST + ':' + PORT);
  });

  it('should updatePortAndHostname', function (done) {
    R.updatePortAndHostname(HOST + ':' + PORT);
    t.equal(R.port, PORT);
    t.equal(R.hostname, HOST);
    done();
  });

  it('should forward data', function (done) {
    R = new RedisClient(stubRedsminEndpoint(function fnWrite(data) {
      t.equal(data, 'test data');
      done();
    }));

    R.onData('test data');
  });

  it('should write data to redis', function (done) {
    RedisClient.net.createConnection = tcreateConnection();

    R.connect('redis://127.0.0.1:6378');

    var spy = sinon.spy(R.socket, 'write');

    R.write('KEYS *');

    t.ok(spy.calledWith('KEYS *'), "write to redis");
    done();
  });

  it('should not be in connected state if the redis connection is closed', function (done) {
    RedisClient.net.createConnection = tcreateConnection();

    R.on('close', function () {
      t.equal(R.connected, false);
      done();
    });

    R.on('connect', _.once(function () {
      R.onClose(new Error('close'));
    }));

    R.connect('redis://127.0.0.1:6378');
  });

  it('should try to connect to redis with backoff', function (done) {
    RedisClient.net.createConnection = tcreateConnection();

    R = new RedisClient(stubRedsminEndpoint(), {
      initialTimeout: 1,
      maxTimeout: 10
    });

    var spy = sinon.spy(R, "onConnected");

    R.on('connect', function () {
      t.ok(true, "connect called");

      if (spy.callCount === 2) {
        R._connect = function () {};
        done();
        return;
      }

      process.nextTick(_.bind(R.onClose, R, new Error('close')));
    });

    R.connect('redis://127.0.0.1:6378');
  });

  it('should use the backoff', function (done) {
    // Simulate that the backoff will always directly hit reconnect
    R.backoff.backoff = function () {
      R.reconnect();
    };

    R._connect = function () {
      t.equal(R.connected, false);
      done();
    };

    R.onClose();
  });

  it('should not throw when onError is called', function (done) {
    var R = new RedisClient(stubRedsminEndpoint());
    t.doesNotThrow(function () {
      R.onError();
    });
    done();
  });
});
<|endoftext|>


<commit_before>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
"use strict";

const path = require("path");
const NORMALIZE_SLASH_DIRECTION_REGEXP = /\\/g;
const PATH_CHARS_REGEXP = /[-[\]{}()*+?.,\\^$|#\s]/g;
const SEPERATOR_REGEXP = /[/\\]$/;
const FRONT_OR_BACK_BANG_REGEXP = /^!|!$/g;
const INDEX_JS_REGEXP = /\/index.js(!|\?|\(query\))/g;

const normalizeBackSlashDirection = (request) => {
	return request.replace(NORMALIZE_SLASH_DIRECTION_REGEXP, "/");
};

const createRegExpForPath = (path) => {
	const regexpTypePartial = path.replace(PATH_CHARS_REGEXP, "\\$&");
	return new RegExp(`(^|!)${regexpTypePartial}`, "g");
};

class RequestShortener {
	constructor(directory) {
		directory = normalizeBackSlashDirection(directory);
		if(SEPERATOR_REGEXP.test(directory)) directory = directory.substr(0, directory.length - 1);

		if(directory) {
			this.currentDirectoryRegExp = createRegExpForPath(directory);
		}

		const dirname = path.dirname(directory);
		const endsWithSeperator = SEPERATOR_REGEXP.test(dirname);
		const parentDirectory = endsWithSeperator ? dirname.substr(0, dirname.length - 1) : dirname;
		if(parentDirectory && parentDirectory !== directory) {
			this.parentDirectoryRegExp = createRegExpForPath(parentDirectory);
		}

		if(__dirname.length >= 2) {
			const buildins = normalizeBackSlashDirection(path.join(__dirname, ".."));
			const buildinsAsModule = this.currentDirectoryRegExp && this.currentDirectoryRegExp.test(buildins);
			this.buildinsAsModule = buildinsAsModule;
			this.buildinsRegExp = createRegExpForPath(buildins);
		}
	}

	shorten(request) {
		if(!request) return request;
		request = normalizeBackSlashDirection(request);
		if(this.buildinsAsModule && this.buildinsRegExp)
			request = request.replace(this.buildinsRegExp, "!(webpack)");
		if(this.currentDirectoryRegExp)
			request = request.replace(this.currentDirectoryRegExp, "!.");
		if(this.parentDirectoryRegExp)
			request = request.replace(this.parentDirectoryRegExp, "!..");
		if(!this.buildinsAsModule && this.buildinsRegExp)
			request = request.replace(this.buildinsRegExp, "!(webpack)");
		request = request.replace(INDEX_JS_REGEXP, "$1");
		return request.replace(FRONT_OR_BACK_BANG_REGEXP, "");
	}
}

module.exports = RequestShortener;
<commit_msg>fix(sp): spelling mistake in const name<commit_after>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
"use strict";

const path = require("path");
const NORMALIZE_SLASH_DIRECTION_REGEXP = /\\/g;
const PATH_CHARS_REGEXP = /[-[\]{}()*+?.,\\^$|#\s]/g;
const SEPARATOR_REGEXP = /[/\\]$/;
const FRONT_OR_BACK_BANG_REGEXP = /^!|!$/g;
const INDEX_JS_REGEXP = /\/index.js(!|\?|\(query\))/g;

const normalizeBackSlashDirection = (request) => {
	return request.replace(NORMALIZE_SLASH_DIRECTION_REGEXP, "/");
};

const createRegExpForPath = (path) => {
	const regexpTypePartial = path.replace(PATH_CHARS_REGEXP, "\\$&");
	return new RegExp(`(^|!)${regexpTypePartial}`, "g");
};

class RequestShortener {
	constructor(directory) {
		directory = normalizeBackSlashDirection(directory);
		if(SEPARATOR_REGEXP.test(directory)) directory = directory.substr(0, directory.length - 1);

		if(directory) {
			this.currentDirectoryRegExp = createRegExpForPath(directory);
		}

		const dirname = path.dirname(directory);
		const endsWithSeperator = SEPARATOR_REGEXP.test(dirname);
		const parentDirectory = endsWithSeperator ? dirname.substr(0, dirname.length - 1) : dirname;
		if(parentDirectory && parentDirectory !== directory) {
			this.parentDirectoryRegExp = createRegExpForPath(parentDirectory);
		}

		if(__dirname.length >= 2) {
			const buildins = normalizeBackSlashDirection(path.join(__dirname, ".."));
			const buildinsAsModule = this.currentDirectoryRegExp && this.currentDirectoryRegExp.test(buildins);
			this.buildinsAsModule = buildinsAsModule;
			this.buildinsRegExp = createRegExpForPath(buildins);
		}
	}

	shorten(request) {
		if(!request) return request;
		request = normalizeBackSlashDirection(request);
		if(this.buildinsAsModule && this.buildinsRegExp)
			request = request.replace(this.buildinsRegExp, "!(webpack)");
		if(this.currentDirectoryRegExp)
			request = request.replace(this.currentDirectoryRegExp, "!.");
		if(this.parentDirectoryRegExp)
			request = request.replace(this.parentDirectoryRegExp, "!..");
		if(!this.buildinsAsModule && this.buildinsRegExp)
			request = request.replace(this.buildinsRegExp, "!(webpack)");
		request = request.replace(INDEX_JS_REGEXP, "$1");
		return request.replace(FRONT_OR_BACK_BANG_REGEXP, "");
	}
}

module.exports = RequestShortener;
<|endoftext|>


<commit_before>// @flow

const RestClient = require('../../api/RestClient')
const { subcommand } = require('../util')

module.exports = {
  command: 'listNamespaces',
  description: `Fetch a list of namespaces published by all known peers.\n`,
  handler: subcommand((opts: {client: RestClient}) => {
    const {client} = opts
    return client.listNamespaces().then(
      namespaces => {
        namespaces.forEach(ns => console.log(ns))
      }
    )
  })
}
<commit_msg>sort listNamespaces output<commit_after>// @flow

const RestClient = require('../../api/RestClient')
const { subcommand } = require('../util')

module.exports = {
  command: 'listNamespaces',
  description: `Fetch a list of namespaces published by all known peers.\n`,
  handler: subcommand((opts: {client: RestClient}) => {
    const {client} = opts
    return client.listNamespaces().then(
      namespaces => {
        namespaces.sort().forEach(ns => console.log(ns))
      }
    )
  })
}
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import {getErrorMessageForField} from '../util/validation';

const showErrorFeedback = (message) => {
    if(message){
        return (

                {message}

        );
    } else {
        return null;
    }
};

class TextField extends React.Component {

    render() {
        const propertyName = this.props.propertyName;
        const displayName = this.props.displayName;
        const inputType = this.props.inputType || 'text';
        const hidden = this.props.hidden || false;

        const errorMessage = getErrorMessageForField(this.props.errors, propertyName);
        const validStyle = errorMessage ? 'invalid' : 'valid';

        if(!hidden){
            return (

                    {displayName} *
                    <input
                        value={this.props.propertyValue}
                        type={inputType}
                        className={`form-control ${validStyle}`}
                        id={propertyName}
                        placeholder={displayName}
                        onChange={(event) => {
                            this.props.changeHandler(propertyName, event.target.value);
                        }}
                    />
                    {
                        showErrorFeedback(errorMessage)
                    }

            );
        }

        return null;
    }
};

TextField.propTypes = {
    propertyName: PropTypes.string.isRequired,
    propertyValue: PropTypes.string.isRequired,
    displayName: PropTypes.string.isRequired,
    changeHandler: PropTypes.func.isRequired,
    inputType: PropTypes.string,
    hidden: PropTypes.bool
};

export default TextField;<commit_msg>- missing errors in prop types.<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import {getErrorMessageForField} from '../util/validation';

const showErrorFeedback = (message) => {
    if(message){
        return (

                {message}

        );
    } else {
        return null;
    }
};

class TextField extends React.Component {

    render() {
        const propertyName = this.props.propertyName;
        const displayName = this.props.displayName;
        const inputType = this.props.inputType || 'text';
        const hidden = this.props.hidden || false;

        const errorMessage = getErrorMessageForField(this.props.errors, propertyName);
        const validStyle = errorMessage ? 'invalid' : 'valid';

        if(!hidden){
            return (

                    {displayName} *
                    <input
                        value={this.props.propertyValue}
                        type={inputType}
                        className={`form-control ${validStyle}`}
                        id={propertyName}
                        placeholder={displayName}
                        onChange={(event) => {
                            this.props.changeHandler(propertyName, event.target.value);
                        }}
                    />
                    {
                        showErrorFeedback(errorMessage)
                    }

            );
        }

        return null;
    }
};

TextField.propTypes = {
    propertyName: PropTypes.string.isRequired,
    propertyValue: PropTypes.string.isRequired,
    displayName: PropTypes.string.isRequired,
    changeHandler: PropTypes.func.isRequired,
    inputType: PropTypes.string,
    hidden: PropTypes.bool,
    errors: PropTypes.object
};

export default TextField;<|endoftext|>


<commit_before>// @flow
import React from 'react';
import { connect } from 'react-redux';
import DateFnsUtils from '@date-io/date-fns';
import {
  AppBar,
  Button,
  Card,
  CardContent,
  CardHeader,
  Checkbox,
  FormControlLabel,
  IconButton,
  Switch,
  TextField,
  Toolbar,
  Typography,
  useTheme,
} from '@material-ui/core';
import {
  ChevronLeft as ChevronLeftIcon,
  ChevronRight as ChevronRightIcon,
  Refresh as RefreshIcon,
} from '@material-ui/icons';
import {
  DatePicker,
  MuiPickersUtilsProvider,
  TimePicker,
} from '@material-ui/pickers';
import useMediaQuery from '@material-ui/core/useMediaQuery';

import useStyles from './Style';
import formatDate from '~/utils/formatDate';
import DeviceField from './DeviceField';
import RemoveAnimationProvider from '../RemoveAnimationProvider';
import DeleteDeviceLink from './DeleteDeviceLink';
import CompanyTokenField from './CompanyTokenField';
import CustomMarkers from './CustomMarkers';
import { type GlobalState } from '~/reducer/state';
import {
  type CompanyToken,
  type Device,
  type MaterialInputElement,
  type Source,
  changeCompanyToken,
  changeDeviceId,
  changeEnableClustering,
  changeEndDate,
  changeIsWatching,
  changeMaxMarkers,
  changeShowGeofenceHits,
  changeShowMarkers,
  changeShowPolyline,
  changeStartDate,
  reload,
} from '~/reducer/dashboard';
const cardMargins = { marginBottom: '10px' };
type StateProps = {|
  companyToken: string,
  companyTokens: Source[],
  deviceId: string,
  devices: Source[],
  enableClustering: boolean,
  endDate: Date,
  hasData: boolean,
  isWatching: boolean,
  maxMarkers: number,
  showGeofenceHits: boolean,
  showMarkers: boolean,
  showPolyline: boolean,
  startDate: Date,
|};
type DispatchProps = {|
  onChangeCompanyToken: (companyToken: string) => any,
  onChangeDeviceId: (deviceId: string) => any,
  onChangeEnableClustering: (value: boolean) => any,
  onChangeEndDate: (date: Date) => any,
  onChangeIsWatching: (value: boolean) => any,
  onChangeMaxMarkers: (value: number) => any,
  onChangeShowGeofenceHits: (value: boolean) => any,
  onChangeShowMarkers: (value: boolean) => any,
  onChangeShowPolyline: (value: boolean) => any,
  onChangeStartDate: (date: Date) => any,
  onReload: () => any,
|};
type Props = {|
  ...StateProps,
  ...DispatchProps,
  setOpen: (open: boolean) => any,
|};
const FilterView = function ({
  companyToken,
  companyTokens,
  deviceId,
  devices,
  enableClustering,
  endDate,
  hasData,
  isWatching,
  maxMarkers,
  onChangeCompanyToken,
  onChangeDeviceId,
  onChangeEnableClustering,
  onChangeEndDate,
  onChangeIsWatching,
  onChangeMaxMarkers,
  onChangeShowGeofenceHits,
  onChangeShowMarkers,
  onChangeShowPolyline,
  onChangeStartDate,
  onReload,
  setOpen,
  showGeofenceHits,
  showMarkers,
  showPolyline,
  startDate,
}: Props): React$Element<any> {
  const theme = useTheme();
  const classes = useStyles();
  const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
  return (

      <AppBar className={classes.appBar} position='static'>
        <Toolbar style={{ justifyContent: 'space-between' }}>
          <IconButton edge='start' onClick={onReload} color='inherit' aria-label='menu'>
            <RefreshIcon />
          </IconButton>
          <Typography variant='h6'>
            Filter
          </Typography>
          <IconButton edge='end' color='inherit' onClick={() => setOpen(false)}>
            {theme.direction === 'ltr' ? <ChevronLeftIcon /> : <ChevronRightIcon />}
          </IconButton>
        </Toolbar>
      </AppBar>

        <MuiPickersUtilsProvider utils={DateFnsUtils}>
          <Card style={cardMargins}>
            <CardHeader className={classes.header} title='Locations' />
            <CardContent className={classes.relative}>
              <RemoveAnimationProvider>
                <CompanyTokenField
                  fullScreen={fullScreen}
                  onChange={onChangeCompanyToken}
                  source={companyTokens}
                  value={companyToken}
                />
                <DeviceField onChange={onChangeDeviceId} source={devices} hasData={hasData} value={deviceId} />
              </RemoveAnimationProvider>
              <DeleteDeviceLink />

                <DatePicker
                  autoOk
                  label='Start date'
                  format='MM-dd'
                  style={{ flex: 1 }}
                  onChange={onChangeStartDate}
                  value={startDate}
                  labelFunc={formatDate}
                />
                <TimePicker
                  autoOk
                  label='Time'
                  style={{ flex: 1 }}
                  onChange={onChangeStartDate}
                  value={startDate}
                />

                <DatePicker
                  autoOk
                  label='End date'
                  format='MM-dd'
                  style={{ flex: 1 }}
                  onChange={onChangeEndDate}
                  value={endDate}
                  labelFunc={formatDate}
                />
                <TimePicker
                  autoOk
                  label='Time'
                  style={{ flex: 1 }}
                  onChange={onChangeEndDate}
                  value={endDate}
                />

              <Button fullWidth className={classes.paddingRow} variant='contained' color='primary' onClick={onReload}>
                <RefreshIcon />
                Reload
              </Button>
              <FormControlLabel
                labelPlacement='start'
                className={classes.switch}
                control={
                  <Switch
                    color='primary'
                    value='watching'
                    checked={isWatching}
                    onChange={(e: MaterialInputElement) => onChangeIsWatching(e.target.checked)}
                    style={{ flex: 1 }}
                  />
                }
                label='Watch mode'
              />
            </CardContent>
          </Card>
          <Card style={cardMargins}>
            <CardHeader className={classes.header} title='Map' />
            <CardContent>
              <FormControlLabel
                control={(
                  <Checkbox
                    color='primary'
                    checked={showMarkers}
                    onChange={(e: MaterialInputElement) => onChangeShowMarkers(e.target.checked)}
                  />
                )}
                label='Show Markers'
              />
              <FormControlLabel
                control={(
                  <Checkbox
                    color='primary'
                    checked={showPolyline}
                    onChange={(e: MaterialInputElement) => onChangeShowPolyline((e.target.checked))}
                  />
                )}
                label='Show Polyline'
              />
              <FormControlLabel
                control={(
                  <Checkbox
                    color='primary'
                    checked={showGeofenceHits}
                    onChange={(e: MaterialInputElement) => onChangeShowGeofenceHits((e.target.checked))}
                  />
                )}
                label='Show Geofences'
              />
              <FormControlLabel
                control={(
                  <Checkbox
                    color='primary'
                    checked={!enableClustering}
                    onChange={(e: MaterialInputElement) => onChangeEnableClustering(!e.target.checked)}
                  />
                )}
                label='Disable Clustering'
              />
              <TextField
                fullWidth
                type='text'
                value={maxMarkers}
                onChange={(e: MaterialInputElement) => onChangeMaxMarkers(+e.target.value)}
                label='Max markers'
              />
            </CardContent>
          </Card>
          <CustomMarkers classes={classes} />
        </MuiPickersUtilsProvider>

  );
};

const mapStateToProps = function (state: GlobalState): StateProps {
  return {
    deviceId: state.dashboard.deviceId,
    companyToken: state.dashboard.companyToken,
    enableClustering: state.dashboard.enableClustering,
    startDate: state.dashboard.startDate,
    endDate: state.dashboard.endDate,
    devices: state.dashboard.devices.map((device: Device) => ({ value: device.id, label: device.name })),
    companyTokens: state.dashboard.companyTokens.map((companyToken: CompanyToken) => ({
      value: companyToken.id,
      label: companyToken.name,
    })),
    hasData: state.dashboard.hasData,
    isWatching: state.dashboard.isWatching,
    showGeofenceHits: state.dashboard.showGeofenceHits,
    showPolyline: state.dashboard.showPolyline,
    showMarkers: state.dashboard.showMarkers,
    maxMarkers: state.dashboard.maxMarkers,
  };
};

const mapDispatchToProps: DispatchProps = {
  onReload: reload,
  onChangeDeviceId: changeDeviceId,
  onChangeCompanyToken: changeCompanyToken,
  onChangeStartDate: changeStartDate,
  onChangeEndDate: changeEndDate,
  onChangeEnableClustering: changeEnableClustering,
  onChangeIsWatching: changeIsWatching,
  onChangeShowMarkers: changeShowMarkers,
  onChangeShowPolyline: changeShowPolyline,
  onChangeShowGeofenceHits: changeShowGeofenceHits,
  onChangeMaxMarkers: changeMaxMarkers,
};

export default connect(mapStateToProps, mapDispatchToProps)(FilterView);
<commit_msg>reverse the logic on all the Map options<commit_after>// @flow
import React from 'react';
import { connect } from 'react-redux';
import DateFnsUtils from '@date-io/date-fns';
import {
  AppBar,
  Button,
  Card,
  CardContent,
  CardHeader,
  Checkbox,
  FormControlLabel,
  IconButton,
  Switch,
  TextField,
  Toolbar,
  Typography,
  useTheme,
} from '@material-ui/core';
import {
  ChevronLeft as ChevronLeftIcon,
  ChevronRight as ChevronRightIcon,
  Refresh as RefreshIcon,
} from '@material-ui/icons';
import {
  DatePicker,
  MuiPickersUtilsProvider,
  TimePicker,
} from '@material-ui/pickers';
import useMediaQuery from '@material-ui/core/useMediaQuery';

import useStyles from './Style';
import formatDate from '~/utils/formatDate';
import DeviceField from './DeviceField';
import RemoveAnimationProvider from '../RemoveAnimationProvider';
import DeleteDeviceLink from './DeleteDeviceLink';
import CompanyTokenField from './CompanyTokenField';
import CustomMarkers from './CustomMarkers';
import { type GlobalState } from '~/reducer/state';
import {
  type CompanyToken,
  type Device,
  type MaterialInputElement,
  type Source,
  changeCompanyToken,
  changeDeviceId,
  changeEnableClustering,
  changeEndDate,
  changeIsWatching,
  changeMaxMarkers,
  changeShowGeofenceHits,
  changeShowMarkers,
  changeShowPolyline,
  changeStartDate,
  reload,
} from '~/reducer/dashboard';
const cardMargins = { marginBottom: '10px' };
type StateProps = {|
  companyToken: string,
  companyTokens: Source[],
  deviceId: string,
  devices: Source[],
  enableClustering: boolean,
  endDate: Date,
  hasData: boolean,
  isWatching: boolean,
  maxMarkers: number,
  showGeofenceHits: boolean,
  showMarkers: boolean,
  showPolyline: boolean,
  startDate: Date,
|};
type DispatchProps = {|
  onChangeCompanyToken: (companyToken: string) => any,
  onChangeDeviceId: (deviceId: string) => any,
  onChangeEnableClustering: (value: boolean) => any,
  onChangeEndDate: (date: Date) => any,
  onChangeIsWatching: (value: boolean) => any,
  onChangeMaxMarkers: (value: number) => any,
  onChangeShowGeofenceHits: (value: boolean) => any,
  onChangeShowMarkers: (value: boolean) => any,
  onChangeShowPolyline: (value: boolean) => any,
  onChangeStartDate: (date: Date) => any,
  onReload: () => any,
|};
type Props = {|
  ...StateProps,
  ...DispatchProps,
  setOpen: (open: boolean) => any,
|};
const FilterView = function ({
  companyToken,
  companyTokens,
  deviceId,
  devices,
  enableClustering,
  endDate,
  hasData,
  isWatching,
  maxMarkers,
  onChangeCompanyToken,
  onChangeDeviceId,
  onChangeEnableClustering,
  onChangeEndDate,
  onChangeIsWatching,
  onChangeMaxMarkers,
  onChangeShowGeofenceHits,
  onChangeShowMarkers,
  onChangeShowPolyline,
  onChangeStartDate,
  onReload,
  setOpen,
  showGeofenceHits,
  showMarkers,
  showPolyline,
  startDate,
}: Props): React$Element<any> {
  const theme = useTheme();
  const classes = useStyles();
  const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
  return (

      <AppBar className={classes.appBar} position='static'>
        <Toolbar style={{ justifyContent: 'space-between' }}>
          <IconButton edge='start' onClick={onReload} color='inherit' aria-label='menu'>
            <RefreshIcon />
          </IconButton>
          <Typography variant='h6'>
            Filter
          </Typography>
          <IconButton edge='end' color='inherit' onClick={() => setOpen(false)}>
            {theme.direction === 'ltr' ? <ChevronLeftIcon /> : <ChevronRightIcon />}
          </IconButton>
        </Toolbar>
      </AppBar>

        <MuiPickersUtilsProvider utils={DateFnsUtils}>
          <Card style={cardMargins}>
            <CardHeader className={classes.header} title='Locations' />
            <CardContent className={classes.relative}>
              <RemoveAnimationProvider>
                <CompanyTokenField
                  fullScreen={fullScreen}
                  onChange={onChangeCompanyToken}
                  source={companyTokens}
                  value={companyToken}
                />
                <DeviceField onChange={onChangeDeviceId} source={devices} hasData={hasData} value={deviceId} />
              </RemoveAnimationProvider>
              <DeleteDeviceLink />

                <DatePicker
                  autoOk
                  label='Start date'
                  format='MM-dd'
                  style={{ flex: 1 }}
                  onChange={onChangeStartDate}
                  value={startDate}
                  labelFunc={formatDate}
                />
                <TimePicker
                  autoOk
                  label='Time'
                  style={{ flex: 1 }}
                  onChange={onChangeStartDate}
                  value={startDate}
                />

                <DatePicker
                  autoOk
                  label='End date'
                  format='MM-dd'
                  style={{ flex: 1 }}
                  onChange={onChangeEndDate}
                  value={endDate}
                  labelFunc={formatDate}
                />
                <TimePicker
                  autoOk
                  label='Time'
                  style={{ flex: 1 }}
                  onChange={onChangeEndDate}
                  value={endDate}
                />

              <Button fullWidth className={classes.paddingRow} variant='contained' color='primary' onClick={onReload}>
                <RefreshIcon />
                Reload
              </Button>
              <FormControlLabel
                labelPlacement='start'
                className={classes.switch}
                control={
                  <Switch
                    color='primary'
                    value='watching'
                    checked={isWatching}
                    onChange={(e: MaterialInputElement) => onChangeIsWatching(e.target.checked)}
                    style={{ flex: 1 }}
                  />
                }
                label='Watch mode'
              />
            </CardContent>
          </Card>
          <Card style={cardMargins}>
            <CardHeader className={classes.header} title='Map' />
            <CardContent>
              <FormControlLabel
                control={(
                  <Checkbox
                    color='primary'
                    checked={!showMarkers}
                    onChange={(e: MaterialInputElement) => onChangeShowMarkers(!e.target.checked)}
                  />
                )}
                label='Hide Markers'
              />
              <FormControlLabel
                control={(
                  <Checkbox
                    color='primary'
                    checked={!showPolyline}
                    onChange={(e: MaterialInputElement) => onChangeShowPolyline(!e.target.checked)}
                  />
                )}
                label='Hide Polyline'
              />
              <FormControlLabel
                control={(
                  <Checkbox
                    color='primary'
                    checked={!showGeofenceHits}
                    onChange={(e: MaterialInputElement) => onChangeShowGeofenceHits(!e.target.checked)}
                  />
                )}
                label='Hide Geofences'
              />
              <FormControlLabel
                control={(
                  <Checkbox
                    color='primary'
                    checked={!enableClustering}
                    onChange={(e: MaterialInputElement) => onChangeEnableClustering(!e.target.checked)}
                  />
                )}
                label='Disable Clustering'
              />
              <TextField
                fullWidth
                type='text'
                value={maxMarkers}
                onChange={(e: MaterialInputElement) => onChangeMaxMarkers(+e.target.value)}
                label='Max markers'
              />
            </CardContent>
          </Card>
          <CustomMarkers classes={classes} />
        </MuiPickersUtilsProvider>

  );
};

const mapStateToProps = function (state: GlobalState): StateProps {
  return {
    deviceId: state.dashboard.deviceId,
    companyToken: state.dashboard.companyToken,
    enableClustering: state.dashboard.enableClustering,
    startDate: state.dashboard.startDate,
    endDate: state.dashboard.endDate,
    devices: state.dashboard.devices.map((device: Device) => ({ value: device.id, label: device.name })),
    companyTokens: state.dashboard.companyTokens.map((companyToken: CompanyToken) => ({
      value: companyToken.id,
      label: companyToken.name,
    })),
    hasData: state.dashboard.hasData,
    isWatching: state.dashboard.isWatching,
    showGeofenceHits: state.dashboard.showGeofenceHits,
    showPolyline: state.dashboard.showPolyline,
    showMarkers: state.dashboard.showMarkers,
    maxMarkers: state.dashboard.maxMarkers,
  };
};

const mapDispatchToProps: DispatchProps = {
  onReload: reload,
  onChangeDeviceId: changeDeviceId,
  onChangeCompanyToken: changeCompanyToken,
  onChangeStartDate: changeStartDate,
  onChangeEndDate: changeEndDate,
  onChangeEnableClustering: changeEnableClustering,
  onChangeIsWatching: changeIsWatching,
  onChangeShowMarkers: changeShowMarkers,
  onChangeShowPolyline: changeShowPolyline,
  onChangeShowGeofenceHits: changeShowGeofenceHits,
  onChangeMaxMarkers: changeMaxMarkers,
};

export default connect(mapStateToProps, mapDispatchToProps)(FilterView);
<|endoftext|>


<commit_before>/*!
{
  "name": "input[type=\"number\"] Localization",
  "property": "localizednumber",
  "tags": ["forms", "localization", "attribute"],
  "authors": ["Peter Janes"],
  "notes": [{
    "name": "Webkit Bug Tracker Listing",
    "href": "https://bugs.webkit.org/show_bug.cgi?id=42484"
  }, {
    "name": "Based on This",
    "href": "https://trac.webkit.org/browser/trunk/LayoutTests/fast/forms/script-tests/input-number-keyoperation.js?rev=80096#L9"
  }],
  "knownBugs": ["Only ever returns true if the browser/OS is configured to use comma as a decimal separator. This is probably fine for most use cases."]
}
!*/
/* DOC
Detects whether input type="number" is capable of receiving and displaying localized numbers, e.g. with comma separator.
*/
define(['Modernizr', 'createElement', 'getBody', 'test/inputtypes', 'test/forms/validation'], function(Modernizr, createElement, getBody) {
  Modernizr.addTest('localizednumber', function() {
    /* this extends our testing of input[type=number], so bomb out if that's missing */
    if (!Modernizr.inputtypes.number) { return false; }
    /* we rely on checkValidity later, so bomb out early if we don't have it */
    if (!Modernizr.formvalidation) { return false; }

    var body = getBody();
    var div = createElement('div');
    var firstChild = body.firstElementChild || body.firstChild;
    var result;

    body.insertBefore(div, firstChild);

    div.innerHTML = '<input type="number" value="1.0" step="0.1"/>';
    var input = div.childNodes[0];
    body.appendChild(div);

    input.focus();
    try {
      document.execCommand('SelectAll', false); // Overwrite current input value, rather than appending text
      document.execCommand('InsertText', false, '1,1');
    } catch (e) {} // prevent warnings in IE

    /* results */
    result = input.type === 'number' && input.valueAsNumber === 1.1 && input.checkValidity();

    /* cleanup */
    body.removeChild(div);
    if (body.fake) {
      body.parentNode.removeChild(body);
    }

    return result;
  });

});
<commit_msg>Issue #2492 inputnumber-l10n test causing unintended scrolls #2492 (#2496)<commit_after>/*!
{
  "name": "input[type=\"number\"] Localization",
  "property": "localizednumber",
  "tags": ["forms", "localization", "attribute"],
  "authors": ["Peter Janes"],
  "notes": [{
    "name": "Webkit Bug Tracker Listing",
    "href": "https://bugs.webkit.org/show_bug.cgi?id=42484"
  }, {
    "name": "Based on This",
    "href": "https://trac.webkit.org/browser/trunk/LayoutTests/fast/forms/script-tests/input-number-keyoperation.js?rev=80096#L9"
  }],
  "knownBugs": ["Only ever returns true if the browser/OS is configured to use comma as a decimal separator. This is probably fine for most use cases."]
}
!*/
/* DOC
Detects whether input type="number" is capable of receiving and displaying localized numbers, e.g. with comma separator.
*/
define(['Modernizr', 'createElement', 'getBody', 'test/inputtypes', 'test/forms/validation'], function(Modernizr, createElement, getBody) {
  Modernizr.addTest('localizednumber', function() {
    /* this extends our testing of input[type=number], so bomb out if that's missing */
    if (!Modernizr.inputtypes.number) { return false; }
    /* we rely on checkValidity later, so bomb out early if we don't have it */
    if (!Modernizr.formvalidation) { return false; }

    var body = getBody();
    var div = createElement('div');
    var firstChild = body.firstElementChild || body.firstChild;
    var result;

    body.insertBefore(div, firstChild);

    div.innerHTML = '<input type="number" value="1.0" step="0.1" style="position: fixed; top: 0;" />';
    var input = div.childNodes[0];
    body.appendChild(div);

    input.focus();
    try {
      document.execCommand('SelectAll', false); // Overwrite current input value, rather than appending text
      document.execCommand('InsertText', false, '1,1');
    } catch (e) {} // prevent warnings in IE

    /* results */
    result = input.type === 'number' && input.valueAsNumber === 1.1 && input.checkValidity();

    /* cleanup */
    body.removeChild(div);
    if (body.fake) {
      body.parentNode.removeChild(body);
    }

    return result;
  });

});
<|endoftext|>


<commit_before>/*$AMPERSAND_VERSION*/
var State = require('ampersand-state');
var CollectionView = require('ampersand-collection-view');
var domify = require('domify');
var uniqueId = require("lodash.uniqueid");
var pick = require("lodash.pick");
var assign = require("lodash.assign");
var forEach = require("lodash.foreach");
var result = require("lodash.result");
var last = require("lodash.last");
var isString = require("lodash.isstring");
var bind = require("lodash.bind");
var flatten = require("lodash.flatten");
var invoke = require("lodash.invoke");
var events = require('events-mixin');
var matches = require('matches-selector');
var bindings = require('ampersand-dom-bindings');
var getPath = require('get-object-path');

function View(attrs) {
    this.cid = uniqueId('view');
    attrs || (attrs = {});
    var parent = attrs.parent;
    delete attrs.parent;
    BaseState.call(this, attrs, {init: false, parent: parent});
    this.on('change:el', this._handleElementChange, this);
    this._upsertBindings();
    this.template = attrs.template || this.template;
    this._cache.rendered = false; // prep `rendered` derived cache immediately
    this.initialize.apply(this, arguments);
    if (this.autoRender && this.template) {
        this.render();
    }
}

var BaseState = State.extend({
    dataTypes: {
        element: {
            set: function (newVal) {
                return {
                    val: newVal,
                    type: newVal instanceof Element ? 'element' : typeof newVal
                };
            },
            compare: function (el1, el2) {
                return el1 === el2;
            }
        },
        collection: {
            set: function (newVal) {
                return {
                    val: newVal,
                    type: newVal && newVal.isCollection ? 'collection' : typeof newVal
                };
            },
            compare: function (currentVal, newVal) {
                return currentVal === newVal;
            }
        }
    },
    props: {
        model: 'state',
        el: 'element',
        collection: 'collection',
    },
    session: {
        _rendered: ['boolean', true, false]
    },
    derived: {
        hasData: {
            deps: ['model'],
            fn: function () {
                return !!this.model;
            }
        },
        rendered: {
            deps: ['_rendered'],
            fn: function() {
                if (this._rendered) {
                    this.trigger('render', this);
                    return true;
                }
                this.trigger('remove', this);
                return false;
            }
        }
    }
});

// Cached regex to split keys for `delegate`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/;

View.prototype = Object.create(BaseState.prototype);

var queryNoElMsg = 'Query cannot be performed as this.el is not defined. Ensure that the view has been rendered.';

// Set up all inheritable properties and methods.
assign(View.prototype, {
    // ## query
    // Get an single element based on CSS selector scoped to this.el
    // if you pass an empty string it return `this.el`.
    // If you pass an element we just return it back.
    // This lets us use `get` to handle cases where users
    // can pass a selector or an already selected element.
    query: function (selector) {
        if (!this.el) {
            throw new Error(queryNoElMsg);
        }
        if (!selector) return this.el;
        if (typeof selector === 'string') {
            if (matches(this.el, selector)) return this.el;
            return this.el.querySelector(selector) || undefined;
        }
        return selector;
    },

    // ## queryAll
    // Returns an array of elements based on CSS selector scoped to this.el
    // if you pass an empty string it return `this.el`. Also includes root
    // element.
    queryAll: function (selector) {
        if (!this.el) {
            throw new Error(queryNoElMsg);
        }
        if (!selector) return [this.el];
        var res = [];
        if (matches(this.el, selector)) res.push(this.el);
        return res.concat(Array.prototype.slice.call(this.el.querySelectorAll(selector)));
    },

    // ## queryByHook
    // Convenience method for fetching element by it's `data-hook` attribute.
    // Also tries to match against root element.
    // Also supports matching 'one' of several space separated hooks.
    queryByHook: function (hook) {
        return this.query('[data-hook~="' + hook + '"]');
    },

    // ## queryAllByHook
    // Convenience method for fetching all elements by their's `data-hook` attribute.
    queryAllByHook: function (hook) {
        return this.queryAll('[data-hook~="' + hook + '"]');
    },

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function () {},

    // **render** is the core function that your view can override. Its job is
    // to populate its element (`this.el`), with the appropriate HTML.
    _render: function () {
        this._upsertBindings();
        this.renderWithTemplate(this);
        this._rendered = true;
        return this;
    },

    // Removes this view by taking the element out of the DOM, and removing any
    // applicable events listeners.
    _remove: function () {
        if (this.el && this.el.parentNode) this.el.parentNode.removeChild(this.el);
        this._rendered = false;
        this._downsertBindings();
        return this;
    },

    // Change the view's element (`this.el` property), including event
    // re-delegation.
    _handleElementChange: function (element, delegate) {
        if (this.eventManager) this.eventManager.unbind();
        this.eventManager = events(this.el, this);
        this.delegateEvents();
        this._applyBindingsForKey();
        return this;
    },

    // Set callbacks, where `this.events` is a hash of
    //
    // *{"event selector": "callback"}*
    //
    //     {
    //       'mousedown .title':  'edit',
    //       'click .button':     'save',
    //       'click .open':       function (e) { ... }
    //     }
    //
    // pairs. Callbacks will be bound to the view, with `this` set properly.
    // Uses event delegation for efficiency.
    // Omitting the selector binds the event to `this.el`.
    // This only works for delegate-able events: not `focus`, `blur`, and
    // not `change`, `submit`, and `reset` in Internet Explorer.
    delegateEvents: function (events) {
        if (!(events || (events = result(this, 'events')))) return this;
        this.undelegateEvents();
        for (var key in events) {
            this.eventManager.bind(key, events[key]);
        }
        return this;
    },

    // Clears all callbacks previously bound to the view with `delegateEvents`.
    // You usually don't need to use this, but may wish to if you have multiple
    // Backbone views attached to the same DOM element.
    undelegateEvents: function () {
        this.eventManager.unbind();
        return this;
    },

    // ## registerSubview
    // Pass it a view. This can be anything with a `remove` method
    registerSubview: function (view) {
        // Storage for our subviews.
        this._subviews || (this._subviews = []);
        this._subviews.push(view);
        // set the parent reference if it has not been set
        if (!view.parent) view.parent = this;
        return view;
    },

    // ## renderSubview
    // Pass it a view instance and a container element
    // to render it in. It's `remove` method will be called
    // when the parent view is destroyed.
    renderSubview: function (view, container) {
        if (typeof container === 'string') {
            container = this.query(container);
        }
        if (!container) container = this.el;
        this.registerSubview(view);
        container.appendChild(view.render().el);
        return view;
    },

    _applyBindingsForKey: function (name) {
        if (!this.el) return;
        var fns = this._parsedBindings.getGrouped(name);
        var item;
        for (item in fns) {
            fns[item].forEach(function (fn) {
                fn(this.el, getPath(this, item), last(item.split('.')));
            }, this);
        }
    },

    _initializeBindings: function () {
        if (!this.bindings) return;
        this.on('all', function (eventName) {
            if (eventName.slice(0, 7) === 'change:') {
                this._applyBindingsForKey(eventName.split(':')[1]);
            }
        }, this);
    },

    // ## _initializeSubviews
    // this is called at setup and grabs declared subviews
    _initializeSubviews: function () {
        if (!this.subviews) return;
        for (var item in this.subviews) {
            this._parseSubview(this.subviews[item], item);
        }
    },

    // ## _parseSubview
    // helper for parsing out the subview declaration and registering
    // the `waitFor` if need be.
    _parseSubview: function (subview, name) {
        //backwards compatibility with older versions, when `container` was a valid property (#114)
        if (subview.container) {
            subview.selector = subview.container;
        }
        var opts = this._parseSubviewOpts(subview);

        function action() {
            var el, subview;
            // if not rendered or we can't find our element, stop here.
            if (!this.el || !(el = this.query(opts.selector))) return;
            if (!opts.waitFor || getPath(this, opts.waitFor)) {
                subview = this[name] = opts.prepareView.call(this, el);
                if (!subview.el) {
                    this.renderSubview(subview, el);
                } else {
                    subview.render();
                    this.registerSubview(subview);
                }
                this.off('change', action);
            }
        }
        // we listen for main `change` items
        this.on('change', action, this);
    },

    // Parser the declarative subview definition.
    // Overload this method to create your own declarative subview style
    _parseSubviewOpts: function (subview, name) {
        var self = this;
        var opts = {
            selector: subview.selector || '[data-hook="' + subview.hook + '"]',
            waitFor: subview.waitFor || '',
            prepareView: subview.prepareView || function () {
                return new subview.constructor({
                    parent: self
                });
            }
        };
        return opts;
    },

    // Shortcut for doing everything we need to do to
    // render and fully replace current root element.
    // Either define a `template` property of your view
    // or pass in a template directly.
    // The template can either be a string or a function.
    // If it's a function it will be passed the `context`
    // argument.
    renderWithTemplate: function (context, templateArg) {
        var template = templateArg || this.template;
        if (!template) throw new Error('Template string or function needed.');
        var newDom = isString(template) ? template : template.call(this, context || this);
        if (isString(newDom)) newDom = domify(newDom);
        var parent = this.el && this.el.parentNode;
        if (parent) parent.replaceChild(newDom, this.el);
        if (newDom.nodeName === '#document-fragment') throw new Error('Views can only have one root element, including comment nodes.');
        this.el = newDom;
        return this;
    },

    // ## cacheElements
    // This is a shortcut for adding reference to specific elements within your view for
    // access later. This avoids excessive DOM queries and makes it easier to update
    // your view if your template changes.
    //
    // In your `render` method. Use it like so:
    //
    //     render: function () {
    //       this.basicRender();
    //       this.cacheElements({
    //         pages: '#pages',
    //         chat: '#teamChat',
    //         nav: 'nav#views ul',
    //         me: '#me',
    //         cheatSheet: '#cheatSheet',
    //         omniBox: '#awesomeSauce'
    //       });
    //     }
    //
    // Then later you can access elements by reference like so: `this.pages`, or `this.chat`.
    cacheElements: function (hash) {
        for (var item in hash) {
            this[item] = this.query(hash[item]);
        }
        return this;
    },

    // ## listenToAndRun
    // Shortcut for registering a listener for a model
    // and also triggering it right away.
    listenToAndRun: function (object, events, handler) {
        var bound = bind(handler, this);
        this.listenTo(object, events, bound);
        bound();
    },

    // ## animateRemove
    // Placeholder for if you want to do something special when they're removed.
    // For example fade it out, etc.
    // Any override here should call `.remove()` when done.
    animateRemove: function () {
        this.remove();
    },

    // ## renderCollection
    // Method for rendering a collections with individual views.
    // Just pass it the collection, and the view to use for the items in the
    // collection. The collectionView is returned.
    renderCollection: function (collection, ViewClass, container, opts) {
        var containerEl = (typeof container === 'string') ? this.query(container) : container;
        var config = assign({
            collection: collection,
            el: containerEl || this.el,
            view: ViewClass,
            parent: this,
            viewOptions: {
                parent: this
            }
        }, opts);
        var collectionView = new CollectionView(config);
        collectionView.render();
        return this.registerSubview(collectionView);
    },

    _setRender: function(obj) {
        Object.defineProperty(obj, 'render', {
            get: function() {
                return this._render;
            },
            set: function(fn) {
                this._render = function() {
                    fn.apply(this, arguments);
                    this._rendered = true;
                    return this;
                };
            }
        });
    },

    _setRemove: function(obj) {
        Object.defineProperty(obj, 'remove', {
            get: function() {
                return this._remove;
            },
            set: function(fn) {
                this._remove = function() {
                    fn.apply(this, arguments);
                    this._rendered = false;
                    return this;
                };
            }
        });
    },

    _downsertBindings: function() {
        var parsedBindings = this._parsedBindings;
        if (!this.bindingsSet) return;
        if (this._subviews) invoke(flatten(this._subviews), 'remove');
        this.stopListening();
        // TODO: Not sure if this is actually necessary.
        // Just trying to de-reference this potentially large
        // amount of generated functions to avoid memory leaks.
        forEach(parsedBindings, function (properties, modelName) {
            forEach(properties, function (value, key) {
                delete parsedBindings[modelName][key];
            });
            delete parsedBindings[modelName];
        });
        this.bindingsSet = false;
    },

    _upsertBindings: function(attrs) {
        attrs = attrs || this;
        if (this.bindingsSet) return;
        this._parsedBindings = bindings(this.bindings, this);
        this._initializeBindings();
        if (attrs.el && !this.autoRender) {
            this._handleElementChange();
        }
        this._initializeSubviews();
        this.bindingsSet = true;
    }
});

View.prototype._setRender(View.prototype);
View.prototype._setRemove(View.prototype);
View.extend = BaseState.extend;
module.exports = View;
<commit_msg>Removed unnecessary parameter and expanded the comments for the _parseSubviewOpts method.<commit_after>/*$AMPERSAND_VERSION*/
var State = require('ampersand-state');
var CollectionView = require('ampersand-collection-view');
var domify = require('domify');
var uniqueId = require("lodash.uniqueid");
var pick = require("lodash.pick");
var assign = require("lodash.assign");
var forEach = require("lodash.foreach");
var result = require("lodash.result");
var last = require("lodash.last");
var isString = require("lodash.isstring");
var bind = require("lodash.bind");
var flatten = require("lodash.flatten");
var invoke = require("lodash.invoke");
var events = require('events-mixin');
var matches = require('matches-selector');
var bindings = require('ampersand-dom-bindings');
var getPath = require('get-object-path');

function View(attrs) {
    this.cid = uniqueId('view');
    attrs || (attrs = {});
    var parent = attrs.parent;
    delete attrs.parent;
    BaseState.call(this, attrs, {init: false, parent: parent});
    this.on('change:el', this._handleElementChange, this);
    this._upsertBindings();
    this.template = attrs.template || this.template;
    this._cache.rendered = false; // prep `rendered` derived cache immediately
    this.initialize.apply(this, arguments);
    if (this.autoRender && this.template) {
        this.render();
    }
}

var BaseState = State.extend({
    dataTypes: {
        element: {
            set: function (newVal) {
                return {
                    val: newVal,
                    type: newVal instanceof Element ? 'element' : typeof newVal
                };
            },
            compare: function (el1, el2) {
                return el1 === el2;
            }
        },
        collection: {
            set: function (newVal) {
                return {
                    val: newVal,
                    type: newVal && newVal.isCollection ? 'collection' : typeof newVal
                };
            },
            compare: function (currentVal, newVal) {
                return currentVal === newVal;
            }
        }
    },
    props: {
        model: 'state',
        el: 'element',
        collection: 'collection',
    },
    session: {
        _rendered: ['boolean', true, false]
    },
    derived: {
        hasData: {
            deps: ['model'],
            fn: function () {
                return !!this.model;
            }
        },
        rendered: {
            deps: ['_rendered'],
            fn: function() {
                if (this._rendered) {
                    this.trigger('render', this);
                    return true;
                }
                this.trigger('remove', this);
                return false;
            }
        }
    }
});

// Cached regex to split keys for `delegate`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/;

View.prototype = Object.create(BaseState.prototype);

var queryNoElMsg = 'Query cannot be performed as this.el is not defined. Ensure that the view has been rendered.';

// Set up all inheritable properties and methods.
assign(View.prototype, {
    // ## query
    // Get an single element based on CSS selector scoped to this.el
    // if you pass an empty string it return `this.el`.
    // If you pass an element we just return it back.
    // This lets us use `get` to handle cases where users
    // can pass a selector or an already selected element.
    query: function (selector) {
        if (!this.el) {
            throw new Error(queryNoElMsg);
        }
        if (!selector) return this.el;
        if (typeof selector === 'string') {
            if (matches(this.el, selector)) return this.el;
            return this.el.querySelector(selector) || undefined;
        }
        return selector;
    },

    // ## queryAll
    // Returns an array of elements based on CSS selector scoped to this.el
    // if you pass an empty string it return `this.el`. Also includes root
    // element.
    queryAll: function (selector) {
        if (!this.el) {
            throw new Error(queryNoElMsg);
        }
        if (!selector) return [this.el];
        var res = [];
        if (matches(this.el, selector)) res.push(this.el);
        return res.concat(Array.prototype.slice.call(this.el.querySelectorAll(selector)));
    },

    // ## queryByHook
    // Convenience method for fetching element by it's `data-hook` attribute.
    // Also tries to match against root element.
    // Also supports matching 'one' of several space separated hooks.
    queryByHook: function (hook) {
        return this.query('[data-hook~="' + hook + '"]');
    },

    // ## queryAllByHook
    // Convenience method for fetching all elements by their's `data-hook` attribute.
    queryAllByHook: function (hook) {
        return this.queryAll('[data-hook~="' + hook + '"]');
    },

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function () {},

    // **render** is the core function that your view can override. Its job is
    // to populate its element (`this.el`), with the appropriate HTML.
    _render: function () {
        this._upsertBindings();
        this.renderWithTemplate(this);
        this._rendered = true;
        return this;
    },

    // Removes this view by taking the element out of the DOM, and removing any
    // applicable events listeners.
    _remove: function () {
        if (this.el && this.el.parentNode) this.el.parentNode.removeChild(this.el);
        this._rendered = false;
        this._downsertBindings();
        return this;
    },

    // Change the view's element (`this.el` property), including event
    // re-delegation.
    _handleElementChange: function (element, delegate) {
        if (this.eventManager) this.eventManager.unbind();
        this.eventManager = events(this.el, this);
        this.delegateEvents();
        this._applyBindingsForKey();
        return this;
    },

    // Set callbacks, where `this.events` is a hash of
    //
    // *{"event selector": "callback"}*
    //
    //     {
    //       'mousedown .title':  'edit',
    //       'click .button':     'save',
    //       'click .open':       function (e) { ... }
    //     }
    //
    // pairs. Callbacks will be bound to the view, with `this` set properly.
    // Uses event delegation for efficiency.
    // Omitting the selector binds the event to `this.el`.
    // This only works for delegate-able events: not `focus`, `blur`, and
    // not `change`, `submit`, and `reset` in Internet Explorer.
    delegateEvents: function (events) {
        if (!(events || (events = result(this, 'events')))) return this;
        this.undelegateEvents();
        for (var key in events) {
            this.eventManager.bind(key, events[key]);
        }
        return this;
    },

    // Clears all callbacks previously bound to the view with `delegateEvents`.
    // You usually don't need to use this, but may wish to if you have multiple
    // Backbone views attached to the same DOM element.
    undelegateEvents: function () {
        this.eventManager.unbind();
        return this;
    },

    // ## registerSubview
    // Pass it a view. This can be anything with a `remove` method
    registerSubview: function (view) {
        // Storage for our subviews.
        this._subviews || (this._subviews = []);
        this._subviews.push(view);
        // set the parent reference if it has not been set
        if (!view.parent) view.parent = this;
        return view;
    },

    // ## renderSubview
    // Pass it a view instance and a container element
    // to render it in. It's `remove` method will be called
    // when the parent view is destroyed.
    renderSubview: function (view, container) {
        if (typeof container === 'string') {
            container = this.query(container);
        }
        if (!container) container = this.el;
        this.registerSubview(view);
        container.appendChild(view.render().el);
        return view;
    },

    _applyBindingsForKey: function (name) {
        if (!this.el) return;
        var fns = this._parsedBindings.getGrouped(name);
        var item;
        for (item in fns) {
            fns[item].forEach(function (fn) {
                fn(this.el, getPath(this, item), last(item.split('.')));
            }, this);
        }
    },

    _initializeBindings: function () {
        if (!this.bindings) return;
        this.on('all', function (eventName) {
            if (eventName.slice(0, 7) === 'change:') {
                this._applyBindingsForKey(eventName.split(':')[1]);
            }
        }, this);
    },

    // ## _initializeSubviews
    // this is called at setup and grabs declared subviews
    _initializeSubviews: function () {
        if (!this.subviews) return;
        for (var item in this.subviews) {
            this._parseSubview(this.subviews[item], item);
        }
    },

    // ## _parseSubview
    // helper for parsing out the subview declaration and registering
    // the `waitFor` if need be.
    _parseSubview: function (subview, name) {
        //backwards compatibility with older versions, when `container` was a valid property (#114)
        if (subview.container) {
            subview.selector = subview.container;
        }
        var opts = this._parseSubviewOpts(subview);

        function action() {
            var el, subview;
            // if not rendered or we can't find our element, stop here.
            if (!this.el || !(el = this.query(opts.selector))) return;
            if (!opts.waitFor || getPath(this, opts.waitFor)) {
                subview = this[name] = opts.prepareView.call(this, el);
                if (!subview.el) {
                    this.renderSubview(subview, el);
                } else {
                    subview.render();
                    this.registerSubview(subview);
                }
                this.off('change', action);
            }
        }
        // we listen for main `change` items
        this.on('change', action, this);
    },

    // Parses the declarative subview definition.
    // You may overload this method to create your own declarative subview style.
    // You must return an object with members 'selector', 'waitFor' and 'prepareView'.
    // waitFor is trigged on the view 'change' event and so one way to extend the deferred view
    // construction is to add an additional property (props) to the view. Then setting this property
    // will satisfy the waitFor condition. You can then extend the prepareView function to pass in
    // additional data from the parent view. This can allow you to have multi-stage rendering of
    // custom data formats and to declaratively define.
    _parseSubviewOpts: function (subview) {
        var self = this;
        var opts = {
            selector: subview.selector || '[data-hook="' + subview.hook + '"]',
            waitFor: subview.waitFor || '',
            prepareView: subview.prepareView || function () {
                return new subview.constructor({
                    parent: self
                });
            }
        };
        return opts;
    },

    // Shortcut for doing everything we need to do to
    // render and fully replace current root element.
    // Either define a `template` property of your view
    // or pass in a template directly.
    // The template can either be a string or a function.
    // If it's a function it will be passed the `context`
    // argument.
    renderWithTemplate: function (context, templateArg) {
        var template = templateArg || this.template;
        if (!template) throw new Error('Template string or function needed.');
        var newDom = isString(template) ? template : template.call(this, context || this);
        if (isString(newDom)) newDom = domify(newDom);
        var parent = this.el && this.el.parentNode;
        if (parent) parent.replaceChild(newDom, this.el);
        if (newDom.nodeName === '#document-fragment') throw new Error('Views can only have one root element, including comment nodes.');
        this.el = newDom;
        return this;
    },

    // ## cacheElements
    // This is a shortcut for adding reference to specific elements within your view for
    // access later. This avoids excessive DOM queries and makes it easier to update
    // your view if your template changes.
    //
    // In your `render` method. Use it like so:
    //
    //     render: function () {
    //       this.basicRender();
    //       this.cacheElements({
    //         pages: '#pages',
    //         chat: '#teamChat',
    //         nav: 'nav#views ul',
    //         me: '#me',
    //         cheatSheet: '#cheatSheet',
    //         omniBox: '#awesomeSauce'
    //       });
    //     }
    //
    // Then later you can access elements by reference like so: `this.pages`, or `this.chat`.
    cacheElements: function (hash) {
        for (var item in hash) {
            this[item] = this.query(hash[item]);
        }
        return this;
    },

    // ## listenToAndRun
    // Shortcut for registering a listener for a model
    // and also triggering it right away.
    listenToAndRun: function (object, events, handler) {
        var bound = bind(handler, this);
        this.listenTo(object, events, bound);
        bound();
    },

    // ## animateRemove
    // Placeholder for if you want to do something special when they're removed.
    // For example fade it out, etc.
    // Any override here should call `.remove()` when done.
    animateRemove: function () {
        this.remove();
    },

    // ## renderCollection
    // Method for rendering a collections with individual views.
    // Just pass it the collection, and the view to use for the items in the
    // collection. The collectionView is returned.
    renderCollection: function (collection, ViewClass, container, opts) {
        var containerEl = (typeof container === 'string') ? this.query(container) : container;
        var config = assign({
            collection: collection,
            el: containerEl || this.el,
            view: ViewClass,
            parent: this,
            viewOptions: {
                parent: this
            }
        }, opts);
        var collectionView = new CollectionView(config);
        collectionView.render();
        return this.registerSubview(collectionView);
    },

    _setRender: function(obj) {
        Object.defineProperty(obj, 'render', {
            get: function() {
                return this._render;
            },
            set: function(fn) {
                this._render = function() {
                    fn.apply(this, arguments);
                    this._rendered = true;
                    return this;
                };
            }
        });
    },

    _setRemove: function(obj) {
        Object.defineProperty(obj, 'remove', {
            get: function() {
                return this._remove;
            },
            set: function(fn) {
                this._remove = function() {
                    fn.apply(this, arguments);
                    this._rendered = false;
                    return this;
                };
            }
        });
    },

    _downsertBindings: function() {
        var parsedBindings = this._parsedBindings;
        if (!this.bindingsSet) return;
        if (this._subviews) invoke(flatten(this._subviews), 'remove');
        this.stopListening();
        // TODO: Not sure if this is actually necessary.
        // Just trying to de-reference this potentially large
        // amount of generated functions to avoid memory leaks.
        forEach(parsedBindings, function (properties, modelName) {
            forEach(properties, function (value, key) {
                delete parsedBindings[modelName][key];
            });
            delete parsedBindings[modelName];
        });
        this.bindingsSet = false;
    },

    _upsertBindings: function(attrs) {
        attrs = attrs || this;
        if (this.bindingsSet) return;
        this._parsedBindings = bindings(this.bindings, this);
        this._initializeBindings();
        if (attrs.el && !this.autoRender) {
            this._handleElementChange();
        }
        this._initializeSubviews();
        this.bindingsSet = true;
    }
});

View.prototype._setRender(View.prototype);
View.prototype._setRemove(View.prototype);
View.extend = BaseState.extend;
module.exports = View;
<|endoftext|>


<commit_before>/**
 * @type jquery.layout Global variable so layout state management
 * can pick it up.
 */
var ss_MainLayout;

(function($) {

	// setup jquery.concrete
	$.concrete.warningLevel = $.concrete.WARN_LEVEL_BESTPRACTISE;

	// global ajax error handlers
	$.ajaxSetup({
		error: function(xmlhttp, status, error) {
			var msg = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.statusText;
			statusMessage(msg, 'bad');
		}
	});

	/**
	 * Available Custom Events:
	 *

	 *
ajaxsubmit

	 *
validate

	 *
loadnewpage

	 *
	 * @class Main LeftAndMain interface with some control
	 * panel and an edit form.
	 * @name ss.LeftAndMain
	 */
	$('.LeftAndMain').concrete('ss', function($){
		return/** @lends ss.EditMemberProfile */ {

			/**
			 * Reference to jQuery.layout element
			 * @type Object
			 */
			MainLayout: null,

			/**
			 * @type Number Interval in which /Security/ping will be checked for a valid login session.
			 */
			PingIntervalSeconds: 5*60,

			onmatch: function() {
				var self = this;

				// Remove loading screen
				$('.ss-loading-screen').hide();
				$('body').removeClass('stillLoading');

				// Layout
				ss_MainLayout = this._setupLayout();
				this.setMainLayout(ss_MainLayout);
				layoutState.options.keys = "west.size,west.isClosed";
				$(window).unload(function(){ layoutState.save('ss_MainLayout');});

				this._setupPinging();
				this._resizeChildren();

				// artificially delay the resize event 200ms
				// to avoid overlapping height changes in different onresize() methods
				$(window).resize(function () {
					var timerID = "timerLeftAndMainResize";
					if (window[timerID]) clearTimeout(window[timerID]);
					window[timerID] = setTimeout(function() {self._resizeChildren();}, 200);
				});

				// If tab has no nested tabs, set overflow to auto
				$(this).find('.tab').not(':has(.tab)').css('overflow', 'auto');

				// trigger resize whenever new tabs are shown
				// @todo This is called multiple times when tabs are loaded
				this.find('.ss-tabset').bind('tabsshow', function() {self._resizeChildren();});

				$('#Form_EditForm').bind('loadnewpage', function() {self._resizeChildren();});

				this._super();
			},

			/**
			 * Initialize jQuery layout manager with the following panes:
			 * - east: Tree, Page Version History, Site Reports
			 * - center: Form
			 * - west: "Insert Image", "Insert Link", "Insert Flash" panes
			 * - north: CMS area menu bar
			 * - south: "Page view", "profile" and "logout" links
			 */
			_setupLayout: function() {
				var self = this;

				// layout containing the tree, CMS menu, the main form etc.
				var savedLayoutSettings = layoutState.load('ss_MainLayout');
				var layoutSettings = jQuery.extend({
					defaults: {
						// TODO Reactivate once we have localized values
						togglerTip_open: '',
						togglerTip_closed: '',
						resizerTip: '',
						sliderTip: '',
						onresize: function() {self._resizeChildren();},
						onopen: function() {self._resizeChildren();}
					},
					north: {
						slidable: false,
						resizable: false,
						size: 35,
						togglerLength_open: 0
					},
					south: {
						slidable: false,
						resizable: false,
						size: 23,
						togglerLength_open: 0
					},
					west: {
						size: 225,
						fxName: "none"
					},
					east: {
						initClosed: true,
						// multiple panels which are triggered through tinymce buttons,
						// so a user shouldn't be able to toggle this panel manually
						initHidden: true,
						spacing_closed: 0,
						fxName: "none",
						size: 250
					},
					center: {}
				}, savedLayoutSettings);
				var layout = $('body').layout(layoutSettings);

				// Adjust tree accordion etc. in left panel to work correctly
				// with jQuery.layout (see http://layout.jquery-dev.net/tips.html#Widget_Accordion)
				this.find("#treepanes").accordion({
					fillSpace: true,
					animated: false
				});

				return layout;
			},

			/**
			 * This function is called by prototype when it receives notification that the user was logged out.
			 * It uses /Security/ping for this purpose, which should return '1' if a valid user session exists.
			 * It redirects back to the login form if the URL is either unreachable, or returns '0'.
			 */
			_setupPinging: function() {
				var onSessionLost = function(xmlhttp, status) {
					if(xmlhttp.status > 400 || xmlhttp.responseText == 0) {
						// TODO will pile up additional alerts when left unattended
						if(window.open('Security/login')) {
						    alert("Please log in and then try again");
						} else {
						    alert("Please enable pop-ups for this site");
						}
					}
				};

				// setup pinging for login expiry
				setInterval(function() {
					jQuery.ajax({
						url: "Security/ping",
						global: false,
						complete: onSessionLost
					});
				}, this.PingIntervalSeconds() * 1000);
			},

			/**
			 * Resize elements in center panel
			 * to fit the boundary box provided by the layout manager
			 */
			_resizeChildren: function() {
				$("#treepanes", this).accordion("resize");
				$('#sitetree_and_tools', this).fitHeightToParent();
				$('#contentPanel form', this).fitHeightToParent();
				$('#contentPanel form fieldset', this).fitHeightToParent();
				$('#contentPanel form fieldset .content', this).fitHeightToParent();
				$('#Form_EditForm').fitHeightToParent();
				$('#Form_EditForm fieldset', this).fitHeightToParent();
				// Order of resizing is important: Outer to inner
				// TODO Only supports two levels of tabs at the moment
				$('#Form_EditForm fieldset > .ss-tabset', this).fitHeightToParent();
				$('#Form_EditForm fieldset > .ss-tabset > .tab', this).fitHeightToParent();
				$('#Form_EditForm fieldset > .ss-tabset > .tab > .ss-tabset', this).fitHeightToParent();
				$('#Form_EditForm fieldset > .ss-tabset > .tab > .ss-tabset > .tab', this).fitHeightToParent();
			}
		};
	});

	/**
	 * @class Make all buttons "hoverable" with jQuery theming.
	 * @name ss.LeftAndMain.Buttons
	 */
	$('.LeftAndMain :submit, .LeftAndMain button, .LeftAndMain :reset').concrete('ss', function($){
		return/** @lends ss.LeftAndMain.Buttons */{
			onmatch: function() {
				this.addClass(
					'ui-state-default ' +
					'ui-corner-all'
				)
				.hover(
					function() {
						$(this).addClass('ui-state-hover');
					},
					function() {
						$(this).removeClass('ui-state-hover');
					}
				)
				.focus(function() {
					$(this).addClass('ui-state-focus');
				})
				.blur(function() {
					$(this).removeClass('ui-state-focus');
				});

				this._super();
			}
		};
	});

	/**
	 * @class Container for tree actions like "create", "search", etc.
	 * @name ss.TreeActions
	 */
	$('#TreeActions').concrete('ss', function($){
		return/** @lends ss.TreeActions */{

			/**
			 * Setup "create", "search", "batch actions" layers above tree.
			 * All tab contents are closed by default.
			 */
			onmatch: function() {
				this.tabs({
					collapsible: true,
					selected: parseInt(jQuery.cookie('ui-tabs-TreeActions'), 10) || null,
					cookie: { expires: 30, path: '/', name: 'ui-tabs-TreeActions' }
				});
			}
		};
	});

	/**
	 * @class Link for editing the profile for a logged-in member
	 * through a modal dialog.
	 * @name ss.EditMemberProfile
	 */
	$('a#EditMemberProfile').concrete('ss', function($){
		return/** @lends ss.EditMemberProfile */{

			onmatch: function() {
				var self = this;

				this.bind('click', function(e) {return self._openPopup();});

				$('body').append(
					'
'
					+ ''
					+ ''
					+ '
'
				);

				var cookieVal = (jQuery.cookie) ? JSON.parse(jQuery.cookie('ss-ui-dialog')) : false;
				$("#ss-ui-dialog").dialog(jQuery.extend({
					autoOpen: false,
					bgiframe: true,
					modal: true,
					height: 300,
					width: 500,
					ghost: true,
					resizeStop: function(e, ui) {
						self._resize();
						self._saveState();
					},
					dragStop: function(e, ui) {
						self._saveState();
					},
					// TODO i18n
					title: 'Edit Profile'
				}, cookieVal)).css('overflow', 'hidden');

				$('#ss-ui-dialog-iframe').bind('load', function(e) {self._resize();});
			},

			_openPopup: function(e) {
				$('#ss-ui-dialog-iframe').attr('src', this.attr('href'));

				$("#ss-ui-dialog").dialog('open');

				return false;
			},

			_resize: function() {
				var iframe = $('#ss-ui-dialog-iframe');
				var container = $('#ss-ui-dialog');

				iframe.attr('width',
					container.innerWidth()
					- parseFloat(container.css('paddingLeft'))
					- parseFloat(container.css('paddingRight'))
				);
				iframe.attr('height',
					container.innerHeight()
					- parseFloat(container.css('paddingTop'))
					- parseFloat(container.css('paddingBottom'))
				);

				this._saveState();
			},

			_saveState: function() {
				var container = $('#ss-ui-dialog');

				// save size in cookie (optional)
				if(jQuery.cookie && container.width() && container.height()) {
					jQuery.cookie(
						'ss-ui-dialog',
						JSON.stringify({
							width: parseInt(container.width(), 10),
							height: parseInt(container.height(), 10),
							position: [
								parseInt(container.offset().top, 10),
								parseInt(container.offset().left, 10)
							]
						}),
						{ expires: 30, path: '/'}
					);
				}
			}
		};
	});

	/**
	 * @class Links for viewing the currently loaded page
	 * in different modes: 'live', 'stage' or 'archived'.
	 * Automatically updates on loading a new page.
	 * @name ss.switchViewLinks
	 * @requires jquery.metadata
	 */
	$('#switchView a').concrete('ss', function($){

		return/** @lends ss.switchViewLinks */{

			/**
			 * @type DOMElement
			 */
			Form: null,

			onmatch: function() {
				var self = this;
				this.setForm($('#Form_EditForm'));

				jQuery('#Form_EditForm').bind('loadnewpage delete', function(e) {self.refresh();});
				self.refresh();
			},

			/**
			 * Parse new links based on the underlying form URLSegment,
			 * preserving the ?stage URL parameters if necessary.
			 */
			refresh: function() {
				// TODO Compatible with nested urls?
				var urlSegment = this.Form().find(':input[name=URLSegment]').val();
				if(urlSegment) {
					var locale = this.Form().find(':input[name=Locale]').val();
					var url = urlSegment;
					if(this.metadata().params) url += '?' + this.metadata().params;
					if(locale) url += ((url.indexOf('?') > 0) ? '&' : '?') + "locale=" + locale;
					this.attr('href', url);
				}

				// hide fields if no URLSegment is present
				this.toggle((urlSegment));
			},

			onclick: function(e) {
				// Open in popup
				window.open($(e.target).attr('href'));
				return false;
			}
		};
	});

})(jQuery);

// Backwards compatibility
var statusMessage = function(text, type) {
	jQuery.noticeAdd({text: text, type: type});
};

var errorMessage = function(text) {
	jQuery.noticeAdd({text: text, type: 'error'});
};

returnFalse = function() {
	return false;
};

/**
 * Find and enable TinyMCE on all htmleditor fields
 * Pulled in from old tinymce.template.js
 */

function nullConverter(url) {
	return url;
};<commit_msg>BUGFIX Only resizing form in LeftAndMain after showing tabs, to ensure we get the right dimensions. Concrete was applying the tabs styling too late and in an async fashion, so we cant use it here.<commit_after>/**
 * @type jquery.layout Global variable so layout state management
 * can pick it up.
 */
var ss_MainLayout;

(function($) {

	// setup jquery.concrete
	$.concrete.warningLevel = $.concrete.WARN_LEVEL_BESTPRACTISE;

	// global ajax error handlers
	$.ajaxSetup({
		error: function(xmlhttp, status, error) {
			var msg = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.statusText;
			statusMessage(msg, 'bad');
		}
	});

	/**
	 * Available Custom Events:
	 *

	 *
ajaxsubmit

	 *
validate

	 *
loadnewpage

	 *
	 * @class Main LeftAndMain interface with some control
	 * panel and an edit form.
	 * @name ss.LeftAndMain
	 */
	$('.LeftAndMain').concrete('ss', function($){
		return/** @lends ss.EditMemberProfile */ {

			/**
			 * Reference to jQuery.layout element
			 * @type Object
			 */
			MainLayout: null,

			/**
			 * @type Number Interval in which /Security/ping will be checked for a valid login session.
			 */
			PingIntervalSeconds: 5*60,

			onmatch: function() {
				var self = this;

				// Remove loading screen
				$('.ss-loading-screen').hide();
				$('body').removeClass('stillLoading');

				// Layout
				ss_MainLayout = this._setupLayout();
				this.setMainLayout(ss_MainLayout);
				layoutState.options.keys = "west.size,west.isClosed";
				$(window).unload(function(){ layoutState.save('ss_MainLayout');});

				this._setupPinging();
				this._resizeChildren();

				// artificially delay the resize event 200ms
				// to avoid overlapping height changes in different onresize() methods
				$(window).resize(function () {
					var timerID = "timerLeftAndMainResize";
					if (window[timerID]) clearTimeout(window[timerID]);
					window[timerID] = setTimeout(function() {self._resizeChildren();}, 200);
				});

				// If tab has no nested tabs, set overflow to auto
				$(this).find('.tab').not(':has(.tab)').css('overflow', 'auto');

				// trigger resize whenever new tabs are shown
				// @todo This is called multiple times when tabs are loaded
				this.find('.ss-tabset').live('tabsshow', function() {self._resizeChildren();});

				// @todo Doesn't resize properly if the response doesn't contain a tabset (see above)
				//$('#Form_EditForm').bind('loadnewpage', function() {self._resizeChildren();});

				this._super();
			},

			/**
			 * Initialize jQuery layout manager with the following panes:
			 * - east: Tree, Page Version History, Site Reports
			 * - center: Form
			 * - west: "Insert Image", "Insert Link", "Insert Flash" panes
			 * - north: CMS area menu bar
			 * - south: "Page view", "profile" and "logout" links
			 */
			_setupLayout: function() {
				var self = this;

				// layout containing the tree, CMS menu, the main form etc.
				var savedLayoutSettings = layoutState.load('ss_MainLayout');
				var layoutSettings = jQuery.extend({
					defaults: {
						// TODO Reactivate once we have localized values
						togglerTip_open: '',
						togglerTip_closed: '',
						resizerTip: '',
						sliderTip: '',
						onresize: function() {self._resizeChildren();},
						onopen: function() {self._resizeChildren();}
					},
					north: {
						slidable: false,
						resizable: false,
						size: 35,
						togglerLength_open: 0
					},
					south: {
						slidable: false,
						resizable: false,
						size: 23,
						togglerLength_open: 0
					},
					west: {
						size: 225,
						fxName: "none"
					},
					east: {
						initClosed: true,
						// multiple panels which are triggered through tinymce buttons,
						// so a user shouldn't be able to toggle this panel manually
						initHidden: true,
						spacing_closed: 0,
						fxName: "none",
						size: 250
					},
					center: {}
				}, savedLayoutSettings);
				var layout = $('body').layout(layoutSettings);

				// Adjust tree accordion etc. in left panel to work correctly
				// with jQuery.layout (see http://layout.jquery-dev.net/tips.html#Widget_Accordion)
				this.find("#treepanes").accordion({
					fillSpace: true,
					animated: false
				});

				return layout;
			},

			/**
			 * This function is called by prototype when it receives notification that the user was logged out.
			 * It uses /Security/ping for this purpose, which should return '1' if a valid user session exists.
			 * It redirects back to the login form if the URL is either unreachable, or returns '0'.
			 */
			_setupPinging: function() {
				var onSessionLost = function(xmlhttp, status) {
					if(xmlhttp.status > 400 || xmlhttp.responseText == 0) {
						// TODO will pile up additional alerts when left unattended
						if(window.open('Security/login')) {
						    alert("Please log in and then try again");
						} else {
						    alert("Please enable pop-ups for this site");
						}
					}
				};

				// setup pinging for login expiry
				setInterval(function() {
					jQuery.ajax({
						url: "Security/ping",
						global: false,
						complete: onSessionLost
					});
				}, this.PingIntervalSeconds() * 1000);
			},

			/**
			 * Resize elements in center panel
			 * to fit the boundary box provided by the layout manager
			 */
			_resizeChildren: function() {
				$("#treepanes", this).accordion("resize");
				$('#sitetree_and_tools', this).fitHeightToParent();
				$('#contentPanel form', this).fitHeightToParent();
				$('#contentPanel form fieldset', this).fitHeightToParent();
				$('#contentPanel form fieldset .content', this).fitHeightToParent();
				$('#Form_EditForm').fitHeightToParent();
				$('#Form_EditForm fieldset', this).fitHeightToParent();
				// Order of resizing is important: Outer to inner
				// TODO Only supports two levels of tabs at the moment
				$('#Form_EditForm fieldset > .ss-tabset', this).fitHeightToParent();
				$('#Form_EditForm fieldset > .ss-tabset > .tab', this).fitHeightToParent();
				$('#Form_EditForm fieldset > .ss-tabset > .tab > .ss-tabset', this).fitHeightToParent();
				$('#Form_EditForm fieldset > .ss-tabset > .tab > .ss-tabset > .tab', this).fitHeightToParent();
			}
		};
	});

	/**
	 * @class Make all buttons "hoverable" with jQuery theming.
	 * @name ss.LeftAndMain.Buttons
	 */
	$('.LeftAndMain :submit, .LeftAndMain button, .LeftAndMain :reset').concrete('ss', function($){
		return/** @lends ss.LeftAndMain.Buttons */{
			onmatch: function() {
				this.addClass(
					'ui-state-default ' +
					'ui-corner-all'
				)
				.hover(
					function() {
						$(this).addClass('ui-state-hover');
					},
					function() {
						$(this).removeClass('ui-state-hover');
					}
				)
				.focus(function() {
					$(this).addClass('ui-state-focus');
				})
				.blur(function() {
					$(this).removeClass('ui-state-focus');
				});

				this._super();
			}
		};
	});

	/**
	 * @class Container for tree actions like "create", "search", etc.
	 * @name ss.TreeActions
	 */
	$('#TreeActions').concrete('ss', function($){
		return/** @lends ss.TreeActions */{

			/**
			 * Setup "create", "search", "batch actions" layers above tree.
			 * All tab contents are closed by default.
			 */
			onmatch: function() {
				this.tabs({
					collapsible: true,
					selected: parseInt(jQuery.cookie('ui-tabs-TreeActions'), 10) || null,
					cookie: { expires: 30, path: '/', name: 'ui-tabs-TreeActions' }
				});
			}
		};
	});

	/**
	 * @class Link for editing the profile for a logged-in member
	 * through a modal dialog.
	 * @name ss.EditMemberProfile
	 */
	$('a#EditMemberProfile').concrete('ss', function($){
		return/** @lends ss.EditMemberProfile */{

			onmatch: function() {
				var self = this;

				this.bind('click', function(e) {return self._openPopup();});

				$('body').append(
					'
'
					+ ''
					+ ''
					+ '
'
				);

				var cookieVal = (jQuery.cookie) ? JSON.parse(jQuery.cookie('ss-ui-dialog')) : false;
				$("#ss-ui-dialog").dialog(jQuery.extend({
					autoOpen: false,
					bgiframe: true,
					modal: true,
					height: 300,
					width: 500,
					ghost: true,
					resizeStop: function(e, ui) {
						self._resize();
						self._saveState();
					},
					dragStop: function(e, ui) {
						self._saveState();
					},
					// TODO i18n
					title: 'Edit Profile'
				}, cookieVal)).css('overflow', 'hidden');

				$('#ss-ui-dialog-iframe').bind('load', function(e) {self._resize();});
			},

			_openPopup: function(e) {
				$('#ss-ui-dialog-iframe').attr('src', this.attr('href'));

				$("#ss-ui-dialog").dialog('open');

				return false;
			},

			_resize: function() {
				var iframe = $('#ss-ui-dialog-iframe');
				var container = $('#ss-ui-dialog');

				iframe.attr('width',
					container.innerWidth()
					- parseFloat(container.css('paddingLeft'))
					- parseFloat(container.css('paddingRight'))
				);
				iframe.attr('height',
					container.innerHeight()
					- parseFloat(container.css('paddingTop'))
					- parseFloat(container.css('paddingBottom'))
				);

				this._saveState();
			},

			_saveState: function() {
				var container = $('#ss-ui-dialog');

				// save size in cookie (optional)
				if(jQuery.cookie && container.width() && container.height()) {
					jQuery.cookie(
						'ss-ui-dialog',
						JSON.stringify({
							width: parseInt(container.width(), 10),
							height: parseInt(container.height(), 10),
							position: [
								parseInt(container.offset().top, 10),
								parseInt(container.offset().left, 10)
							]
						}),
						{ expires: 30, path: '/'}
					);
				}
			}
		};
	});

	/**
	 * @class Links for viewing the currently loaded page
	 * in different modes: 'live', 'stage' or 'archived'.
	 * Automatically updates on loading a new page.
	 * @name ss.switchViewLinks
	 * @requires jquery.metadata
	 */
	$('#switchView a').concrete('ss', function($){

		return/** @lends ss.switchViewLinks */{

			/**
			 * @type DOMElement
			 */
			Form: null,

			onmatch: function() {
				var self = this;
				this.setForm($('#Form_EditForm'));

				jQuery('#Form_EditForm').bind('loadnewpage delete', function(e) {self.refresh();});
				self.refresh();
			},

			/**
			 * Parse new links based on the underlying form URLSegment,
			 * preserving the ?stage URL parameters if necessary.
			 */
			refresh: function() {
				// TODO Compatible with nested urls?
				var urlSegment = this.Form().find(':input[name=URLSegment]').val();
				if(urlSegment) {
					var locale = this.Form().find(':input[name=Locale]').val();
					var url = urlSegment;
					if(this.metadata().params) url += '?' + this.metadata().params;
					if(locale) url += ((url.indexOf('?') > 0) ? '&' : '?') + "locale=" + locale;
					this.attr('href', url);
				}

				// hide fields if no URLSegment is present
				this.toggle((urlSegment));
			},

			onclick: function(e) {
				// Open in popup
				window.open($(e.target).attr('href'));
				return false;
			}
		};
	});

})(jQuery);

// Backwards compatibility
var statusMessage = function(text, type) {
	jQuery.noticeAdd({text: text, type: type});
};

var errorMessage = function(text) {
	jQuery.noticeAdd({text: text, type: 'error'});
};

returnFalse = function() {
	return false;
};

/**
 * Find and enable TinyMCE on all htmleditor fields
 * Pulled in from old tinymce.template.js
 */

function nullConverter(url) {
	return url;
};<|endoftext|>