<commit_before>/**
 *
 *  Meet-Up Event Planner
 *  Copyright 2015 Justin Varghese John All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      https://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
 *
 */

 /* eslint-env browser */
 /* global Firebase google */
(function() {
  /**
   * Creates an Array of DOM nodes that match the selector
   * @param selector {string} CSS selector - selector to match against
   * @return {array} Array of DOM nodes
   */
  function getDomNodeArray(selector) {
    var nodes = Array.prototype.slice.apply(document.querySelectorAll(selector));
    if (!nodes) {
      nodes = [];
    }
    return nodes;
  }

  /*
  Adding guests
   */
  var newGuest = document.querySelector('input.new-guest');
  var people = document.querySelector('.attendees');
  var guestsDisplay = document.querySelector('.card-detail-actual.guests');
  var addButton = document.querySelector('#add-attendee');
  newGuest.onkeydown = function(evt) {
    if (evt.keyIdentifier === 'Enter') {
      let enteredPerson = document.createElement('div');
      let enteredPerson2 = document.createElement('div');
      let deletePerson = document.createElement('button');
      deletePerson.innerHTML = '-';
      deletePerson.parent = enteredPerson;

      deletePerson.onclick = function() {
        let dataAttr = this.parent.getAttribute('data-guest');
        let guestToRemove = document.querySelector(`[data-guest="${dataAttr}"]`);
        people.removeChild(this.parent);
        // remove from card-detail preview
        guestsDisplay.removeChild(guestToRemove);
      };
      enteredPerson.innerHTML = newGuest.value;
      // for easy access later
      enteredPerson.setAttribute('data-guest', newGuest.value);
      enteredPerson.appendChild(deletePerson);
      // add to card-detail preview
      enteredPerson2.innerHTML = newGuest.value;
      // for easy access later
      enteredPerson2.setAttribute('data-guest', newGuest.value);
      guestsDisplay.appendChild(enteredPerson2);

      people.appendChild(enteredPerson);
      newGuest.value = '';
    }
  };
  addButton.onclick = function() {
    let enteredPerson = document.createElement('div');
    let enteredPerson2 = document.createElement('div');
    let deletePerson = document.createElement('button');
    deletePerson.innerHTML = '-';
    deletePerson.parent = enteredPerson;

    deletePerson.onclick = function() {
      let dataAttr = this.parent.getAttribute('data-guest');
      let guestToRemove = document.querySelector(`[data-guest="${dataAttr}"]`);
      people.removeChild(this.parent);
      // remove from card-detail preview
      guestsDisplay.removeChild(guestToRemove);
    };
    enteredPerson.innerHTML = newGuest.value;
    // for easy access later
    enteredPerson.setAttribute('data-guest', newGuest.value);
    enteredPerson.appendChild(deletePerson);
    // add to card-detail preview
    enteredPerson2.innerHTML = newGuest.value;
    // for easy access later
    enteredPerson2.setAttribute('data-guest', newGuest.value);
    guestsDisplay.appendChild(enteredPerson2);

    people.appendChild(enteredPerson);
    newGuest.value = '';
  };

  /*
  Adding functionality to hide/show guest in card-detail preview
   */
  var attendeesCheckbox = document.getElementById('attendees-list');
  var guestsDisplayContainer = document.querySelector('.card-detail.guests');
  attendeesCheckbox.onclick = function() {
    if (this.checked === true) {
      guestsDisplayContainer.classList.remove('hidden');
    } else {
      guestsDisplayContainer.classList.add('hidden');
    }
  };

  /*
  class="model model-*" Refer to inputs by the name after class model-*
   */
  function Model() {
    var self = this;

    function Binding(elem, value) {
      elem = elem || null;
      value = value || null;

      this.elem = elem;
      this.value = value;
      this.hasChanged = false;
      this.oninput = function() {};
      this.onchange = function() {};
    }

    // pulls values from elems of class="model model-*" to create Model's raw info and set up 1-way binding
    getDomNodeArray('.model').forEach(function(elem) {
      elem.classList.forEach(function(className) {
        var possiblyMatch = className.match(/model\-/g);
        if (possiblyMatch) {
          // create the Model value
          var name = className.slice(6);
          var value = elem.value;

          self[name] = self[name] || new Binding(elem, value);
          elem.binding = elem.binding || self[name];

          // bind data oninput
          elem.oninput = function() {
            self[name].hasChanged = true;
            self[name] = self[name] || new Binding(elem, value);
            self[name].value = elem.value;

            // for callbacks
            self[name].oninput();
          };

          // bind data onchange
          elem.onchange = function() {
            // self[name].hasChanged = true;
            // self[name] = self[name] || new Binding(elem, value);
            // self[name].value = elem.value;

            // for callbacks
            self[name].onchange();
          };
        }
      });
    });
  }

  /*
  To create a placeholder effect. Assumes that display element starts with 'placeholder' class
   */
  function displayWithPlaceholder(inputBinding, displayElem, placeholder) {
    if (displayElem.classList.contains('placeholder')) {
      displayElem.classList.remove('placeholder');
    }
    if (inputBinding.value === '') {
      inputBinding.value = placeholder;
      // let the model know that input has gone back to default
      inputBinding.hasChanged = false;

      displayElem.classList.add('placeholder');
    }
    displayElem.innerHTML = inputBinding.value;
  }

  /*
  Set up Google Maps autocomplete location input
   */
  var input = document.getElementById('location');
  var whereDisplay = document.querySelector('.card-detail-actual.where');
  var autocomplete = new google.maps.places.Autocomplete(input);

  autocomplete.addListener('place_changed', () => {
    let place = autocomplete.getPlace();
    displayWithPlaceholder({
      value: place.formatted_address
    }, whereDisplay, 'Location');
  });

  /*
  Creating Model
   */
  var model = new Model();

  // validation methods
  var validateTitle = function() {
    return model.title.hasChanged;
  };
  var validateDescription = function() {
    return model.type.hasChanged;
  };
  var validateHost = function() {
    return model.host.hasChanged;
  };
  var validateLocation = function() {
    return model.location.hasChanged;
  };
  var validateStartDatetime = function() {
    return model.starttime.elem.value;
  };
  var validateEndDatetime = function() {
    return model.endtime.elem.value;
  };
  var validateAttendees = function() {
    if (people.length > 0) {
      return true;
    }
    return false;
  };
  var validateAttendeeEmails = function() {
    var areReal = false;
    var emailRegex = new RegExp('^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$');
    people.forEach(function(guest, index) {
      var result = emailRegex.exec(guest.value);
      if (result && result.index === 0) {
        if (index === 0) {
          areReal = true;
        } else {
          areReal = areReal && true;
        }
      } else {
        areReal = areReal && false;
      }
    });
    return areReal;
  };

  // Update the view oninput
  model.title.oninput = function() {
    var titleDisplay = document.querySelector('.title-display');
    displayWithPlaceholder(model.title, titleDisplay, 'Untitled Event');
  };

  model.type.oninput = function() {
    var typeDisplay = document.querySelector('.card-detail-actual.what');
    displayWithPlaceholder(model.type, typeDisplay, 'Description');
  };

  model.host.oninput = function() {
    var hostDisplay = document.querySelector('.card-detail-actual.who');
    displayWithPlaceholder(model.host, hostDisplay, 'Host');
  };

  model.starttime.oninput = function() {
    var startTimeDisplay = document.querySelector('.card-detail-actual.starttime');
    displayWithPlaceholder(model.starttime, startTimeDisplay, 'Start Time');
  };

  model.endtime.oninput = function() {
    var endTimeDisplay = document.querySelector('.card-detail-actual.endtime');
    displayWithPlaceholder(model.endtime, endTimeDisplay, 'End Time');
  };

  function displayValid(model) {
    model.elem.classList.remove('invalid');
    model.elem.classList.add('valid');

    let validationIcon = model.elem.parentElement.previousElementSibling;
    if (validationIcon.classList.contains('material-icons')) {
      validationIcon.textContent = 'checkmark';
    }
  }
  function displayInvalid(model) {
    model.elem.classList.remove('valid');
    model.elem.classList.add('invalid');

    let validationIcon = model.elem.parentElement.previousElementSibling;
    if (validationIcon.classList.contains('material-icons')) {
      validationIcon.textContent = 'error';
    }
  }
  // update the view onchange
  model.title.onchange = function() {
    if (validateTitle()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  model.type.onchange = function() {
    if (validateDescription()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  model.host.onchange = function() {
    if (validateHost()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  model.location.onchange = function() {
    if (validateLocation()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  model.starttime.onchange = function() {
    if (validateStartDatetime()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  model.endtime.onchange = function() {
    if (validateEndDatetime()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  function validate() {
    var self = this;
    var allGood = false;
    var errorMessage = 'Please correct the following errors:
';
    var people = getDomNodeArray('.people>div');

    var validations = [{
      errorMessage: 'Please include an event title.',
      validationMethod: validateTitle
    }, {
      errorMessage: 'Please include an event description.',
      validationMethod: validateDescription
    }, {
      errorMessage: 'Please include at least one attendee.',
      validationMethod: validateAttendees
    }, {
      errorMessage: 'Please include a valid email addresses.',
      validationMethod: validateAttendeeEmails
    }];

    validations.forEach(function(val, index) {
      if (val.validationMethod(self)) {
        if (index === 0) {
          allGood = true;
        } else {
          allGood = allGood && true;
        }
      } else {
        allGood = allGood && false;
        errorMessage = errorMessage + val.errorMessage + '
';
      }
    });
    errorMessage = errorMessage.trim();

    return {
      isValid: allGood,
      errorMessage: errorMessage
    };
  }

  var createButton = document.querySelector('button#create');
  createButton.onclick = function() {
    var validState = validate();

    if (!validState.isValid) {
      var errorMessage = document.querySelector('.error-message');
      errorMessage.innerHTML = validState.errorMessage;
    } else {
      alert('Valid form. Thanks for submitting!');
    }
  };
})();
<commit_msg>Fix default event action from breaking form<commit_after>/**
 *
 *  Meet-Up Event Planner
 *  Copyright 2015 Justin Varghese John All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      https://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
 *
 */

 /* eslint-env browser */
 /* global Firebase google */
(function() {
  /**
   * Creates an Array of DOM nodes that match the selector
   * @param selector {string} CSS selector - selector to match against
   * @return {array} Array of DOM nodes
   */
  function getDomNodeArray(selector) {
    var nodes = Array.prototype.slice.apply(document.querySelectorAll(selector));
    if (!nodes) {
      nodes = [];
    }
    return nodes;
  }

  /*
  Adding guests
   */
  var newGuest = document.querySelector('input.new-guest');
  var people = document.querySelector('.attendees');
  var guestsDisplay = document.querySelector('.card-detail-actual.guests');
  var addButton = document.querySelector('#add-attendee');
  newGuest.onkeydown = function(evt) {
    if (evt.keyIdentifier === 'Enter') {
      let enteredPerson = document.createElement('div');
      let enteredPerson2 = document.createElement('div');
      let deletePerson = document.createElement('button');
      deletePerson.innerHTML = '-';
      deletePerson.parent = enteredPerson;

      deletePerson.onclick = function() {
        let dataAttr = this.parent.getAttribute('data-guest');
        let guestToRemove = document.querySelector(`[data-guest="${dataAttr}"]`);
        people.removeChild(this.parent);
        // remove from card-detail preview
        guestsDisplay.removeChild(guestToRemove);
      };
      enteredPerson.innerHTML = newGuest.value;
      // for easy access later
      enteredPerson.setAttribute('data-guest', newGuest.value);
      enteredPerson.appendChild(deletePerson);
      // add to card-detail preview
      enteredPerson2.innerHTML = newGuest.value;
      // for easy access later
      enteredPerson2.setAttribute('data-guest', newGuest.value);
      guestsDisplay.appendChild(enteredPerson2);

      people.appendChild(enteredPerson);
      newGuest.value = '';
    }
  };
  addButton.onclick = function(event) {
    event.preventDefault();
    let enteredPerson = document.createElement('div');
    let enteredPerson2 = document.createElement('div');
    let deletePerson = document.createElement('button');
    deletePerson.innerHTML = '-';
    deletePerson.parent = enteredPerson;

    deletePerson.onclick = function(event) {
      event.preventDefault();
      let dataAttr = this.parent.getAttribute('data-guest');
      let guestToRemove = document.querySelector(`[data-guest="${dataAttr}"]`);
      people.removeChild(this.parent);
      // remove from card-detail preview
      guestsDisplay.removeChild(guestToRemove);
    };
    enteredPerson.innerHTML = newGuest.value;
    // for easy access later
    enteredPerson.setAttribute('data-guest', newGuest.value);
    enteredPerson.appendChild(deletePerson);
    // add to card-detail preview
    enteredPerson2.innerHTML = newGuest.value;
    // for easy access later
    enteredPerson2.setAttribute('data-guest', newGuest.value);
    guestsDisplay.appendChild(enteredPerson2);

    people.appendChild(enteredPerson);
    newGuest.value = '';
  };

  /*
  Adding functionality to hide/show guest in card-detail preview
   */
  var attendeesCheckbox = document.getElementById('attendees-list');
  var guestsDisplayContainer = document.querySelector('.card-detail.guests');
  attendeesCheckbox.onclick = function() {
    if (this.checked === true) {
      guestsDisplayContainer.classList.remove('hidden');
    } else {
      guestsDisplayContainer.classList.add('hidden');
    }
  };

  /*
  class="model model-*" Refer to inputs by the name after class model-*
   */
  function Model() {
    var self = this;

    function Binding(elem, value) {
      elem = elem || null;
      value = value || null;

      this.elem = elem;
      this.value = value;
      this.hasChanged = false;
      this.oninput = function() {};
      this.onchange = function() {};
    }

    // pulls values from elems of class="model model-*" to create Model's raw info and set up 1-way binding
    getDomNodeArray('.model').forEach(function(elem) {
      elem.classList.forEach(function(className) {
        var possiblyMatch = className.match(/model\-/g);
        if (possiblyMatch) {
          // create the Model value
          var name = className.slice(6);
          var value = elem.value;

          self[name] = self[name] || new Binding(elem, value);
          elem.binding = elem.binding || self[name];

          // bind data oninput
          elem.oninput = function() {
            self[name].hasChanged = true;
            self[name] = self[name] || new Binding(elem, value);
            self[name].value = elem.value;

            // for callbacks
            self[name].oninput();
          };

          // bind data onchange
          elem.onchange = function() {
            // self[name].hasChanged = true;
            // self[name] = self[name] || new Binding(elem, value);
            // self[name].value = elem.value;

            // for callbacks
            self[name].onchange();
          };
        }
      });
    });
  }

  /*
  To create a placeholder effect. Assumes that display element starts with 'placeholder' class
   */
  function displayWithPlaceholder(inputBinding, displayElem, placeholder) {
    if (displayElem.classList.contains('placeholder')) {
      displayElem.classList.remove('placeholder');
    }
    if (inputBinding.value === '') {
      inputBinding.value = placeholder;
      // let the model know that input has gone back to default
      inputBinding.hasChanged = false;

      displayElem.classList.add('placeholder');
    }
    displayElem.innerHTML = inputBinding.value;
  }

  /*
  Set up Google Maps autocomplete location input
   */
  var input = document.getElementById('location');
  var whereDisplay = document.querySelector('.card-detail-actual.where');
  var autocomplete = new google.maps.places.Autocomplete(input);

  autocomplete.addListener('place_changed', () => {
    let place = autocomplete.getPlace();
    displayWithPlaceholder({
      value: place.formatted_address
    }, whereDisplay, 'Location');
  });

  /*
  Creating Model
   */
  var model = new Model();

  // validation methods
  var validateTitle = function() {
    return model.title.hasChanged;
  };
  var validateDescription = function() {
    return model.type.hasChanged;
  };
  var validateHost = function() {
    return model.host.hasChanged;
  };
  var validateLocation = function() {
    return model.location.hasChanged;
  };
  var validateStartDatetime = function() {
    return model.starttime.elem.value;
  };
  var validateEndDatetime = function() {
    return model.endtime.elem.value;
  };
  var validateAttendees = function() {
    if (people.length > 0) {
      return true;
    }
    return false;
  };
  var validateAttendeeEmails = function() {
    var areReal = false;
    var emailRegex = new RegExp('^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$');
    people.forEach(function(guest, index) {
      var result = emailRegex.exec(guest.value);
      if (result && result.index === 0) {
        if (index === 0) {
          areReal = true;
        } else {
          areReal = areReal && true;
        }
      } else {
        areReal = areReal && false;
      }
    });
    return areReal;
  };

  // Update the view oninput
  model.title.oninput = function() {
    var titleDisplay = document.querySelector('.title-display');
    displayWithPlaceholder(model.title, titleDisplay, 'Untitled Event');
  };

  model.type.oninput = function() {
    var typeDisplay = document.querySelector('.card-detail-actual.what');
    displayWithPlaceholder(model.type, typeDisplay, 'Description');
  };

  model.host.oninput = function() {
    var hostDisplay = document.querySelector('.card-detail-actual.who');
    displayWithPlaceholder(model.host, hostDisplay, 'Host');
  };

  model.starttime.oninput = function() {
    var startTimeDisplay = document.querySelector('.card-detail-actual.starttime');
    displayWithPlaceholder(model.starttime, startTimeDisplay, 'Start Time');
  };

  model.endtime.oninput = function() {
    var endTimeDisplay = document.querySelector('.card-detail-actual.endtime');
    displayWithPlaceholder(model.endtime, endTimeDisplay, 'End Time');
  };

  function displayValid(model) {
    model.elem.classList.remove('invalid');
    model.elem.classList.add('valid');

    let validationIcon = model.elem.parentElement.previousElementSibling;
    if (validationIcon.classList.contains('material-icons')) {
      validationIcon.textContent = 'checkmark';
    }
  }
  function displayInvalid(model) {
    model.elem.classList.remove('valid');
    model.elem.classList.add('invalid');

    let validationIcon = model.elem.parentElement.previousElementSibling;
    if (validationIcon.classList.contains('material-icons')) {
      validationIcon.textContent = 'error';
    }
  }
  // update the view onchange
  model.title.onchange = function() {
    if (validateTitle()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  model.type.onchange = function() {
    if (validateDescription()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  model.host.onchange = function() {
    if (validateHost()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  model.location.onchange = function() {
    if (validateLocation()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  model.starttime.onchange = function() {
    if (validateStartDatetime()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  model.endtime.onchange = function() {
    if (validateEndDatetime()) {
      displayValid(this);
    } else {
      displayInvalid(this);
    }
  };

  function validate() {
    var self = this;
    var allGood = false;
    var errorMessage = 'Please correct the following errors:
';
    var people = getDomNodeArray('.people>div');

    var validations = [{
      errorMessage: 'Please include an event title.',
      validationMethod: validateTitle
    }, {
      errorMessage: 'Please include an event description.',
      validationMethod: validateDescription
    }, {
      errorMessage: 'Please include at least one attendee.',
      validationMethod: validateAttendees
    }, {
      errorMessage: 'Please include a valid email addresses.',
      validationMethod: validateAttendeeEmails
    }];

    validations.forEach(function(val, index) {
      if (val.validationMethod(self)) {
        if (index === 0) {
          allGood = true;
        } else {
          allGood = allGood && true;
        }
      } else {
        allGood = allGood && false;
        errorMessage = errorMessage + val.errorMessage + '
';
      }
    });
    errorMessage = errorMessage.trim();

    return {
      isValid: allGood,
      errorMessage: errorMessage
    };
  }

  var createButton = document.querySelector('button#create');
  createButton.onclick = function() {
    var validState = validate();

    if (!validState.isValid) {
      var errorMessage = document.querySelector('.error-message');
      errorMessage.innerHTML = validState.errorMessage;
    } else {
      alert('Valid form. Thanks for submitting!');
    }
  };
})();
<|endoftext|>


<commit_before>console.log( "=== simpread focus controlbar load ===" )

var fcontrol = ( function() {
    var template = '
\

\

\

\

\

\

';

    function FControl() {
        this.$parent = null;
        this.$target = null;
    }

    /*
        Add focus constrol bar
    */
    FControl.prototype.Render = function( root ) {
        console.log( "=== simpread focus controlbar add ===" );
        this.$parent = $(root);
        $( root ).append( template );
        this.$target = $( ".ks-simpread-constrolbar" ).find( "span" );
        addStyle( this.$target );
        addEventHandler( this.$target, root );
    }

    /*
        Remove focus constrol bar
    */
    FControl.prototype.Remove = function() {
        console.log( "=== simpread focus controlbar remove ===" );
        this.$target.off( "click" );
        this.$target.remove();
    }

    /*
        Add focus constrol bar style
    */
    function addStyle( $target ) {
        var path = chrome.extension.getURL("/");
        $($target[0]).attr( "style", "background-image:url(" + path + "assets/images/top.png)"     );
        $($target[1]).attr( "style", "background-image:url(" + path + "assets/images/setting.png)" );
        $($target[2]).attr( "style", "background-image:url(" + path + "assets/images/close.png)"   );
    }

    /*
        Add focus constrol bar event
    */
    function addEventHandler( $target, root ) {
        $target.click( function( event) {
            switch ( $(event.currentTarget).attr( "class" ) ) {
                case "topicon":
                    console.log("==== focus control top active ====")
                    break;
                case "settingicon":
                    console.log("==== focus control setting active ====")
                    break;
                case "closeicon":
                    console.log("==== focus control close active ====")
                    $(root).click();
                    break;
            }
        })
    }

    return new FControl();
})();
<commit_msg>Add move to top logic.<commit_after>console.log( "=== simpread focus controlbar load ===" )

var fcontrol = ( function() {
    var timer,
        template = '
\

\

\

\

\

\

';

    function FControl() {
        this.$parent = null;
        this.$target = null;
    }

    /*
        Add focus constrol bar
    */
    FControl.prototype.Render = function( root ) {
        console.log( "=== simpread focus controlbar add ===" );
        this.$parent = $(root);
        $( root ).append( template );
        this.$target = $( ".ks-simpread-constrolbar" ).find( "span" );
        addStyle( this.$target );
        addEventHandler( this.$target, root );
    }

    /*
        Remove focus constrol bar
    */
    FControl.prototype.Remove = function() {
        console.log( "=== simpread focus controlbar remove ===" );
        this.$target.off( "click" );
        this.$target.remove();
    }

    /*
        Add focus constrol bar style
    */
    function addStyle( $target ) {
        var path = chrome.extension.getURL("/");
        $($target[0]).attr( "style", "background-image:url(" + path + "assets/images/top.png)"     );
        $($target[1]).attr( "style", "background-image:url(" + path + "assets/images/setting.png)" );
        $($target[2]).attr( "style", "background-image:url(" + path + "assets/images/close.png)"   );
    }

    /*
        Add focus constrol bar event
    */
    function addEventHandler( $target, root ) {
        $target.click( function( event) {
            switch ( $(event.currentTarget).attr( "class" ) ) {
                case "topicon":
                    console.log("==== focus control top active ====")
                    moveTop();
                    break;
                case "settingicon":
                    console.log("==== focus control setting active ====")
                    break;
                case "closeicon":
                    console.log("==== focus control close active ====")
                    $(root).click();
                    break;
            }
        });
        /*$( document ).scroll( function( event ) {
            var osTop = document.body.scrollTop;
            if ( osTop >= document.documentElement.clientHeight ) {
                $target.find( "span" ).hide();
            }else{
                $target.find( "span" ).show();
            }
            if( !isTop ) {
                clearInterval( timer );
            }
            isTop = false;
        });*/
    }

    /*
        Move top
    */
    function moveTop() {
        timer = setInterval( function() {
            var osTop = document.body.scrollTop;
            var speed = Math.floor( -osTop / 3 );
            document.body.scrollTop = osTop + speed;
            isTop = true;
            if( osTop == 0 ) {
                clearInterval( timer );
            }
        }, 30 );
    }

    return new FControl();
})();<|endoftext|>


<commit_before>import _ from 'lodash';
import Store from '../data/Store';
import realm from '../data/Realm';

import { BackgroundGeolocation } from './BackgroundProcess';
import { loadCachedGeofenceTriggers } from './Triggers';
import { showToast, notifyOnBackground } from './Notifications';

// Cache a MapPage component for to update when geofencing triggers are crossed.
let activeMap = null;
// Timer to suppress repeated notifications when geofences get updated.
let supressNotificationsTimestamp = 0;

/**
 * Event function for when geofences are entered, exited, or dwelled on.
 * @param  {objects} params Parameters provided by the crossed geofence
 */
export function crossGeofence(params) {
  console.log(`Geofence crossed: ${params.action} - ${params.identifier}`);

  // Update Map
  if (activeMap && activeMap.active) {
    activeMap.updateMarkers(params);
  }

  try {
    const trigger = realm.objects('GeofenceTrigger').filtered(`id = "${params.identifier}"`)[0];
    if (trigger) {
      // Notify on entry
      if (_.lowerCase(params.action) === 'enter' && supressNotificationsTimestamp < Date.now()) {
        const form = realm.objects('Form').filtered(`id = "${trigger.formId}"`)[0];
        const survey = realm.objects('Survey').filtered(`id = "${trigger.surveyId}"`)[0];

        if (form && survey) {
          notifyOnBackground(`${form.title} - New geofence form available.`, true);

          showToast(form.title, 'New geofence form available.', 'globe', 8, () => {
            Store.navigator.push({
              path: 'form',
              title: survey.title,
              forms: form,
              survey: survey,
              type: 'geofence',
            });
          });

          // Supress local notifications in case the API receives multiple geofence crossing events in rapid succession.
          supressNotificationsTimestamp = Date.now() + 500;
        }
      }

      // Update Geofence Trigger
      realm.write(() => {
        trigger.triggered = true;
        trigger.inRange = _.lowerCase(params.action) !== 'exit';
        trigger.updateTimestamp = Date.now();
      });
    } else {
      console.warn('Trigger not found.');
    }
  } catch (e) {
    console.warn(e);
  }
}

/**
 * Caches a MapPage component inside a variable for easy access
 * @param {element} component     React class element to be cached
 */
export function setActiveMap(component) {
  activeMap = component;
  BackgroundGeolocation.on('geofence', crossGeofence);
}

/**
 * Clears and deactivates the currently cached map element
 * @param  {element} component    React class element to be cleared
 */
export function clearActiveMap(component) {
  if (activeMap === component) {
    component.active = false;
    activeMap = null;
  }
}

/**
 * Erases all active geofences in the native geofencing api.
 * @param  {Function} callback Callback function to execute afterwards.
 */
export function resetGeofences(callback) {
  supressNotificationsTimestamp = Date.now() + 5000;
  BackgroundGeolocation.removeGeofences(() => {
      console.log('Cleared current geofencing settings.');
      callback();
    }, (err) => {
      console.warn('Error resetting geofencing settings.');
      console.warn(err);
      callback(err);
    }
  );
}

/**
 * Creates native geofence objects using the 3rd-party geolocation library.
 * Erases previous set of native geofences and adds new ones based on geofence triggers.
 */
export function setupGeofences(callback) {
  // BackgroundGeolocation.stop()

  loadCachedGeofenceTriggers({excludeCompleted: true}, (err, response) => {
    resetGeofences((err2) => {
      if (err2) {
        console.warn('Unable to load geofence triggers');
        console.warn(err2);
        return;
      }

      const triggerGeofences = response.map((trigger) => {
        return {
          identifier: trigger.id,
          radius: trigger.radius,
          latitude: trigger.latitude,
          longitude: trigger.longitude,
          notifyOnEntry: true,
          notifyOnExit: true,
          notifyOnDwell: true,
          loiteringDelay: 30000,
        };
      });

      console.log(`Adding ${triggerGeofences.length} geofences...`);
      supressNotificationsTimestamp = Date.now() + 5000;
      BackgroundGeolocation.addGeofences(triggerGeofences, () => {
        console.log('Successfully added geofences.');
      }, (err3) => {
        console.warn('Failed to add geofences.', err3);
      });

      if (callback) {
        callback();
      }
    });
    BackgroundGeolocation.on('geofence', crossGeofence);
  });
}

/**
 * Adds a single geofence to the native geolocation API.
 * @param {object} trigger Realm 'Trigger' object containing the geofence's required data.
 */
export function addGeofence(trigger) {
  const geofence = {
    identifier: trigger.id,
    radius: trigger.radius,
    latitude: trigger.latitude,
    longitude: trigger.longitude,
    notifyOnEntry: true,
    notifyOnExit: true,
    notifyOnDwell: false,
    loiteringDelay: 60000,
  };

  supressNotificationsTimestamp = Date.now() + 5000;
  BackgroundGeolocation.addGeofence(geofence, () => {
    console.log('Successfully added geofence.');
    BackgroundGeolocation.on('geofence', crossGeofence);
  }, (error) => {
    console.warn('Failed to add geofence.', error);
  });
}

/**
 * Removes a Geofence from the background check
 * @param  {string} id Geofence indentifier
 */
export function removeGeofenceById(id) {
  supressNotificationsTimestamp = Date.now() + 5000;
  BackgroundGeolocation.removeGeofence(id, () => {
    console.log(`Removed geofence: ${id}`);
  });
}

/**
 * Returns an object containing last recorded geolocation data via an async callback
 * @param  {Function} callback   Returns object containing the current latitude, longitude, accuracy, and timestamp.
 */
export function getUserLocationData(callback) {
  let coords = {
    latitude: 0,
    longitude: 0,
    accuracy: 0,
    timestamp: new Date(),
  };

  if (Store.backgroundServiceState === 'deactivated') {
    console.log('deactivated, returning blank data');
    callback(coords);
    return;
  }

  try {
    BackgroundGeolocation.getLocations((locations) => {
      const current = locations[0];
      if (current && current.coords) {
        coords = {
          latitude: current.coords.latitude,
          longitude: current.coords.longitude,
          accuracy: current.coords.accuracy,
          timestamp: current.timestamp,
        };
        callback(coords);
      } else {
        callback(coords);
      }
    }, () => {
      callback(coords);
    });
  } catch (e) {
    callback(coords);
  }
}

/**
 * Dev: logs a list of the currently active geofences in the native API.
 */
export function logActiveGeofences() {
  BackgroundGeolocation.getGeofences((geofences) => {
    console.log('active geofences: ');
    console.log(geofences);
  });
}
<commit_msg>Abort creating geofences if no triggers are found<commit_after>import _ from 'lodash';
import Store from '../data/Store';
import realm from '../data/Realm';

import { BackgroundGeolocation } from './BackgroundProcess';
import { loadCachedGeofenceTriggers } from './Triggers';
import { showToast, notifyOnBackground } from './Notifications';

// Cache a MapPage component for to update when geofencing triggers are crossed.
let activeMap = null;
// Timer to suppress repeated notifications when geofences get updated.
let supressNotificationsTimestamp = 0;

/**
 * Event function for when geofences are entered, exited, or dwelled on.
 * @param  {objects} params Parameters provided by the crossed geofence
 */
export function crossGeofence(params) {
  console.log(`Geofence crossed: ${params.action} - ${params.identifier}`);

  // Update Map
  if (activeMap && activeMap.active) {
    activeMap.updateMarkers(params);
  }

  try {
    const trigger = realm.objects('GeofenceTrigger').filtered(`id = "${params.identifier}"`)[0];
    if (trigger) {
      // Notify on entry
      if (_.lowerCase(params.action) === 'enter' && supressNotificationsTimestamp < Date.now()) {
        const form = realm.objects('Form').filtered(`id = "${trigger.formId}"`)[0];
        const survey = realm.objects('Survey').filtered(`id = "${trigger.surveyId}"`)[0];

        if (form && survey) {
          notifyOnBackground(`${form.title} - New geofence form available.`, true);

          showToast(form.title, 'New geofence form available.', 'globe', 8, () => {
            Store.navigator.push({
              path: 'form',
              title: survey.title,
              forms: form,
              survey: survey,
              type: 'geofence',
            });
          });

          // Supress local notifications in case the API receives multiple geofence crossing events in rapid succession.
          supressNotificationsTimestamp = Date.now() + 500;
        }
      }

      // Update Geofence Trigger
      realm.write(() => {
        trigger.triggered = true;
        trigger.inRange = _.lowerCase(params.action) !== 'exit';
        trigger.updateTimestamp = Date.now();
      });
    } else {
      console.warn('Trigger not found.');
    }
  } catch (e) {
    console.warn(e);
  }
}

/**
 * Caches a MapPage component inside a variable for easy access
 * @param {element} component     React class element to be cached
 */
export function setActiveMap(component) {
  activeMap = component;
  BackgroundGeolocation.on('geofence', crossGeofence);
}

/**
 * Clears and deactivates the currently cached map element
 * @param  {element} component    React class element to be cleared
 */
export function clearActiveMap(component) {
  if (activeMap === component) {
    component.active = false;
    activeMap = null;
  }
}

/**
 * Erases all active geofences in the native geofencing api.
 * @param  {Function} callback Callback function to execute afterwards.
 */
export function resetGeofences(callback) {
  supressNotificationsTimestamp = Date.now() + 5000;
  BackgroundGeolocation.removeGeofences(() => {
      console.log('Cleared current geofencing settings.');
      callback();
    }, (err) => {
      console.warn('Error resetting geofencing settings.');
      console.warn(err);
      callback(err);
    }
  );
}

/**
 * Creates native geofence objects using the 3rd-party geolocation library.
 * Erases previous set of native geofences and adds new ones based on geofence triggers.
 */
export function setupGeofences(callback) {
  // BackgroundGeolocation.stop()

  loadCachedGeofenceTriggers({excludeCompleted: true}, (err, response) => {
    resetGeofences((err2) => {
      if (err2) {
        console.warn('Unable to load geofence triggers');
        console.warn(err2);
        return;
      }

      const triggerGeofences = response.map((trigger) => {
        return {
          identifier: trigger.id,
          radius: trigger.radius,
          latitude: trigger.latitude,
          longitude: trigger.longitude,
          notifyOnEntry: true,
          notifyOnExit: true,
          notifyOnDwell: true,
          loiteringDelay: 30000,
        };
      });

      console.log(`Adding ${triggerGeofences.length} geofences...`);
      if (triggerGeofences.length === 0) {
        console.log(`Unable to add geofences: No triggers found.`);
        if (callback) {
          callback();
        }
        return;
      }

      supressNotificationsTimestamp = Date.now() + 5000;
      BackgroundGeolocation.addGeofences(triggerGeofences, () => {
        console.log('Successfully added geofences.');
      }, (err3) => {
        console.warn('Failed to add geofences.', err3);
      });

      if (callback) {
        callback();
      }
    });
    BackgroundGeolocation.on('geofence', crossGeofence);
  });
}

/**
 * Adds a single geofence to the native geolocation API.
 * @param {object} trigger Realm 'Trigger' object containing the geofence's required data.
 */
export function addGeofence(trigger) {
  const geofence = {
    identifier: trigger.id,
    radius: trigger.radius,
    latitude: trigger.latitude,
    longitude: trigger.longitude,
    notifyOnEntry: true,
    notifyOnExit: true,
    notifyOnDwell: false,
    loiteringDelay: 60000,
  };

  supressNotificationsTimestamp = Date.now() + 5000;
  BackgroundGeolocation.addGeofence(geofence, () => {
    console.log('Successfully added geofence.');
    BackgroundGeolocation.on('geofence', crossGeofence);
  }, (error) => {
    console.warn('Failed to add geofence.', error);
  });
}

/**
 * Removes a Geofence from the background check
 * @param  {string} id Geofence indentifier
 */
export function removeGeofenceById(id) {
  supressNotificationsTimestamp = Date.now() + 5000;
  BackgroundGeolocation.removeGeofence(id, () => {
    console.log(`Removed geofence: ${id}`);
  });
}

/**
 * Returns an object containing last recorded geolocation data via an async callback
 * @param  {Function} callback   Returns object containing the current latitude, longitude, accuracy, and timestamp.
 */
export function getUserLocationData(callback) {
  let coords = {
    latitude: 0,
    longitude: 0,
    accuracy: 0,
    timestamp: new Date(),
  };

  if (Store.backgroundServiceState === 'deactivated') {
    console.log('deactivated, returning blank data');
    callback(coords);
    return;
  }

  try {
    BackgroundGeolocation.getLocations((locations) => {
      const current = locations[0];
      if (current && current.coords) {
        coords = {
          latitude: current.coords.latitude,
          longitude: current.coords.longitude,
          accuracy: current.coords.accuracy,
          timestamp: current.timestamp,
        };
        callback(coords);
      } else {
        callback(coords);
      }
    }, () => {
      callback(coords);
    });
  } catch (e) {
    callback(coords);
  }
}

/**
 * Dev: logs a list of the currently active geofences in the native API.
 */
export function logActiveGeofences() {
  BackgroundGeolocation.getGeofences((geofences) => {
    console.log('active geofences: ');
    console.log(geofences);
  });
}
<|endoftext|>


<commit_before>// Copyright 2013-2017, University of Colorado Boulder

/**
 * A Screen is the largest chunk of a simulation. (Java sims used the term Module, but that term
 * is too overloaded to use with JavaScript and Git.)
 *

 * When creating a Sim, Screens are supplied as the arguments. They can be specified as object literals or through instances of this class.
 * This class may centralize default behavior or state for Screens in the future, but right now it only allows you to create
 * Sims without using named parameter object literals.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var Dimension2 = require( 'DOT/Dimension2' );
  var inherit = require( 'PHET_CORE/inherit' );
  var joist = require( 'JOIST/joist' );
  var JoistA11yStrings = require( 'JOIST/JoistA11yStrings' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var Tandem = require( 'TANDEM/Tandem' );
  var PropertyIO = require( 'AXON/PropertyIO' );

  // phet-io modules
  var BooleanIO = require( 'ifphetio!PHET_IO/types/BooleanIO' );

  // a11y strings
  var screenNamePatternString = JoistA11yStrings.screenNamePattern.value;
  var simScreenString = JoistA11yStrings.simScreen.value;

  // constants
  var MINIMUM_HOME_SCREEN_ICON_SIZE = new Dimension2( 548, 373 );
  var MINIMUM_NAVBAR_ICON_SIZE = new Dimension2( 147, 100 );
  var NAVBAR_ICON_ASPECT_RATIO = MINIMUM_NAVBAR_ICON_SIZE.width / MINIMUM_NAVBAR_ICON_SIZE.height;
  var HOME_SCREEN_ICON_ASPECT_RATIO = MINIMUM_HOME_SCREEN_ICON_SIZE.width / MINIMUM_HOME_SCREEN_ICON_SIZE.height;
  var ICON_ASPECT_RATIO_TOLERANCE = 5E-3; // how close to the ideal aspect ratio an icon must be

  // Home screen and navigation bar icons must have the same aspect ratio, see https://github.com/phetsims/joist/issues/76
  assert && assert( Math.abs( HOME_SCREEN_ICON_ASPECT_RATIO - HOME_SCREEN_ICON_ASPECT_RATIO ) < ICON_ASPECT_RATIO_TOLERANCE,
    'MINIMUM_HOME_SCREEN_ICON_SIZE and MINIMUM_NAVBAR_ICON_SIZE must have the same aspect ratio' );

  /**
   * @param {function} createModel
   * @param {function:Object } createView - function( model )
   * @param {Object} [options]
   * @constructor
   */
  function Screen( createModel, createView, options ) {

    options = _.extend( {

      // {string|null} name of the sim, as displayed to the user.
      // For single-screen sims, there is no home screen or navigation bar, and null is OK.
      // For multi-screen sims, this must be provided.
      name: null,

      // {Property.<Color|string>} background color of the Screen
      backgroundColorProperty: new Property( 'white' ),

      // {Node} icon shown on the home screen.
      // For single-screen sims, there is no home screen and the default is OK.
      homeScreenIcon: new Rectangle( 0, 0, MINIMUM_HOME_SCREEN_ICON_SIZE.width, MINIMUM_HOME_SCREEN_ICON_SIZE.height, {
        fill: 'white',
        stroke: 'black'
      } ),

      // {Node|null} icon shown in the navigation bar. If null, then the home screen icon will be used, scaled to fit.
      navigationBarIcon: null,

      // dt cap in seconds, see https://github.com/phetsims/joist/issues/130
      maxDT: 0.5,

      tandem: Tandem.required,

      // a11y - The description that is used when interacting with screen icons/buttons in joist.
      // This is often a full but short sentence with a period at the end of it.
      descriptionContent: null
    }, options );

    // navigationBarIcon defaults to homeScreenIcon, and will be scaled down
    if ( !options.navigationBarIcon ) {
      options.navigationBarIcon = options.homeScreenIcon;
    }

    // Validate icon sizes
    validateIconSize( options.homeScreenIcon, MINIMUM_HOME_SCREEN_ICON_SIZE, HOME_SCREEN_ICON_ASPECT_RATIO, 'homeScreenIcon' );
    validateIconSize( options.navigationBarIcon, MINIMUM_NAVBAR_ICON_SIZE, NAVBAR_ICON_ASPECT_RATIO, 'navigationBarIcon' );

    // @private (read-only, joist)
    this.screenTandem = options.tandem;

    assert && assert( !options.backgroundColor, 'Please provide backgroundColorProperty instead' );

    // @public
    this.backgroundColorProperty = options.backgroundColorProperty;

    // @public (read-only)
    this.name = options.name;
    this.homeScreenIcon = options.homeScreenIcon;
    this.navigationBarIcon = options.navigationBarIcon;

    // @public (read-only, joist)
    this.maxDT = options.maxDT;

    // @private
    this.createModel = createModel;
    this.createView = createView;

    // Construction of the model and view are delayed and controlled to enable features like
    // a) faster loading when only loading certain screens
    // b) showing a loading progress bar <not implemented>
    this._model = null; // @private
    this._view = null;  // @private

    // @public {Property.<boolean>} indicates whether the Screen is active. Clients can read this, joist sets it.
    // To prevent potential visual glitches, the value should change only while the screen's view is invisible.
    // That is: transitions from false to true before a Screen becomes visible, and from true to false after a Screen becomes invisible.
    this.activeProperty = new Property( true, {
      tandem: options.tandem.createTandem( 'activeProperty' ),
      phetioType: PropertyIO( BooleanIO ),
      phetioReadOnly: true,
      phetioInstanceDocumentation: 'Indicates whether the screen is active.  For single-screen simulations, the screen is always active.'
    } );

    // @public (a11y)
    this.descriptionContent = '';
    if ( options.descriptionContent ) {
      this.descriptionContent = options.descriptionContent;
    }
    else if ( options.name ) {
      this.descriptionContent = StringUtils.fillIn( screenNamePatternString, {
        name: options.name
      } );
    }
    else {
      this.descriptionContent = simScreenString; // fall back on generic name
    }

    var self = this;
    assert && this.activeProperty.lazyLink( function( isActive ) {
      assert( self._view, 'isActive should not change before the Screen view has been initialized' );

      // In phet-io mode, the state of a sim can be set without a deterministic order. The activeProperty could be
      // changed before the view's visibility is set.
      if ( !phet.phetio ) {
        assert( !self._view.isVisible(), 'isActive should not change while the Screen view is visible' );
      }
    } );
  }

  joist.register( 'Screen', Screen );

  /**
   * Convenience function to validate the sizes for the home screen icon and navigation bar icon.
   * @param {Node} icon - the icon to validate
   * @param {Dimension2} minimumSize - the minimum allowed size for the icon
   * @param {number} aspectRatio - the required aspect ratio
   * @param {string} name - the name of the icon type (for assert messages)
   */
  var validateIconSize = function( icon, minimumSize, aspectRatio, name ) {
    assert && assert( icon.width >= minimumSize.width, name + ' is too small: ' + icon.width );
    assert && assert( icon.height >= minimumSize.height, name + ' is too small: ' + icon.height );

    // Validate home screen aspect ratio
    var actualAspectRatio = icon.width / icon.height;
    assert && assert(
      Math.abs( aspectRatio - actualAspectRatio ) < ICON_ASPECT_RATIO_TOLERANCE,
      name + ' has invalid aspect ratio: ' + actualAspectRatio
    );
  };

  /**
   * For showing ScreenView layoutBounds with 'dev' query parameter.
   * @param {Bounds2} layoutBounds
   * @returns {Node}
   */
  var devCreateLayoutBoundsNode = function( layoutBounds ) {
    return new Path( Shape.bounds( layoutBounds ), {
      stroke: 'red',
      lineWidth: 3,
      pickable: false
    } );
  };

  /**
   * For showing ScreenView layoutBounds with 'showVisibleBounds' query parameter.
   * @param {ScreenView} screenView
   * @returns {Node}
   */
  var devCreateVisibleBoundsNode = function( screenView ) {
    var path = new Path( Shape.bounds( screenView.visibleBoundsProperty.value ), {
      stroke: 'blue',
      lineWidth: 6,
      pickable: false
    } );
    screenView.visibleBoundsProperty.link( function( visibleBounds ) {
      path.shape = Shape.bounds( visibleBounds );
    } );
    return path;
  };

  return inherit( Object, Screen, {

    // @public
    reset: function() {
      // Background color not reset, as it's a responsibility of the code that changes the property
    },

    // @public - Returns the model (if it has been constructed)
    get model() {
      assert && assert( this._model, 'Model has not yet been constructed' );
      return this._model;
    },

    // @public - Returns the view (if it has been constructed)
    get view() {
      assert && assert( this._view, 'View has not yet been constructed' );
      return this._view;
    },

    /**
     * Initialize the model.  Clients should use either this or initializeModelAndView
     * Clients may want to use this method to gain more control over the creation process
     * @public (joist-internal)
     */
    initializeModel: function() {
      assert && assert( this._model === null, 'there was already a model' );
      this._model = this.createModel();
    },

    /**
     * Initialize the view.  Clients should use either this or initializeModelAndView
     * Clients may want to use this method to gain more control over the creation process
     * @public (joist-internal)
     */
    initializeView: function() {
      assert && assert( this._view === null, 'there was already a view' );
      this._view = this.createView( this.model );
      this._view.setVisible( false ); // a Screen is invisible until selected

      // Show the home screen's layoutBounds
      if ( phet.chipper.queryParameters.dev ) {
        this._view.addChild( devCreateLayoutBoundsNode( this._view.layoutBounds ) );
      }

      // For debugging, make it possible to see the visibleBounds.  This is not included with ?dev since
      // it should just be equal to what you see.
      if ( phet.chipper.queryParameters.showVisibleBounds ) {
        this._view.addChild( devCreateVisibleBoundsNode( this._view ) );
      }
    },

    // Initialize both the model and view
    initializeModelAndView: function() {
      this.initializeModel();
      this.initializeView();
    }
  }, {

    // @public
    MINIMUM_HOME_SCREEN_ICON_SIZE: MINIMUM_HOME_SCREEN_ICON_SIZE,

    // @public
    MINIMUM_NAVBAR_ICON_SIZE: MINIMUM_NAVBAR_ICON_SIZE
  } );
} );<commit_msg>use backgroundColorProperty for default homeScreenIcon #480<commit_after>// Copyright 2013-2017, University of Colorado Boulder

/**
 * A Screen is the largest chunk of a simulation. (Java sims used the term Module, but that term
 * is too overloaded to use with JavaScript and Git.)
 *

 * When creating a Sim, Screens are supplied as the arguments. They can be specified as object literals or through instances of this class.
 * This class may centralize default behavior or state for Screens in the future, but right now it only allows you to create
 * Sims without using named parameter object literals.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var Dimension2 = require( 'DOT/Dimension2' );
  var inherit = require( 'PHET_CORE/inherit' );
  var joist = require( 'JOIST/joist' );
  var JoistA11yStrings = require( 'JOIST/JoistA11yStrings' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var Tandem = require( 'TANDEM/Tandem' );
  var PropertyIO = require( 'AXON/PropertyIO' );

  // phet-io modules
  var BooleanIO = require( 'ifphetio!PHET_IO/types/BooleanIO' );

  // a11y strings
  var screenNamePatternString = JoistA11yStrings.screenNamePattern.value;
  var simScreenString = JoistA11yStrings.simScreen.value;

  // constants
  var MINIMUM_HOME_SCREEN_ICON_SIZE = new Dimension2( 548, 373 );
  var MINIMUM_NAVBAR_ICON_SIZE = new Dimension2( 147, 100 );
  var NAVBAR_ICON_ASPECT_RATIO = MINIMUM_NAVBAR_ICON_SIZE.width / MINIMUM_NAVBAR_ICON_SIZE.height;
  var HOME_SCREEN_ICON_ASPECT_RATIO = MINIMUM_HOME_SCREEN_ICON_SIZE.width / MINIMUM_HOME_SCREEN_ICON_SIZE.height;
  var ICON_ASPECT_RATIO_TOLERANCE = 5E-3; // how close to the ideal aspect ratio an icon must be

  // Home screen and navigation bar icons must have the same aspect ratio, see https://github.com/phetsims/joist/issues/76
  assert && assert( Math.abs( HOME_SCREEN_ICON_ASPECT_RATIO - HOME_SCREEN_ICON_ASPECT_RATIO ) < ICON_ASPECT_RATIO_TOLERANCE,
    'MINIMUM_HOME_SCREEN_ICON_SIZE and MINIMUM_NAVBAR_ICON_SIZE must have the same aspect ratio' );

  /**
   * @param {function} createModel
   * @param {function:Object } createView - function( model )
   * @param {Object} [options]
   * @constructor
   */
  function Screen( createModel, createView, options ) {

    options = _.extend( {

      // {string|null} name of the sim, as displayed to the user.
      // For single-screen sims, there is no home screen or navigation bar, and null is OK.
      // For multi-screen sims, this must be provided.
      name: null,

      // {Property.<Color|string>} background color of the Screen
      backgroundColorProperty: new Property( 'white' ),

      // {Node|null} icon shown on the home screen. If null, then a default is created.
      // For single-screen sims, there is no home screen and the default is OK.
      homeScreenIcon: null,

      // {Node|null} icon shown in the navigation bar. If null, then the home screen icon will be used, scaled to fit.
      navigationBarIcon: null,

      // dt cap in seconds, see https://github.com/phetsims/joist/issues/130
      maxDT: 0.5,

      tandem: Tandem.required,

      // a11y - The description that is used when interacting with screen icons/buttons in joist.
      // This is often a full but short sentence with a period at the end of it.
      descriptionContent: null
    }, options );

    // Create a default homeScreenIcon, using the Screen's background color
    if ( !options.homeScreenIcon ) {
      options.homeScreenIcon = new Rectangle( 0, 0, MINIMUM_HOME_SCREEN_ICON_SIZE.width, MINIMUM_HOME_SCREEN_ICON_SIZE.height, {
        fill: options.backgroundColorProperty.value,
        stroke: 'black'
      } );
    }

    // navigationBarIcon defaults to homeScreenIcon, and will be scaled down
    if ( !options.navigationBarIcon ) {
      options.navigationBarIcon = options.homeScreenIcon;
    }

    // Validate icon sizes
    validateIconSize( options.homeScreenIcon, MINIMUM_HOME_SCREEN_ICON_SIZE, HOME_SCREEN_ICON_ASPECT_RATIO, 'homeScreenIcon' );
    validateIconSize( options.navigationBarIcon, MINIMUM_NAVBAR_ICON_SIZE, NAVBAR_ICON_ASPECT_RATIO, 'navigationBarIcon' );

    // @private (read-only, joist)
    this.screenTandem = options.tandem;

    assert && assert( !options.backgroundColor, 'Please provide backgroundColorProperty instead' );

    // @public
    this.backgroundColorProperty = options.backgroundColorProperty;

    // @public (read-only)
    this.name = options.name;
    this.homeScreenIcon = options.homeScreenIcon;
    this.navigationBarIcon = options.navigationBarIcon;

    // @public (read-only, joist)
    this.maxDT = options.maxDT;

    // @private
    this.createModel = createModel;
    this.createView = createView;

    // Construction of the model and view are delayed and controlled to enable features like
    // a) faster loading when only loading certain screens
    // b) showing a loading progress bar <not implemented>
    this._model = null; // @private
    this._view = null;  // @private

    // @public {Property.<boolean>} indicates whether the Screen is active. Clients can read this, joist sets it.
    // To prevent potential visual glitches, the value should change only while the screen's view is invisible.
    // That is: transitions from false to true before a Screen becomes visible, and from true to false after a Screen becomes invisible.
    this.activeProperty = new Property( true, {
      tandem: options.tandem.createTandem( 'activeProperty' ),
      phetioType: PropertyIO( BooleanIO ),
      phetioReadOnly: true,
      phetioInstanceDocumentation: 'Indicates whether the screen is active.  For single-screen simulations, the screen is always active.'
    } );

    // @public (a11y)
    this.descriptionContent = '';
    if ( options.descriptionContent ) {
      this.descriptionContent = options.descriptionContent;
    }
    else if ( options.name ) {
      this.descriptionContent = StringUtils.fillIn( screenNamePatternString, {
        name: options.name
      } );
    }
    else {
      this.descriptionContent = simScreenString; // fall back on generic name
    }

    var self = this;
    assert && this.activeProperty.lazyLink( function( isActive ) {
      assert( self._view, 'isActive should not change before the Screen view has been initialized' );

      // In phet-io mode, the state of a sim can be set without a deterministic order. The activeProperty could be
      // changed before the view's visibility is set.
      if ( !phet.phetio ) {
        assert( !self._view.isVisible(), 'isActive should not change while the Screen view is visible' );
      }
    } );
  }

  joist.register( 'Screen', Screen );

  /**
   * Convenience function to validate the sizes for the home screen icon and navigation bar icon.
   * @param {Node} icon - the icon to validate
   * @param {Dimension2} minimumSize - the minimum allowed size for the icon
   * @param {number} aspectRatio - the required aspect ratio
   * @param {string} name - the name of the icon type (for assert messages)
   */
  var validateIconSize = function( icon, minimumSize, aspectRatio, name ) {
    assert && assert( icon.width >= minimumSize.width, name + ' is too small: ' + icon.width );
    assert && assert( icon.height >= minimumSize.height, name + ' is too small: ' + icon.height );

    // Validate home screen aspect ratio
    var actualAspectRatio = icon.width / icon.height;
    assert && assert(
      Math.abs( aspectRatio - actualAspectRatio ) < ICON_ASPECT_RATIO_TOLERANCE,
      name + ' has invalid aspect ratio: ' + actualAspectRatio
    );
  };

  /**
   * For showing ScreenView layoutBounds with 'dev' query parameter.
   * @param {Bounds2} layoutBounds
   * @returns {Node}
   */
  var devCreateLayoutBoundsNode = function( layoutBounds ) {
    return new Path( Shape.bounds( layoutBounds ), {
      stroke: 'red',
      lineWidth: 3,
      pickable: false
    } );
  };

  /**
   * For showing ScreenView layoutBounds with 'showVisibleBounds' query parameter.
   * @param {ScreenView} screenView
   * @returns {Node}
   */
  var devCreateVisibleBoundsNode = function( screenView ) {
    var path = new Path( Shape.bounds( screenView.visibleBoundsProperty.value ), {
      stroke: 'blue',
      lineWidth: 6,
      pickable: false
    } );
    screenView.visibleBoundsProperty.link( function( visibleBounds ) {
      path.shape = Shape.bounds( visibleBounds );
    } );
    return path;
  };

  return inherit( Object, Screen, {

    // @public
    reset: function() {
      // Background color not reset, as it's a responsibility of the code that changes the property
    },

    // @public - Returns the model (if it has been constructed)
    get model() {
      assert && assert( this._model, 'Model has not yet been constructed' );
      return this._model;
    },

    // @public - Returns the view (if it has been constructed)
    get view() {
      assert && assert( this._view, 'View has not yet been constructed' );
      return this._view;
    },

    /**
     * Initialize the model.  Clients should use either this or initializeModelAndView
     * Clients may want to use this method to gain more control over the creation process
     * @public (joist-internal)
     */
    initializeModel: function() {
      assert && assert( this._model === null, 'there was already a model' );
      this._model = this.createModel();
    },

    /**
     * Initialize the view.  Clients should use either this or initializeModelAndView
     * Clients may want to use this method to gain more control over the creation process
     * @public (joist-internal)
     */
    initializeView: function() {
      assert && assert( this._view === null, 'there was already a view' );
      this._view = this.createView( this.model );
      this._view.setVisible( false ); // a Screen is invisible until selected

      // Show the home screen's layoutBounds
      if ( phet.chipper.queryParameters.dev ) {
        this._view.addChild( devCreateLayoutBoundsNode( this._view.layoutBounds ) );
      }

      // For debugging, make it possible to see the visibleBounds.  This is not included with ?dev since
      // it should just be equal to what you see.
      if ( phet.chipper.queryParameters.showVisibleBounds ) {
        this._view.addChild( devCreateVisibleBoundsNode( this._view ) );
      }
    },

    // Initialize both the model and view
    initializeModelAndView: function() {
      this.initializeModel();
      this.initializeView();
    }
  }, {

    // @public
    MINIMUM_HOME_SCREEN_ICON_SIZE: MINIMUM_HOME_SCREEN_ICON_SIZE,

    // @public
    MINIMUM_NAVBAR_ICON_SIZE: MINIMUM_NAVBAR_ICON_SIZE
  } );
} );<|endoftext|>


<commit_before>Ajaxer = {
  mobileApi: 'index.php',

  // Format of params:
  // params = {
  //   url: 'http://api.lol.com',
  //   isMobile: 1,
  //   success: function(data) {
  //     // Do something wth data
  //   },
  //   error: function(jqXHR, text, err) {
  //     // if DEBUG then console.log "something, something, error"
  //   },
  // }

  getPlainText: function(params) {
    params.dataType = 'text';
    this.get(params);
  },

  getJson: function(params) {
    params.dataType = 'json';
    this.get(params);
  },

  getXml: function(params) {
    params.dataType = 'xml';
    this.get(params);
  },

  getHtml: function(params) {
    params.dataType = 'html';
    this.get(params);
  },

  get: function(params) {
    if (params == undefined) {
      console.log('ERROR: Params is required. Check ajaxer.js to see format of params.');
      return;
    }
    if (params.url == undefined) {
      console.log('ERROR: URL missing from params.');
      return;
    }
    if (params.dataType == undefined) {
      console.log('ERROR: Do not use Ajaxer.get() directly, use getXml, getJson or one of the others instead.');
      return;
    }
    if (params.success == undefined) {
     console.log('ERROR: Params is missing success function. The success function should use the results for something useful.');
      return;
    }
    if (params.error == undefined) {
     console.log('ERROR: Params is missing error function. Error handling must be in place.');
      return;
    }

    var self = this;

    if (params.isMobile) {
      // Notifier Mobile
      var dataBlob = {};
      dataBlob.url = params.url;
      if (params.data != undefined) {
        dataBlob.data = params.data;
      }
      $.ajax({
        type: 'POST',
        data: dataBlob,
        url: self.mobileApi,
        dataType: params.dataType,
        success: params.success,
        error: params.error,
      });
    }
    else {
      // Notifier
      $.ajax({
        type: (params.data ? 'POST' : 'GET'),
        data: (params.data ? params.data : ''),
        url: params.url,
        dataType: params.dataType,
        success: params.success,
        error: params.error,
      });
    }
  },
}
<commit_msg>ajaxer: adding a IS_MOBILE-check<commit_after>Ajaxer = {
  mobileApi: 'index.php',

  // Format of params:
  // params = {
  //   url: 'http://api.lol.com',
  //   data: {sth: 'something', lol: 'laugh out LOUD'}, // Only use if you want a POST request
  //   success: function(data) {
  //     // Do something wth data
  //   },
  //   error: function(jqXHR, text, err) {
  //     // if DEBUG then console.log "something, something, error"
  //   },
  // }

  getPlainText: function(params) {
    params.dataType = 'text';
    this.get(params);
  },

  getJson: function(params) {
    params.dataType = 'json';
    this.get(params);
  },

  getXml: function(params) {
    params.dataType = 'xml';
    this.get(params);
  },

  getHtml: function(params) {
    params.dataType = 'html';
    this.get(params);
  },

  get: function(params) {
    if (params == undefined) {
      console.log('ERROR: Params is required. Check ajaxer.js to see format of params.');
      return;
    }
    if (params.url == undefined) {
      console.log('ERROR: URL missing from params.');
      return;
    }
    if (params.dataType == undefined) {
      console.log('ERROR: Do not use Ajaxer.get() directly, use getXml, getJson or one of the others instead.');
      return;
    }
    if (params.success == undefined) {
     console.log('ERROR: Params is missing success function. The success function should use the results for something useful.');
      return;
    }
    if (params.error == undefined) {
     console.log('ERROR: Params is missing error function. Error handling must be in place.');
      return;
    }

    var self = this;
    // Using the constant window.IS_MOBILE here is a small hack
    // which saves a considerable amount of code clutter
    if (window.IS_MOBILE) {
      // Notifier Mobile
      var dataBlob = {};
      dataBlob.url = params.url;
      if (params.data != undefined) {
        dataBlob.data = params.data;
      }
      $.ajax({
        type: 'POST',
        data: dataBlob,
        url: self.mobileApi,
        dataType: params.dataType,
        success: params.success,
        error: params.error,
      });
    }
    else {
      // Notifier
      $.ajax({
        type: (params.data ? 'POST' : 'GET'),
        data: (params.data ? params.data : ''),
        url: params.url,
        dataType: params.dataType,
        success: params.success,
        error: params.error,
      });
    }
  },
}
<|endoftext|>


<commit_before>import React from "react";
import TableProvider from "components/TableProvider";
import Table from "components/Table";
import Actions from "components/Actions";
import CDeleteLink from "components/CDeleteLink";
import CEditLink from "components/CEditLink";
import CNewBtn from "components/CNewBtn";
import PageHeader from "components/bs4/PageHeader";
import {Button} from "react-bootstrap4";
import app from "app";

export default class extends React.Component {
  handleClick = (reload) => {
    app.post(app.url('admin/wechat-groups/sync-from-wechat'))
      .then(ret => $.msg(ret, () => reload()));
  };

  render() {
    return <>
      <TableProvider>
        {({reload}) => <>
          <PageHeader>

              {wei.hasWechatGroup && <Button variant="default" onClick={this.handleClick.bind(reload)}>
                从微信同步分组
              </Button>}
              {' '}
              <CNewBtn/>

            {wei.page.controllerTitle}
          </PageHeader>

 cell > 0 ? '已同步' : '未同步',
              },
              {
                text: '操作',
                formatter: (cell, row) => <Actions>
                  <CEditLink id={row.id}/>
                  <CDeleteLink id={row.id}/>
                </Actions>
              },
            ]}
          />
        </>}
      </TableProvider>
    </>;
  }
}
<commit_msg>feat: 更正reload操作<commit_after>import React from "react";
import TableProvider from "components/TableProvider";
import Table from "components/Table";
import Actions from "components/Actions";
import CDeleteLink from "components/CDeleteLink";
import CEditLink from "components/CEditLink";
import CNewBtn from "components/CNewBtn";
import PageHeader from "components/bs4/PageHeader";
import {Button} from "react-bootstrap4";
import app from "app";

export default class extends React.Component {
  handleClick = (reload) => {
    app.post(app.url('admin/wechat-groups/sync-from-wechat'))
      .then(ret => $.msg(ret, () => reload()));
  };

  render() {
    return <>
      <TableProvider>
        {({reload}) => <>
          <PageHeader>

              {wei.hasWechatGroup && <Button variant="default" onClick={this.handleClick.bind(this, reload)}>
                从微信同步分组
              </Button>}
              {' '}
              <CNewBtn/>

            {wei.page.controllerTitle}
          </PageHeader>

 cell > 0 ? '已同步' : '未同步',
              },
              {
                text: '操作',
                formatter: (cell, row) => <Actions>
                  <CEditLink id={row.id}/>
                  <CDeleteLink id={row.id}/>
                </Actions>
              },
            ]}
          />
        </>}
      </TableProvider>
    </>;
  }
}
<|endoftext|>


<commit_before>'use strict';

angular.module('responymous', ['ngAnimate', 'ngCookies', 'ngTouch', 'ui.router', 'firebase'])
  .config(function ($stateProvider, $urlRouterProvider) {
    $stateProvider
      .state('home', {
        url: '/',
        templateUrl: 'app/partials/main.html',
        controller: 'MainCtrl'
      })
      .state('student', {
        url: '/student',
        templateUrl: 'app/partials/student.html'
      })
      .state('instructor', {
        url: '/instructor',
        templateUrl: 'app/partials/instructor.html'
      });

    $urlRouterProvider.otherwise('/');
  })
;
<commit_msg>Added UI route to instructor page and controller<commit_after>'use strict';

angular.module('responymous', ['ngAnimate', 'ngCookies', 'ngTouch', 'ui.router', 'firebase'])
  .config(function ($stateProvider, $urlRouterProvider) {
    $stateProvider
      .state('home', {
        url: '/',
        templateUrl: 'app/partials/main.html',
        controller: 'MainCtrl'
      })
      .state('student', {
        url: '/student',
        templateUrl: 'app/partials/student.html'
      })
      .state('instructor', {
        url: '/instructor',
        templateUrl: 'app/partials/instructor.html',
        controller: 'ResponseCtrl'
      });

    $urlRouterProvider.otherwise('/');
  })
;
<|endoftext|>


<commit_before>// This is the most horrifying code I've written in a while.

var tests = {};

function describe(desc, func) {
  var parent = tests;
  tests = tests[desc] = {};
  func();
  tests = parent;
}
function it(desc, func) {
  tests[desc] = func;
}

function noopChain() {
  var out = function() { return out; },
      props = ['be', 'not', 'true', 'false', 'undefined', 'equal', 'eql', 'truthy', 'empty', 'have', 'length', 'include'];
  for(var i = 0, len = props.length; i < len; ++i) {
    Object.defineProperty(out, props[i], {
      get: function() {
        return out;
      }
    });
  }
  return out;
}

if(typeof require === 'function') {
  global.describe = describe;
  global.it = it;
  require('../test/test.js');
} else {
  var require = function require(path) {
    var module = {};
    var exports = module.exports = {};
    var data;
    if(path === 'must') {
      return noopChain();
    } else if(path === '..') {
      data = read('../whether-manager.js');
    } else {
      data = read(path);
    }
    (new Function('module', 'exports', 'require', data))(module, exports, require);
    return module.exports;
  }
  require('../test/test.js');
}

if(typeof dateNow !== 'function') {
  dateNow = function() {
    return Date.now();
  };
}

function call(name, iterations, object, timer, indent) {
  indent = indent || "";
  var t = timer && timer(indent, name);
  t && t.announce && t.announce();
  for(var key in object) {
    var v = object[key];
    if(typeof v === 'function') {
      var t2 = timer && timer(indent + "  ", key);
      for(var i = 0; i < iterations; ++i) {
        v();
      }
      t2 && t2();
    } else {
      call(key, iterations, v, timer, indent + "  ");
    }
  }
  t && t();
}

var output = {};

var s = dateNow();
for(var i = 0; i < 5000; ++i) {
  call("Warmup", 1, tests);
}
output["Warmup"] = dateNow() - s;

call("Total", 50000, tests, function(indent, name) {
  var parent = output;
  var child = output = parent[name] = 0;
  var s;
  var out = function() {
    var time = dateNow() - s;
    if(child) {
      child["Total"] = time;
    } else {
      parent[name] = time;
    }
    output = parent;
  };
  out.announce = function() {
    child = output = parent[name] = {};
  }
  s = dateNow();
  return out;
});

console.log(JSON.stringify(output, null, 2));
<commit_msg>Adjusted benchmark script<commit_after>// This is the most horrifying code I've written in a while.

var tests = {};

function describe(desc, func) {
  var parent = tests;
  tests = tests[desc] = {};
  func();
  tests = parent;
}
function it(desc, func) {
  tests[desc] = func;
}

function noopChain() {
  var out = function() { return out; },
      props = ['be', 'not', 'true', 'false', 'undefined', 'equal', 'eql', 'truthy', 'empty', 'have', 'length', 'include'];
  for(var i = 0, len = props.length; i < len; ++i) {
    Object.defineProperty(out, props[i], {
      get: function() {
        return out;
      }
    });
  }
  return out;
}

if(typeof require === 'function') {
  global.describe = describe;
  global.it = it;
  require('../test/test.js');
} else {
  var require = function require(path) {
    var module = {};
    var exports = module.exports = {};
    if(path === 'must') {
      return noopChain();
    } else if(path === '..') {
      (new Function('module', 'exports', 'require', read('../whether-manager.js')))(module, exports, require);
    } else {
      (new Function('module', 'exports', 'require', read(path)))(module, exports, require);
    }
    return module.exports;
  }
  require('../test/test.js');
}

if(typeof dateNow !== 'function') {
  dateNow = function() {
    return Date.now();
  };
}

function call(name, iterations, object, timer, indent) {
  indent = indent || "";
  var t = timer && timer(indent, name);
  t && t.announce && t.announce();
  for(var key in object) {
    var v = object[key];
    if(typeof v === 'function') {
      var t2 = timer && timer(indent + "  ", key);
      for(var i = 0; i < iterations; ++i) {
        v();
      }
      t2 && t2();
    } else {
      call(key, iterations, v, timer, indent + "  ");
    }
  }
  t && t();
}

var output = {};

var s = dateNow();
for(var i = 0; i < 5000; ++i) {
  call("Warmup", 1, tests);
}
output["Warmup"] = dateNow() - s;

call("Total", 50000, tests, function(indent, name) {
  var parent = output;
  var child = output = parent[name] = 0;
  var s;
  var out = function() {
    var time = dateNow() - s;
    if(child) {
      child["Total"] = time;
    } else {
      parent[name] = time;
    }
    output = parent;
  };
  out.announce = function() {
    child = output = parent[name] = {};
  }
  s = dateNow();
  return out;
});

console.log(JSON.stringify(output, null, 2));
<|endoftext|>


<commit_before>/* Copyright 2012 Xavier Mamano, http://github.com/jorix/OL-Ragbag
 * Published under MIT license. All rights reserved. */

/**
 * @requires OpenLayers/Control/ModifyFeature.js
 */

/**
 * Class: OpenLayers.Control.ModifyFeature
 * Patch for ModifyFeature control of OpenLayers.
 *
 * Extends the ModifyFeature control behavior to allow delete by del-key the
 *     feature that have been selected for modification.
 */
(function(){

    var _prototype = OpenLayers.Control.ModifyFeature.prototype;
    // Add event types on OL 2.11 or lower
    if (_prototype.EVENT_TYPES) {
        _prototype.EVENT_TYPES.push("beforefeaturedeleted", "featuredeleted");
    }

    /**
     * APIProperty: featureDelkeyMode
     * {String} Mode in which the del-key acts to remove the feature that is
     *     selected for modification. The available modes are: "none", "always",
     *     "bounds" (only be deleted if when pressing del-key the mouse is
     *     within the feature bounds) and "hover" (only be deleted pressing
     *     del-key when the mouse is placed over the feature)
     */
    _prototype.featureDelkeyMode = "bounds";

    /**
     * APIProperty: deferDelete
     * {Boolean} Instead of removing features from the layer, set feature
     *     states of deleted features to DELETE.  This assumes a save strategy
     *     or other component is in charge of removing features from the
     *     layer.  Default is false.  If false, deleted features will be
     *     immediately removed from the layer.
     */
    _prototype.deferDelete = false;

    /**
     * APIProperty: escapeCode
     * {Integer} Keycode for cancel a vertex drag.  Set to null to
     *     disable cancel vertex drad by keypress.  Default is 27.
     */
    _prototype.escapeCode = 27;

    /**
     * Property: initialVertex
     * {Object} Internal use
     */
    _prototype.initialVertex = null;

    var _base_handleKeypress = _prototype.handleKeypress;
    /**
     * Method: handleKeypress
     * Called by the feature handler on keypress.  This is used to delete
     *     vertices and features. If the <deleteCode> property is set, vertices
     *     will be deleted when a feature is selected for modification and
     *     the mouse is over a vertex, otherwise the feature will be depending
     *     of <featureDelkeyMode>
     *
     * Parameters:
     * evt - {Event} Keypress event.
     */
    _prototype.handleKeypress = function(evt) {
        if(!this.feature) {
            return;
        }
        var code = evt.keyCode;
        // check for delete key
        if (OpenLayers.Util.indexOf(this.deleteCodes, code) != -1) {
            var vertex = this.dragControl.feature;
            if (!vertex) {
            // patch
                var confirm = false,
                    feature = this.feature;
                switch (this.featureDelkeyMode) {
                    case "always":
                        confirm = true;
                        break;
                    case "hover":
                        var handlerFeature = this.selectControl && this.selectControl.handlers.feature;
                        if (handlerFeature) {
                            confirm = (feature === feature.layer.getFeatureFromEvent(handlerFeature.evt));
                        }
                        break;
                    case "bounds":
                        var handlerFeature = this.selectControl && this.selectControl.handlers.feature;
                        if (handlerFeature) {
                            var xy = handlerFeature.evt.xy;
                            var location = this.map.getLonLatFromPixel(xy);
                            confirm = feature.geometry.getBounds().containsLonLat(location);
                        }
                }
                if (confirm) {
                    var layer = feature.layer;
                    if(feature.state === OpenLayers.State.INSERT ||
                                                                !this.deferDelete) {
                        OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
                        this.justUnselectFeature(feature);
                        layer.destroyFeatures([feature]);
                    } else {
                        var cont = this.events.triggerEvent(
                            "beforefeaturedeleted", {feature: feature}
                        );
                        if (cont !== false) {
                            OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
                            this.justUnselectFeature(feature);
                            feature.state = OpenLayers.State.DELETE;
                            layer.drawFeature(feature);
                            this.events.triggerEvent(
                                "featuredeleted", {feature: feature}
                            );
                        }
                    }
                }
            } else {
            // call base code
                _base_handleKeypress.call(this, evt);
            }
        // check for escape key
        } else if (this.escapeCode == code && this.dragControl.feature) {
            var vertex = this.dragControl.feature;
            if(vertex &&
                       OpenLayers.Util.indexOf(this.vertices, vertex) != -1 &&
                       this.dragControl.handlers.drag.dragging &&
                       vertex.geometry.parent) {
                var initialVertex = this.initialVertex;
                if (initialVertex.id === vertex.id) {
                    this.dragControl.handlers.drag.deactivate();
                    this.dragControl.handlers.drag.activate();
                    if (initialVertex.index) { // is virtual
                        _base_handleKeypress.call(this, OpenLayers.Util.applyDefaults({keyCode: 46}, evt));
                    } else {
                        vertex.geometry.x = initialVertex.x;
                        vertex.geometry.y = initialVertex.y;
                        vertex.geometry.clearBounds();
                        this.layer.drawFeature(this.feature, this.standalone ? undefined :
                                            this.selectControl.renderIntent);
                        this.dragComplete(vertex);
                    }
                }
            }
        }
    };

    var _base_dragStart = _prototype.dragStart;
    /**
     * Method: dragStart
     * Called by the drag feature control with before a feature is dragged.
     *     This method is used to differentiate between points and vertices
     *     of higher order geometries.  This respects the <geometryTypes>
     *     property and forces a select of points when the drag control is
     *     already active (and stops events from propagating to the select
     *     control).
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>} The point or vertex about to be
     *     dragged.
     * pixel - {<OpenLayers.Pixel>} Pixel location of the mouse event.
     */
    _prototype.dragStart = function(feature, pixel) {
        this.initialVertex = {
            index: feature._index,
            id: feature.id,
            x: feature.geometry.x,
            y: feature.geometry.y,
        };
        _base_dragStart.apply(this, arguments);
    };

    /**
     * Method: justUnselectFeature
     * Called to unselect the feature.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>} The unselected feature.
     */
    _prototype.justUnselectFeature = function(feature) {
        this.layer.removeFeatures(this.vertices, {silent: true});
        this.vertices = [];
        this.layer.destroyFeatures(this.virtualVertices, {silent: true});
        this.virtualVertices = [];
        if(this.dragHandle) {
            this.layer.destroyFeatures([this.dragHandle], {silent: true});
            delete this.dragHandle;
        }
        if(this.radiusHandle) {
            this.layer.destroyFeatures([this.radiusHandle], {silent: true});
            delete this.radiusHandle;
        }
        this.feature = null;
        this.dragControl.deactivate();
        this.modified = false;
    };
})();<commit_msg>When canceling dragging of a real vertex trigger "vertexmodified".<commit_after>/* Copyright 2012 Xavier Mamano, http://github.com/jorix/OL-Ragbag
 * Published under MIT license. All rights reserved. */

/**
 * @requires OpenLayers/Control/ModifyFeature.js
 */

/**
 * Class: OpenLayers.Control.ModifyFeature
 * Patch for ModifyFeature control of OpenLayers.
 *
 * Extends the ModifyFeature control behavior to allow delete by del-key the
 *     feature that have been selected for modification.
 */
(function(){

    var _prototype = OpenLayers.Control.ModifyFeature.prototype;
    // Add event types on OL 2.11 or lower
    if (_prototype.EVENT_TYPES) {
        _prototype.EVENT_TYPES.push("beforefeaturedeleted", "featuredeleted");
    }

    /**
     * APIProperty: featureDelkeyMode
     * {String} Mode in which the del-key acts to remove the feature that is
     *     selected for modification. The available modes are: "none", "always",
     *     "bounds" (only be deleted if when pressing del-key the mouse is
     *     within the feature bounds) and "hover" (only be deleted pressing
     *     del-key when the mouse is placed over the feature)
     */
    _prototype.featureDelkeyMode = "bounds";

    /**
     * APIProperty: deferDelete
     * {Boolean} Instead of removing features from the layer, set feature
     *     states of deleted features to DELETE.  This assumes a save strategy
     *     or other component is in charge of removing features from the
     *     layer.  Default is false.  If false, deleted features will be
     *     immediately removed from the layer.
     */
    _prototype.deferDelete = false;

    /**
     * APIProperty: escapeCode
     * {Integer} Keycode for cancel a vertex drag.  Set to null to
     *     disable cancel vertex drad by keypress.  Default is 27.
     */
    _prototype.escapeCode = 27;

    /**
     * Property: initialVertex
     * {Object} Internal use
     */
    _prototype.initialVertex = null;

    var _base_handleKeypress = _prototype.handleKeypress;
    /**
     * Method: handleKeypress
     * Called by the feature handler on keypress.  This is used to delete
     *     vertices and features. If the <deleteCode> property is set, vertices
     *     will be deleted when a feature is selected for modification and
     *     the mouse is over a vertex, otherwise the feature will be depending
     *     of <featureDelkeyMode>
     *
     * Parameters:
     * evt - {Event} Keypress event.
     */
    _prototype.handleKeypress = function(evt) {
        if(!this.feature) {
            return;
        }
        var vertex,
            code = evt.keyCode;
        // check for delete key
        if (OpenLayers.Util.indexOf(this.deleteCodes, code) != -1) {
            vertex = this.dragControl.feature;
            if (!vertex) {
            // patch
                var confirm = false,
                    feature = this.feature;
                switch (this.featureDelkeyMode) {
                    case "always":
                        confirm = true;
                        break;
                    case "hover":
                        var handlerFeature = this.selectControl && this.selectControl.handlers.feature;
                        if (handlerFeature) {
                            confirm = (feature === feature.layer.getFeatureFromEvent(handlerFeature.evt));
                        }
                        break;
                    case "bounds":
                        var handlerFeature = this.selectControl && this.selectControl.handlers.feature;
                        if (handlerFeature) {
                            var xy = handlerFeature.evt.xy;
                            var location = this.map.getLonLatFromPixel(xy);
                            confirm = feature.geometry.getBounds().containsLonLat(location);
                        }
                }
                if (confirm) {
                    var layer = feature.layer;
                    if(feature.state === OpenLayers.State.INSERT ||
                                                                !this.deferDelete) {
                        OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
                        this.justUnselectFeature(feature);
                        layer.destroyFeatures([feature]);
                    } else {
                        var cont = this.events.triggerEvent(
                            "beforefeaturedeleted", {feature: feature}
                        );
                        if (cont !== false) {
                            OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
                            this.justUnselectFeature(feature);
                            feature.state = OpenLayers.State.DELETE;
                            layer.drawFeature(feature);
                            this.events.triggerEvent(
                                "featuredeleted", {feature: feature}
                            );
                        }
                    }
                }
            } else {
            // call base code
                _base_handleKeypress.call(this, evt);
            }
        // check for escape key
        } else if (this.escapeCode == code && this.dragControl.feature) {
            vertex = this.dragControl.feature;
            if (vertex &&
                       OpenLayers.Util.indexOf(this.vertices, vertex) != -1 &&
                       this.dragControl.handlers.drag.dragging &&
                       vertex.geometry.parent) {
                var initialVertex = this.initialVertex;
                if (initialVertex.id === vertex.id) {
                    this.dragControl.handlers.drag.deactivate();
                    this.dragControl.handlers.drag.activate();
                    if (initialVertex.index) {
                    // is virtual
                        _base_handleKeypress.call(
                            this,
                            OpenLayers.Util.applyDefaults({keyCode: 46}, evt)
                        );
                    } else {
                    // is real
                        vertex.geometry.x = initialVertex.x;
                        vertex.geometry.y = initialVertex.y;
                        vertex.geometry.clearBounds();
                        this.layer.events.triggerEvent("vertexmodified", {
                            vertex: vertex.geometry,
                            feature: this.feature,
                            pixel: new OpenLayers.Pixel(
                                         initialVertex.pixx, initialVertex.pixy)
                        });
                        this.layer.drawFeature(
                            this.feature,
                            this.standalone ? undefined :
                                              this.selectControl.renderIntent
                        );
                        this.dragComplete(vertex);
                    }
                }
            }
        }
    };

    var _base_dragStart = _prototype.dragStart;
    /**
     * Method: dragStart
     * Called by the drag feature control with before a feature is dragged.
     *     This method is used to differentiate between points and vertices
     *     of higher order geometries.  This respects the <geometryTypes>
     *     property and forces a select of points when the drag control is
     *     already active (and stops events from propagating to the select
     *     control).
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>} The point or vertex about to be
     *     dragged.
     * pixel - {<OpenLayers.Pixel>} Pixel location of the mouse event.
     */
    _prototype.dragStart = function(feature, pixel) {
        this.initialVertex = {
            index: feature._index,
            id: feature.id,
            x: feature.geometry.x,
            y: feature.geometry.y,
            pixx: pixel.x,
            pixy: pixel.y
        };
        _base_dragStart.apply(this, arguments);
    };

    /**
     * Method: justUnselectFeature
     * Called to unselect the feature.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>} The unselected feature.
     */
    _prototype.justUnselectFeature = function(feature) {
        this.layer.removeFeatures(this.vertices, {silent: true});
        this.vertices = [];
        this.layer.destroyFeatures(this.virtualVertices, {silent: true});
        this.virtualVertices = [];
        if(this.dragHandle) {
            this.layer.destroyFeatures([this.dragHandle], {silent: true});
            delete this.dragHandle;
        }
        if(this.radiusHandle) {
            this.layer.destroyFeatures([this.radiusHandle], {silent: true});
            delete this.radiusHandle;
        }
        this.feature = null;
        this.dragControl.deactivate();
        this.modified = false;
    };
})();<|endoftext|>