<commit_before>'use strict';

var Contacts = {
  _loaded: false,
  get view() {
    delete this.view;
    return this.view = document.getElementById('contacts-view-scrollable');
  },
  setup: function contactsSetup() {
    document.getElementById('contacts').addEventListener('change',
      (function contactTabChanged(evt) {
        // loading contacts the first time the view appears
        this.load();

        ContactDetails.hide();
      }).bind(this));
  },
  load: function contactsLoad() {
    if (this._loaded) {
      return;
    }

    // Could be much easier to have an argument named 'parameters' pass as
    // a second argument that I can omit
    this.find(['id', 'displayName'], this.show.bind(this));
  },

  find: function contactsFind(fields, callback) {
    // Ideally I would like to choose the ordering
    // It also misses simple constaints like the one you can pass to the
    // webSMS API
    window.navigator.mozContacts.find(fields, function findCallback(contacts) {
      contacts.sort(function contactsSort(a, b) {
        return a.name.familyName[0] > b.name.familyName[0];
      });
      callback(contacts);
    });
  },

  findByNumber: function findByNumber(number, callback) {
    this.find(['id', 'phones'], function findByNumberCallback(contacts) {
      var results = contacts.filter(function findNumber(contact) {
        return (contact.phones.indexOf(number) !== -1);
      });
      var contact = results[0];
      if (contact) {
        callback(contact);
      }
    });
  },
  show: function contactsShow(contacts) {
    var content = '';
    var currentLetter = '';

    var count = contacts.length;
    for (var i = 0; i < count; i++) {
      var contact = contacts[i];
      var displayName = contact.displayName;

      var name = contact.name.familyName[0];
      if (currentLetter != name[0]) {
        currentLetter = name[0].toUpperCase();

        content += '
' +
                   '' +
                      currentLetter +
                   '
';
      }

      content += '
';
      for (var key in contact.name) {
        content += '' + contact.name[key] + ' ';
      }
      content += '
';
    }

    var contactsContainer = document.getElementById('contacts-container');
    contactsContainer.innerHTML = content;
    this.filter();

    this._loaded = true;
  },
  filter: function contactsFilter(value) {
    var container = document.getElementById('contacts-container');
    var contacts = container.children;

    var count = contacts.length;
    for (var i = 0; i < count; i++) {
      var contact = contacts[i];
      if (contact.className == 'contact-header')
        continue;

      var name = contact.querySelector('span').textContent;
      var rule = new RegExp(value, 'gi');
      contact.hidden = (name.search(rule) == -1);
    }

    // If there is 0 childs for a particular letter, hide it.
    var enabledLetters = [];
    for (var i = 0; i < count; i++) {
      var contactHeader = contacts[i];
      if (contactHeader.className != 'contact-header')
        continue;

      var nextSibling = contactHeader.nextSibling;
      while (nextSibling && nextSibling.className == 'contact') {
        if (!nextSibling.hidden) {
          enabledLetters.push(contactHeader.id);
          break;
        }
        nextSibling = nextSibling.nextSibling;
      }
    }

    // Reflect the change in the shortcut letter
    var shortcuts = document.getElementById('contacts-shortcuts').children;
    for (var j = 0; j < shortcuts.length; j++) {
      var shortcut = shortcuts[j];
      var targetId = shortcut.name;
      var header = document.getElementById(targetId);

      var disabled = (enabledLetters.indexOf(targetId) == -1);
      if (header && disabled) {
        shortcut.setAttribute('data-disabled', 'true');
        header.hidden = true;
      } else if (disabled) {
        shortcut.setAttribute('data-disabled', 'true');
      } else {
        shortcut.removeAttribute('data-disabled');
        header.hidden = false;
      }
    }
  },
  anchor: function contactsAnchor(targetId) {
    var target = document.getElementById(targetId);
    if (!target)
      return;

    var top = target.getBoundingClientRect().top;
    var scrollable = document.getElementById('contacts-view-scrollable');
    var scrollableTop = scrollable.getBoundingClientRect().top;
    scrollable.scrollTop = (top - scrollableTop) + scrollable.scrollTop;
  },
  showDetails: function contactsShowDetails(evt) {
    // I'm under the impression that there will be a better way to do this
    // with the final API (ie. getting a contact from an id)
    var contactId = evt.target.id;

    this.find(['id'], function showDetailCallback(contacts) {
      var results = contacts.filter(function finById(contact) {
        return (contact.id == contactId);
      });
      var contact = results[0];
      if (contact) {
        ContactDetails.show(contact);
      }
    });
  },
  create: function contactsCreate() {
    // creating an empty contact
    var contact = {
      name: {
        familyName: [],
        givenName: []
      },
      phones: [],
      emails: []
    };
    ContactDetails.show(contact);
  }
};

var ShortcutsHandler = {
  setup: function sh_setup() {
    ['touchstart', 'touchmove', 'touchend'].forEach((function(evt) {
      this.shortcutsBar.addEventListener(evt, this, true);
    }).bind(this));
  },

  get shortcutsBar() {
    delete this.shortcutsBar;
    return this.shortcutsBar = document.getElementById('contacts-shortcuts');
  },

  get shortcutsBackground() {
    delete this.shortcutsBackground;
    var id = 'contacts-shortcuts-background';
    return this.shortcutsBackground = document.getElementById(id);
  },

  handleEvent: function sh_handleEvent(evt) {
    // preventing the events from bubbling to the contacts list
    evt.preventDefault();

    switch (evt.type) {
      case 'touchstart':
        this.startTracking();
      case 'touchmove': // fall through
        this.anchorForPosition(evt.targetTouches[0].clientY);
        break;

      case 'touchend':
        this.stopTracking();
        break;
    }
  },

  startTracking: function sh_startTracking() {
    this.shortcutsBackground.classList.add('tracking');

    // we keep a reference to the horizontal center of the zone
    // it allows us to keep anchoring correctly if the user gets
    // out of the zone while swiping
    var rect = this.shortcutsBar.getBoundingClientRect();
    this._positionX = rect.left + (rect.width / 2);
  },
  stopTracking: function sh_stopTracking() {
    this.shortcutsBackground.classList.remove('tracking');
    delete this._positionX;
  },
  anchorForPosition: function sh_anchorForPosition(positionY) {
    // only inspecting the vertical point of the touch
    var target = document.elementFromPoint(this._positionX, positionY).name;
    Contacts.anchor(target);
  }
};

var ContactDetails = {
  _editing: false,
  setup: function cd_setup() {
    window.addEventListener('keyup', this, true);
  },
  get overlay() {
    delete this.overlay;
    return this.overlay = document.getElementById('contacts-overlay');
  },
  get container() {
    delete this.container;
    return this.container =
      document.getElementById('contact-details-container');
  },
  get view() {
    delete this.view;
    return this.view = document.getElementById('contact-details-view');
  },
  get editButton() {
    delete this.editButton;
    return this.editButton = document.getElementById('contact-edit-button');
  },
  set contact(contact) {
    delete this._contact;
    this._contact = contact;
    this.render();
  },

  execute: function cd_execute(evt) {
    var action = evt.currentTarget.dataset.action;
    if (!this[action]) {
      this.hide();
      return;
    }

    this[action](evt);
  },

  show: function cd_show(contact) {
    if (typeof contact != 'undefined') {
      this.contact = contact;
    }

    var overlay = this.overlay;
    overlay.classList.add('displayed');

    // directly entering the edit mode if this is a new contact
    if (!this._contact.id) {
      this.edit();
    }
  },

  hide: function cd_hide() {
    if (!this.overlay.classList.contains('displayed')) {
      return false;
    }

    var overlay = this.overlay;
    overlay.classList.add('hidden');
    overlay.addEventListener('transitionend', function fadeWait() {
      overlay.removeEventListener('transitionend', fadeWait);

      overlay.classList.remove('hidden');
      overlay.classList.remove('displayed');
    });

    this.endEditing();
    return true;
  },
  add: function cd_add(evt) {
    var parent = evt.currentTarget.parentNode;
    var type = '';
    switch (parent.id) {
      case 'contact-phones':
        type = 'tel';
        break;
      case 'contact-emails':
        type = 'email';
        break;
      default:
        break;
    }

    var newElement = document.createElement('div');
    newElement.innerHTML = this.inputFragment(type, '', false);
    parent.insertBefore(newElement, evt.currentTarget);

    newElement.querySelector('input').focus();
  },
  remove: function cd_remove(element) {
    element.parentNode.removeChild(element);
  },
  edit: function cd_edit() {
    if (this._editing) {
      return;
    }
    this._editing = true;

    this.smoothTransition((function cd_editTransition() {
      this.editButton.dataset.action = 'save';
      this.view.classList.add('editing');
      // making the fields editable
      var inputs = this.view.getElementsByTagName('input');
      for (var i = 0; i < inputs.length; i++) {
        var input = inputs[i];
        input.disabled = false;
      }
    }).bind(this));
  },
  save: function cd_save() {
    // TODO: actually save the contact

    this.endEditing();
  },
  destroy: function cd_destroy() {
    // TODO: destroy the contact
    this.hide();
  },
  call: function cd_call(evt) {
    if (this._editing) {
      return;
    }

    var number = evt.target.dataset.number;
    if (number) {
      CallHandler.call(number);
    }
  },

  endEditing: function cd_endEditing() {
    if (!this._editing) {
      return false;
    }
    this._editing = false;

    this.smoothTransition((function cd_endEditingTransition() {
      this.editButton.dataset.action = 'edit';
      this.view.classList.remove('editing');
      // disabling the fields and cleaning up
      var inputs = this.view.getElementsByTagName('input');
      for (var i = 0; i < inputs.length; i++) {
        var input = inputs[i];
        input.disabled = true;

        if (input.value.length == 0) {
          this.remove(input.parentNode);
        }
      }
    }).bind(this));
    return true;
  },
  // scrolling to the right position when one of the fields
  // takes focus
  autoscroll: function cd_autoscroll(event) {
    var element = event.currentTarget;
    var self = this;

    var scrollInPlace = function cd_autoscrollMove() {
      var bounds = element.getBoundingClientRect();
      var bottom = bounds.top + bounds.height;
      var yDelta = bottom - self.container.getBoundingClientRect().height;
      if (yDelta > 0) {
        self.container.scrollTop += yDelta;
      }
    };
    scrollInPlace();

    //also listening to the next resize for keyboard handling
    window.addEventListener('resize', function cd_afterResize() {
      window.removeEventListener('resize', cd_afterResize);

      scrollInPlace();
    });
  },

  // back button handling
  handleEvent: function cd_handleEvent(evt) {
    if (evt.type !== 'keyup' || evt.keyCode != evt.DOM_VK_ESCAPE) {
      return;
    }

    if (this.endEditing() || this.hide()) {
      evt.preventDefault();
    }
  },

  render: function cd_render() {
    var names = '';
    for (var key in this._contact.name) {
      names += '  ' + this._contact.name[key];
    }
    document.getElementById('contact-name').innerHTML = names;

    document.getElementById('contact-photo').innerHTML =
      profilePictureForNumber(this._contact.id);

    var addAttr = 'data-action="add" onclick="ContactDetails.execute(event)"';
    var phones = '';
    this._contact.phones.forEach(function phoneIterator(phone) {
      phones += '
' +
                'phone' +
                '  ' + this.inputFragment('tel', phone) +
                '
';
    }, this);
    phones += '
' +
              '  Add phone' +
              '
';
    document.getElementById('contact-phones').innerHTML = phones;

    var emails = '';
    var emailArr = this._contact.emails;
    emailArr.forEach(function emailIterator(email) {
      emails += '
email' +
                this.inputFragment('email', email) + '
';
    }, this);
    emails += '
' +
              '  Add email' +
              '
';
    document.getElementById('contact-emails').innerHTML = emails;
  },
  inputFragment: function cd_inputFragment(type, value, disabled) {
    disabled = (typeof disabled == 'undefined') ? true : disabled;

    return '
' + value + '
';
  },
  smoothTransition: function cd_smoothTransition(callback) {
    var detailsView = this.view;
    detailsView.classList.add('hidden');
    detailsView.addEventListener('transitionend', function cd_smoothFinish() {
      detailsView.removeEventListener('transitionend', cd_smoothFinish);

      callback();

      detailsView.classList.remove('hidden');
    });
  }

};

window.addEventListener('load', function contactSetup(evt) {
  window.removeEventListener('load', contactSetup);
  Contacts.setup();
  ShortcutsHandler.setup();
  ContactDetails.setup();

  // click outside details container to close
  document.getElementById('contacts-overlay').addEventListener('click', function(evt) {
    ContactDetails.hide();
  });
  document.getElementById('contact-details-container').addEventListener('click', function(evt) {
    evt.stopPropagation();
  });

});
<commit_msg>quick and dirty scrollwheel fallback on contacts list for browser users<commit_after>'use strict';

var Contacts = {
  _loaded: false,
  get view() {
    delete this.view;
    return this.view = document.getElementById('contacts-view-scrollable');
  },
  setup: function contactsSetup() {
    document.getElementById('contacts').addEventListener('change',
      (function contactTabChanged(evt) {
        // loading contacts the first time the view appears
        this.load();

        ContactDetails.hide();
      }).bind(this));
  },
  load: function contactsLoad() {
    if (this._loaded) {
      return;
    }

    // Could be much easier to have an argument named 'parameters' pass as
    // a second argument that I can omit
    this.find(['id', 'displayName'], this.show.bind(this));
  },

  find: function contactsFind(fields, callback) {
    // Ideally I would like to choose the ordering
    // It also misses simple constaints like the one you can pass to the
    // webSMS API
    window.navigator.mozContacts.find(fields, function findCallback(contacts) {
      contacts.sort(function contactsSort(a, b) {
        return a.name.familyName[0] > b.name.familyName[0];
      });
      callback(contacts);
    });
  },

  findByNumber: function findByNumber(number, callback) {
    this.find(['id', 'phones'], function findByNumberCallback(contacts) {
      var results = contacts.filter(function findNumber(contact) {
        return (contact.phones.indexOf(number) !== -1);
      });
      var contact = results[0];
      if (contact) {
        callback(contact);
      }
    });
  },
  show: function contactsShow(contacts) {
    var content = '';
    var currentLetter = '';

    var count = contacts.length;
    for (var i = 0; i < count; i++) {
      var contact = contacts[i];
      var displayName = contact.displayName;

      var name = contact.name.familyName[0];
      if (currentLetter != name[0]) {
        currentLetter = name[0].toUpperCase();

        content += '
' +
                   '' +
                      currentLetter +
                   '
';
      }

      content += '
';
      for (var key in contact.name) {
        content += '' + contact.name[key] + ' ';
      }
      content += '
';
    }

    var contactsContainer = document.getElementById('contacts-container');
    contactsContainer.innerHTML = content;
    this.filter();

    this._loaded = true;
  },
  filter: function contactsFilter(value) {
    var container = document.getElementById('contacts-container');
    var contacts = container.children;

    var count = contacts.length;
    for (var i = 0; i < count; i++) {
      var contact = contacts[i];
      if (contact.className == 'contact-header')
        continue;

      var name = contact.querySelector('span').textContent;
      var rule = new RegExp(value, 'gi');
      contact.hidden = (name.search(rule) == -1);
    }

    // If there is 0 childs for a particular letter, hide it.
    var enabledLetters = [];
    for (var i = 0; i < count; i++) {
      var contactHeader = contacts[i];
      if (contactHeader.className != 'contact-header')
        continue;

      var nextSibling = contactHeader.nextSibling;
      while (nextSibling && nextSibling.className == 'contact') {
        if (!nextSibling.hidden) {
          enabledLetters.push(contactHeader.id);
          break;
        }
        nextSibling = nextSibling.nextSibling;
      }
    }

    // Reflect the change in the shortcut letter
    var shortcuts = document.getElementById('contacts-shortcuts').children;
    for (var j = 0; j < shortcuts.length; j++) {
      var shortcut = shortcuts[j];
      var targetId = shortcut.name;
      var header = document.getElementById(targetId);

      var disabled = (enabledLetters.indexOf(targetId) == -1);
      if (header && disabled) {
        shortcut.setAttribute('data-disabled', 'true');
        header.hidden = true;
      } else if (disabled) {
        shortcut.setAttribute('data-disabled', 'true');
      } else {
        shortcut.removeAttribute('data-disabled');
        header.hidden = false;
      }
    }
  },
  anchor: function contactsAnchor(targetId) {
    var target = document.getElementById(targetId);
    if (!target)
      return;

    var top = target.getBoundingClientRect().top;
    var scrollable = document.getElementById('contacts-view-scrollable');
    var scrollableTop = scrollable.getBoundingClientRect().top;
    scrollable.scrollTop = (top - scrollableTop) + scrollable.scrollTop;
  },
  showDetails: function contactsShowDetails(evt) {
    // I'm under the impression that there will be a better way to do this
    // with the final API (ie. getting a contact from an id)
    var contactId = evt.target.id;

    this.find(['id'], function showDetailCallback(contacts) {
      var results = contacts.filter(function finById(contact) {
        return (contact.id == contactId);
      });
      var contact = results[0];
      if (contact) {
        ContactDetails.show(contact);
      }
    });
  },
  create: function contactsCreate() {
    // creating an empty contact
    var contact = {
      name: {
        familyName: [],
        givenName: []
      },
      phones: [],
      emails: []
    };
    ContactDetails.show(contact);
  }
};

var ShortcutsHandler = {
  setup: function sh_setup() {
    ['touchstart', 'touchmove', 'touchend'].forEach((function(evt) {
      this.shortcutsBar.addEventListener(evt, this, true);
    }).bind(this));
  },

  get shortcutsBar() {
    delete this.shortcutsBar;
    return this.shortcutsBar = document.getElementById('contacts-shortcuts');
  },

  get shortcutsBackground() {
    delete this.shortcutsBackground;
    var id = 'contacts-shortcuts-background';
    return this.shortcutsBackground = document.getElementById(id);
  },

  handleEvent: function sh_handleEvent(evt) {
    // preventing the events from bubbling to the contacts list
    evt.preventDefault();

    switch (evt.type) {
      case 'touchstart':
        this.startTracking();
      case 'touchmove': // fall through
        this.anchorForPosition(evt.targetTouches[0].clientY);
        break;

      case 'touchend':
        this.stopTracking();
        break;
    }
  },

  startTracking: function sh_startTracking() {
    this.shortcutsBackground.classList.add('tracking');

    // we keep a reference to the horizontal center of the zone
    // it allows us to keep anchoring correctly if the user gets
    // out of the zone while swiping
    var rect = this.shortcutsBar.getBoundingClientRect();
    this._positionX = rect.left + (rect.width / 2);
  },
  stopTracking: function sh_stopTracking() {
    this.shortcutsBackground.classList.remove('tracking');
    delete this._positionX;
  },
  anchorForPosition: function sh_anchorForPosition(positionY) {
    // only inspecting the vertical point of the touch
    var target = document.elementFromPoint(this._positionX, positionY).name;
    Contacts.anchor(target);
  }
};

var ContactDetails = {
  _editing: false,
  setup: function cd_setup() {
    window.addEventListener('keyup', this, true);
  },
  get overlay() {
    delete this.overlay;
    return this.overlay = document.getElementById('contacts-overlay');
  },
  get container() {
    delete this.container;
    return this.container =
      document.getElementById('contact-details-container');
  },
  get view() {
    delete this.view;
    return this.view = document.getElementById('contact-details-view');
  },
  get editButton() {
    delete this.editButton;
    return this.editButton = document.getElementById('contact-edit-button');
  },
  set contact(contact) {
    delete this._contact;
    this._contact = contact;
    this.render();
  },

  execute: function cd_execute(evt) {
    var action = evt.currentTarget.dataset.action;
    if (!this[action]) {
      this.hide();
      return;
    }

    this[action](evt);
  },

  show: function cd_show(contact) {
    if (typeof contact != 'undefined') {
      this.contact = contact;
    }

    var overlay = this.overlay;
    overlay.classList.add('displayed');

    // directly entering the edit mode if this is a new contact
    if (!this._contact.id) {
      this.edit();
    }
  },

  hide: function cd_hide() {
    if (!this.overlay.classList.contains('displayed')) {
      return false;
    }

    var overlay = this.overlay;
    overlay.classList.add('hidden');
    overlay.addEventListener('transitionend', function fadeWait() {
      overlay.removeEventListener('transitionend', fadeWait);

      overlay.classList.remove('hidden');
      overlay.classList.remove('displayed');
    });

    this.endEditing();
    return true;
  },
  add: function cd_add(evt) {
    var parent = evt.currentTarget.parentNode;
    var type = '';
    switch (parent.id) {
      case 'contact-phones':
        type = 'tel';
        break;
      case 'contact-emails':
        type = 'email';
        break;
      default:
        break;
    }

    var newElement = document.createElement('div');
    newElement.innerHTML = this.inputFragment(type, '', false);
    parent.insertBefore(newElement, evt.currentTarget);

    newElement.querySelector('input').focus();
  },
  remove: function cd_remove(element) {
    element.parentNode.removeChild(element);
  },
  edit: function cd_edit() {
    if (this._editing) {
      return;
    }
    this._editing = true;

    this.smoothTransition((function cd_editTransition() {
      this.editButton.dataset.action = 'save';
      this.view.classList.add('editing');
      // making the fields editable
      var inputs = this.view.getElementsByTagName('input');
      for (var i = 0; i < inputs.length; i++) {
        var input = inputs[i];
        input.disabled = false;
      }
    }).bind(this));
  },
  save: function cd_save() {
    // TODO: actually save the contact

    this.endEditing();
  },
  destroy: function cd_destroy() {
    // TODO: destroy the contact
    this.hide();
  },
  call: function cd_call(evt) {
    if (this._editing) {
      return;
    }

    var number = evt.target.dataset.number;
    if (number) {
      CallHandler.call(number);
    }
  },

  endEditing: function cd_endEditing() {
    if (!this._editing) {
      return false;
    }
    this._editing = false;

    this.smoothTransition((function cd_endEditingTransition() {
      this.editButton.dataset.action = 'edit';
      this.view.classList.remove('editing');
      // disabling the fields and cleaning up
      var inputs = this.view.getElementsByTagName('input');
      for (var i = 0; i < inputs.length; i++) {
        var input = inputs[i];
        input.disabled = true;

        if (input.value.length == 0) {
          this.remove(input.parentNode);
        }
      }
    }).bind(this));
    return true;
  },
  // scrolling to the right position when one of the fields
  // takes focus
  autoscroll: function cd_autoscroll(event) {
    var element = event.currentTarget;
    var self = this;

    var scrollInPlace = function cd_autoscrollMove() {
      var bounds = element.getBoundingClientRect();
      var bottom = bounds.top + bounds.height;
      var yDelta = bottom - self.container.getBoundingClientRect().height;
      if (yDelta > 0) {
        self.container.scrollTop += yDelta;
      }
    };
    scrollInPlace();

    //also listening to the next resize for keyboard handling
    window.addEventListener('resize', function cd_afterResize() {
      window.removeEventListener('resize', cd_afterResize);

      scrollInPlace();
    });
  },

  // back button handling
  handleEvent: function cd_handleEvent(evt) {
    if (evt.type !== 'keyup' || evt.keyCode != evt.DOM_VK_ESCAPE) {
      return;
    }

    if (this.endEditing() || this.hide()) {
      evt.preventDefault();
    }
  },

  render: function cd_render() {
    var names = '';
    for (var key in this._contact.name) {
      names += '  ' + this._contact.name[key];
    }
    document.getElementById('contact-name').innerHTML = names;

    document.getElementById('contact-photo').innerHTML =
      profilePictureForNumber(this._contact.id);

    var addAttr = 'data-action="add" onclick="ContactDetails.execute(event)"';
    var phones = '';
    this._contact.phones.forEach(function phoneIterator(phone) {
      phones += '
' +
                'phone' +
                '  ' + this.inputFragment('tel', phone) +
                '
';
    }, this);
    phones += '
' +
              '  Add phone' +
              '
';
    document.getElementById('contact-phones').innerHTML = phones;

    var emails = '';
    var emailArr = this._contact.emails;
    emailArr.forEach(function emailIterator(email) {
      emails += '
email' +
                this.inputFragment('email', email) + '
';
    }, this);
    emails += '
' +
              '  Add email' +
              '
';
    document.getElementById('contact-emails').innerHTML = emails;
  },
  inputFragment: function cd_inputFragment(type, value, disabled) {
    disabled = (typeof disabled == 'undefined') ? true : disabled;

    return '
' + value + '
';
  },
  smoothTransition: function cd_smoothTransition(callback) {
    var detailsView = this.view;
    detailsView.classList.add('hidden');
    detailsView.addEventListener('transitionend', function cd_smoothFinish() {
      detailsView.removeEventListener('transitionend', cd_smoothFinish);

      callback();

      detailsView.classList.remove('hidden');
    });
  }

};

window.addEventListener('load', function contactSetup(evt) {
  window.removeEventListener('load', contactSetup);
  Contacts.setup();
  ShortcutsHandler.setup();
  ContactDetails.setup();

  // click outside details container to close
  document.getElementById('contacts-overlay').addEventListener('click', function(evt) {
    ContactDetails.hide();
  });
  document.getElementById('contact-details-container').addEventListener('click', function(evt) {
    evt.stopPropagation();
  });

  // add scrollwheel fallback for browser users
  var scrollNode = Contacts.view;
  scrollNode.addEventListener('DOMMouseScroll', wheel, false);

  function wheel(evt){
    var s;
    if (!evt) {
      s = -window.event.wheelDelta;
    } else {
      s = evt.detail;
    }
    scrollNode.scrollTop += 10 * ((s > 0) ? 1 : -1);
  }
});
<|endoftext|>


<commit_before>(function() {
    'use strict';

    var webpage  = require('webpage');
    var base64   = require('./libs/base64.js');
    var Renderer = function(data) { this.init(data); };

    Renderer.prototype.init = function(data) {
        this.html = data.html;

        if (data.width && data.height) {
            this.width = parseInt(data.width, 10);
            this.height = parseInt(data.height, 10);
        }

        this.timeout = data.timeout ? parseInt(data.timeout) : 100;
        if (this.timeout > 1000) { this.timeout = 1000; }

        this.page = webpage.create();

        this.setOnRenderCallback(function() {});

        this.page.onCallback = this.onPhantomCallback.bind(this);

        this.page.onConsoleMessage = function(message, lineNum, sourceId) {
            console.log('CONSOLE: ' + (typeof message == 'string' ? message : JSON.stringify(message)));
        };
    };

    Renderer.prototype.setResponse = function(response) {
        this.response = response;
    };

    Renderer.prototype.loadPage = function() {
        if (this.width > 0 && this.height > 0) {
            this.page.viewportSize = { width: this.width, height: this.height };
        }
        this.page.open('about:blank', this.onPageReady.bind(this));
    };

    Renderer.prototype.setOnRenderCallback = function(callback) {
        this.onRenderCompleteCallback = callback;
    };

    Renderer.prototype.onPhantomCallback = function(message) {
        var data = base64.decode(this.page.renderBase64('png'));
        var decoded = '';

        for (var i = 0; i < data.length; i++) {
            decoded = decoded + String.fromCharCode(data[i]);
        }

        this.onRenderCompleteCallback(
            this.response,
            decoded
        );

        this.page.close();
    };

    Renderer.prototype.onPageReady = function() {
        this.page.content = this.html;
        var pageReady = function() {
            setTimeout(function() {
                window.callPhantom('OK');
            }, this.timeout);
        };
        this.page.evaluate(pageReady);
    };

    Renderer.prototype.render = function() {
        this.loadPage();
    };

    module.exports = Renderer;

})();<commit_msg>Readd window.onload use<commit_after>(function() {
    'use strict';

    var webpage  = require('webpage');
    var base64   = require('./libs/base64.js');
    var Renderer = function(data) { this.init(data); };

    Renderer.prototype.init = function(data) {
        this.html = data.html;

        if (data.width && data.height) {
            this.width = parseInt(data.width, 10);
            this.height = parseInt(data.height, 10);
        }

        this.timeout = data.timeout ? parseInt(data.timeout) : 100;
        if (this.timeout > 1000) { this.timeout = 1000; }

        this.page = webpage.create();

        this.setOnRenderCallback(function() {});

        this.page.onCallback = this.onPhantomCallback.bind(this);

        this.page.onConsoleMessage = function(message, lineNum, sourceId) {
            console.log('CONSOLE: ' + (typeof message == 'string' ? message : JSON.stringify(message)));
        };
    };

    Renderer.prototype.setResponse = function(response) {
        this.response = response;
    };

    Renderer.prototype.loadPage = function() {
        if (this.width > 0 && this.height > 0) {
            this.page.viewportSize = { width: this.width, height: this.height };
        }
        this.page.open('about:blank', this.onPageReady.bind(this));
    };

    Renderer.prototype.setOnRenderCallback = function(callback) {
        this.onRenderCompleteCallback = callback;
    };

    Renderer.prototype.onPhantomCallback = function(message) {
        var data = base64.decode(this.page.renderBase64('png'));
        var decoded = '';

        for (var i = 0; i < data.length; i++) {
            decoded = decoded + String.fromCharCode(data[i]);
        }

        this.onRenderCompleteCallback(
            this.response,
            decoded
        );

        this.page.close();
    };

    Renderer.prototype.onPageReady = function() {
        this.page.content = this.html;
        var pageReady = function() {
            window.onload = function() {
                window.callPhantom('OK');
            };
            setTimeout(function() {
                window.callPhantom('OK');
            }, this.timeout);
        };
        this.page.evaluate(pageReady);
    };

    Renderer.prototype.render = function() {
        this.loadPage();
    };

    module.exports = Renderer;

})();<|endoftext|>


<commit_before>
function capitaliseFirstLetter(string) {
	if (Ember.empty(string)) return "";
	return string.charAt(0).toUpperCase() + string.slice(1);
}

FLOW.attributeTypeControl = Ember.Object.create({
  content: [
    Ember.Object.create({
      label: "text",
      value: "String"
    }), Ember.Object.create({
      label: "number",
      value: "Double"
    })
  ]
});

FLOW.attributeControl = Ember.ArrayController.create({
  sortProperties: null,
  sortAscending: true,
  content: null,

  setFilteredContent: function () {
    this.set('content', FLOW.store.filter(FLOW.Metric, function (item) {
      return true;
    }));
  },

  // load all Survey Groups
  populate: function () {
    FLOW.store.find(FLOW.Metric);
    this.setFilteredContent();
    this.set('sortProperties', ['name']);
    this.set('sortAscending', true);
  },

  getSortInfo: function () {
    this.set('sortProperties', FLOW.tableColumnControl.get('sortProperties'));
    this.set('sortAscending', FLOW.tableColumnControl.get('sortAscending'));
  }
});

FLOW.cascadeResourceControl = Ember.ArrayController.create({
	content:null,
	published:null,
	statusUpdateTrigger:false,
	levelNames:null,
	displayLevelName1: null, displayLevelName2: null, displayLevelName3: null,
	displayLevelNum1: null, displayLevelNum2: null, displayLevelNum3: null,

	populate: function() {
		this.set('content', FLOW.store.find(FLOW.CascadeResource));
		this.set('published',FLOW.store.filter(FLOW.CascadeResource,function(item){
			return item.get('status') === 'PUBLISHED';
		}));
	},

	setLevelNamesArray: function(){
		var i=1, levelNamesArray=[], numLevels;
		numLevels = FLOW.selectedControl.selectedCascadeResource.get('numLevels');

		// store the level names in an array
		FLOW.selectedControl.selectedCascadeResource.get('levelNames').forEach(function(item){
			if (i <= numLevels) {
				levelNamesArray.push(Ember.Object.create({
					levelName: item,
					level:i}));
				i++;
			}
		});
		this.set('levelNames',levelNamesArray);
	},

	setDisplayLevelNames: function(){
		var skip, names, numLevels;
		skip = FLOW.cascadeNodeControl.get('skip');
		names = this.get('levelNames');
		numLevels = FLOW.selectedControl.selectedCascadeResource.get('numLevels');
		this.set('displayLevelName1',names[skip].get('levelName'));
		if (numLevels > 1) {
			this.set('displayLevelName2',names[skip + 1].get('levelName'));
		} else {
			this.set('displayLevelName2',"");
		}
		if (numLevels > 2) {
			this.set('displayLevelName3',names[skip + 2].get('levelName'));
		} else {
			this.set('displayLevelName3',"");
		}
		this.set('displayLevelNum1',skip + 1);
		this.set('displayLevelNum2',skip + 2);
		this.set('displayLevelNum3',skip + 3);
	},

	publish: function(cascadeResourceId){
		FLOW.store.findQuery(FLOW.Action, {
		    action: 'publishCascade',
		    cascadeResourceId: cascadeResourceId
		});
	},

	hasQuestions: function () {
		if (!FLOW.selectedControl.selectedCascadeResource || !FLOW.selectedControl.selectedCascadeResource.get('keyId')) {
			return;
		}
		FLOW.store.findQuery(FLOW.Question, {cascadeResourceId: FLOW.selectedControl.selectedCascadeResource.get('keyId')});
	}.observes('FLOW.selectedControl.selectedCascadeResource'),

	triggerStatusUpdate: function(){
		this.toggleProperty('statusUpdateTrigger');
	},

	currentStatus: function () {
		// hack to get translation keys, don't delete them
		// {{t _not_published}}
		// {{t _publishing}}
		// {{t _published}}
		var status;
		if (!FLOW.selectedControl.selectedCascadeResource) {
			return '';
		}
		status = ('_' + FLOW.selectedControl.selectedCascadeResource.get('status')).toLowerCase();
		return Ember.String.loc(status);
	}.property('FLOW.selectedControl.selectedCascadeResource','this.statusUpdateTrigger'),

	isPublished: function () {
		if (!FLOW.selectedControl.selectedCascadeResource) {
			return false;
		}
		return FLOW.selectedControl.selectedCascadeResource.get('status') === 'PUBLISHED';
	}.property('FLOW.selectedControl.selectedCascadeResource','this.statusUpdateTrigger')
});

FLOW.cascadeNodeControl = Ember.ArrayController.create({
	content:null,
	level1:[], level2:[], level3:[], level4:[], level5:[], level6:[], level7:[],
	displayLevel1:[], displayLevel2:[], displayLevel3:[],
	parentNode:[],
	selectedNode:[],
	selectedNodeTrigger: true,
	skip: 0,

	emptyNodes: function(start){
		var i;
		for (i=start ; i < 6 ; i++){
			this.selectedNode[i]=null;
			this.set('level' + i,[]);
		}
	},

	toggleSelectedNodeTrigger:function (){
		this.toggleProperty('selectedNodeTrigger');
	},

	setDisplayLevels: function(){
		this.set('displayLevel1',this.get('level' + (this.get('skip') + 1)));
		this.set('displayLevel2',this.get('level' + (this.get('skip') + 2)));
		this.set('displayLevel3',this.get('level' + (this.get('skip') + 3)));
	},

	populate: function(cascadeResourceId, level, parentNodeId) {
		if (!cascadeResourceId) {
			return;
		}
		this.set('content',FLOW.store.findQuery(FLOW.CascadeNode, {
	        cascadeResourceId: cascadeResourceId,
	        parentNodeId: parentNodeId
	      }));
		this.set('level' + level, FLOW.store.filter(FLOW.CascadeNode, function(item){
			return (item.get('parentNodeId') == parentNodeId && item.get('cascadeResourceId') == cascadeResourceId);
		}));
		this.parentNode[level] = parentNodeId;
		FLOW.cascadeNodeControl.setDisplayLevels();
	},

	addNode: function(cascadeResourceId, level, text, code) {
		var parentNodeId;
		if (level == 1) {
			parentNodeId = 0;
		} else {
			parentNodeId = this.get('parentNode')[level];
		}
		FLOW.store.createRecord(FLOW.CascadeNode, {
			"code": code,
			"name": capitaliseFirstLetter(text),
			"nodeId": null,
			"parentNodeId": parentNodeId,
			"cascadeResourceId": cascadeResourceId
        });
		if (FLOW.selectedControl.selectedCascadeResource.get('status') == 'PUBLISHED'){
			FLOW.selectedControl.selectedCascadeResource.set('status','NOT_PUBLISHED');
			FLOW.cascadeResourceControl.triggerStatusUpdate();
		}
		FLOW.store.commit();
		this.populate(cascadeResourceId, level, parentNodeId);
	},
});

FLOW.surveyInstanceControl = Ember.ArrayController.create({
  sortProperties: ['collectionDate'],
  sortAscending: false,
  selectedSurvey: null,
  content: null,
  sinceArray: [],
  pageNumber: 0,

  populate: function () {
    this.get('sinceArray').pushObject(FLOW.metaControl.get('since'));
    this.set('content', FLOW.store.findQuery(FLOW.SurveyInstance, {}));
  },

  doInstanceQuery: function (surveyId, deviceId, since, beginDate, endDate, submitterName, countryCode, level1, level2) {
    this.set('content', FLOW.store.findQuery(FLOW.SurveyInstance, {
      'surveyId': surveyId,
      'deviceId': deviceId,
      'since': since,
      'beginDate': beginDate,
      'endDate': endDate,
      'submitterName': submitterName,
      'countryCode': countryCode,
      'level1': level1,
      'level2': level2
    }));
  },

  contentChanged: function() {
    var mutableContents = [];

    this.get('arrangedContent').forEach(function(item) {
        mutableContents.pushObject(item);
    });

    this.set('currentContents', mutableContents);
  }.observes('content', 'content.isLoaded'),

  removeInstance: function(instance) {
    this.get('currentContents').forEach(function(item, i, currentContents) {
        if (item.get('id') == instance.get('id')) {
            currentContents.removeAt(i, 1);
        }
    });
  },

  allAreSelected: function (key, value) {
    if (arguments.length === 2) {
      this.setEach('isSelected', value);
      return value;
    } else {
      return !this.get('isEmpty') && this.everyProperty('isSelected', true);
    }
  }.property('@each.isSelected'),

  atLeastOneSelected: function () {
    return this.filterProperty('isSelected', true).get('length');
  }.property('@each.isSelected'),

  // fired from tableColumnView.sort
  getSortInfo: function () {
    this.set('sortProperties', FLOW.tableColumnControl.get('sortProperties'));
    this.set('sortAscending', FLOW.tableColumnControl.get('sortAscending'));
  },

  /* extract surveyId from the survey instance and
  query userControl to determine whether the current
  user is allowed to delete data for this survey*/

  userCanDeleteData: function() {
    var surveyInstance = this.get('content').get('firstObject'); //assumes entire list in array is from same survey
    if(surveyInstance && surveyInstance.get('surveyId')) {
        return FLOW.surveyControl.userCanDeleteData(surveyInstance.get('surveyId'));
    }
    return false; // prevents deletion incase no surveyId found
  }.property('content'),
});

FLOW.surveyedLocaleControl = Ember.ArrayController.create({
  sortProperties: ['collectionDate'],
  sortAscending: false,
  selectedSurvey: null,
  content: null,
  sinceArray: [],
  pageNumber: 0,

  populate: function () {
    this.get('sinceArray').pushObject(FLOW.metaControl.get('since'));
    this.set('content', FLOW.store.findQuery(FLOW.SurveyedLocale, {}));
  },

  contentChanged: function() {
	var mutableContents = [];

	this.get('arrangedContent').forEach(function(item) {
	  mutableContents.pushObject(item);
	});

	this.set('currentContents', mutableContents);
  }.observes('content', 'content.isLoaded'),

  removeLocale: function(locale) {
	this.get('currentContents').forEach(function(item, i, currentContents) {
	  if (item.get('id') == locale.get('id')) {
	    currentContents.removeAt(i, 1);
	  }
    });
  },

  /* the current user is able to delete surveyed locales
    stored in the 'content' property of this controller */
  userCanDelete: function() {
    if(this.get('content') === null) {
        return false;
    }
    var surveyedLocale = this.get('content').get('firstObject'); // locale delete only allowed if enabled for the entire monitoring group
    if(surveyedLocale && surveyedLocale.get('surveyGroupId')) {
        return FLOW.surveyGroupControl.userCanDeleteData(surveyedLocale.get('surveyGroupId'));
    }
    return false; // prevents deletion incase no surveyId found
  }.property('content'),
});

FLOW.questionAnswerControl = Ember.ArrayController.create({
  content: null,

  doQuestionAnswerQuery: function (surveyInstanceId) {
    this.set('content', FLOW.store.findQuery(FLOW.QuestionAnswer, {
      'surveyInstanceId': surveyInstanceId
    }));
  }
});

FLOW.locationControl = Ember.ArrayController.create({
  selectedCountry: null,
  content:null,
  level1Content:null,
  level2Content:null,
  selectedLevel1: null,
  selectedLevel2: null,

  populateLevel1: function(){
    if (!Ember.none(this.get('selectedCountry')) && this.selectedCountry.get('iso').length > 0){
    this.set('level1Content',FLOW.store.findQuery(FLOW.SubCountry,{
      countryCode:this.selectedCountry.get('iso'),
      level:1,
      parentId:null
      }));
    }
  }.observes('this.selectedCountry'),

 populateLevel2: function(){
    if (!Ember.none(this.get('selectedLevel1')) && this.selectedLevel1.get('name').length > 0){
    this.set('level2Content',FLOW.store.findQuery(FLOW.SubCountry,{
      countryCode:this.selectedCountry.get('iso'),
      level:2,
      parentId:this.selectedLevel1.get('keyId')
      }));
    }
  }.observes('this.selectedLevel1')

});
<commit_msg>[#1115] Remove `userCanDeleteData` property from survey instance control<commit_after>
function capitaliseFirstLetter(string) {
	if (Ember.empty(string)) return "";
	return string.charAt(0).toUpperCase() + string.slice(1);
}

FLOW.attributeTypeControl = Ember.Object.create({
  content: [
    Ember.Object.create({
      label: "text",
      value: "String"
    }), Ember.Object.create({
      label: "number",
      value: "Double"
    })
  ]
});

FLOW.attributeControl = Ember.ArrayController.create({
  sortProperties: null,
  sortAscending: true,
  content: null,

  setFilteredContent: function () {
    this.set('content', FLOW.store.filter(FLOW.Metric, function (item) {
      return true;
    }));
  },

  // load all Survey Groups
  populate: function () {
    FLOW.store.find(FLOW.Metric);
    this.setFilteredContent();
    this.set('sortProperties', ['name']);
    this.set('sortAscending', true);
  },

  getSortInfo: function () {
    this.set('sortProperties', FLOW.tableColumnControl.get('sortProperties'));
    this.set('sortAscending', FLOW.tableColumnControl.get('sortAscending'));
  }
});

FLOW.cascadeResourceControl = Ember.ArrayController.create({
	content:null,
	published:null,
	statusUpdateTrigger:false,
	levelNames:null,
	displayLevelName1: null, displayLevelName2: null, displayLevelName3: null,
	displayLevelNum1: null, displayLevelNum2: null, displayLevelNum3: null,

	populate: function() {
		this.set('content', FLOW.store.find(FLOW.CascadeResource));
		this.set('published',FLOW.store.filter(FLOW.CascadeResource,function(item){
			return item.get('status') === 'PUBLISHED';
		}));
	},

	setLevelNamesArray: function(){
		var i=1, levelNamesArray=[], numLevels;
		numLevels = FLOW.selectedControl.selectedCascadeResource.get('numLevels');

		// store the level names in an array
		FLOW.selectedControl.selectedCascadeResource.get('levelNames').forEach(function(item){
			if (i <= numLevels) {
				levelNamesArray.push(Ember.Object.create({
					levelName: item,
					level:i}));
				i++;
			}
		});
		this.set('levelNames',levelNamesArray);
	},

	setDisplayLevelNames: function(){
		var skip, names, numLevels;
		skip = FLOW.cascadeNodeControl.get('skip');
		names = this.get('levelNames');
		numLevels = FLOW.selectedControl.selectedCascadeResource.get('numLevels');
		this.set('displayLevelName1',names[skip].get('levelName'));
		if (numLevels > 1) {
			this.set('displayLevelName2',names[skip + 1].get('levelName'));
		} else {
			this.set('displayLevelName2',"");
		}
		if (numLevels > 2) {
			this.set('displayLevelName3',names[skip + 2].get('levelName'));
		} else {
			this.set('displayLevelName3',"");
		}
		this.set('displayLevelNum1',skip + 1);
		this.set('displayLevelNum2',skip + 2);
		this.set('displayLevelNum3',skip + 3);
	},

	publish: function(cascadeResourceId){
		FLOW.store.findQuery(FLOW.Action, {
		    action: 'publishCascade',
		    cascadeResourceId: cascadeResourceId
		});
	},

	hasQuestions: function () {
		if (!FLOW.selectedControl.selectedCascadeResource || !FLOW.selectedControl.selectedCascadeResource.get('keyId')) {
			return;
		}
		FLOW.store.findQuery(FLOW.Question, {cascadeResourceId: FLOW.selectedControl.selectedCascadeResource.get('keyId')});
	}.observes('FLOW.selectedControl.selectedCascadeResource'),

	triggerStatusUpdate: function(){
		this.toggleProperty('statusUpdateTrigger');
	},

	currentStatus: function () {
		// hack to get translation keys, don't delete them
		// {{t _not_published}}
		// {{t _publishing}}
		// {{t _published}}
		var status;
		if (!FLOW.selectedControl.selectedCascadeResource) {
			return '';
		}
		status = ('_' + FLOW.selectedControl.selectedCascadeResource.get('status')).toLowerCase();
		return Ember.String.loc(status);
	}.property('FLOW.selectedControl.selectedCascadeResource','this.statusUpdateTrigger'),

	isPublished: function () {
		if (!FLOW.selectedControl.selectedCascadeResource) {
			return false;
		}
		return FLOW.selectedControl.selectedCascadeResource.get('status') === 'PUBLISHED';
	}.property('FLOW.selectedControl.selectedCascadeResource','this.statusUpdateTrigger')
});

FLOW.cascadeNodeControl = Ember.ArrayController.create({
	content:null,
	level1:[], level2:[], level3:[], level4:[], level5:[], level6:[], level7:[],
	displayLevel1:[], displayLevel2:[], displayLevel3:[],
	parentNode:[],
	selectedNode:[],
	selectedNodeTrigger: true,
	skip: 0,

	emptyNodes: function(start){
		var i;
		for (i=start ; i < 6 ; i++){
			this.selectedNode[i]=null;
			this.set('level' + i,[]);
		}
	},

	toggleSelectedNodeTrigger:function (){
		this.toggleProperty('selectedNodeTrigger');
	},

	setDisplayLevels: function(){
		this.set('displayLevel1',this.get('level' + (this.get('skip') + 1)));
		this.set('displayLevel2',this.get('level' + (this.get('skip') + 2)));
		this.set('displayLevel3',this.get('level' + (this.get('skip') + 3)));
	},

	populate: function(cascadeResourceId, level, parentNodeId) {
		if (!cascadeResourceId) {
			return;
		}
		this.set('content',FLOW.store.findQuery(FLOW.CascadeNode, {
	        cascadeResourceId: cascadeResourceId,
	        parentNodeId: parentNodeId
	      }));
		this.set('level' + level, FLOW.store.filter(FLOW.CascadeNode, function(item){
			return (item.get('parentNodeId') == parentNodeId && item.get('cascadeResourceId') == cascadeResourceId);
		}));
		this.parentNode[level] = parentNodeId;
		FLOW.cascadeNodeControl.setDisplayLevels();
	},

	addNode: function(cascadeResourceId, level, text, code) {
		var parentNodeId;
		if (level == 1) {
			parentNodeId = 0;
		} else {
			parentNodeId = this.get('parentNode')[level];
		}
		FLOW.store.createRecord(FLOW.CascadeNode, {
			"code": code,
			"name": capitaliseFirstLetter(text),
			"nodeId": null,
			"parentNodeId": parentNodeId,
			"cascadeResourceId": cascadeResourceId
        });
		if (FLOW.selectedControl.selectedCascadeResource.get('status') == 'PUBLISHED'){
			FLOW.selectedControl.selectedCascadeResource.set('status','NOT_PUBLISHED');
			FLOW.cascadeResourceControl.triggerStatusUpdate();
		}
		FLOW.store.commit();
		this.populate(cascadeResourceId, level, parentNodeId);
	},
});

FLOW.surveyInstanceControl = Ember.ArrayController.create({
  sortProperties: ['collectionDate'],
  sortAscending: false,
  selectedSurvey: null,
  content: null,
  sinceArray: [],
  pageNumber: 0,

  populate: function () {
    this.get('sinceArray').pushObject(FLOW.metaControl.get('since'));
    this.set('content', FLOW.store.findQuery(FLOW.SurveyInstance, {}));
  },

  doInstanceQuery: function (surveyId, deviceId, since, beginDate, endDate, submitterName, countryCode, level1, level2) {
    this.set('content', FLOW.store.findQuery(FLOW.SurveyInstance, {
      'surveyId': surveyId,
      'deviceId': deviceId,
      'since': since,
      'beginDate': beginDate,
      'endDate': endDate,
      'submitterName': submitterName,
      'countryCode': countryCode,
      'level1': level1,
      'level2': level2
    }));
  },

  contentChanged: function() {
    var mutableContents = [];

    this.get('arrangedContent').forEach(function(item) {
        mutableContents.pushObject(item);
    });

    this.set('currentContents', mutableContents);
  }.observes('content', 'content.isLoaded'),

  removeInstance: function(instance) {
    this.get('currentContents').forEach(function(item, i, currentContents) {
        if (item.get('id') == instance.get('id')) {
            currentContents.removeAt(i, 1);
        }
    });
  },

  allAreSelected: function (key, value) {
    if (arguments.length === 2) {
      this.setEach('isSelected', value);
      return value;
    } else {
      return !this.get('isEmpty') && this.everyProperty('isSelected', true);
    }
  }.property('@each.isSelected'),

  atLeastOneSelected: function () {
    return this.filterProperty('isSelected', true).get('length');
  }.property('@each.isSelected'),

  // fired from tableColumnView.sort
  getSortInfo: function () {
    this.set('sortProperties', FLOW.tableColumnControl.get('sortProperties'));
    this.set('sortAscending', FLOW.tableColumnControl.get('sortAscending'));
  }
});

FLOW.surveyedLocaleControl = Ember.ArrayController.create({
  sortProperties: ['collectionDate'],
  sortAscending: false,
  selectedSurvey: null,
  content: null,
  sinceArray: [],
  pageNumber: 0,

  populate: function () {
    this.get('sinceArray').pushObject(FLOW.metaControl.get('since'));
    this.set('content', FLOW.store.findQuery(FLOW.SurveyedLocale, {}));
  },

  contentChanged: function() {
	var mutableContents = [];

	this.get('arrangedContent').forEach(function(item) {
	  mutableContents.pushObject(item);
	});

	this.set('currentContents', mutableContents);
  }.observes('content', 'content.isLoaded'),

  removeLocale: function(locale) {
	this.get('currentContents').forEach(function(item, i, currentContents) {
	  if (item.get('id') == locale.get('id')) {
	    currentContents.removeAt(i, 1);
	  }
    });
  },

  /* the current user is able to delete surveyed locales
    stored in the 'content' property of this controller */
  userCanDelete: function() {
    if(this.get('content') === null) {
        return false;
    }
    var surveyedLocale = this.get('content').get('firstObject'); // locale delete only allowed if enabled for the entire monitoring group
    if(surveyedLocale && surveyedLocale.get('surveyGroupId')) {
        return FLOW.surveyGroupControl.userCanDeleteData(surveyedLocale.get('surveyGroupId'));
    }
    return false; // prevents deletion incase no surveyId found
  }.property('content'),
});

FLOW.questionAnswerControl = Ember.ArrayController.create({
  content: null,

  doQuestionAnswerQuery: function (surveyInstanceId) {
    this.set('content', FLOW.store.findQuery(FLOW.QuestionAnswer, {
      'surveyInstanceId': surveyInstanceId
    }));
  }
});

FLOW.locationControl = Ember.ArrayController.create({
  selectedCountry: null,
  content:null,
  level1Content:null,
  level2Content:null,
  selectedLevel1: null,
  selectedLevel2: null,

  populateLevel1: function(){
    if (!Ember.none(this.get('selectedCountry')) && this.selectedCountry.get('iso').length > 0){
    this.set('level1Content',FLOW.store.findQuery(FLOW.SubCountry,{
      countryCode:this.selectedCountry.get('iso'),
      level:1,
      parentId:null
      }));
    }
  }.observes('this.selectedCountry'),

 populateLevel2: function(){
    if (!Ember.none(this.get('selectedLevel1')) && this.selectedLevel1.get('name').length > 0){
    this.set('level2Content',FLOW.store.findQuery(FLOW.SubCountry,{
      countryCode:this.selectedCountry.get('iso'),
      level:2,
      parentId:this.selectedLevel1.get('keyId')
      }));
    }
  }.observes('this.selectedLevel1')

});
<|endoftext|>


<commit_before>var Controller = function ($scope) {

    $scope.PersonalImage = "img/Logo.jpg";

    $scope.FullName = "Alaa Hamed AbdElhamed";

    $scope.JobTitle = "Full Stack Software developer";

    $scope.NickName = "@egydev";

    $scope.Education="Computer Science Graduate AinShams University with grade Very Good"
    $scope.Projects = [];

    $scope.Companies = [];

    $scope.ClientSideTechnologies = [];

    $scope.ServerSideTechnologies = [];

    $scope.SelectedProject = {};

    $scope.SelectedImageUrl="null";

$scope.HideSelectedImage=function()
{
    $scope.SelectedImageUrl="null";
}
   $scope.SetSelectedUrl=function(url)
   {
     $scope.SelectedImageUrl=url;
   }
    $scope.ProjectClicked = function (project) {
        $scope.SelectedProject = project;
    }

    $scope.Init = function () {

        $scope.Projects = [
          {
              Title: "Iqraa",
              Description: "This is a web application that provides quran reading ability",
              UsedPlaces:"Public use project",
              ScreenShots:["img/portfolio/geoExp/logo.png"],
              WebLink: "https://iqraaweb.github.io",
              Src: "img/logo.jpg",
              Technologies:"Technologies used are  HTML5 , CSS3 , JQuery , Angular Js,Bootstrap",
              MainImage:"img/logo.jpg"
          },
            {
                Title: "Geo-Explorer (Web)",
                Description: "This is a web application that provides different geo-spatial functions like searching ,redlining , measuring ,exporting map ,drawing cad , etc....",
                AwesomeIcon: "globe",
                UsedPlaces:"It was used in Hail and Madinah municipalities and also in  Riyadh Agency for Municipal and Rural Affairs and also KSA Ministry of Municipal and Rural Affairs.",
                ScreenShots:["img/portfolio/geoExp/logo.png","img/portfolio/geoExp/secondScreen.png"],
                WebLink: "http://83.101.140.98/hajlexp/",
                Technologies:"Technologies used are  HTML5 , CSS3 , JQuery , Arcgis Javascript , Ajax , Web API and SOE.",
                MainImage:"img/portfolio/geoExp/logo.png"
            },

            {
                Title: "Geo-Explorer (Mobile)",
                Description: "This is a mobile application that provides different geo-spatial functions like searching ,redlining , measuring and searching for online building license transaction .",
                AwesomeIcon: "globe",
                UsedPlaces:"It was used in Riyadh Agency for Municipal and Rural Affairs .",
                ScreenShots:["img/portfolio/mobileExp/logo.png","img/portfolio/mobileExp/first.png","img/portfolio/mobileExp/second.png"],
                WebLink: "https://play.google.com/store/apps/details?id=io.cordova.myapp382999",
                Technologies:"Technologies used are HTML5 , CSS3 , JQuery , Arcgis Javascript , Ajax , Web API and Phonegap .",
                MainImage:"img/portfolio/mobileExp/logo.png"

            },

            {
                Title: "License Permits System",
                Description: "This is a web based application used for license permit generation. It handles different types of transactions like survey report , new licnese ,renew license , modify license and other types",
                AwesomeIcon: "building-o",
                UsedPlaces:"It was used in Hail Municipality",
                ScreenShots:["img/portfolio/licSystem/logo.png","img/portfolio/licSystem/first.png","img/portfolio/licSystem/third.png"],
                WebLink: "http://83.101.140.98/hajl",
                Technologies:"Technologies used are HTML5 , CSS3 , JQuery , Arcgis Javascript ,Angular Js , Ajax , Web API and oracle.",
                MainImage:"img/portfolio/licSystem/logo.png"
            },
            {
                Title: "Complaint System (Web,Mobile)",
                Description: "This is a web application that provides governmental facilities managment system using gis based solution through making complaints using mobile application and storing it in a spatial way so that we can do later gis operations on it like spatial search and classification in the web application",
                AwesomeIcon: "bullhorn",
                UsedPlaces:"It was used in Mekkah municipality",
                ScreenShots:["img/portfolio/complaintsSys/logo.png","img/portfolio/complaintsSys/fifth.png","img/portfolio/complaintsSys/sixth.png","img/portfolio/complaintsSys/seventh.png","img/portfolio/complaintsSys/first.png","img/portfolio/complaintsSys/second.png","img/portfolio/complaintsSys/third.png"],
                WebLink: "http://www.tam-holymakkah.com:70/MakkahComplaintJavaScript/login.html",
                MobileLink: "https://play.google.com/store/apps/details?id=com.phonegap.newmekkaComplaints",
                Technologies:"Technologies used are  HTML5 , CSS3 , JQuery , Arcgis Javascript , Ajax  ,oracle, Web API and Phonegap .",
                MainImage:"img/portfolio/complaintsSys/logo.png"
            },
            {
                Title: "Madinah Building Condtions Managment",
                Description: "This is a web application that provides  ability to insert and edit building condition in a spatail way thrugh feature editing",
                Src: "img/portfolio/Madina/logo.jpg",
                UsedPlaces:"It was used in Madina municipality",
                ScreenShots:["img/portfolio/Madina/logo.jpg"],
                Technologies:"Technologies used are  HTML5 , CSS , JQuery , Arcgis Javascript , Ajax .",
                MainImage:"img/portfolio/Madina/logo.jpg"
            },
            {
                Title: "DotGeo",
                Description: "This is a web application that provides different geo-spatial functions like searching ,redlining , measuring ,exporting map ,editing,classification , etc.... ",
                Src: "img/portfolio/dotGeo/logo.jpg",
                UsedPlaces:"It was used in Egypt Ministry of planning",
                ScreenShots:["img/portfolio/dotGeo/logo.jpg"],
                Technologies:"Technologies used are silverlight and oracle",
                MainImage:"img/portfolio/dotGeo/logo.jpg",
                WebLink:"http://www.esrinea.com/Geo_Overview.html"
            },{
                Title: "Bajyal",
                Description: "This is a web application that provides different career detection and personality test exams like holland exam for detecting student preferences",
                Src: "img/company/bajyal.png",
                UsedPlaces:"It was used in King AbdElaziz University",
                ScreenShots:["img/company/bajyal.png"],
                Technologies:"Technologies used are Asp.net Web forms , MVP , Wcf ,MS Testing framework , Sql Server",
                MainImage:"img/company/bajyal.png",
                WebLink:"http://bajyal.com/"
            }

        ];

        $scope.Companies = [
             {
                 Src: "img/company/bajyal.png",
                 Position: "Full Stack Web Software developer",
                 Interval: "(Jan 2012 - Jan 2013)",
                 Link: "http://www.benaaalajyal.com/"

             },

            {
                Src: "img/company/esrinea.png",
                Position: "Full Stack Web Software developer",
                Interval: "(Jan 2013 - Jul 2014)",
                Link: "http://www.esrinea.com/"

            }, {
                Src: "img/company/alkan.jpg",
                Position: "FullStack Web Software developer",
                Interval: "(July 2014 - Sep 2014)",
                Link: "http://www.alkancit.com/frontend/pages/Home.aspx"

            },
        {
            Src: "img/company/crossOver.png",
            Position: ".Net Software developer",
            Interval: "(Nov 2015 - Present)",
            Link: "https://www.crossover.com/"

        },
                         {
                             Src: "img/company/daralriyadh.png",
                             Position: "FullStack Software developer",
                             Interval: "(Nov 2014 - Present)",
                             Link: "http://www.daralriyadh.com/"
                         }
        ];

        $scope.ClientSideTechnologies = ["Javascript", "Html", "Css3", "JQuery", "Angular Js","Html5" ,"Bootstrap","dojo","Arcgis Javascript API","Silverlight","Phonegap"];

        $scope.ServerSideTechnologies = ["PHP", "Asp.net", "Wcf", "MVC", "MVP","Web API", "CakePhp","Ado.Net","Entity framework","MS Testing"];
    }

};

var application = angular.module("app", []);

application.controller("Controller", Controller);
<commit_msg>Intiali commit<commit_after>var Controller = function ($scope) {

    $scope.PersonalImage = "img/Logo.jpg";

    $scope.FullName = "Alaa Hamed AbdElhamed";

    $scope.JobTitle = "Full Stack Software developer";

    $scope.NickName = "@egydev";

    $scope.Education="Computer Science Graduate AinShams University with grade Very Good"
    $scope.Projects = [];

    $scope.Companies = [];

    $scope.ClientSideTechnologies = [];

    $scope.ServerSideTechnologies = [];

    $scope.SelectedProject = {};

    $scope.SelectedImageUrl="null";

$scope.HideSelectedImage=function()
{
    $scope.SelectedImageUrl="null";
}
   $scope.SetSelectedUrl=function(url)
   {
     $scope.SelectedImageUrl=url;
   }
    $scope.ProjectClicked = function (project) {
        $scope.SelectedProject = project;
    }

    $scope.Init = function () {

        $scope.Projects = [
          {
              Title: "Iqraa",
              Description: "This is a web application that provides quran listening ability",
              UsedPlaces:"Public use project",
              ScreenShots:["img/portfolio/geoExp/logo.png"],
              WebLink: "https://iqraaweb.github.io",
              Src: "img/logo.jpg",
              Technologies:"Technologies used are  HTML5 , CSS3 , JQuery , Angular Js,Bootstrap",
              MainImage:"img/logo.jpg"
          },
            {
                Title: "Geo-Explorer (Web)",
                Description: "This is a web application that provides different geo-spatial functions like searching ,redlining , measuring ,exporting map ,drawing cad , etc....",
                AwesomeIcon: "globe",
                UsedPlaces:"It was used in Hail and Madinah municipalities and also in  Riyadh Agency for Municipal and Rural Affairs and also KSA Ministry of Municipal and Rural Affairs.",
                ScreenShots:["img/portfolio/geoExp/logo.png","img/portfolio/geoExp/secondScreen.png"],
                WebLink: "http://83.101.140.98/hajlexp/",
                Technologies:"Technologies used are  HTML5 , CSS3 , JQuery , Arcgis Javascript , Ajax , Web API and SOE.",
                MainImage:"img/portfolio/geoExp/logo.png"
            },

            {
                Title: "Geo-Explorer (Mobile)",
                Description: "This is a mobile application that provides different geo-spatial functions like searching ,redlining , measuring and searching for online building license transaction .",
                AwesomeIcon: "globe",
                UsedPlaces:"It was used in Riyadh Agency for Municipal and Rural Affairs .",
                ScreenShots:["img/portfolio/mobileExp/logo.png","img/portfolio/mobileExp/first.png","img/portfolio/mobileExp/second.png"],
                WebLink: "https://play.google.com/store/apps/details?id=io.cordova.myapp382999",
                Technologies:"Technologies used are HTML5 , CSS3 , JQuery , Arcgis Javascript , Ajax , Web API and Phonegap .",
                MainImage:"img/portfolio/mobileExp/logo.png"

            },

            {
                Title: "License Permits System",
                Description: "This is a web based application used for license permit generation. It handles different types of transactions like survey report , new licnese ,renew license , modify license and other types",
                AwesomeIcon: "building-o",
                UsedPlaces:"It was used in Hail Municipality",
                ScreenShots:["img/portfolio/licSystem/logo.png","img/portfolio/licSystem/first.png","img/portfolio/licSystem/third.png"],
                WebLink: "http://83.101.140.98/hajl",
                Technologies:"Technologies used are HTML5 , CSS3 , JQuery , Arcgis Javascript ,Angular Js , Ajax , Web API and oracle.",
                MainImage:"img/portfolio/licSystem/logo.png"
            },
            {
                Title: "Complaint System (Web,Mobile)",
                Description: "This is a web application that provides governmental facilities managment system using gis based solution through making complaints using mobile application and storing it in a spatial way so that we can do later gis operations on it like spatial search and classification in the web application",
                AwesomeIcon: "bullhorn",
                UsedPlaces:"It was used in Mekkah municipality",
                ScreenShots:["img/portfolio/complaintsSys/logo.png","img/portfolio/complaintsSys/fifth.png","img/portfolio/complaintsSys/sixth.png","img/portfolio/complaintsSys/seventh.png","img/portfolio/complaintsSys/first.png","img/portfolio/complaintsSys/second.png","img/portfolio/complaintsSys/third.png"],
                WebLink: "http://www.tam-holymakkah.com:70/MakkahComplaintJavaScript/login.html",
                MobileLink: "https://play.google.com/store/apps/details?id=com.phonegap.newmekkaComplaints",
                Technologies:"Technologies used are  HTML5 , CSS3 , JQuery , Arcgis Javascript , Ajax  ,oracle, Web API and Phonegap .",
                MainImage:"img/portfolio/complaintsSys/logo.png"
            },
            {
                Title: "Madinah Building Condtions Managment",
                Description: "This is a web application that provides  ability to insert and edit building condition in a spatail way thrugh feature editing",
                Src: "img/portfolio/Madina/logo.jpg",
                UsedPlaces:"It was used in Madina municipality",
                ScreenShots:["img/portfolio/Madina/logo.jpg"],
                Technologies:"Technologies used are  HTML5 , CSS , JQuery , Arcgis Javascript , Ajax .",
                MainImage:"img/portfolio/Madina/logo.jpg"
            },
            {
                Title: "DotGeo",
                Description: "This is a web application that provides different geo-spatial functions like searching ,redlining , measuring ,exporting map ,editing,classification , etc.... ",
                Src: "img/portfolio/dotGeo/logo.jpg",
                UsedPlaces:"It was used in Egypt Ministry of planning",
                ScreenShots:["img/portfolio/dotGeo/logo.jpg"],
                Technologies:"Technologies used are silverlight and oracle",
                MainImage:"img/portfolio/dotGeo/logo.jpg",
                WebLink:"http://www.esrinea.com/Geo_Overview.html"
            },{
                Title: "Bajyal",
                Description: "This is a web application that provides different career detection and personality test exams like holland exam for detecting student preferences",
                Src: "img/company/bajyal.png",
                UsedPlaces:"It was used in King AbdElaziz University",
                ScreenShots:["img/company/bajyal.png"],
                Technologies:"Technologies used are Asp.net Web forms , MVP , Wcf ,MS Testing framework , Sql Server",
                MainImage:"img/company/bajyal.png",
                WebLink:"http://bajyal.com/"
            }

        ];

        $scope.Companies = [
             {
                 Src: "img/company/bajyal.png",
                 Position: "Full Stack Web Software developer",
                 Interval: "(Jan 2012 - Jan 2013)",
                 Link: "http://www.benaaalajyal.com/"

             },

            {
                Src: "img/company/esrinea.png",
                Position: "Full Stack Web Software developer",
                Interval: "(Jan 2013 - Jul 2014)",
                Link: "http://www.esrinea.com/"

            }, {
                Src: "img/company/alkan.jpg",
                Position: "FullStack Web Software developer",
                Interval: "(July 2014 - Sep 2014)",
                Link: "http://www.alkancit.com/frontend/pages/Home.aspx"

            },
        {
            Src: "img/company/crossOver.png",
            Position: ".Net Software developer",
            Interval: "(Nov 2015 - Present)",
            Link: "https://www.crossover.com/"

        },
                         {
                             Src: "img/company/daralriyadh.png",
                             Position: "FullStack Software developer",
                             Interval: "(Nov 2014 - Present)",
                             Link: "http://www.daralriyadh.com/"
                         }
        ];

        $scope.ClientSideTechnologies = ["Javascript", "Html", "Css3", "JQuery", "Angular Js","Html5" ,"Bootstrap","dojo","Arcgis Javascript API","Silverlight","Phonegap"];

        $scope.ServerSideTechnologies = ["PHP", "Asp.net", "Wcf", "MVC", "MVP","Web API", "CakePhp","Ado.Net","Entity framework","MS Testing"];
    }

};

var application = angular.module("app", []);

application.controller("Controller", Controller);
<|endoftext|>


<commit_before>// Following https://github.com/Leaflet/Leaflet/blob/master/PLUGIN-GUIDE.md
(function (factory, window) {

	// define an AMD module that relies on 'leaflet'
	if (typeof define === 'function' && define.amd) {
		define(['leaflet'], factory);

	// define a Common JS module that relies on 'leaflet'
	} else if (typeof exports === 'object') {
		module.exports = factory(require('leaflet'));
	}

	// attach your plugin to the global 'L' variable
	if (typeof window !== 'undefined' && window.L) {
		window.L.Control.MiniMap = factory(L);
		window.L.control.minimap = function (layer, options) {
			return new window.L.Control.MiniMap(layer, options);
		};
	}
}(function (L) {

	var MiniMap = L.Control.extend({
		options: {
			position: 'bottomright',
			toggleDisplay: false,
			zoomLevelOffset: -5,
			zoomLevelFixed: false,
			centerFixed: false,
			zoomAnimation: false,
			autoToggleDisplay: false,
			width: 150,
			height: 150,
			collapsedWidth: 19,
			collapsedHeight: 19,
			aimingRectOptions: {color: '#ff7800', weight: 1, clickable: false},
			shadowRectOptions: {color: '#000000', weight: 1, clickable: false, opacity: 0, fillOpacity: 0},
			strings: {hideText: 'Hide MiniMap', showText: 'Show MiniMap'}
		},

		// layer is the map layer to be shown in the minimap
		initialize: function (layer, options) {
			L.Util.setOptions(this, options);
			// Make sure the aiming rects are non-clickable even if the user tries to set them clickable (most likely by forgetting to specify them false)
			this.options.aimingRectOptions.clickable = false;
			this.options.shadowRectOptions.clickable = false;
			this._layer = layer;
		},

		onAdd: function (map) {

			this._mainMap = map;

			// Creating the container and stopping events from spilling through to the main map.
			this._container = L.DomUtil.create('div', 'leaflet-control-minimap');
			this._container.style.width = this.options.width + 'px';
			this._container.style.height = this.options.height + 'px';
			L.DomEvent.disableClickPropagation(this._container);
			L.DomEvent.on(this._container, 'mousewheel', L.DomEvent.stopPropagation);

			this._miniMap = new L.Map(this._container,
			{
				attributionControl: false,
				dragging: !this.options.centerFixed,
				zoomControl: false,
				zoomAnimation: this.options.zoomAnimation,
				autoToggleDisplay: this.options.autoToggleDisplay,
				touchZoom: this.options.centerFixed ? 'center' : !this._isZoomLevelFixed(),
				scrollWheelZoom: this.options.centerFixed ? 'center' : !this._isZoomLevelFixed(),
				doubleClickZoom: this.options.centerFixed ? 'center' : !this._isZoomLevelFixed(),
				boxZoom: !this._isZoomLevelFixed(),
				crs: map.options.crs
			});

			this._miniMap.addLayer(this._layer);

			// These bools are used to prevent infinite loops of the two maps notifying each other that they've moved.
			this._mainMapMoving = false;
			this._miniMapMoving = false;

			// Keep a record of this to prevent auto toggling when the user explicitly doesn't want it.
			this._userToggledDisplay = false;
			this._minimized = false;

			if (this.options.toggleDisplay) {
				this._addToggleButton();
			}

			this._miniMap.whenReady(L.Util.bind(function () {
				this._aimingRect = L.rectangle(this._mainMap.getBounds(), this.options.aimingRectOptions).addTo(this._miniMap);
				this._shadowRect = L.rectangle(this._mainMap.getBounds(), this.options.shadowRectOptions).addTo(this._miniMap);
				this._mainMap.on('moveend', this._onMainMapMoved, this);
				this._mainMap.on('move', this._onMainMapMoving, this);
				this._miniMap.on('movestart', this._onMiniMapMoveStarted, this);
				this._miniMap.on('move', this._onMiniMapMoving, this);
				this._miniMap.on('moveend', this._onMiniMapMoved, this);
			}, this));

			return this._container;
		},

		addTo: function (map) {
			L.Control.prototype.addTo.call(this, map);

			var center = this.options.centerFixed || this._mainMap.getCenter();
			this._miniMap.setView(center, this._decideZoom(true));
			this._setDisplay(this._decideMinimized());
			return this;
		},

		onRemove: function (map) {
			this._mainMap.off('moveend', this._onMainMapMoved, this);
			this._mainMap.off('move', this._onMainMapMoving, this);
			this._miniMap.off('moveend', this._onMiniMapMoved, this);

			this._miniMap.removeLayer(this._layer);
		},

		changeLayer: function (layer) {
			this._miniMap.removeLayer(this._layer);
			this._layer = layer;
			this._miniMap.addLayer(this._layer);
		},

		_addToggleButton: function () {
			this._toggleDisplayButton = this.options.toggleDisplay ? this._createButton(
				'', this.options.strings.hideText, ('leaflet-control-minimap-toggle-display leaflet-control-minimap-toggle-display-' +
				this.options.position), this._container, this._toggleDisplayButtonClicked, this) : undefined;

			this._toggleDisplayButton.style.width = this.options.collapsedWidth + 'px';
			this._toggleDisplayButton.style.height = this.options.collapsedHeight + 'px';
		},

		_createButton: function (html, title, className, container, fn, context) {
			var link = L.DomUtil.create('a', className, container);
			link.innerHTML = html;
			link.href = '#';
			link.title = title;

			var stop = L.DomEvent.stopPropagation;

			L.DomEvent
				.on(link, 'click', stop)
				.on(link, 'mousedown', stop)
				.on(link, 'dblclick', stop)
				.on(link, 'click', L.DomEvent.preventDefault)
				.on(link, 'click', fn, context);

			return link;
		},

		_toggleDisplayButtonClicked: function () {
			this._userToggledDisplay = true;
			if (!this._minimized) {
				this._minimize();
				this._toggleDisplayButton.title = this.options.strings.showText;
			} else {
				this._restore();
				this._toggleDisplayButton.title = this.options.strings.hideText;
			}
		},

		_setDisplay: function (minimize) {
			if (minimize !== this._minimized) {
				if (!this._minimized) {
					this._minimize();
				} else {
					this._restore();
				}
			}
		},

		_minimize: function () {
			// hide the minimap
			if (this.options.toggleDisplay) {
				this._container.style.width = this.options.collapsedWidth + 'px';
				this._container.style.height = this.options.collapsedHeight + 'px';
				this._toggleDisplayButton.className += (' minimized-' + this.options.position);
			} else {
				this._container.style.display = 'none';
			}
			this._minimized = true;
		},

		_restore: function () {
			if (this.options.toggleDisplay) {
				this._container.style.width = this.options.width + 'px';
				this._container.style.height = this.options.height + 'px';
				this._toggleDisplayButton.className = this._toggleDisplayButton.className
					.replace('minimized-'	+ this.options.position, '');
			} else {
				this._container.style.display = 'block';
			}
			this._minimized = false;
		},

		_onMainMapMoved: function (e) {
			if (!this._miniMapMoving) {
				var center = this.options.centerFixed || this._mainMap.getCenter();

				this._mainMapMoving = true;
				this._miniMap.setView(center, this._decideZoom(true));
				this._setDisplay(this._decideMinimized());
			} else {
				this._miniMapMoving = false;
			}
			this._aimingRect.setBounds(this._mainMap.getBounds());
		},

		_onMainMapMoving: function (e) {
			this._aimingRect.setBounds(this._mainMap.getBounds());
		},

		_onMiniMapMoveStarted: function (e) {
			if (!this.options.centerFixed) {
				var lastAimingRect = this._aimingRect.getBounds();
				var sw = this._miniMap.latLngToContainerPoint(lastAimingRect.getSouthWest());
				var ne = this._miniMap.latLngToContainerPoint(lastAimingRect.getNorthEast());
				this._lastAimingRectPosition = {sw: sw, ne: ne};
			}
		},

		_onMiniMapMoving: function (e) {
			if (!this.options.centerFixed) {
				if (!this._mainMapMoving && this._lastAimingRectPosition) {
					this._shadowRect.setBounds(new L.LatLngBounds(this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.sw), this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.ne)));
					this._shadowRect.setStyle({opacity: 1, fillOpacity: 0.3});
				}
			}
		},

		_onMiniMapMoved: function (e) {
			if (!this._mainMapMoving) {
				this._miniMapMoving = true;
				this._mainMap.setView(this._mainMap.getCenter(), this._decideZoom(false));
				this._shadowRect.setStyle({opacity: 0, fillOpacity: 0});
			} else {
				this._mainMapMoving = false;
			}
		},

		_isZoomLevelFixed: function () {
			var zoomLevelFixed = this.options.zoomLevelFixed;
			return this._isDefined(zoomLevelFixed) && this._isInteger(zoomLevelFixed);
		},

		_decideZoom: function (fromMaintoMini) {
			if (!this._isZoomLevelFixed()) {
				if (fromMaintoMini) {
					return this._mainMap.getZoom() + this.options.zoomLevelOffset;
				} else {
					var currentDiff = this._miniMap.getZoom() - this._mainMap.getZoom();
					var proposedZoom = this._miniMap.getZoom() - this.options.zoomLevelOffset;
					var toRet;

					if (currentDiff > this.options.zoomLevelOffset && this._mainMap.getZoom() < this._miniMap.getMinZoom() - this.options.zoomLevelOffset) {
						// This means the miniMap is zoomed out to the minimum zoom level and can't zoom any more.
						if (this._miniMap.getZoom() > this._lastMiniMapZoom) {
							// This means the user is trying to zoom in by using the minimap, zoom the main map.
							toRet = this._mainMap.getZoom() + 1;
							// Also we cheat and zoom the minimap out again to keep it visually consistent.
							this._miniMap.setZoom(this._miniMap.getZoom() - 1);
						} else {
							// Either the user is trying to zoom out past the mini map's min zoom or has just panned using it, we can't tell the difference.
							// Therefore, we ignore it!
							toRet = this._mainMap.getZoom();
						}
					} else {
						// This is what happens in the majority of cases, and always if you configure the min levels + offset in a sane fashion.
						toRet = proposedZoom;
					}
					this._lastMiniMapZoom = this._miniMap.getZoom();
					return toRet;
				}
			} else {
				if (fromMaintoMini) {
					return this.options.zoomLevelFixed;
				} else {
					return this._mainMap.getZoom();
				}
			}
		},

		_decideMinimized: function () {
			if (this._userToggledDisplay) {
				return this._minimized;
			}

			if (this.options.autoToggleDisplay) {
				if (this._mainMap.getBounds().contains(this._miniMap.getBounds())) {
					return true;
				}
				return false;
			}

			return this._minimized;
		},

		_isInteger: function (value) {
			return typeof value === 'number';
		},

		_isDefined: function (value) {
			return typeof value !== 'undefined';
		}
	});

	L.Map.mergeOptions({
		miniMapControl: false
	});

	L.Map.addInitHook(function () {
		if (this.options.miniMapControl) {
			this.miniMapControl = (new MiniMap()).addTo(this);
		}
	});

	return MiniMap;

}, window));
<commit_msg>introduce init parameter to set map options<commit_after>// Following https://github.com/Leaflet/Leaflet/blob/master/PLUGIN-GUIDE.md
(function (factory, window) {

	// define an AMD module that relies on 'leaflet'
	if (typeof define === 'function' && define.amd) {
		define(['leaflet'], factory);

	// define a Common JS module that relies on 'leaflet'
	} else if (typeof exports === 'object') {
		module.exports = factory(require('leaflet'));
	}

	// attach your plugin to the global 'L' variable
	if (typeof window !== 'undefined' && window.L) {
		window.L.Control.MiniMap = factory(L);
		window.L.control.minimap = function (layer, options) {
			return new window.L.Control.MiniMap(layer, options);
		};
	}
}(function (L) {

	var MiniMap = L.Control.extend({
		options: {
			position: 'bottomright',
			toggleDisplay: false,
			zoomLevelOffset: -5,
			zoomLevelFixed: false,
			centerFixed: false,
			zoomAnimation: false,
			autoToggleDisplay: false,
			width: 150,
			height: 150,
			collapsedWidth: 19,
			collapsedHeight: 19,
			aimingRectOptions: {color: '#ff7800', weight: 1, clickable: false},
			shadowRectOptions: {color: '#000000', weight: 1, clickable: false, opacity: 0, fillOpacity: 0},
			strings: {hideText: 'Hide MiniMap', showText: 'Show MiniMap'}
		},

		// layer is the map layer to be shown in the minimap
		initialize: function (layer, options, mapoptions) {
			L.Util.setOptions(this, options);
			this.mapoptions = mapoptions || {};  // Allows definition / override of Leaflet map options.
			// Make sure the aiming rects are non-clickable even if the user tries to set them clickable (most likely by forgetting to specify them false)
			this.options.aimingRectOptions.clickable = false;
			this.options.shadowRectOptions.clickable = false;
			this._layer = layer;
		},

		onAdd: function (map) {

			this._mainMap = map;

			// Creating the container and stopping events from spilling through to the main map.
			this._container = L.DomUtil.create('div', 'leaflet-control-minimap');
			this._container.style.width = this.options.width + 'px';
			this._container.style.height = this.options.height + 'px';
			L.DomEvent.disableClickPropagation(this._container);
			L.DomEvent.on(this._container, 'mousewheel', L.DomEvent.stopPropagation);

			var mapoptions = {
				attributionControl: false,
				dragging: !this.options.centerFixed,
				zoomControl: false,
				zoomAnimation: this.options.zoomAnimation,
				autoToggleDisplay: this.options.autoToggleDisplay,
				touchZoom: this.options.centerFixed ? 'center' : !this._isZoomLevelFixed(),
				scrollWheelZoom: this.options.centerFixed ? 'center' : !this._isZoomLevelFixed(),
				doubleClickZoom: this.options.centerFixed ? 'center' : !this._isZoomLevelFixed(),
				boxZoom: !this._isZoomLevelFixed(),
				crs: map.options.crs
			};
			mapoptions = L.Util.extend(mapoptions, this.mapoptions);

			this._miniMap = new L.Map(this._container, mapoptions);

			this._miniMap.addLayer(this._layer);

			// These bools are used to prevent infinite loops of the two maps notifying each other that they've moved.
			this._mainMapMoving = false;
			this._miniMapMoving = false;

			// Keep a record of this to prevent auto toggling when the user explicitly doesn't want it.
			this._userToggledDisplay = false;
			this._minimized = false;

			if (this.options.toggleDisplay) {
				this._addToggleButton();
			}

			this._miniMap.whenReady(L.Util.bind(function () {
				this._aimingRect = L.rectangle(this._mainMap.getBounds(), this.options.aimingRectOptions).addTo(this._miniMap);
				this._shadowRect = L.rectangle(this._mainMap.getBounds(), this.options.shadowRectOptions).addTo(this._miniMap);
				this._mainMap.on('moveend', this._onMainMapMoved, this);
				this._mainMap.on('move', this._onMainMapMoving, this);
				this._miniMap.on('movestart', this._onMiniMapMoveStarted, this);
				this._miniMap.on('move', this._onMiniMapMoving, this);
				this._miniMap.on('moveend', this._onMiniMapMoved, this);
			}, this));

			return this._container;
		},

		addTo: function (map) {
			L.Control.prototype.addTo.call(this, map);

			var center = this.options.centerFixed || this._mainMap.getCenter();
			this._miniMap.setView(center, this._decideZoom(true));
			this._setDisplay(this._decideMinimized());
			return this;
		},

		onRemove: function (map) {
			this._mainMap.off('moveend', this._onMainMapMoved, this);
			this._mainMap.off('move', this._onMainMapMoving, this);
			this._miniMap.off('moveend', this._onMiniMapMoved, this);

			this._miniMap.removeLayer(this._layer);
		},

		changeLayer: function (layer) {
			this._miniMap.removeLayer(this._layer);
			this._layer = layer;
			this._miniMap.addLayer(this._layer);
		},

		_addToggleButton: function () {
			this._toggleDisplayButton = this.options.toggleDisplay ? this._createButton(
				'', this.options.strings.hideText, ('leaflet-control-minimap-toggle-display leaflet-control-minimap-toggle-display-' +
				this.options.position), this._container, this._toggleDisplayButtonClicked, this) : undefined;

			this._toggleDisplayButton.style.width = this.options.collapsedWidth + 'px';
			this._toggleDisplayButton.style.height = this.options.collapsedHeight + 'px';
		},

		_createButton: function (html, title, className, container, fn, context) {
			var link = L.DomUtil.create('a', className, container);
			link.innerHTML = html;
			link.href = '#';
			link.title = title;

			var stop = L.DomEvent.stopPropagation;

			L.DomEvent
				.on(link, 'click', stop)
				.on(link, 'mousedown', stop)
				.on(link, 'dblclick', stop)
				.on(link, 'click', L.DomEvent.preventDefault)
				.on(link, 'click', fn, context);

			return link;
		},

		_toggleDisplayButtonClicked: function () {
			this._userToggledDisplay = true;
			if (!this._minimized) {
				this._minimize();
				this._toggleDisplayButton.title = this.options.strings.showText;
			} else {
				this._restore();
				this._toggleDisplayButton.title = this.options.strings.hideText;
			}
		},

		_setDisplay: function (minimize) {
			if (minimize !== this._minimized) {
				if (!this._minimized) {
					this._minimize();
				} else {
					this._restore();
				}
			}
		},

		_minimize: function () {
			// hide the minimap
			if (this.options.toggleDisplay) {
				this._container.style.width = this.options.collapsedWidth + 'px';
				this._container.style.height = this.options.collapsedHeight + 'px';
				this._toggleDisplayButton.className += (' minimized-' + this.options.position);
			} else {
				this._container.style.display = 'none';
			}
			this._minimized = true;
		},

		_restore: function () {
			if (this.options.toggleDisplay) {
				this._container.style.width = this.options.width + 'px';
				this._container.style.height = this.options.height + 'px';
				this._toggleDisplayButton.className = this._toggleDisplayButton.className
					.replace('minimized-'	+ this.options.position, '');
			} else {
				this._container.style.display = 'block';
			}
			this._minimized = false;
		},

		_onMainMapMoved: function (e) {
			if (!this._miniMapMoving) {
				var center = this.options.centerFixed || this._mainMap.getCenter();

				this._mainMapMoving = true;
				this._miniMap.setView(center, this._decideZoom(true));
				this._setDisplay(this._decideMinimized());
			} else {
				this._miniMapMoving = false;
			}
			this._aimingRect.setBounds(this._mainMap.getBounds());
		},

		_onMainMapMoving: function (e) {
			this._aimingRect.setBounds(this._mainMap.getBounds());
		},

		_onMiniMapMoveStarted: function (e) {
			if (!this.options.centerFixed) {
				var lastAimingRect = this._aimingRect.getBounds();
				var sw = this._miniMap.latLngToContainerPoint(lastAimingRect.getSouthWest());
				var ne = this._miniMap.latLngToContainerPoint(lastAimingRect.getNorthEast());
				this._lastAimingRectPosition = {sw: sw, ne: ne};
			}
		},

		_onMiniMapMoving: function (e) {
			if (!this.options.centerFixed) {
				if (!this._mainMapMoving && this._lastAimingRectPosition) {
					this._shadowRect.setBounds(new L.LatLngBounds(this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.sw), this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.ne)));
					this._shadowRect.setStyle({opacity: 1, fillOpacity: 0.3});
				}
			}
		},

		_onMiniMapMoved: function (e) {
			if (!this._mainMapMoving) {
				this._miniMapMoving = true;
				this._mainMap.setView(this._mainMap.getCenter(), this._decideZoom(false));
				this._shadowRect.setStyle({opacity: 0, fillOpacity: 0});
			} else {
				this._mainMapMoving = false;
			}
		},

		_isZoomLevelFixed: function () {
			var zoomLevelFixed = this.options.zoomLevelFixed;
			return this._isDefined(zoomLevelFixed) && this._isInteger(zoomLevelFixed);
		},

		_decideZoom: function (fromMaintoMini) {
			if (!this._isZoomLevelFixed()) {
				if (fromMaintoMini) {
					return this._mainMap.getZoom() + this.options.zoomLevelOffset;
				} else {
					var currentDiff = this._miniMap.getZoom() - this._mainMap.getZoom();
					var proposedZoom = this._miniMap.getZoom() - this.options.zoomLevelOffset;
					var toRet;

					if (currentDiff > this.options.zoomLevelOffset && this._mainMap.getZoom() < this._miniMap.getMinZoom() - this.options.zoomLevelOffset) {
						// This means the miniMap is zoomed out to the minimum zoom level and can't zoom any more.
						if (this._miniMap.getZoom() > this._lastMiniMapZoom) {
							// This means the user is trying to zoom in by using the minimap, zoom the main map.
							toRet = this._mainMap.getZoom() + 1;
							// Also we cheat and zoom the minimap out again to keep it visually consistent.
							this._miniMap.setZoom(this._miniMap.getZoom() - 1);
						} else {
							// Either the user is trying to zoom out past the mini map's min zoom or has just panned using it, we can't tell the difference.
							// Therefore, we ignore it!
							toRet = this._mainMap.getZoom();
						}
					} else {
						// This is what happens in the majority of cases, and always if you configure the min levels + offset in a sane fashion.
						toRet = proposedZoom;
					}
					this._lastMiniMapZoom = this._miniMap.getZoom();
					return toRet;
				}
			} else {
				if (fromMaintoMini) {
					return this.options.zoomLevelFixed;
				} else {
					return this._mainMap.getZoom();
				}
			}
		},

		_decideMinimized: function () {
			if (this._userToggledDisplay) {
				return this._minimized;
			}

			if (this.options.autoToggleDisplay) {
				if (this._mainMap.getBounds().contains(this._miniMap.getBounds())) {
					return true;
				}
				return false;
			}

			return this._minimized;
		},

		_isInteger: function (value) {
			return typeof value === 'number';
		},

		_isDefined: function (value) {
			return typeof value !== 'undefined';
		}
	});

	L.Map.mergeOptions({
		miniMapControl: false
	});

	L.Map.addInitHook(function () {
		if (this.options.miniMapControl) {
			this.miniMapControl = (new MiniMap()).addTo(this);
		}
	});

	return MiniMap;

}, window));
<|endoftext|>


<commit_before>/**
 * Emerging Citizens
 * Developed by Engagement Lab, 2016
 * ==============
 * Game entry view controller.
 *
 * Help: http://keystonejs.com/docs/getting-started/#routesviews-firstview
 *
 * @class game
 * @static
 * @author Johnny Richardson
 *
 * ==========
 */

var keystone = require('keystone');

exports = module.exports = function(req, res) {

  var view = new keystone.View(req, res);
  var locals = res.locals;

  locals.viewType = 'player';
  locals.section = 'login';

  // Save host to allow path specification for socket.io
  locals.socketHost = (process.env.NODE_ENV === 'staging') ? 'qa.emergingcitizens.org' ? req.headers.host;

  // Enable debugging on staging/dev only
  if(req.params.debug === 'debug' && process.env.NODE_ENV !== 'production')
	  locals.debug = true;

  view.on('init', function(next) {
    next();
  });

  // Render the view
  view.render('game/player');

};
<commit_msg>Syntax fix<commit_after>/**
 * Emerging Citizens
 * Developed by Engagement Lab, 2016
 * ==============
 * Game entry view controller.
 *
 * Help: http://keystonejs.com/docs/getting-started/#routesviews-firstview
 *
 * @class game
 * @static
 * @author Johnny Richardson
 *
 * ==========
 */

var keystone = require('keystone');

exports = module.exports = function(req, res) {

  var view = new keystone.View(req, res);
  var locals = res.locals;

  locals.viewType = 'player';
  locals.section = 'login';

  // Save host to allow path specification for socket.io
  locals.socketHost = (process.env.NODE_ENV === 'staging') ? 'qa.emergingcitizens.org' : req.headers.host;

  // Enable debugging on staging/dev only
  if(req.params.debug === 'debug' && process.env.NODE_ENV !== 'production')
	  locals.debug = true;

  view.on('init', function(next) {
    next();
  });

  // Render the view
  view.render('game/player');

};
<|endoftext|>