<commit_before>/*---------------------
	:: <%- entity %>
	-> adapter
---------------------*/
var adapter = {

	identity: '<%- entity %>',

	// The complete set of reusable connections from the main app to this adapter
	clamps: {},

	// Required method to set up the adapter
	// This checks the collection being registered for a few uniqueness characteristics you might be interested in
	// (e.g. if this is MySQL, is the hostname, username, password, and database the same?)
	// This keeps the number of clamps to a minimum, even though this code is being shared in a polymorphic fashion
	registerCollection: function (collection, cb) {

		// If a suitably similar clamp to this adapter already exists, use it
		var found = _.find(this.clamps, function (link) {

			// Put similarity criteria here
			return _.isEqual(link, collection);
		});
		if (found) this.clamps[collection.identity] = found;

		// Otherwise, create an artifact of this clamp in case it can be reused
		else this.clamps[collection.identity] = {

			// Store identifying info about this clamp so that it can be reused safely
			somethingAboutMe: collection.somethingAboutMe
		};

		cb();
	}
};

_.bindAll(adapter);
module.exports = adapter;<commit_msg>Removed identity from adapter blueprint (this gets automatically included by loader)<commit_after>/*---------------------
	:: <%- entity %>
	-> adapter
---------------------*/
var adapter = {

	// The complete set of reusable connections from the main app to this adapter
	clamps: {},

	// Required method to set up the adapter
	// This checks the collection being registered for a few uniqueness characteristics you might be interested in
	// (e.g. if this is MySQL, is the hostname, username, password, and database the same?)
	// This keeps the number of clamps to a minimum, even though this code is being shared in a polymorphic fashion
	registerCollection: function (collection, cb) {

		// If a suitably similar clamp to this adapter already exists, use it
		var found = _.find(this.clamps, function (link) {

			// Put similarity criteria here
			return _.isEqual(link, collection);
		});
		if (found) this.clamps[collection.identity] = found;

		// Otherwise, create an artifact of this clamp in case it can be reused
		else this.clamps[collection.identity] = {

			// Store identifying info about this clamp so that it can be reused safely
			somethingAboutMe: collection.somethingAboutMe
		};

		cb();
	}
};

_.bindAll(adapter);
module.exports = adapter;<|endoftext|>


<commit_before>/* global require, exports, console */
/** Manages framescripts and communication with those scripts

    Likely the only function you'll use is `callScript()`
    */
const { getBrowserForTab } = require('sdk/tabs/utils');
const { defer } = require('sdk/core/promise');
const { viewFor } = require("sdk/view/core");
const { setTimeout, clearTimeout } = require("sdk/timers");

let DEBUG = false;
let loadedTimestamp = Date.now();

/** call setDebug(true) to get debug information on the console */
exports.setDebug = function (val) {
  DEBUG = val;
};

function logDebug() {
  if (! DEBUG) {
    return;
  }
  let args = [];
  for (var i=0; i<arguments.length; i++) {
    if (typeof arguments[i] == "object" &&
      JSON.stringify(arguments[i]).length > 100) {
        args.push("[Object keys: " + JSON.stringify(Object.keys(arguments[i])) + "]");
    } else {
      args.push(arguments[i]);
    }
  }
  console.info.apply(console, args);
}

/** Gets the XUL browser object for a given tab (XUL tab or SDK tab) */
function getBrowser(tab) {
  if (tab && tab.attach) {
    // Convert from SDK tab to XUL tab
    tab = viewFor(tab);
  }
  return getBrowserForTab(tab);
}

/** Ensures a script is attached to a given tab

    Framescripts are long-lived, and live longer than any one page that is
    loaded.  This attaches tracking attributes to the browser to see which
    scripts have been attached. */
exports.addScript = function (tab, script) {
  let browser = getBrowser(tab);
  let scripts = browser.framescripterEnabledScripts;
  if (! scripts) {
    scripts = browser.framescripterEnabledScripts = {};
  }
  if (! scripts[script]) {
    logDebug("Adding script:", script);
    let browserMM = browser.messageManager;
    if (! browserMM) {
      console.error("Could not get messageManager from " + browser);
      throw new Error("Could not get messageManager");
    }
    browserMM.loadFrameScript(script + "?bust=" + loadedTimestamp, false);
    scripts[script] = {};
  } else {
    logDebug("Script already loaded:", script);
  }
};

exports.unload = function () {
  for (let tab of require("sdk/tabs")) {
    let browser = getBrowser(tab);
    let scripts = browser.framescripterEnabledScripts;
    if (! scripts) {
      continue;
    }
    let browserMM = browser.messageManager;
    browserMM.sendAsyncMessage("pageshot@disable");
    delete browser.framescripterEnabledScripts;
  }
};

// Used to match up incoming with outgoing messages
let idCounter = 1;

function makeId() {
  return idCounter++;
}

const pendingDeferreds = {};
const pendingTimeouts = {};

/** Adds the script to the given tab

    Adds listeners to handle the two messages: a message with the name
    `message + ":call"` is sent to the framescript, and we expect a message
    with the name `message + ":return"`.  Each will include `callId` in the
    payload, to match up a call with a return.

    The payload includes any arguments to send to the framescript.

    Returns a promise that resolves when the return message is sent.
    */
exports.callScript = function (tab, script, message, payload, timeout) {
  if (timeout === undefined) {
    timeout = 3000;
  }
  exports.addScript(tab, script);
  let browser = getBrowser(tab);
  let messages = browser.framescripterEnabledScripts[script];
  let browserMM = browser.messageManager;
  if (! messages[message]) {
    browserMM.addMessageListener(message + ":return", callScriptReturner);
    messages[message] = true;
    logDebug("Adding frame listener for:", message + ":return");
  }
  let deferred = defer();
  let id = makeId();
  pendingDeferreds[id] = deferred;
  payload.callId = id;
  logDebug("Sending [" + message + ":call]/" + id + " with payload:", payload);
  browserMM.sendAsyncMessage(message + ":call", payload);
  if (timeout) {
    pendingTimeouts[id] = setTimeout(function () {
      if (pendingDeferreds[id]) {
        // The promise has not yet completed
        var deferred = pendingDeferreds[id];
        delete pendingDeferreds[id];
        delete pendingTimeouts[id];
        logDebug("Script timed out:", script, method, "after:", timeout);
        deferred.reject(new Error("Timeout after " + timeout + "ms"));
      } else {
        logDebug("Timeout ran despite deferred being completed, for:", script, method);
      }
    }, timeout)
  }
  return deferred.promise;
};

// Handler for a specific event type, used to resolve promises:
function callScriptReturner(event) {
  let deferred = pendingDeferreds[event.data.callId];
  if (! deferred) {
    console.error("Got deferred that has expired:", event.data.callId);
    throw new Error("Expired deferred");
  }
  delete pendingDeferreds[event.data.callId];
  if (pendingTimeouts[event.data.callId]) {
    clearTimeout(pendingTimeouts[event.data.callId]);
    delete pendingTimeouts[event.data.callId];
  }
  logDebug("Received /" + event.data.callId + " with payload:", event.data);
  if (event.data.error) {
    deferred.reject(event.data.error);
  } else {
    delete event.data.callId;
    deferred.resolve(event.data);
  }
}
<commit_msg>Typo in log messages<commit_after>/* global require, exports, console */
/** Manages framescripts and communication with those scripts

    Likely the only function you'll use is `callScript()`
    */
const { getBrowserForTab } = require('sdk/tabs/utils');
const { defer } = require('sdk/core/promise');
const { viewFor } = require("sdk/view/core");
const { setTimeout, clearTimeout } = require("sdk/timers");

let DEBUG = false;
let loadedTimestamp = Date.now();

/** call setDebug(true) to get debug information on the console */
exports.setDebug = function (val) {
  DEBUG = val;
};

function logDebug() {
  if (! DEBUG) {
    return;
  }
  let args = [];
  for (var i=0; i<arguments.length; i++) {
    if (typeof arguments[i] == "object" &&
      JSON.stringify(arguments[i]).length > 100) {
        args.push("[Object keys: " + JSON.stringify(Object.keys(arguments[i])) + "]");
    } else {
      args.push(arguments[i]);
    }
  }
  console.info.apply(console, args);
}

/** Gets the XUL browser object for a given tab (XUL tab or SDK tab) */
function getBrowser(tab) {
  if (tab && tab.attach) {
    // Convert from SDK tab to XUL tab
    tab = viewFor(tab);
  }
  return getBrowserForTab(tab);
}

/** Ensures a script is attached to a given tab

    Framescripts are long-lived, and live longer than any one page that is
    loaded.  This attaches tracking attributes to the browser to see which
    scripts have been attached. */
exports.addScript = function (tab, script) {
  let browser = getBrowser(tab);
  let scripts = browser.framescripterEnabledScripts;
  if (! scripts) {
    scripts = browser.framescripterEnabledScripts = {};
  }
  if (! scripts[script]) {
    logDebug("Adding script:", script);
    let browserMM = browser.messageManager;
    if (! browserMM) {
      console.error("Could not get messageManager from " + browser);
      throw new Error("Could not get messageManager");
    }
    browserMM.loadFrameScript(script + "?bust=" + loadedTimestamp, false);
    scripts[script] = {};
  } else {
    logDebug("Script already loaded:", script);
  }
};

exports.unload = function () {
  for (let tab of require("sdk/tabs")) {
    let browser = getBrowser(tab);
    let scripts = browser.framescripterEnabledScripts;
    if (! scripts) {
      continue;
    }
    let browserMM = browser.messageManager;
    browserMM.sendAsyncMessage("pageshot@disable");
    delete browser.framescripterEnabledScripts;
  }
};

// Used to match up incoming with outgoing messages
let idCounter = 1;

function makeId() {
  return idCounter++;
}

const pendingDeferreds = {};
const pendingTimeouts = {};

/** Adds the script to the given tab

    Adds listeners to handle the two messages: a message with the name
    `message + ":call"` is sent to the framescript, and we expect a message
    with the name `message + ":return"`.  Each will include `callId` in the
    payload, to match up a call with a return.

    The payload includes any arguments to send to the framescript.

    Returns a promise that resolves when the return message is sent.
    */
exports.callScript = function (tab, script, message, payload, timeout) {
  if (timeout === undefined) {
    timeout = 3000;
  }
  exports.addScript(tab, script);
  let browser = getBrowser(tab);
  let messages = browser.framescripterEnabledScripts[script];
  let browserMM = browser.messageManager;
  if (! messages[message]) {
    browserMM.addMessageListener(message + ":return", callScriptReturner);
    messages[message] = true;
    logDebug("Adding frame listener for:", message + ":return");
  }
  let deferred = defer();
  let id = makeId();
  pendingDeferreds[id] = deferred;
  payload.callId = id;
  logDebug("Sending [" + message + ":call]/" + id + " with payload:", payload);
  browserMM.sendAsyncMessage(message + ":call", payload);
  if (timeout) {
    pendingTimeouts[id] = setTimeout(function () {
      if (pendingDeferreds[id]) {
        // The promise has not yet completed
        var deferred = pendingDeferreds[id];
        delete pendingDeferreds[id];
        delete pendingTimeouts[id];
        logDebug("Script timed out:", script, message, "after:", timeout);
        deferred.reject(new Error("Timeout after " + timeout + "ms"));
      } else {
        logDebug("Timeout ran despite deferred being completed, for:", script, message);
      }
    }, timeout)
  }
  return deferred.promise;
};

// Handler for a specific event type, used to resolve promises:
function callScriptReturner(event) {
  let deferred = pendingDeferreds[event.data.callId];
  if (! deferred) {
    console.error("Got deferred that has expired:", event.data.callId);
    throw new Error("Expired deferred");
  }
  delete pendingDeferreds[event.data.callId];
  if (pendingTimeouts[event.data.callId]) {
    clearTimeout(pendingTimeouts[event.data.callId]);
    delete pendingTimeouts[event.data.callId];
  }
  logDebug("Received /" + event.data.callId + " with payload:", event.data);
  if (event.data.error) {
    deferred.reject(event.data.error);
  } else {
    delete event.data.callId;
    deferred.resolve(event.data);
  }
}
<|endoftext|>


<commit_before>
// CSS
import styles from './style.css';

import React from 'react';
import PureComponent from 'react-pure-render/component';

import config from '../../../config';
import IconButton from '../../IconButton';

class ControlBtn extends PureComponent {

    constructor() {
        super();
        this._onDeleteSurveyClick = this._onDeleteSurveyClick.bind(this);
        this._onAddSurveyClick = this._onAddSurveyClick.bind(this);
        this._onPreviewSurveyClick = this._onPreviewSurveyClick.bind(this);
        this._onReportSurveyClick = this._onReportSurveyClick.bind(this);
    }

    render() {
        const { selectedSurveys } = this.props;
        return (

                    <IconButton
                        id="createBtn"
                        string="Create Survey"
                        i18nKey={false}
                        onClick={this._onAddSurveyClick}
                    />
                    <IconButton
                        id="shareBtn"
                        string="Share"
                        i18nKey={false}
                        img="share"
                        disabled={!selectedSurveys.length}
                        onClick={this._onPreviewSurveyClick}
                    />
                    <IconButton
                        id="reportBtn"
                        string="Report"
                        i18nKey={false}
                        img="report"
                        disabled
                        onClick={this._onReportSurveyClick}
                    />
                    <IconButton
                        id="delBtn"
                        string="Delete"
                        i18nKey={false}
                        img="delete"
                        disabled={!selectedSurveys.length}
                        onClick={this._onDeleteSurveyClick}
                    />

        );
    }

    _onAddSurveyClick() {
        const { editSubjectActions } = this.props;
        editSubjectActions.openEdit(true);
    }

    _onDeleteSurveyClick() {
        const { surveysActions } = this.props;
        surveysActions.deleteSurvey();
    }

    _onReportSurveyClick() {
        const { account, selectedSurveys } = this.props;
        const url = `${config.baseURL}/api/v1/mgnt/report/`;
        window.open(`${url}${account.accountid}/${selectedSurveys}`, '_blank');
    }

    _onPreviewSurveyClick() {
        const { account, selectedSurveys } = this.props;
        const url = `${config.baseURL}/feedback/index.html`;
        window.open(`${url}?accountid=${account.accountid}&surveyid=${selectedSurveys}`, '_blank');
    }
}

export default ControlBtn;
<commit_msg>Enable export CSV button<commit_after>
// CSS
import styles from './style.css';

import React from 'react';
import PureComponent from 'react-pure-render/component';

import config from '../../../config';
import IconButton from '../../IconButton';

class ControlBtn extends PureComponent {

    constructor() {
        super();
        this._onDeleteSurveyClick = this._onDeleteSurveyClick.bind(this);
        this._onAddSurveyClick = this._onAddSurveyClick.bind(this);
        this._onPreviewSurveyClick = this._onPreviewSurveyClick.bind(this);
        this._onReportSurveyClick = this._onReportSurveyClick.bind(this);
    }

    render() {
        const { selectedSurveys } = this.props;
        return (

                    <IconButton
                        id="createBtn"
                        string="Create Survey"
                        i18nKey={false}
                        onClick={this._onAddSurveyClick}
                    />
                    <IconButton
                        id="shareBtn"
                        string="Share"
                        i18nKey={false}
                        img="share"
                        disabled={!selectedSurveys.length}
                        onClick={this._onPreviewSurveyClick}
                    />
                    <IconButton
                        id="reportBtn"
                        string="Report"
                        i18nKey={false}
                        img="report"
                        disabled={!selectedSurveys.length}
                        onClick={this._onReportSurveyClick}
                    />
                    <IconButton
                        id="delBtn"
                        string="Delete"
                        i18nKey={false}
                        img="delete"
                        disabled={!selectedSurveys.length}
                        onClick={this._onDeleteSurveyClick}
                    />

        );
    }

    _onAddSurveyClick() {
        const { editSubjectActions } = this.props;
        editSubjectActions.openEdit(true);
    }

    _onDeleteSurveyClick() {
        const { surveysActions } = this.props;
        surveysActions.deleteSurvey();
    }

    _onReportSurveyClick() {
        const { account, selectedSurveys } = this.props;
        const url = `${config.baseURL}/api/v1/mgnt/report/`;
        window.open(`${url}${account.accountid}/${selectedSurveys}?v=${Date.now()}`, '_blank');
    }

    _onPreviewSurveyClick() {
        const { account, selectedSurveys } = this.props;
        const url = `${config.baseURL}/feedback/index.html`;
        window.open(`${url}?accountid=${account.accountid}&surveyid=${selectedSurveys}`, '_blank');
    }
}

export default ControlBtn;
<|endoftext|>


<commit_before>document.querySelector('#cogbutton').addEventListener('click', function() {
  document.querySelector('#settingspanel').classList.toggle('show');
});
// also on blur, or on document.body when target isn't in the settingspanel

function Line(doc, text, index) {
  this.element = document.createElement('div');
  this.element.classList.add('line');
  this.element.setAttribute('contenteditable', '');
  this.contentelement = document.createElement('div');
  this.contentelement.classList.add('linecontent');
  this.element.appendChild(this.contentelement);
  this.contentelement.appendChild(document.createTextNode(text || ''));
  if(index !== undefined && doc.lines[index]) {
    doc.editor.insertBefore(this.element, doc.lines[index].element);
    doc.lines.splice(index, 0, this);
  } else {
    doc.editor.appendChild(this.element);
    doc.lines.push(this);
  }
  doc.lineCountChanged();

  this.setText = function(_text) {
    this.contentelement.textContent = _text;
  }
  this.getText = function() {
    return this.contentelement.textContent;
  }
  this.getNextLine = function() {
    return doc.lines[this.getIndex() + 1];
  }
  this.getPreviousLine = function() {
    return doc.lines[this.getIndex() - 1];
  }
  this.getIndex = function() {
    return doc.lines.indexOf(this);
  }
  this.remove = function() {
    doc.editor.removeChild(this.element);
    doc.lines.splice(this.getIndex(), 1);
    doc.lineCountChanged();
  }
  this.getLength = function() {
    return this.getText().length;
  }
  this.getCaretPos = function() {
    return window.getSelection().anchorOffset;
  }

  var line = this;

  this.insertAtCaret = function(data) {
    var oldcontent = [
      line.getText().substring(0, line.getCaretPos()),
      line.getText().substring(line.getCaretPos())
    ];
    var split = data.split('\n');
    var caret = split[split.length - 1].length;
    if(split.length == 1) caret += oldcontent[0].length;
    split[0] = oldcontent[0] + split[0];
    split[split.length - 1] = split[split.length - 1] + oldcontent[1];
    var currentline = line;
    var index = line.getIndex();
    currentline.setText(split[0]);
    for(let i = 1; i < split.length; i++) {
      currentline = new Line(doc, split[i], ++index);
    }
    doc.setSelect(currentline.contentelement, caret)
  }

  this.element.addEventListener('paste', function(e) {
    if(e.clipboardData.types.indexOf('text/plain') > -1) {
        var data = e.clipboardData.getData('text/plain');
        //split at caret
        line.insertAtCaret(data);
    }
    e.preventDefault();
  });

  this.element.addEventListener('click', function(e) {
    doc.column = line.getCaretPos();
  });

  this.element.addEventListener('keydown', function(e) {
    console.log(e)
    switch (e.code) {
      case 'Backspace': {
        // this logic should be different for selections, esp. multiline
        if(line.getCaretPos() === 0) {
          let prev = line.getPreviousLine();
          if(!prev) {
            e.preventDefault();
            return false;
          }
          let oldprevlength = prev.getLength();
          prev.setText(prev.getText() + line.getText());
          doc.setSelect(prev.contentelement, oldprevlength);
          line.remove();
          e.preventDefault();
          return false;
        } else {
          return true;
        }
      }
      case 'Enter': {
        let text = line.getText();
        let caret = line.getCaretPos();
        line.setText(text.substring(0,caret));
        let newline = new Line(doc, text.substring(caret) , line.getIndex() + 1);
        doc.setSelect(newline.contentelement, 0);
        e.preventDefault();
        return false;
      }
      case 'ArrowUp': {
        if(line.getIndex() !== 0) {
          let prev = line.getPreviousLine();
          if(doc.column > prev.getLength()) {
            doc.setSelect(prev.contentelement, prev.getLength());
          } else {
            doc.setSelect(prev.contentelement, doc.column);
          }
          e.preventDefault();
          return false;
        } else {
          doc.column = 0;
          return true;
        }
      }
      case 'ArrowDown': {
        if(line.getIndex() < doc.lines.length - 1) {
          let next = line.getNextLine();
          if(doc.column > next.getLength()) {
            doc.setSelect(next.contentelement, next.getLength());
          } else {
            doc.setSelect(next.contentelement, doc.column);
          }
          e.preventDefault();
          return false;
        } else {
          doc.column = line.getLength();
          return true;
        }
      }
      case 'ArrowLeft': {
        if(line.getIndex() !== 0 && line.getCaretPos() === 0) {
          let prev = line.getPreviousLine();
          doc.setSelect(prev.contentelement, prev.getLength());
          doc.column = prev.getLength();
          e.preventDefault();
          return false;
        } else {
          doc.column = line.getCaretPos() - 1;
          return true;
        }
      }
      case 'ArrowRight': {
        if((line.getIndex() < doc.lines.length - 1) && line.getCaretPos() === line.getLength()) {
          let next = line.getNextLine();
          doc.setSelect(next.contentelement, 0);
          doc.column = 0;
          e.preventDefault();
          return false;
        } else {
          doc.column = line.getCaretPos() + 1;
          return true;
        }
      }
      case 'Tab': {
        line.insertAtCaret(doc.tab);
        e.preventDefault();
        return false;
      }
      default: {
        doc.column = line.getCaretPos() + 1;
      }
    }
  });
}

function Document(text) {
  this.lines = [];

  this.style = document.createElement('style');
  document.head.append(this.style);
  var doc = this;
  this.lineCountChanged = function() {
    // make sure line numbers are all the same width
    if(doc.lines.length) {
      setTimeout(function() {
        doc.style.innerHTML = `.line::before { width: ${doc.lines.length.toString().length + 1}ex; }`;
      });
    }
  }

  this.tab = '    ';

  this.editor = document.querySelector('#editor');

  this.setSelect = function(elem1, index1, elem2, index2) {

    var getTextElement = function(contentelement) {
      if(contentelement.children.length != 1) {
        let text = contentelement.textContent;
        while (contentelement.firstChild) {
          contentelement.removeChild(contentelement.firstChild);
        }
        let textnode = document.createTextNode(text);
        contentelement.appendChild(textnode);
        return textnode;
      } else {
        return contentelement.firstChild;
      }
    }

    var range = document.createRange();
    var sel = window.getSelection();
    range.setStart(getTextElement(elem1), index1);
    if(elem2) {
      range.setEnd(getTextElement(elem2), index2);
    } else {
      range.collapse();
    }
    sel.removeAllRanges();
    sel.addRange(range);
  }

  if(text === undefined) text = '';
  var split = text.split('\n');
  for(let i = 0; i < split.length; i++) {
    new Line(this, split[i]);
  }

  var lastLine = this.lines[this.lines.length - 1]
  this.setSelect(lastLine.contentelement, lastLine.getLength());
  this.column = lastLine.getLength();
}

// Initialize
(function() {
  window.editor = new Document(`document.addEventListener('copy', function(e){
    e.clipboardData.setData('text/plain', 'Hello, world!');
    e.clipboardData.setData('text/html', 'Hello, world!');
    e.preventDefault(); // We want our data, not data from any selection, to be written to the clipboard
});`);
})();
<commit_msg>start handling insert letter manually<commit_after>document.querySelector('#cogbutton').addEventListener('click', function() {
  document.querySelector('#settingspanel').classList.toggle('show');
});
// also on blur, or on document.body when target isn't in the settingspanel

function Line(doc, text, index) {
  this.element = document.createElement('div');
  this.element.classList.add('line');
  this.element.setAttribute('contenteditable', '');
  this.contentelement = document.createElement('div');
  this.contentelement.classList.add('linecontent');
  this.element.appendChild(this.contentelement);
  this.contentelement.appendChild(document.createTextNode(text || ''));
  if(index !== undefined && doc.lines[index]) {
    doc.editor.insertBefore(this.element, doc.lines[index].element);
    doc.lines.splice(index, 0, this);
  } else {
    doc.editor.appendChild(this.element);
    doc.lines.push(this);
  }
  doc.lineCountChanged();

  this.setText = function(_text) {
    this.contentelement.textContent = _text;
  }
  this.getText = function() {
    return this.contentelement.textContent;
  }
  this.getNextLine = function() {
    return doc.lines[this.getIndex() + 1];
  }
  this.getPreviousLine = function() {
    return doc.lines[this.getIndex() - 1];
  }
  this.getIndex = function() {
    return doc.lines.indexOf(this);
  }
  this.remove = function() {
    doc.editor.removeChild(this.element);
    doc.lines.splice(this.getIndex(), 1);
    doc.lineCountChanged();
  }
  this.getLength = function() {
    return this.getText().length;
  }
  this.getCaretPos = function() {
    return window.getSelection().anchorOffset;
  }

  var line = this;

  this.insertAtCaret = function(data) {
    var oldcontent = [
      line.getText().substring(0, line.getCaretPos()),
      line.getText().substring(line.getCaretPos())
    ];
    var split = data.split('\n');
    var caret = split[split.length - 1].length;
    if(split.length == 1) caret += oldcontent[0].length;
    split[0] = oldcontent[0] + split[0];
    split[split.length - 1] = split[split.length - 1] + oldcontent[1];
    var currentline = line;
    var index = line.getIndex();
    currentline.setText(split[0]);
    for(let i = 1; i < split.length; i++) {
      currentline = new Line(doc, split[i], ++index);
    }
    doc.setSelect(currentline.contentelement, caret)
  }

  this.element.addEventListener('paste', function(e) {
    if(e.clipboardData.types.indexOf('text/plain') > -1) {
        var data = e.clipboardData.getData('text/plain');
        //split at caret
        line.insertAtCaret(data);
    }
    e.preventDefault();
  });

  this.element.addEventListener('click', function(e) {
    doc.column = line.getCaretPos();
  });

  this.element.addEventListener('keydown', function(e) {
    console.log(e)
    switch (e.code) {
      case 'Backspace': {
        // this logic should be different for selections, esp. multiline
        if(line.getCaretPos() === 0) {
          let prev = line.getPreviousLine();
          if(!prev) {
            e.preventDefault();
            return false;
          }
          let oldprevlength = prev.getLength();
          prev.setText(prev.getText() + line.getText());
          doc.setSelect(prev.contentelement, oldprevlength);
          line.remove();
          e.preventDefault();
          return false;
        } else {
          return true;
        }
      }
      case 'Enter': {
        let text = line.getText();
        let caret = line.getCaretPos();
        line.setText(text.substring(0,caret));
        let newline = new Line(doc, text.substring(caret) , line.getIndex() + 1);
        doc.setSelect(newline.contentelement, 0);
        e.preventDefault();
        return false;
      }
      case 'ArrowUp': {
        if(line.getIndex() !== 0) {
          let prev = line.getPreviousLine();
          if(doc.column > prev.getLength()) {
            doc.setSelect(prev.contentelement, prev.getLength());
          } else {
            doc.setSelect(prev.contentelement, doc.column);
          }
          e.preventDefault();
          return false;
        } else {
          doc.column = 0;
          return true;
        }
      }
      case 'ArrowDown': {
        if(line.getIndex() < doc.lines.length - 1) {
          let next = line.getNextLine();
          if(doc.column > next.getLength()) {
            doc.setSelect(next.contentelement, next.getLength());
          } else {
            doc.setSelect(next.contentelement, doc.column);
          }
          e.preventDefault();
          return false;
        } else {
          doc.column = line.getLength();
          return true;
        }
      }
      case 'ArrowLeft': {
        if(line.getIndex() !== 0 && line.getCaretPos() === 0) {
          let prev = line.getPreviousLine();
          doc.setSelect(prev.contentelement, prev.getLength());
          doc.column = prev.getLength();
          e.preventDefault();
          return false;
        } else {
          doc.column = line.getCaretPos() - 1;
          return true;
        }
      }
      case 'ArrowRight': {
        if((line.getIndex() < doc.lines.length - 1) && line.getCaretPos() === line.getLength()) {
          let next = line.getNextLine();
          doc.setSelect(next.contentelement, 0);
          doc.column = 0;
          e.preventDefault();
          return false;
        } else {
          doc.column = line.getCaretPos() + 1;
          return true;
        }
      }
      case 'Tab': {
        line.insertAtCaret(doc.tab);
        doc.column = line.getCaretPos();
        e.preventDefault();
        return false;
      }
      // Evil hax to match letter keys
      case (e.code.match(/Key[A-Z]/) ? e.code : false): {
        console.log('appended ' + e.key);
        line.insertAtCaret(e.key);
        doc.column = line.getCaretPos();
        e.preventDefault();
        return false;
      }
      default: {
        return true;
      }
    }
  });
}

function Document(text) {
  this.lines = [];

  this.style = document.createElement('style');
  document.head.append(this.style);
  var doc = this;
  this.lineCountChanged = function() {
    // make sure line numbers are all the same width
    if(doc.lines.length) {
      setTimeout(function() {
        doc.style.innerHTML = `.line::before { width: ${doc.lines.length.toString().length + 1}ex; }`;
      });
    }
  }

  this.tab = '    ';

  this.editor = document.querySelector('#editor');

  this.setSelect = function(elem1, index1, elem2, index2) {

    var getTextElement = function(contentelement) {
      if(contentelement.children.length != 1) {
        let text = contentelement.textContent;
        while (contentelement.firstChild) {
          contentelement.removeChild(contentelement.firstChild);
        }
        let textnode = document.createTextNode(text);
        contentelement.appendChild(textnode);
        return textnode;
      } else {
        return contentelement.firstChild;
      }
    }

    var range = document.createRange();
    var sel = window.getSelection();
    range.setStart(getTextElement(elem1), index1);
    if(elem2) {
      range.setEnd(getTextElement(elem2), index2);
    } else {
      range.collapse();
    }
    sel.removeAllRanges();
    sel.addRange(range);
  }

  if(text === undefined) text = '';
  var split = text.split('\n');
  for(let i = 0; i < split.length; i++) {
    new Line(this, split[i]);
  }

  var lastLine = this.lines[this.lines.length - 1]
  this.setSelect(lastLine.contentelement, lastLine.getLength());
  this.column = lastLine.getLength();
}

// Initialize
(function() {
  window.editor = new Document(`document.addEventListener('copy', function(e){
    e.clipboardData.setData('text/plain', 'Hello, world!');
    e.clipboardData.setData('text/html', 'Hello, world!');
    e.preventDefault(); // We want our data, not data from any selection, to be written to the clipboard
});`);
})();
<|endoftext|>


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

define(
['config', 'bigl', 'stapes', 'googlemaps', 'sv_svc'],
function(config, L, Stapes, GMaps, sv_svc) {

  var SEARCH_FAIL_BALLOON_TIME = 1100;

  var ClickSearchModule = Stapes.subclass({
    constructor: function(map) {
      var self = this;

      this.map = map;

      this.search_fail_balloon = new GMaps.InfoWindow({
        content: '',
        disableAutoPan: true
      });
      this.ballon_close_timeout = null;

      GMaps.event.addListener(this.map, 'click', function(event) {
        self.close_search_fail_balloon();

        // determine min/max search radius based on zoom level
        var min_search_radius;
        var max_search_radius;

        var current_zoom = self.map.getZoom();

        if (current_zoom <= 10) {
          min_search_radius = 400;
          max_search_radius = 1600;
        } else if (current_zoom <= 12) {
          min_search_radius = 100;
          max_search_radius = 400;
        } else if (current_zoom <= 14) {
          min_search_radius = 50;
          max_search_radius = 200;
        } else {
          min_search_radius = 50;
          max_search_radius = 50;
        }

        sv_svc.getPanoramaByLocation(
          event.latLng,
          min_search_radius,
          function(data, stat, search_latlng) {
            if(stat == GMaps.StreetViewStatus.OK) {
              self.emit('search_result', data);
            } else {
              self.open_search_fail_balloon(search_latlng);
            }
          },
          max_search_radius
        );
      });
    },

    open_search_fail_balloon: function(latlng) {
      var self = this;

      this.close_search_fail_balloon();

      this.search_fail_balloon.setPosition(latlng);
      this.search_fail_balloon.open(this.map);

      this.balloon_close_timeout = setTimeout(
        function() {
          self.close_search_fail_balloon();
        },
        SEARCH_FAIL_BALLOON_TIME
      );
    },

    close_search_fail_balloon: function() {
      clearTimeout(this.balloon_close_timeout);
      this.search_fail_balloon.close();
    },
  });

  return ClickSearchModule;
});
<commit_msg>Broader search radius on map clicks<commit_after>/*
** Copyright 2013 Google Inc.
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**    http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

define(
['config', 'bigl', 'stapes', 'googlemaps', 'sv_svc'],
function(config, L, Stapes, GMaps, sv_svc) {

  var SEARCH_FAIL_BALLOON_TIME = 1100;

  var ClickSearchModule = Stapes.subclass({
    constructor: function(map) {
      var self = this;

      this.map = map;

      this.search_fail_balloon = new GMaps.InfoWindow({
        content: '',
        disableAutoPan: true
      });
      this.ballon_close_timeout = null;

      GMaps.event.addListener(this.map, 'click', function(event) {
        self.close_search_fail_balloon();

        // determine min/max search radius based on zoom level
        var min_search_radius;
        var max_search_radius;

        var current_zoom = self.map.getZoom();

        if (current_zoom <= 10) {
          min_search_radius = 400;
          max_search_radius = 3200;
        } else if (current_zoom <= 12) {
          min_search_radius = 100;
          max_search_radius = 800;
        } else if (current_zoom <= 14) {
          min_search_radius = 50;
          max_search_radius = 400;
        } else {
          min_search_radius = 50;
          max_search_radius = 200;
        }

        console.debug('min', min_search_radius, 'max', max_search_radius);

        sv_svc.getPanoramaByLocation(
          event.latLng,
          min_search_radius,
          function(data, stat, search_latlng) {
            if(stat == GMaps.StreetViewStatus.OK) {
              self.emit('search_result', data);
            } else {
              self.open_search_fail_balloon(search_latlng);
            }
          },
          max_search_radius
        );
      });
    },

    open_search_fail_balloon: function(latlng) {
      var self = this;

      this.close_search_fail_balloon();

      this.search_fail_balloon.setPosition(latlng);
      this.search_fail_balloon.open(this.map);

      this.balloon_close_timeout = setTimeout(
        function() {
          self.close_search_fail_balloon();
        },
        SEARCH_FAIL_BALLOON_TIME
      );
    },

    close_search_fail_balloon: function() {
      clearTimeout(this.balloon_close_timeout);
      this.search_fail_balloon.close();
    },
  });

  return ClickSearchModule;
});
<|endoftext|>


<commit_before>import Ember from 'ember';

const keyCodeToEventMap = {
  27: 'esc',
  37: 'leftArrow',
  39: 'rightArrow'
};

export default Ember.Mixin.create({
  mergedProperties: ['keyEvents'],
  keyEvents: {},

  setupKeyHandling: function() {
    this.$(document).on(`keyup.${this.get('elementId')}`, (event) => {
      var key = keyCodeToEventMap[event.keyCode];
      var keyEvent = this.get('keyEvents')[key];

      if (keyEvent) {
        keyEvent.call(this, event);
      }
    });
  }.on('didInsertElement'),

  tearDownKeyHandling: function() {
    this.$(document).off(`keyup.${this.get('elementId')}`);
  }.on('willDestroyElement')
});
<commit_msg>Enable all arrow events<commit_after>import Ember from 'ember';

const keyCodeToEventMap = {
  27: 'esc',
  37: 'leftArrow',
  38: 'upArrow',
  39: 'rightArrow',
  40: 'downArrow'
};

export default Ember.Mixin.create({
  mergedProperties: ['keyEvents'],
  keyEvents: {},

  setupKeyHandling: function() {
    this.$(document).on(`keyup.${this.get('elementId')}`, (event) => {
      var key = keyCodeToEventMap[event.keyCode];
      var keyEvent = this.get('keyEvents')[key];

      if (keyEvent) {
        keyEvent.call(this, event);
      }
    });
  }.on('didInsertElement'),

  tearDownKeyHandling: function() {
    this.$(document).off(`keyup.${this.get('elementId')}`);
  }.on('willDestroyElement')
});
<|endoftext|>


<commit_before>psc.namespace('admin');

psc.admin.ProvisionableUser = function (username, memberships) {
  this.username = username;
  this.memberships = memberships;
  if (!this.memberships) {
    this.memberships = {}
  }
  this.changes = [];

  function scopeTypeFromCollectionName(collectionName) {
    return collectionName == 'studies' ? 'study' :
      collectionName.substring(0, collectionName.length - 1);
  }

  function collectionNameFromScopeType(scopeType) {
    return scopeType == 'study' ? 'studies' : scopeType + 's';
  }

  var registerChange = _(function(newChange) {
    if (window['console'] && console.log) {
      console.log("Registering", newChange.kind, "of", newChange.role, newChange);
    }

    var otherKind = newChange.kind == 'add' ? 'remove' : 'add';
    var reverses = _(this.changes).select(function (change) {
      var candidate = change.kind == otherKind &&
        change.role == newChange.role;
      var exact;
      if (newChange.scopeType) {
        exact = newChange.scopeType == change.scopeType &&
          newChange.scopeIdentifier == change.scopeIdentifier;
      } else {
        exact = !change.scopeType;
      }
      return candidate && exact;
    });
    if (reverses.length != 0) {
      this.changes = _(this.changes).reject(function (change) {
        return _(reverses).include(change);
      });
    } else {
      this.changes.push(newChange);
    }

    jQuery(this).trigger('membership-change', newChange);
  }).bind(this)

  this.add = function (roleKey, scope) {
    var newMembership = false;
    if (this.memberships[roleKey] === undefined) {
      this.memberships[roleKey] = {};
      newMembership = true;
    }
    var m = this.memberships[roleKey];
    if (typeof scope === 'object') {
      _(scope).each(function (value, key) {
        if (!m[key]) { m[key] = []; }
        _(value).each(function (ident) {
          if (_(m[key]).indexOf(ident) == -1) {
            m[key].push(ident);
            registerChange({
              role: roleKey,
              kind: "add",
              scopeType: scopeTypeFromCollectionName(key),
              scopeIdentifier: ident
            });
          }
        }, this);
      }, this);
    }
    if (newMembership) {
      registerChange({
        role: roleKey,
        kind: 'add'
      });
    }
  };

  this.remove = function (roleKey, scope) {
    if (this.memberships[roleKey] === undefined) { return; }
    var m = this.memberships[roleKey];
    if (typeof scope == 'object') {
      _(scope).each(function (identList, name) {
        if (!m[name]) { _.breakLoop(); }
        _(identList).each(function (ident) {
          var loc = _(m[name]).indexOf(ident);
          if (loc == -1) return;
          m[name].splice(loc, 1);
          registerChange({
            role: roleKey,
            kind: "remove",
            scopeType: scopeTypeFromCollectionName(name),
            scopeIdentifier: ident
          });
        }, this);
      }, this);
    } else {
      _(this.memberships[roleKey]).each(function (identList, name) {
        _(identList).each(function (ident) {
          registerChange({
            role: roleKey,
            kind: "remove",
            scopeType: scopeTypeFromCollectionName(name),
            scopeIdentifier: ident
          });
        });
      });
      delete this.memberships[roleKey]
      registerChange({
        role: roleKey,
        kind: "remove"
      });
    }
  };

  this.hasMembership = function (roleKey, scope) {
    if (!this.memberships[roleKey]) { return false; }
    if (!scope) { return true; }
    var scopeType = _(scope).chain().keys().first().value();
    var collectionName = collectionNameFromScopeType(scopeType);
    if (!this.memberships[roleKey][collectionName]) { return false; }
    return _(this.memberships[roleKey][collectionName]).include(scope[scopeType]);
  }
}
<commit_msg>Add methods to check if a user has any/all roles. Part of #1155.<commit_after>psc.namespace('admin');

psc.admin.ProvisionableUser = function (username, memberships) {
  this.username = username;
  this.memberships = memberships;
  if (!this.memberships) {
    this.memberships = {}
  }
  this.changes = [];

  function scopeTypeFromCollectionName(collectionName) {
    return collectionName == 'studies' ? 'study' :
      collectionName.substring(0, collectionName.length - 1);
  }

  function collectionNameFromScopeType(scopeType) {
    return scopeType == 'study' ? 'studies' : scopeType + 's';
  }

  var registerChange = _(function(newChange) {
    if (window['console'] && console.log) {
      console.log("Registering", newChange.kind, "of", newChange.role, newChange);
    }

    var otherKind = newChange.kind == 'add' ? 'remove' : 'add';
    var reverses = _(this.changes).select(function (change) {
      var candidate = change.kind == otherKind &&
        change.role == newChange.role;
      var exact;
      if (newChange.scopeType) {
        exact = newChange.scopeType == change.scopeType &&
          newChange.scopeIdentifier == change.scopeIdentifier;
      } else {
        exact = !change.scopeType;
      }
      return candidate && exact;
    });
    if (reverses.length != 0) {
      this.changes = _(this.changes).reject(function (change) {
        return _(reverses).include(change);
      });
    } else {
      this.changes.push(newChange);
    }

    jQuery(this).trigger('membership-change', newChange);
  }).bind(this)

  this.add = function (roleKey, scope) {
    var newMembership = false;
    if (this.memberships[roleKey] === undefined) {
      this.memberships[roleKey] = {};
      newMembership = true;
    }
    var m = this.memberships[roleKey];
    if (typeof scope === 'object') {
      _(scope).each(function (value, key) {
        if (!m[key]) { m[key] = []; }
        _(value).each(function (ident) {
          if (_(m[key]).indexOf(ident) == -1) {
            m[key].push(ident);
            registerChange({
              role: roleKey,
              kind: "add",
              scopeType: scopeTypeFromCollectionName(key),
              scopeIdentifier: ident
            });
          }
        }, this);
      }, this);
    }
    if (newMembership) {
      registerChange({
        role: roleKey,
        kind: 'add'
      });
    }
  };

  this.remove = function (roleKey, scope) {
    if (this.memberships[roleKey] === undefined) { return; }
    var m = this.memberships[roleKey];
    if (typeof scope == 'object') {
      _(scope).each(function (identList, name) {
        if (!m[name]) { _.breakLoop(); }
        _(identList).each(function (ident) {
          var loc = _(m[name]).indexOf(ident);
          if (loc == -1) return;
          m[name].splice(loc, 1);
          registerChange({
            role: roleKey,
            kind: "remove",
            scopeType: scopeTypeFromCollectionName(name),
            scopeIdentifier: ident
          });
        }, this);
      }, this);
    } else {
      _(this.memberships[roleKey]).each(function (identList, name) {
        _(identList).each(function (ident) {
          registerChange({
            role: roleKey,
            kind: "remove",
            scopeType: scopeTypeFromCollectionName(name),
            scopeIdentifier: ident
          });
        });
      });
      delete this.memberships[roleKey]
      registerChange({
        role: roleKey,
        kind: "remove"
      });
    }
  };

  this.hasMembership = function (roleKey, scope) {
    if (!this.memberships[roleKey]) { return false; }
    if (!scope) { return true; }
    var scopeType = _(scope).chain().keys().first().value();
    var collectionName = collectionNameFromScopeType(scopeType);
    if (!this.memberships[roleKey][collectionName]) { return false; }
    return _(this.memberships[roleKey][collectionName]).include(scope[scopeType]);
  };

  function explodeMembership(combination, role, scope) {
    var c = combination;
    if (typeof m === 'object') {
        role = c;
      } else if (_(m).isArray()) {
        role = c[0];
        scope = c[1];
      }
  }
  this.hasAnyMemberships = function (memberships) {
    _(memberships).any(function(m) {
      var role, scope;
      explodeMembership(m, role, scope);
      this.hasMembership(role, scope);
    });
  };

  this.hasAllMemberships = function() {
    _(memberships).all(function(m) {
      var role, scope;
      explodeMembership(m, role, scope);
      this.hasMembership(role, scope);
    });
  }
}
<|endoftext|>


<commit_before>import Ember from 'ember';
import DS from 'ember-data';
import decorateAdapter from './base-store/decorate-adapter';
import decorateSerializer from './base-store/decorate-serializer';
import decorateAPICall from './base-store/decorate-api-call';

/**
 * This should be used as store:main
 *
 * @class BaseStore
 * @extends DS.Store
 */
export default DS.Store.extend({
  onlineStore: null,
  offlineStore: null,
  offlineGlobals: Ember.inject.service('offline-globals'),

  /**
   * Global instance of Syncer that contains methods to sync model.
   *
   * @property syncer
   * @type Syncer
   * @readOnly
   */
  syncer: null,

  init() {
    this._super(...arguments);
    let owner = Ember.getOwner(this);
    if (Ember.isNone(this.get('onlineStore'))) {
      let onlineStore = DS.Store.create(owner.ownerInjection());
      this.set('onlineStore', onlineStore);
    }

    let syncer = owner.lookup('syncer:main');
    this.set('syncer', syncer);

    let offlineStore = owner.lookup('store:local');
    this.set('offlineStore', offlineStore);
  },

  /**
    This method returns a fresh collection from the server, regardless of if there is already records
    in the store or not.
    @method findAll
    @param {String} modelName
    @param {Object} options
    @param {Boolean} [options.useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Promise} promise
  */
  findAll(modelName, options) {
    let offlineStore = this.get('offlineStore');
    let useOnlineStore = options ? options.useOnlineStore : null;
    let useOnlineStoreCondition = (useOnlineStore === true) || (useOnlineStore === null && this._isOnline());
    return useOnlineStoreCondition ? this._decorateMethodAndCall('all', 'findAll', arguments, 1) : offlineStore.findAll.apply(offlineStore, arguments);
  },

  /**
   * This method returns a record for a given type and id combination.
   * NOTE: this will trigger syncUp twice, this is OK. And since this is
   *  a public method, we probably want to preserve this.
    @method findRecord
    @param {String} modelName
    @param {String|Integer} id
    @param {Object} options
    @param {Boolean} [options.useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Promise} promise
   */
  findRecord(modelName, id, options) {
    let offlineStore = this.get('offlineStore');
    let useOnlineStore = options ? options.useOnlineStore : null;
    let useOnlineStoreCondition = (useOnlineStore === true) || (useOnlineStore === null && this._isOnline());
    return useOnlineStoreCondition ? this._decorateMethodAndCall('single', 'findRecord', arguments, 2) : offlineStore.findRecord.apply(offlineStore, arguments);
  },

  /**
    This method is called by the record's `reload` method.
    This method calls the adapter's `find` method, which returns a promise. When
    **that** promise resolves, `reloadRecord` will resolve the promise returned
    by the record's `reload`.
    @method reloadRecord
    @private
    @param {DS.Model} internalModel
    @return {Promise} promise
  */
  reloadRecord() {
    let offlineStore = this.get('offlineStore');
    return this._isOnline() ? this._decorateMethodAndCall('single', 'reloadRecord', arguments, -1) : offlineStore.reloadRecord.apply(offlineStore, arguments);
  },

  /**
    Query for records that meet certain criteria. Resolves with DS.RecordArray.
    @method query
    @param {String} modelName
    @param {Object} query
    @param {Boolean} [query.useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Promise} promise
  */
  query(modelName, query) {
    let offlineStore = this.get('offlineStore');
    let useOnlineStore = query ? query.useOnlineStore : null;
    if (query.useOnlineStore) {
      delete query.useOnlineStore;
    }
    let useOnlineStoreCondition = (useOnlineStore === true) || (useOnlineStore === null && this._isOnline());
    return useOnlineStoreCondition ? this._decorateMethodAndCall('multiple', 'query', arguments, -1) : offlineStore.query.apply(offlineStore, arguments);
  },

  /**
    Query for record that meet certain criteria. Resolves with single record.
    @method query
    @param {String} modelName
    @param {Object} query
    @param {Boolean} [query.useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Promise} promise
  */
  queryRecord(modelName, query) {
    let offlineStore = this.get('offlineStore');
    let useOnlineStore = query ? query.useOnlineStore : null;
    if (query.useOnlineStore) {
      delete query.useOnlineStore;
    }
    let useOnlineStoreCondition = (useOnlineStore === true) || (useOnlineStore === null && this._isOnline());
    return useOnlineStoreCondition ? this._decorateMethodAndCall('single', 'queryRecord', arguments, -1) : offlineStore.queryRecord.apply(offlineStore, arguments);
  },

  /**
    Create a new record in the current store. The properties passed to this method are set on the newly created record.
    @method createRecord
    @param {String} modelName
    @param {Object} inputProperties A hash of properties to set on the newly created record.
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Promise} promise
  */
  createRecord() {
    return this._callSuperMethod('createRecord', 2, arguments);
  },

  /**
    For symmetry, a record can be deleted via the store.
    @method deleteRecord
    @param {DS.Model} record
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
  */
  deleteRecord() {
    return this._callSuperMethod('deleteRecord', 1, arguments);
  },

  /**
    Get the reference for the specified record.
    @method getReference
    @param {String} type
    @param {String|Integer} id
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {RecordReference} promise
  */
  getReference() {
    return this._callSuperMethod('getReference', 2, arguments);
  },

  /**
    Returns true if a record for a given type and ID is already loaded.
    @method hasRecordForId
    @param {String|DS.Model} modelName
    @param {String|Integer} inputId
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Boolean}
  */
  hasRecordForId() {
    return this._callSuperMethod('hasRecordForId', 2, arguments);
  },

  /**
    Converts a json payload into the normalized form that {{#crossLink "BaseStore/push:method"}}{{/crossLink}} expects.
    @method normalize
    @param {String} modelName The name of the model type for this payload
    @param {Object} payload
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Object} The normalized payload
  */
  normalize() {
    return this._callSuperMethod('normalize', 2, arguments);
  },

  /**
    This method returns a filtered array that contains all of the known records for a given type in the store.
    @method peekAll
    @param {String} modelName
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {DS.RecordArray}
  */
  peekAll() {
    return this._callSuperMethod('peekAll', 1, arguments);
  },

  /**
    Get a record by a given type and ID without triggering a fetch.
    @method peekRecord
    @param {String} modelName
    @param {String|Integer} id
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {DS.Model|null} Record
  */
  peekRecord() {
    return this._callSuperMethod('peekRecord', 2, arguments);
  },

  /**
    Push some data for a given type into the store.
    @method push
    @param {Object} data
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {DS.Model|Array} The record(s) that was created or updated.
  */
  push() {
    return this._callSuperMethod('push', 1, arguments);
  },

  /**
    Push some raw data into the store.
    @method pushPayload
    @param {String} modelName
    @param {Object} inputPayload
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
  */
  pushPayload() {
    return this._callSuperMethod('pushPayload', 2, arguments);
  },

  /**
    This method returns if a certain record is already loaded in the store.
    @method recordIsLoaded
    @param {String} modelName
    @param {String|Integer} id
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Boolean}
  */
  recordIsLoaded() {
    return this._callSuperMethod('recordIsLoaded', 2, arguments);
  },

  /**
    This method unloads all records in the store.
    @method unloadAll
    @param {String} modelName
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
  */
  unloadAll() {
    return this._callSuperMethod('unloadAll', 1, arguments);
  },

  /**
    For symmetry, a record can be unloaded via the store. Only non-dirty records can be unloaded.
    @method unloadRecord
    @param {DS.Model} record
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
  */
  unloadRecord() {
    return this._callSuperMethod('unloadRecord', 1, arguments);
  },

  /**
    Returns an instance of the adapter for a given type.
    @method adapterFor
    @param {String} modelName
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {DS.Adapter or subclass} Adapter
  */
  adapterFor(modelName, useOnlineStore) {
    let onlineStore = this.get('onlineStore');
    let offlineStore = this.get('offlineStore');
    let adapter = onlineStore.adapterFor(modelName);
    if (this.get('offlineGlobals').get('isOfflineEnabled')) {
      let useOnlineStoreCondition = useOnlineStore || (Ember.isNone(useOnlineStore) && this._isOnline());
      return useOnlineStoreCondition ? decorateAdapter.call(this, adapter) : offlineStore.adapterFor(modelName);
    }
    else {
      return adapter;
    }
  },

  /**
    Returns an instance of the serializer for a given type.
    @method serializerFor
    @param {String} modelName
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {DS.Serializer or subclass} Serializer
  */
  serializerFor(modelName, useOnlineStore) {
    let onlineStore = this.get('onlineStore');
    let offlineStore = this.get('offlineStore');
    let serializer = onlineStore.serializerFor(modelName);
    if (this.get('offlineGlobals').get('isOfflineEnabled')) {
      let useOnlineStoreCondition = useOnlineStore || (Ember.isNone(useOnlineStore) && this._isOnline());
      return useOnlineStoreCondition ? decorateSerializer.call(this, serializer) : offlineStore.serializerFor(modelName);
    }
    else {
      return serializer;
    }
  },

  /*
    Decorate specified method of online store for add extra syncing features.
  */
  _decorateMethodAndCall(finderType, originMethodName, originMethodArguments, optionsIndex) {
    let onlineStore = this.get('onlineStore');
    let originMethod = onlineStore[originMethodName];
    let decoratedMethod = decorateAPICall(finderType, originMethod);
	  if (optionsIndex > -1) {
      let options = originMethodArguments[optionsIndex];
	    options = this.get('offlineGlobals').get('isOfflineEnabled') ? options : Ember.$.extend(true, { bypass: true }, options);
      originMethodArguments[optionsIndex] = options;
    }

    return decoratedMethod.apply(onlineStore, originMethodArguments);
  },

  /*
    Detect global online status.
  */
  _isOnline() {
    return this.get('offlineGlobals.isOnline');
  },

  /*
    Pass control flow to online or offline store depend on global online status
    and explicitly specified store to use.
  */
  _callSuperMethod(methodName, useOnlineStoreParamNum, ...args) {
    let onlineStore = this.get('onlineStore');
    let offlineStore = this.get('offlineStore');
    let useOnlineStore = (args[0].length - 1) >= useOnlineStoreParamNum ? args[0][useOnlineStoreParamNum] : null;
    let useOnlineStoreCondition = useOnlineStore || (Ember.isNone(useOnlineStore) && this._isOnline());
    return useOnlineStoreCondition ? onlineStore[methodName].apply(onlineStore, args[0]) : offlineStore[methodName].apply(offlineStore, args[0]);
  }
});
<commit_msg>Fix incorrect eval of `useOnlineStore` option value<commit_after>import Ember from 'ember';
import DS from 'ember-data';
import decorateAdapter from './base-store/decorate-adapter';
import decorateSerializer from './base-store/decorate-serializer';
import decorateAPICall from './base-store/decorate-api-call';

/**
  Base class for application store.
  service:store should point to instance of that class.

  @class BaseStore
  @namespace Offline
  @extends DS.Store
*/
export default DS.Store.extend({
  /**
    Store that use for making requests in online mode.
    It can be specified in application that use offline mode support.
    If it is not specified then instance of DS.Store
    is set as value of this property during initialization of {{#crossLink "BaseStore"}}{{/crossLink}} class.

    @property onlineStore
    @type DS.Store
  */
  onlineStore: null,

  /**
    Store that use for making requests in offline mode.
    By default it is set to global instane of {{#crossLink "LocalStore"}}{{/crossLink}} class.

    @property offlineStore
    @type DS.Store
  */
  offlineStore: null,
  offlineGlobals: Ember.inject.service('offline-globals'),

  /**
    Global instance of {{#crossLink "Syncer"}}{{/crossLink}} class that contains methods to sync model.

    @property syncer
    @type Syncer
    @readOnly
  */
  syncer: null,

  /*
    Store initialization.
  */
  init() {
    this._super(...arguments);
    let owner = Ember.getOwner(this);

    // Set online store if it is not specified in application explicitly.
    if (Ember.isNone(this.get('onlineStore'))) {
      let onlineStore = DS.Store.create(owner.ownerInjection());
      this.set('onlineStore', onlineStore);
    }

    // Set Syncer.
    let syncer = owner.lookup('syncer:main');
    this.set('syncer', syncer);

    // Set offline store.
    let offlineStore = owner.lookup('store:local');
    this.set('offlineStore', offlineStore);
  },

  /**
    This method returns a fresh collection from the server, regardless of if there is already records
    in the store or not.
    @method findAll
    @param {String} modelName
    @param {Object} options
    @param {Boolean} [options.useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Promise} promise
  */
  findAll(modelName, options) {
    let offlineStore = this.get('offlineStore');
    let useOnlineStore = options && options.useOnlineStore ? options.useOnlineStore : null;
    let useOnlineStoreCondition = (useOnlineStore === true) || (useOnlineStore === null && this._isOnline());
    return useOnlineStoreCondition ? this._decorateMethodAndCall('all', 'findAll', arguments, 1) : offlineStore.findAll.apply(offlineStore, arguments);
  },

  /**
   * This method returns a record for a given type and id combination.
   * NOTE: this will trigger syncUp twice, this is OK. And since this is
   *  a public method, we probably want to preserve this.
    @method findRecord
    @param {String} modelName
    @param {String|Integer} id
    @param {Object} options
    @param {Boolean} [options.useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Promise} promise
   */
  findRecord(modelName, id, options) {
    let offlineStore = this.get('offlineStore');
    let useOnlineStore = options && options.useOnlineStore ? options.useOnlineStore : null;
    let useOnlineStoreCondition = (useOnlineStore === true) || (useOnlineStore === null && this._isOnline());
    return useOnlineStoreCondition ? this._decorateMethodAndCall('single', 'findRecord', arguments, 2) : offlineStore.findRecord.apply(offlineStore, arguments);
  },

  /**
    This method is called by the record's `reload` method.
    This method calls the adapter's `find` method, which returns a promise. When
    **that** promise resolves, `reloadRecord` will resolve the promise returned
    by the record's `reload`.
    @method reloadRecord
    @private
    @param {DS.Model} internalModel
    @return {Promise} promise
  */
  reloadRecord() {
    let offlineStore = this.get('offlineStore');
    return this._isOnline() ? this._decorateMethodAndCall('single', 'reloadRecord', arguments, -1) : offlineStore.reloadRecord.apply(offlineStore, arguments);
  },

  /**
    Query for records that meet certain criteria. Resolves with [DS.RecordArray](http://emberjs.com/api/data/classes/DS.RecordArray.html).
	For more information see [query method](http://emberjs.com/api/data/classes/DS.Store.html#method_query) of [DS.Store](http://emberjs.com/api/data/classes/DS.Store.html).
    @method query
    @param {String} modelName
    @param {Object} query
    @param {Boolean} [query.useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Promise} promise
  */
  query(modelName, query) {
    let offlineStore = this.get('offlineStore');
    let useOnlineStore = query && query.useOnlineStore ? query.useOnlineStore : null;
    if (query.useOnlineStore) {
      delete query.useOnlineStore;
    }
    let useOnlineStoreCondition = (useOnlineStore === true) || (useOnlineStore === null && this._isOnline());
    return useOnlineStoreCondition ? this._decorateMethodAndCall('multiple', 'query', arguments, -1) : offlineStore.query.apply(offlineStore, arguments);
  },

  /**
    Query for record that meet certain criteria. Resolves with single record.
    @method query
    @param {String} modelName
    @param {Object} query
    @param {Boolean} [query.useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Promise} promise
  */
  queryRecord(modelName, query) {
    let offlineStore = this.get('offlineStore');
    let useOnlineStore = query && query.useOnlineStore ? query.useOnlineStore : null;
    if (query.useOnlineStore) {
      delete query.useOnlineStore;
    }
    let useOnlineStoreCondition = (useOnlineStore === true) || (useOnlineStore === null && this._isOnline());
    return useOnlineStoreCondition ? this._decorateMethodAndCall('single', 'queryRecord', arguments, -1) : offlineStore.queryRecord.apply(offlineStore, arguments);
  },

  /**
    Create a new record in the current store. The properties passed to this method are set on the newly created record.
    @method createRecord
    @param {String} modelName
    @param {Object} inputProperties A hash of properties to set on the newly created record.
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Promise} promise
  */
  createRecord() {
    return this._callSuperMethod('createRecord', 2, arguments);
  },

  /**
    For symmetry, a record can be deleted via the store.
    @method deleteRecord
    @param {DS.Model} record
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
  */
  deleteRecord() {
    return this._callSuperMethod('deleteRecord', 1, arguments);
  },

  /**
    Get the reference for the specified record.
    @method getReference
    @param {String} type
    @param {String|Integer} id
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {RecordReference} promise
  */
  getReference() {
    return this._callSuperMethod('getReference', 2, arguments);
  },

  /**
    Returns true if a record for a given type and ID is already loaded.
    @method hasRecordForId
    @param {String|DS.Model} modelName
    @param {String|Integer} inputId
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Boolean}
  */
  hasRecordForId() {
    return this._callSuperMethod('hasRecordForId', 2, arguments);
  },

  /**
    Converts a json payload into the normalized form that {{#crossLink "BaseStore/push:method"}}{{/crossLink}} expects.
    @method normalize
    @param {String} modelName The name of the model type for this payload
    @param {Object} payload
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Object} The normalized payload
  */
  normalize() {
    return this._callSuperMethod('normalize', 2, arguments);
  },

  /**
    This method returns a filtered array that contains all of the known records for a given type in the store.
    @method peekAll
    @param {String} modelName
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {DS.RecordArray}
  */
  peekAll() {
    return this._callSuperMethod('peekAll', 1, arguments);
  },

  /**
    Get a record by a given type and ID without triggering a fetch.
    @method peekRecord
    @param {String} modelName
    @param {String|Integer} id
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {DS.Model|null} Record
  */
  peekRecord() {
    return this._callSuperMethod('peekRecord', 2, arguments);
  },

  /**
    Push some data for a given type into the store.
    @method push
    @param {Object} data
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {DS.Model|Array} The record(s) that was created or updated.
  */
  push() {
    return this._callSuperMethod('push', 1, arguments);
  },

  /**
    Push some raw data into the store.
    @method pushPayload
    @param {String} modelName
    @param {Object} inputPayload
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
  */
  pushPayload() {
    return this._callSuperMethod('pushPayload', 2, arguments);
  },

  /**
    This method returns if a certain record is already loaded in the store.
    @method recordIsLoaded
    @param {String} modelName
    @param {String|Integer} id
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {Boolean}
  */
  recordIsLoaded() {
    return this._callSuperMethod('recordIsLoaded', 2, arguments);
  },

  /**
    This method unloads all records in the store.
    @method unloadAll
    @param {String} modelName
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
  */
  unloadAll() {
    return this._callSuperMethod('unloadAll', 1, arguments);
  },

  /**
    For symmetry, a record can be unloaded via the store. Only non-dirty records can be unloaded.
    @method unloadRecord
    @param {DS.Model} record
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
  */
  unloadRecord() {
    return this._callSuperMethod('unloadRecord', 1, arguments);
  },

  /**
    Returns an instance of the adapter for a given type.
    @method adapterFor
    @param {String} modelName
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {DS.Adapter or subclass} Adapter
  */
  adapterFor(modelName, useOnlineStore) {
    let onlineStore = this.get('onlineStore');
    let offlineStore = this.get('offlineStore');
    let adapter = onlineStore.adapterFor(modelName);
    if (this.get('offlineGlobals').get('isOfflineEnabled')) {
      let useOnlineStoreCondition = useOnlineStore || (Ember.isNone(useOnlineStore) && this._isOnline());
      return useOnlineStoreCondition ? decorateAdapter.call(this, adapter) : offlineStore.adapterFor(modelName);
    }
    else {
      return adapter;
    }
  },

  /**
    Returns an instance of the serializer for a given type.
    @method serializerFor
    @param {String} modelName
    @param {Boolean} [useOnlineStore] Allow to explicitly specify online or offline store using independently of global online status
    @return {DS.Serializer or subclass} Serializer
  */
  serializerFor(modelName, useOnlineStore) {
    let onlineStore = this.get('onlineStore');
    let offlineStore = this.get('offlineStore');
    let serializer = onlineStore.serializerFor(modelName);
    if (this.get('offlineGlobals').get('isOfflineEnabled')) {
      let useOnlineStoreCondition = useOnlineStore || (Ember.isNone(useOnlineStore) && this._isOnline());
      return useOnlineStoreCondition ? decorateSerializer.call(this, serializer) : offlineStore.serializerFor(modelName);
    }
    else {
      return serializer;
    }
  },

  /*
    Decorate specified method of online store for add extra syncing features.
  */
  _decorateMethodAndCall(finderType, originMethodName, originMethodArguments, optionsIndex) {
    let onlineStore = this.get('onlineStore');
    let originMethod = onlineStore[originMethodName];
    let decoratedMethod = decorateAPICall(finderType, originMethod);
	  if (optionsIndex > -1) {
      let options = originMethodArguments[optionsIndex];
	    options = this.get('offlineGlobals').get('isOfflineEnabled') ? options : Ember.$.extend(true, { bypass: true }, options);
      originMethodArguments[optionsIndex] = options;
    }

    return decoratedMethod.apply(onlineStore, originMethodArguments);
  },

  /*
    Detect global online status.
  */
  _isOnline() {
    return this.get('offlineGlobals.isOnline');
  },

  /*
    Pass control flow to online or offline store depend on global online status
    and explicitly specified store to use.
  */
  _callSuperMethod(methodName, useOnlineStoreParamNum, ...args) {
    let onlineStore = this.get('onlineStore');
    let offlineStore = this.get('offlineStore');
    let useOnlineStore = (args[0].length - 1) >= useOnlineStoreParamNum ? args[0][useOnlineStoreParamNum] : null;
    let useOnlineStoreCondition = useOnlineStore || (Ember.isNone(useOnlineStore) && this._isOnline());
    return useOnlineStoreCondition ? onlineStore[methodName].apply(onlineStore, args[0]) : offlineStore[methodName].apply(offlineStore, args[0]);
  }
});
<|endoftext|>


<commit_before>// Copyright 2014-2020, University of Colorado Boulder

/**
 * A single radio button. This class is designed to be part of a RadioButtonGroup and there should be no need to use it
 * outside of RadioButtonGroup. It is called RadioButtonGroupMember to differentiate from RadioButton, which already
 * exists.
 *
 * @author Aaron Davis (PhET Interactive Simulations)
 */

import Emitter from '../../../axon/js/Emitter.js';
import merge from '../../../phet-core/js/merge.js';
import Color from '../../../scenery/js/util/Color.js';
import PaintColorProperty from '../../../scenery/js/util/PaintColorProperty.js';
import pushButtonSoundPlayer from '../../../tambo/js/shared-sound-players/pushButtonSoundPlayer.js';
import EventType from '../../../tandem/js/EventType.js';
import PhetioObject from '../../../tandem/js/PhetioObject.js';
import Tandem from '../../../tandem/js/Tandem.js';
import ColorConstants from '../ColorConstants.js';
import sun from '../sun.js';
import ButtonModel from './ButtonModel.js';
import RadioButtonInteractionState from './RadioButtonInteractionState.js';
import RadioButtonInteractionStateProperty from './RadioButtonInteractionStateProperty.js';
import RectangularButton from './RectangularButton.js';

class RadioButtonGroupMember extends RectangularButton {

  /**
   * @param {Property} property axon Property that can take on a set of values, one for each radio button in the group
   * @param {Object} value value when this radio button is selected
   * @param {Object} [options]
   */
  constructor( property, value, options ) {

    options = merge( {
      // The fill for the rectangle behind the radio buttons.  Default color is bluish color, as in the other button library.
      baseColor: ColorConstants.LIGHT_BLUE,
      disabledBaseColor: ColorConstants.LIGHT_GRAY,

      // Opacity can be set separately for the buttons and button content.
      selectedButtonOpacity: 1,
      deselectedButtonOpacity: 0.6,
      selectedContentOpacity: 1,
      deselectedContentOpacity: 0.6,
      overButtonOpacity: 0.8,
      overContentOpacity: 0.8,

      selectedStroke: 'black',
      deselectedStroke: new Color( 50, 50, 50 ),
      selectedLineWidth: 1.5,
      deselectedLineWidth: 1,

      // The following options specify highlight behavior overrides, leave as null to get the default behavior
      // Note that highlighting applies only to deselected buttons
      overFill: null,
      overStroke: null,
      overLineWidth: null,

      // The default appearance uses the color values specified above, but other appearances could be specified for more
      // customized behavior.  Generally setting the color values above should be enough to specify the desired look.
      buttonAppearanceStrategy: flatAppearanceStrategy,

      // {Playable|null} - sound generation - If set to null a default will be used that is based on this button's
      // position within the radio button group.  Can be set to Playable.NO_SOUND to disable.
      soundPlayer: null,

      // pdom
      tagName: 'input',
      inputType: 'radio',
      labelTagName: 'label',
      containerTagName: 'li',
      appendDescription: true,
      appendLabel: true,

      // phet-io
      tandem: Tandem.REQUIRED,
      phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
    }, options );

    assert && assert( options.tandem instanceof Tandem, 'invalid tandem' );
    assert && assert( !options.tandem.supplied || options.tandem.name.endsWith( 'RadioButton' ),
      `RadioButtonGroupMember tandem.name must end with RadioButton: ${options.tandem.phetioID}` );

    // @private
    // Note it shares a tandem with this, so the emitter will be instrumented as a child of the button
    const buttonModel = new ButtonModel( {
      tandem: options.tandem
    } );

    const interactionStateProperty = new RadioButtonInteractionStateProperty( buttonModel, property, value );

    super( buttonModel, interactionStateProperty, options );

    // @public for use in RadioButtonGroup for managing the labels
    this.interactionStateProperty = interactionStateProperty;

    // pdom - Specify the default value for assistive technology, this attribute is needed in addition to
    // the 'checked' Property to mark this element as the default selection since 'checked' may be set before
    // we are finished adding RadioButtonGroupMembers to the RadioButtonGroup.
    if ( property.value === value ) {
      this.setAccessibleAttribute( 'checked', 'checked' );
    }

    // pdom - when the Property changes, make sure the correct radio button is marked as 'checked' so that this button
    // receives focus on 'tab'
    const accessibleCheckedListener = newValue => {
      this.accessibleChecked = ( newValue === value );
    };
    property.link( accessibleCheckedListener );

    // @private - the Property this button changes
    this.property = property;

    // @private - the value that is set to the Property when this button is pressed
    this.value = value;

    // @private
    this.firedEmitter = new Emitter( {
      tandem: options.tandem.createTandem( 'firedEmitter' ),
      phetioDocumentation: 'Emits when the radio button is pressed',
      phetioReadOnly: options.phetioReadOnly,
      phetioEventType: EventType.USER
    } );

    this.firedEmitter.addListener( () => property.set( value ) );

    // When the button model triggers an event, fire from the radio button
    buttonModel.downProperty.link( down => {
      if ( !down && buttonModel.overProperty.get() && !buttonModel.interrupted ) {
        this.fire();
      }
    } );

    // sound generation
    const soundPlayer = options.soundPlayer || pushButtonSoundPlayer;
    const playSound = () => { soundPlayer.play(); };
    buttonModel.produceSoundEmitter.addListener( playSound );

    // @private
    this.disposeRadioButtonGroupMember = () => {
      property.unlink( accessibleCheckedListener );
      this.firedEmitter.dispose();
      buttonModel.dispose();
      this.interactionStateProperty.dispose();
    };
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.disposeRadioButtonGroupMember();
    super.dispose();
  }

  /**
   * @public (read-only) - fire on up if the button is enabled, public for use in the accessibility tree
   */
  fire() {

    // Note that @protected this.buttonModel is defined in superclass
    if ( this.buttonModel.enabledProperty.get() ) {
      this.firedEmitter.emit();
      this.buttonModel.produceSoundEmitter.emit();
    }
  }
}

/**
 * Appearance strategy for radio buttons that look flat, i.e. no shading or highlighting, but
 * that change color on mouseover, press, selected, etc.
 *
 * @param {Node} button
 * @param {Property} interactionStateProperty
 * @param {Property} baseColorProperty
 * @param {Object} [options]
 */
function flatAppearanceStrategy( button, interactionStateProperty, baseColorProperty, options ) {

  // Dynamic fills and strokes
  const overFill = new PaintColorProperty( options.overFill || baseColorProperty, {
    luminanceFactor: options.overFill ? 0 : 0.4
  } );
  const pressedFill = new PaintColorProperty( baseColorProperty, {
    luminanceFactor: -0.4
  } );
  const overStroke = new PaintColorProperty( options.overStroke || options.deselectedStroke, {
    luminanceFactor: options.overStroke ? 0 : -0.4
  } );

  // Cache colors
  button.cachedPaints = [
    baseColorProperty, overFill, pressedFill, overStroke, options.selectedStroke, options.deselectedStroke
  ];

  // Change colors and opacity to match interactionState
  function interactionStateListener( interactionState ) {
    switch( interactionState ) {

      case RadioButtonInteractionState.SELECTED:
      case RadioButtonInteractionState.DISABLED_SELECTED:
        button.fill = baseColorProperty;
        button.stroke = options.selectedStroke;
        button.lineWidth = options.selectedLineWidth;
        button.opacity = options.selectedButtonOpacity;
        break;

      case RadioButtonInteractionState.DESELECTED:
      case RadioButtonInteractionState.DISABLED_DESELECTED:
        button.fill = baseColorProperty;
        button.stroke = options.deselectedStroke;
        button.lineWidth = options.deselectedLineWidth;
        button.opacity = options.deselectedButtonOpacity;
        break;

      // mouseover for deselected buttons
      case RadioButtonInteractionState.OVER:
        button.fill = overFill;
        button.stroke = overStroke;
        button.lineWidth = ( options.overLineWidth ) ? options.overLineWidth : options.deselectedLineWidth;
        button.opacity = options.overButtonOpacity;
        break;

      case RadioButtonInteractionState.PRESSED:
        button.fill = pressedFill;
        button.stroke = options.deselectedStroke;
        button.lineWidth = options.deselectedLineWidth;
        button.opacity = options.selectedButtonOpacity;
        break;

      default:
        throw new Error( `unsupported interactionState: ${interactionState}` );
    }
  }
  interactionStateProperty.link( interactionStateListener );

  // @public
  this.dispose = () => {
    if ( interactionStateProperty.hasListener( interactionStateListener ) ) {
      interactionStateProperty.unlink( interactionStateListener );
    }
    overStroke.dispose();
    overFill.dispose();
    pressedFill.dispose();
  };
}

sun.register( 'RadioButtonGroupMember', RadioButtonGroupMember );
export default RadioButtonGroupMember;<commit_msg>rename RadioButtonGroupMember.FlatAppearanceStrategy, https://github.com/phetsims/sun/issues/644<commit_after>// Copyright 2014-2020, University of Colorado Boulder

/**
 * A single radio button. This class is designed to be part of a RadioButtonGroup and there should be no need to use it
 * outside of RadioButtonGroup. It is called RadioButtonGroupMember to differentiate from RadioButton, which already
 * exists.
 *
 * @author Aaron Davis (PhET Interactive Simulations)
 */

import Emitter from '../../../axon/js/Emitter.js';
import merge from '../../../phet-core/js/merge.js';
import Color from '../../../scenery/js/util/Color.js';
import PaintColorProperty from '../../../scenery/js/util/PaintColorProperty.js';
import pushButtonSoundPlayer from '../../../tambo/js/shared-sound-players/pushButtonSoundPlayer.js';
import EventType from '../../../tandem/js/EventType.js';
import PhetioObject from '../../../tandem/js/PhetioObject.js';
import Tandem from '../../../tandem/js/Tandem.js';
import ColorConstants from '../ColorConstants.js';
import sun from '../sun.js';
import ButtonModel from './ButtonModel.js';
import RadioButtonInteractionState from './RadioButtonInteractionState.js';
import RadioButtonInteractionStateProperty from './RadioButtonInteractionStateProperty.js';
import RectangularButton from './RectangularButton.js';

class RadioButtonGroupMember extends RectangularButton {

  /**
   * @param {Property} property axon Property that can take on a set of values, one for each radio button in the group
   * @param {Object} value value when this radio button is selected
   * @param {Object} [options]
   */
  constructor( property, value, options ) {

    options = merge( {
      // The fill for the rectangle behind the radio buttons.  Default color is bluish color, as in the other button library.
      baseColor: ColorConstants.LIGHT_BLUE,
      disabledBaseColor: ColorConstants.LIGHT_GRAY,

      // Opacity can be set separately for the buttons and button content.
      selectedButtonOpacity: 1,
      deselectedButtonOpacity: 0.6,
      selectedContentOpacity: 1,
      deselectedContentOpacity: 0.6,
      overButtonOpacity: 0.8,
      overContentOpacity: 0.8,

      selectedStroke: 'black',
      deselectedStroke: new Color( 50, 50, 50 ),
      selectedLineWidth: 1.5,
      deselectedLineWidth: 1,

      // The following options specify highlight behavior overrides, leave as null to get the default behavior
      // Note that highlighting applies only to deselected buttons
      overFill: null,
      overStroke: null,
      overLineWidth: null,

      // The default appearance uses the color values specified above, but other appearances could be specified for more
      // customized behavior.  Generally setting the color values above should be enough to specify the desired look.
      buttonAppearanceStrategy: RadioButtonGroupMember.FlatAppearanceStrategy,

      // {Playable|null} - sound generation - If set to null a default will be used that is based on this button's
      // position within the radio button group.  Can be set to Playable.NO_SOUND to disable.
      soundPlayer: null,

      // pdom
      tagName: 'input',
      inputType: 'radio',
      labelTagName: 'label',
      containerTagName: 'li',
      appendDescription: true,
      appendLabel: true,

      // phet-io
      tandem: Tandem.REQUIRED,
      phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
    }, options );

    assert && assert( options.tandem instanceof Tandem, 'invalid tandem' );
    assert && assert( !options.tandem.supplied || options.tandem.name.endsWith( 'RadioButton' ),
      `RadioButtonGroupMember tandem.name must end with RadioButton: ${options.tandem.phetioID}` );

    // @private
    // Note it shares a tandem with this, so the emitter will be instrumented as a child of the button
    const buttonModel = new ButtonModel( {
      tandem: options.tandem
    } );

    const interactionStateProperty = new RadioButtonInteractionStateProperty( buttonModel, property, value );

    super( buttonModel, interactionStateProperty, options );

    // @public for use in RadioButtonGroup for managing the labels
    this.interactionStateProperty = interactionStateProperty;

    // pdom - Specify the default value for assistive technology, this attribute is needed in addition to
    // the 'checked' Property to mark this element as the default selection since 'checked' may be set before
    // we are finished adding RadioButtonGroupMembers to the RadioButtonGroup.
    if ( property.value === value ) {
      this.setAccessibleAttribute( 'checked', 'checked' );
    }

    // pdom - when the Property changes, make sure the correct radio button is marked as 'checked' so that this button
    // receives focus on 'tab'
    const accessibleCheckedListener = newValue => {
      this.accessibleChecked = ( newValue === value );
    };
    property.link( accessibleCheckedListener );

    // @private - the Property this button changes
    this.property = property;

    // @private - the value that is set to the Property when this button is pressed
    this.value = value;

    // @private
    this.firedEmitter = new Emitter( {
      tandem: options.tandem.createTandem( 'firedEmitter' ),
      phetioDocumentation: 'Emits when the radio button is pressed',
      phetioReadOnly: options.phetioReadOnly,
      phetioEventType: EventType.USER
    } );

    this.firedEmitter.addListener( () => property.set( value ) );

    // When the button model triggers an event, fire from the radio button
    buttonModel.downProperty.link( down => {
      if ( !down && buttonModel.overProperty.get() && !buttonModel.interrupted ) {
        this.fire();
      }
    } );

    // sound generation
    const soundPlayer = options.soundPlayer || pushButtonSoundPlayer;
    const playSound = () => { soundPlayer.play(); };
    buttonModel.produceSoundEmitter.addListener( playSound );

    // @private
    this.disposeRadioButtonGroupMember = () => {
      property.unlink( accessibleCheckedListener );
      this.firedEmitter.dispose();
      buttonModel.dispose();
      this.interactionStateProperty.dispose();
    };
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.disposeRadioButtonGroupMember();
    super.dispose();
  }

  /**
   * @public (read-only) - fire on up if the button is enabled, public for use in the accessibility tree
   */
  fire() {

    // Note that @protected this.buttonModel is defined in superclass
    if ( this.buttonModel.enabledProperty.get() ) {
      this.firedEmitter.emit();
      this.buttonModel.produceSoundEmitter.emit();
    }
  }
}

/**
 * Appearance strategy for radio buttons that look flat, i.e. no shading or highlighting, but
 * that change color on mouseover, press, selected, etc.
 *
 * @param {Node} button
 * @param {Property} interactionStateProperty
 * @param {Property} baseColorProperty
 * @param {Object} [options]
 * @constructor
 * @public
 */
RadioButtonGroupMember.FlatAppearanceStrategy = function( button, interactionStateProperty, baseColorProperty, options ) {

  // Dynamic fills and strokes
  const overFill = new PaintColorProperty( options.overFill || baseColorProperty, {
    luminanceFactor: options.overFill ? 0 : 0.4
  } );
  const pressedFill = new PaintColorProperty( baseColorProperty, {
    luminanceFactor: -0.4
  } );
  const overStroke = new PaintColorProperty( options.overStroke || options.deselectedStroke, {
    luminanceFactor: options.overStroke ? 0 : -0.4
  } );

  // Cache colors
  button.cachedPaints = [
    baseColorProperty, overFill, pressedFill, overStroke, options.selectedStroke, options.deselectedStroke
  ];

  // Change colors and opacity to match interactionState
  function interactionStateListener( interactionState ) {
    switch( interactionState ) {

      case RadioButtonInteractionState.SELECTED:
      case RadioButtonInteractionState.DISABLED_SELECTED:
        button.fill = baseColorProperty;
        button.stroke = options.selectedStroke;
        button.lineWidth = options.selectedLineWidth;
        button.opacity = options.selectedButtonOpacity;
        break;

      case RadioButtonInteractionState.DESELECTED:
      case RadioButtonInteractionState.DISABLED_DESELECTED:
        button.fill = baseColorProperty;
        button.stroke = options.deselectedStroke;
        button.lineWidth = options.deselectedLineWidth;
        button.opacity = options.deselectedButtonOpacity;
        break;

      // mouseover for deselected buttons
      case RadioButtonInteractionState.OVER:
        button.fill = overFill;
        button.stroke = overStroke;
        button.lineWidth = ( options.overLineWidth ) ? options.overLineWidth : options.deselectedLineWidth;
        button.opacity = options.overButtonOpacity;
        break;

      case RadioButtonInteractionState.PRESSED:
        button.fill = pressedFill;
        button.stroke = options.deselectedStroke;
        button.lineWidth = options.deselectedLineWidth;
        button.opacity = options.selectedButtonOpacity;
        break;

      default:
        throw new Error( `unsupported interactionState: ${interactionState}` );
    }
  }
  interactionStateProperty.link( interactionStateListener );

  // @public
  this.dispose = () => {
    if ( interactionStateProperty.hasListener( interactionStateListener ) ) {
      interactionStateProperty.unlink( interactionStateListener );
    }
    overStroke.dispose();
    overFill.dispose();
    pressedFill.dispose();
  };
};

sun.register( 'RadioButtonGroupMember', RadioButtonGroupMember );
export default RadioButtonGroupMember;<|endoftext|>


<commit_before>/*
 * Copyright 2005-2011 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function requiredsSet() {
    var message = '';
    // 178_attribute is Namespace
    if (document.getElementById('178_attribute').options[document.getElementById('178_attribute').selectedIndex].value == '') {
      message =  message + 'Please select a Namespace.\n';
    }
    // 204 is Agenda Name
    if (document.getElementById('204').value == '') {
      message = message + 'Please enter the Agenda Name.\n';
    }
    // 230 is Agenda Context
    if (document.getElementById('230').value == '') {
      message = message + 'Please Lookup (click the magnifying glass icon) the Agenda Context.\n';
    }
    return message;
}

<commit_msg>Better handling of nulls (prevent js explosions) in requiredAlerts.js<commit_after>/*
 * Copyright 2005-2011 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function requiredsSet() {
    var message = '';
    // 178_attribute is Namespace
    if (document.getElementById('178_attribute') != null &&
        document.getElementById('178_attribute').selectedIndex != null &&
        document.getElementById('178_attribute').options[document.getElementById('178_attribute').selectedIndex].value == '') {
      message =  message + 'Please select a Namespace.\n';
    }
    // 204 is Agenda Name
    if (document.getElementById('204') != null &&
        document.getElementById('204').value == '') {
      message = message + 'Please enter the Agenda Name.\n';
    }
    // 230 is Agenda Context
    if (document.getElementById('230') != null &&
        document.getElementById('230').value == '') {
      message = message + 'Please Lookup (click the magnifying glass icon) the Agenda Context.\n';
    }
    return message;
}

<|endoftext|>