<commit_before>(function(){

var cont = angular.module('poll-princeton.controllers');

cont.controller('accountController', function ($scope, $filter, $http, $location) {

  document.title = 'My Account | PollPrinceton'

  $('body').css('padding-top', '50px');

  $scope.fetchedDevs = false;
  $scope.fetchedPolls = false;
  $scope.fetching = true;
  $scope.noMorePolls = false;
  $scope.polls = [];
  $scope.currentPolls = 0;
  $scope.sort = "popular";

  var onlyUser = '/true';
  var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

  $scope.fetchDevs = function () {
    $http.get('http://' + window.location.hostname + '/ppapi/devs').success(function (data, status, headers, config) {
      $scope.devs = data;
      $scope.fetchedDevs = true;
    });
  };

  $scope.deletePolls = function () {
    $http.get('http://' + window.location.hostname + '/ppapi/polls/delete/all').success(function (data, status, headers, config) {
    });
  };

  $scope.fetchAllPolls = function () {
    $http.get('http://' + window.location.hostname + '/ppapi/polls/get/all').success(function (data, status, headers, config) {
      $scope.polls = data;
      for (var i in $scope.polls) {
        var thisPoll = $scope.polls[i];
        var date = new Date(thisPoll.time);
        thisPoll.humanTime = months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
      }
      $scope.fetchedPolls = true;
    });
  };

  //Get call returns an array of objects containing three things: pollData (a complete poll with all data),
  //userVote (true for up, false for down) and userResponse (however Ryan implemented it. -1 for no response I think)
  $scope.fetch10Best = function () {
    do {
      var user = localStorage.getItem('netid');
      var ticket = localStorage.getItem('ticket');
    } while (user == null || ticket == null);
    $http.get('http://' + window.location.hostname + '/ppapi/polls/get/popular/'
        + user + '/' + ticket + '/' + $scope.currentPolls + onlyUser).success(function (data, status, headers, config) {
      $scope.fetching = false;
      if (data.err) {
        $scope.noMorePolls = true;
        return;
      }
      $scope.polls = data;
      $scope.currentPolls = 10;
      for (var i in $scope.polls) {
        var thisPoll = $scope.polls[i].pollData;
        var date = new Date(thisPoll.time);
        thisPoll.humanTime = months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
        thisPoll.numResponses = thisPoll.responses.reduce(function(a, b) {
          return a + b;
        });
      }
      $scope.fetchedPolls = true;
    });
  };

  $scope.fetchNext10Best = function () {
    if ($scope.noMorePolls) return;
    do {
      var user = localStorage.getItem('netid');
      var ticket = localStorage.getItem('ticket');
    } while (user == null || ticket == null);
    $http.get('http://' + window.location.hostname + '/ppapi/polls/get/popular/'
        + user + '/' + ticket + '/' + $scope.currentPolls + onlyUser)
      .success(function (data, status, headers, config) {
        $scope.fetching = false;
        if (data.err) {
          $scope.noMorePolls = true;
          return;
        }
        var polls = data;
        $scope.currentPolls += 10;
        for (var i in polls) {
          var thisPoll = polls[i].pollData;
          var date = new Date(thisPoll.time);
          thisPoll.humanTime = months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
          thisPoll.numResponses = thisPoll.responses.reduce(function(a, b) {
            return a + b;
          });
          $scope.polls.push(polls[i]);
        }
      });
  };

  $scope.fetch10New = function () {
    do {
      var user = localStorage.getItem('netid');
      var ticket = localStorage.getItem('ticket');
    } while (user == null || ticket == null);
    $http.get('http://' + window.location.hostname + '/ppapi/polls/get/newest/'
        + user + '/' + ticket + '/' + $scope.currentPolls + onlyUser).success(function (data, status, headers, config) {
      $scope.fetching = false;
      if (data.err) {
        $scope.noMorePolls = true;
        return;
      }
      $scope.polls = data;
      $scope.currentPolls = 10;
      for (var i in $scope.polls) {
        var thisPoll = $scope.polls[i].pollData;
        var date = new Date(thisPoll.time);
        thisPoll.humanTime = months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
        thisPoll.numResponses = thisPoll.responses.reduce(function(a, b) {
          return a + b;
        });
      }
      $scope.fetchedPolls = true;
    });
  };

  $scope.fetchNext10New = function () {
    if ($scope.noMorePolls) return;
    do {
      var user = localStorage.getItem('netid');
      var ticket = localStorage.getItem('ticket');
    } while (user == null || ticket == null);
    var user = localStorage.getItem('netid');
    $http.get('http://' + window.location.hostname + '/ppapi/polls/get/newest/'
        + user + '/' + ticket + '/' + $scope.currentPolls + onlyUser)
      .success(function (data, status, headers, config) {
        $scope.fetching = false;
        if (data.err) {
          $scope.noMorePolls = true;
          return;
        }
        var polls = data;
        $scope.currentPolls += 10;
        for (var i in polls) {
          var thisPoll = polls[i].pollData;
          var date = new Date(thisPoll.time);
          thisPoll.humanTime = months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
          thisPoll.numResponses = thisPoll.responses.reduce(function(a, b) {
            return a + b;
          });
          $scope.polls.push(polls[i]);
        }
      });
  };

  $scope.fetchMore = function () {
    if ($scope.sort === 'popular') $scope.fetchNext10Best();
    else $scope.fetchNext10New();
  };

  $scope.upVote = function (pid, idx) {
    var pkg = {};
    var user = localStorage.getItem('netid');
    pkg.upOrDown = true;
    pkg.pollID = pid;
    pkg.netid = user;
    $http.post('/ppapi/polls/vote', pkg)
      .success(function (data, status, headers, config) {
        $scope.polls[idx].pollData.score = data.score;
        $scope.polls[idx].userVote = data.userVote;
      })
      .error(function (data, status, headers, config) {
        alert('Something went wrong. Please try to submit again in a few moments.')
        $('.vote-submit').attr('disabled', false);
      });
  };

  $scope.downVote = function (pid, idx) {
    var pkg = {};
    var user = localStorage.getItem('netid');
    pkg.upOrDown = false;
    pkg.pollID = pid;
    pkg.netid = user;
    $http.post('/ppapi/polls/vote', pkg)
      .success(function (data, status, headers, config) {
        $scope.polls[idx].pollData.score = data.score;
        $scope.polls[idx].userVote = data.userVote;
      })
      .error(function (data, status, headers, config) {
        alert('Something went wrong. Please try to submit again in a few moments.')
        $('.vote-submit').attr('disabled', false);
      });
  };

  $scope.deletePolls = function () {
    $http.get('http://' + window.location.hostname + '/ppapi/polls/delete/all').success(function (data, status, headers, config) {
    });
  };

  $scope.sortPopular = function () {
    if ($scope.sort !== 'popular') {
      $('.polls').fadeOut(function () {
        $scope.noMorePolls = false;
        $scope.sort = 'popular';
        $scope.polls = [];
        $scope.currentPolls = 0;
        $scope.fetch10Best();
        $('.polls').fadeIn();
      });
    }
  };

  $scope.sortNewest = function () {
    if ($scope.sort !== 'newest') {
      $('.polls').fadeOut(function () {
        $scope.noMorePolls = false;
        $scope.sort = 'newest';
        $scope.polls = [];
        $scope.currentPolls = 0;
        $scope.fetch10New();
        $('.polls').fadeIn();
      });
    }
  };

  $scope.goToPoll = function (pid) {
    window.location = 'http://' + window.location.hostname + window.location.pathname + '#/poll?p=' + pid;
  };

  $scope.refresh = function () {
    location.reload();
  };

  $scope.fetch10Best();

  $(window).scroll(function () {
    if ($scope.fetchedPolls && $(window).height() + $(window).scrollTop() == $(document).height()) {
      $scope.fetching = true;
      $scope.fetchMore();
    }
  });
});

})();
<commit_msg>Updated up/downvoting on account page w/ auth<commit_after>(function(){

var cont = angular.module('poll-princeton.controllers');

cont.controller('accountController', function ($scope, $filter, $http, $location) {

  document.title = 'My Account | PollPrinceton'

  $('body').css('padding-top', '50px');

  $scope.fetchedDevs = false;
  $scope.fetchedPolls = false;
  $scope.fetching = true;
  $scope.noMorePolls = false;
  $scope.polls = [];
  $scope.currentPolls = 0;
  $scope.sort = "popular";

  var onlyUser = '/true';
  var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

  $scope.fetchDevs = function () {
    $http.get('http://' + window.location.hostname + '/ppapi/devs').success(function (data, status, headers, config) {
      $scope.devs = data;
      $scope.fetchedDevs = true;
    });
  };

  $scope.deletePolls = function () {
    $http.get('http://' + window.location.hostname + '/ppapi/polls/delete/all').success(function (data, status, headers, config) {
    });
  };

  $scope.fetchAllPolls = function () {
    $http.get('http://' + window.location.hostname + '/ppapi/polls/get/all').success(function (data, status, headers, config) {
      $scope.polls = data;
      for (var i in $scope.polls) {
        var thisPoll = $scope.polls[i];
        var date = new Date(thisPoll.time);
        thisPoll.humanTime = months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
      }
      $scope.fetchedPolls = true;
    });
  };

  //Get call returns an array of objects containing three things: pollData (a complete poll with all data),
  //userVote (true for up, false for down) and userResponse (however Ryan implemented it. -1 for no response I think)
  $scope.fetch10Best = function () {
    do {
      var user = localStorage.getItem('netid');
      var ticket = localStorage.getItem('ticket');
    } while (user == null || ticket == null);
    $http.get('http://' + window.location.hostname + '/ppapi/polls/get/popular/'
        + user + '/' + ticket + '/' + $scope.currentPolls + onlyUser).success(function (data, status, headers, config) {
      $scope.fetching = false;
      if (data.err) {
        $scope.noMorePolls = true;
        return;
      }
      $scope.polls = data;
      $scope.currentPolls = 10;
      for (var i in $scope.polls) {
        var thisPoll = $scope.polls[i].pollData;
        var date = new Date(thisPoll.time);
        thisPoll.humanTime = months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
        thisPoll.numResponses = thisPoll.responses.reduce(function(a, b) {
          return a + b;
        });
      }
      $scope.fetchedPolls = true;
    });
  };

  $scope.fetchNext10Best = function () {
    if ($scope.noMorePolls) return;
    do {
      var user = localStorage.getItem('netid');
      var ticket = localStorage.getItem('ticket');
    } while (user == null || ticket == null);
    $http.get('http://' + window.location.hostname + '/ppapi/polls/get/popular/'
        + user + '/' + ticket + '/' + $scope.currentPolls + onlyUser)
      .success(function (data, status, headers, config) {
        $scope.fetching = false;
        if (data.err) {
          $scope.noMorePolls = true;
          return;
        }
        var polls = data;
        $scope.currentPolls += 10;
        for (var i in polls) {
          var thisPoll = polls[i].pollData;
          var date = new Date(thisPoll.time);
          thisPoll.humanTime = months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
          thisPoll.numResponses = thisPoll.responses.reduce(function(a, b) {
            return a + b;
          });
          $scope.polls.push(polls[i]);
        }
      });
  };

  $scope.fetch10New = function () {
    do {
      var user = localStorage.getItem('netid');
      var ticket = localStorage.getItem('ticket');
    } while (user == null || ticket == null);
    $http.get('http://' + window.location.hostname + '/ppapi/polls/get/newest/'
        + user + '/' + ticket + '/' + $scope.currentPolls + onlyUser).success(function (data, status, headers, config) {
      $scope.fetching = false;
      if (data.err) {
        $scope.noMorePolls = true;
        return;
      }
      $scope.polls = data;
      $scope.currentPolls = 10;
      for (var i in $scope.polls) {
        var thisPoll = $scope.polls[i].pollData;
        var date = new Date(thisPoll.time);
        thisPoll.humanTime = months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
        thisPoll.numResponses = thisPoll.responses.reduce(function(a, b) {
          return a + b;
        });
      }
      $scope.fetchedPolls = true;
    });
  };

  $scope.fetchNext10New = function () {
    if ($scope.noMorePolls) return;
    do {
      var user = localStorage.getItem('netid');
      var ticket = localStorage.getItem('ticket');
    } while (user == null || ticket == null);
    var user = localStorage.getItem('netid');
    $http.get('http://' + window.location.hostname + '/ppapi/polls/get/newest/'
        + user + '/' + ticket + '/' + $scope.currentPolls + onlyUser)
      .success(function (data, status, headers, config) {
        $scope.fetching = false;
        if (data.err) {
          $scope.noMorePolls = true;
          return;
        }
        var polls = data;
        $scope.currentPolls += 10;
        for (var i in polls) {
          var thisPoll = polls[i].pollData;
          var date = new Date(thisPoll.time);
          thisPoll.humanTime = months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
          thisPoll.numResponses = thisPoll.responses.reduce(function(a, b) {
            return a + b;
          });
          $scope.polls.push(polls[i]);
        }
      });
  };

  $scope.fetchMore = function () {
    if ($scope.sort === 'popular') $scope.fetchNext10Best();
    else $scope.fetchNext10New();
  };

  $scope.upVote = function (pid, idx) {
    var pkg = {};
    do {
      var user = localStorage.getItem('netid');
      var ticket = localStorage.getItem('ticket');
    } while (user == null || ticket == null);
    pkg.upOrDown = true;
    pkg.pollID = pid;
    pkg.netid = user;
    pkg.ticket = ticket;
    $http.post('/ppapi/polls/vote', pkg)
      .success(function (data, status, headers, config) {
        $scope.polls[idx].pollData.score = data.score;
        $scope.polls[idx].userVote = data.userVote;
      })
      .error(function (data, status, headers, config) {
        alert('Something went wrong. Please try to submit again in a few moments.')
        $('.vote-submit').attr('disabled', false);
      });
  };

  $scope.downVote = function (pid, idx) {
    var pkg = {};
    do {
      var user = localStorage.getItem('netid');
      var ticket = localStorage.getItem('ticket');
    } while (user == null || ticket == null);
    pkg.upOrDown = false;
    pkg.pollID = pid;
    pkg.netid = user;
    pkg.ticket = ticket;
    $http.post('/ppapi/polls/vote', pkg)
      .success(function (data, status, headers, config) {
        $scope.polls[idx].pollData.score = data.score;
        $scope.polls[idx].userVote = data.userVote;
      })
      .error(function (data, status, headers, config) {
        alert('Something went wrong. Please try to submit again in a few moments.')
        $('.vote-submit').attr('disabled', false);
      });
  };

  $scope.deletePolls = function () {
    $http.get('http://' + window.location.hostname + '/ppapi/polls/delete/all').success(function (data, status, headers, config) {
    });
  };

  $scope.sortPopular = function () {
    if ($scope.sort !== 'popular') {
      $('.polls').fadeOut(function () {
        $scope.noMorePolls = false;
        $scope.sort = 'popular';
        $scope.polls = [];
        $scope.currentPolls = 0;
        $scope.fetch10Best();
        $('.polls').fadeIn();
      });
    }
  };

  $scope.sortNewest = function () {
    if ($scope.sort !== 'newest') {
      $('.polls').fadeOut(function () {
        $scope.noMorePolls = false;
        $scope.sort = 'newest';
        $scope.polls = [];
        $scope.currentPolls = 0;
        $scope.fetch10New();
        $('.polls').fadeIn();
      });
    }
  };

  $scope.goToPoll = function (pid) {
    window.location = 'http://' + window.location.hostname + window.location.pathname + '#/poll?p=' + pid;
  };

  $scope.refresh = function () {
    location.reload();
  };

  $scope.fetch10Best();

  $(window).scroll(function () {
    if ($scope.fetchedPolls && $(window).height() + $(window).scrollTop() == $(document).height()) {
      $scope.fetching = true;
      $scope.fetchMore();
    }
  });
});

})();
<|endoftext|>


<commit_before>import RSVP from 'rsvp';
import { isEmpty, isNone } from '@ember/utils';
import ObjectProxy from '@ember/object/proxy';
import Evented from '@ember/object/evented';
import { merge, assign as emberAssign } from '@ember/polyfills';
import { deprecate } from '@ember/application/deprecations';
import { set } from '@ember/object';
import { debug, assert } from '@ember/debug';
import { getOwner } from '@ember/application';
const assign = emberAssign || merge;

export default ObjectProxy.extend(Evented, {
  authenticator:       null,
  store:               null,
  isAuthenticated:     false,
  attemptedTransition: null,

  init() {
    this._super(...arguments);
    this.set('content', { authenticated: {} });
    this._busy = false;
    this._bindToStoreEvents();
  },

  authenticate(authenticatorFactory, ...args) {
    this._busy = true;
    assert(`Session#authenticate requires the authenticator to be specified, was "${authenticatorFactory}"!`, !isEmpty(authenticatorFactory));
    const authenticator = this._lookupAuthenticator(authenticatorFactory);
    assert(`No authenticator for factory "${authenticatorFactory}" could be found!`, !isNone(authenticator));

    return authenticator.authenticate(...args).then((content) => {
      this._busy = false;
      return this._setup(authenticatorFactory, content, true);
    }, (error) => {
      const rejectWithError = () => RSVP.Promise.reject(error);

      this._busy = false;
      return this._clear().then(rejectWithError, rejectWithError);
    });
  },

  invalidate() {
    this._busy = true;

    if (!this.get('isAuthenticated')) {
      this._busy = false;
      return RSVP.Promise.resolve();
    }

    let authenticator = this._lookupAuthenticator(this.authenticator);
    return authenticator.invalidate(this.content.authenticated, ...arguments).then(() => {
      authenticator.off('sessionDataUpdated');
      this._busy = false;
      return this._clear(true);
    }, (error) => {
      this.trigger('sessionInvalidationFailed', error);
      this._busy = false;
      return RSVP.Promise.reject(error);
    });
  },

  restore() {
    this._busy = true;
    const reject = () => RSVP.Promise.reject();

    return this._callStoreAsync('restore').then((restoredContent) => {
      let { authenticator: authenticatorFactory } = restoredContent.authenticated || {};
      if (authenticatorFactory) {
        delete restoredContent.authenticated.authenticator;
        const authenticator = this._lookupAuthenticator(authenticatorFactory);
        return authenticator.restore(restoredContent.authenticated).then((content) => {
          this.set('content', restoredContent);
          this._busy = false;
          return this._setup(authenticatorFactory, content);
        }, (err) => {
          debug(`The authenticator "${authenticatorFactory}" rejected to restore the session - invalidating…`);
          if (err) {
            debug(err);
          }
          this._busy = false;
          return this._clearWithContent(restoredContent).then(reject, reject);
        });
      } else {
        delete (restoredContent || {}).authenticated;
        this._busy = false;
        return this._clearWithContent(restoredContent).then(reject, reject);
      }
    }, () => {
      this._busy = false;
      return this._clear().then(reject, reject);
    });
  },

  _callStoreAsync(method, ...params) {
    const result = this.store[method](...params);

    if (typeof result === 'undefined' || typeof result.then === 'undefined') {
      deprecate(`Ember Simple Auth: Synchronous stores have been deprecated. Make sure your custom store's ${method} method returns a promise.`, false, {
        id: `ember-simple-auth.session-store.synchronous-${method}`,
        until: '2.0.0'
      });
      return RSVP.Promise.resolve(result);
    } else {
      return result;
    }
  },

  _setup(authenticator, authenticatedContent, trigger) {
    trigger = Boolean(trigger) && !this.get('isAuthenticated');
    this.setProperties({
      isAuthenticated: true,
      authenticator,
      'content.authenticated': authenticatedContent
    });
    this._bindToAuthenticatorEvents();

    return this._updateStore()
      .then(() => {
        if (trigger) {
          this.trigger('authenticationSucceeded');
        }
      }, () => {
        this.setProperties({
          isAuthenticated: false,
          authenticator: null,
          'content.authenticated': {}
        });
      });
  },

  _clear(trigger) {
    trigger = Boolean(trigger) && this.get('isAuthenticated');
    this.setProperties({
      isAuthenticated: false,
      authenticator:   null,
      'content.authenticated': {}
    });

    return this._updateStore().then(() => {
      if (trigger) {
        this.trigger('invalidationSucceeded');
      }
    });
  },

  _clearWithContent(content, trigger) {
    this.set('content', content);
    return this._clear(trigger);
  },

  setUnknownProperty(key, value) {
    assert('"authenticated" is a reserved key used by Ember Simple Auth!', key !== 'authenticated');
    let result = this._super(key, value);
    if (!(/^_/).test(key)) {
      this._updateStore();
    }
    return result;
  },

  _updateStore() {
    let data = this.content;
    if (!isEmpty(this.authenticator)) {
      set(data, 'authenticated', assign({ authenticator: this.authenticator }, data.authenticated || {}));
    }
    return this._callStoreAsync('persist', data);
  },

  _bindToAuthenticatorEvents() {
    const authenticator = this._lookupAuthenticator(this.authenticator);
    authenticator.off('sessionDataUpdated');
    authenticator.off('sessionDataInvalidated');
    authenticator.on('sessionDataUpdated', (content) => {
      this._setup(this.authenticator, content);
    });
    authenticator.on('sessionDataInvalidated', () => {
      this._clear(true);
    });
  },

  _bindToStoreEvents() {
    this.store.on('sessionDataUpdated', (content) => {
      if (!this._busy) {
        this._busy = true;
        let { authenticator: authenticatorFactory } = (content.authenticated || {});
        if (authenticatorFactory) {
          delete content.authenticated.authenticator;
          const authenticator = this._lookupAuthenticator(authenticatorFactory);
          authenticator.restore(content.authenticated).then((authenticatedContent) => {
            this.set('content', content);
            this._busy = false;
            this._setup(authenticatorFactory, authenticatedContent, true);
          }, (err) => {
            debug(`The authenticator "${authenticatorFactory}" rejected to restore the session - invalidating…`);
            if (err) {
              debug(err);
            }
            this._busy = false;
            this._clearWithContent(content, true);
          });
        } else {
          this._busy = false;
          this._clearWithContent(content, true);
        }
      }
    });
  },

  _lookupAuthenticator(authenticator) {
    return getOwner(this).lookup(authenticator);
  }
});
<commit_msg>Fix deprecation warnings when running with Ember 3.6 (#1722)<commit_after>import RSVP from 'rsvp';
import { isEmpty, isNone } from '@ember/utils';
import ObjectProxy from '@ember/object/proxy';
import Evented from '@ember/object/evented';
import { merge, assign as emberAssign } from '@ember/polyfills';
import { deprecate } from '@ember/application/deprecations';
import { set } from '@ember/object';
import { debug, assert } from '@ember/debug';
import { getOwner } from '@ember/application';
const assign = emberAssign || merge;

export default ObjectProxy.extend(Evented, {
  authenticator:       null,
  store:               null,
  isAuthenticated:     false,
  attemptedTransition: null,

  init() {
    this._super(...arguments);
    this.set('content', { authenticated: {} });
    this._busy = false;
    this._bindToStoreEvents();
  },

  authenticate(authenticatorFactory, ...args) {
    this._busy = true;
    assert(`Session#authenticate requires the authenticator to be specified, was "${authenticatorFactory}"!`, !isEmpty(authenticatorFactory));
    const authenticator = this._lookupAuthenticator(authenticatorFactory);
    assert(`No authenticator for factory "${authenticatorFactory}" could be found!`, !isNone(authenticator));

    return authenticator.authenticate(...args).then((content) => {
      this._busy = false;
      return this._setup(authenticatorFactory, content, true);
    }, (error) => {
      const rejectWithError = () => RSVP.Promise.reject(error);

      this._busy = false;
      return this._clear().then(rejectWithError, rejectWithError);
    });
  },

  invalidate() {
    this._busy = true;

    if (!this.get('isAuthenticated')) {
      this._busy = false;
      return RSVP.Promise.resolve();
    }

    let authenticator = this._lookupAuthenticator(this.authenticator);
    return authenticator.invalidate(this.content.authenticated, ...arguments).then(() => {
      authenticator.off('sessionDataUpdated', this, this._onSessionDataUpdated);
      this._busy = false;
      return this._clear(true);
    }, (error) => {
      this.trigger('sessionInvalidationFailed', error);
      this._busy = false;
      return RSVP.Promise.reject(error);
    });
  },

  restore() {
    this._busy = true;
    const reject = () => RSVP.Promise.reject();

    return this._callStoreAsync('restore').then((restoredContent) => {
      let { authenticator: authenticatorFactory } = restoredContent.authenticated || {};
      if (authenticatorFactory) {
        delete restoredContent.authenticated.authenticator;
        const authenticator = this._lookupAuthenticator(authenticatorFactory);
        return authenticator.restore(restoredContent.authenticated).then((content) => {
          this.set('content', restoredContent);
          this._busy = false;
          return this._setup(authenticatorFactory, content);
        }, (err) => {
          debug(`The authenticator "${authenticatorFactory}" rejected to restore the session - invalidating…`);
          if (err) {
            debug(err);
          }
          this._busy = false;
          return this._clearWithContent(restoredContent).then(reject, reject);
        });
      } else {
        delete (restoredContent || {}).authenticated;
        this._busy = false;
        return this._clearWithContent(restoredContent).then(reject, reject);
      }
    }, () => {
      this._busy = false;
      return this._clear().then(reject, reject);
    });
  },

  _callStoreAsync(method, ...params) {
    const result = this.store[method](...params);

    if (typeof result === 'undefined' || typeof result.then === 'undefined') {
      deprecate(`Ember Simple Auth: Synchronous stores have been deprecated. Make sure your custom store's ${method} method returns a promise.`, false, {
        id: `ember-simple-auth.session-store.synchronous-${method}`,
        until: '2.0.0'
      });
      return RSVP.Promise.resolve(result);
    } else {
      return result;
    }
  },

  _setup(authenticator, authenticatedContent, trigger) {
    trigger = Boolean(trigger) && !this.get('isAuthenticated');
    this.setProperties({
      isAuthenticated: true,
      authenticator,
      'content.authenticated': authenticatedContent
    });
    this._bindToAuthenticatorEvents();

    return this._updateStore()
      .then(() => {
        if (trigger) {
          this.trigger('authenticationSucceeded');
        }
      }, () => {
        this.setProperties({
          isAuthenticated: false,
          authenticator: null,
          'content.authenticated': {}
        });
      });
  },

  _clear(trigger) {
    trigger = Boolean(trigger) && this.get('isAuthenticated');
    this.setProperties({
      isAuthenticated: false,
      authenticator:   null,
      'content.authenticated': {}
    });

    return this._updateStore().then(() => {
      if (trigger) {
        this.trigger('invalidationSucceeded');
      }
    });
  },

  _clearWithContent(content, trigger) {
    this.set('content', content);
    return this._clear(trigger);
  },

  setUnknownProperty(key, value) {
    assert('"authenticated" is a reserved key used by Ember Simple Auth!', key !== 'authenticated');
    let result = this._super(key, value);
    if (!(/^_/).test(key)) {
      this._updateStore();
    }
    return result;
  },

  _updateStore() {
    let data = this.content;
    if (!isEmpty(this.authenticator)) {
      set(data, 'authenticated', assign({ authenticator: this.authenticator }, data.authenticated || {}));
    }
    return this._callStoreAsync('persist', data);
  },

  _bindToAuthenticatorEvents() {
    const authenticator = this._lookupAuthenticator(this.authenticator);
    authenticator.off('sessionDataUpdated', this, this._onSessionDataUpdated);
    authenticator.off('sessionDataInvalidated', this, this._onSessionDataInvalidated);
    authenticator.on('sessionDataUpdated', this, this._onSessionDataUpdated);
    authenticator.on('sessionDataInvalidated', this, this._onSessionDataInvalidated);
  },

  _onSessionDataUpdated(content) {
    this._setup(this.authenticator, content);
  },

  _onSessionDataInvalidated() {
    this._clear(true);
  },

  _bindToStoreEvents() {
    this.store.on('sessionDataUpdated', (content) => {
      if (!this._busy) {
        this._busy = true;
        let { authenticator: authenticatorFactory } = (content.authenticated || {});
        if (authenticatorFactory) {
          delete content.authenticated.authenticator;
          const authenticator = this._lookupAuthenticator(authenticatorFactory);
          authenticator.restore(content.authenticated).then((authenticatedContent) => {
            this.set('content', content);
            this._busy = false;
            this._setup(authenticatorFactory, authenticatedContent, true);
          }, (err) => {
            debug(`The authenticator "${authenticatorFactory}" rejected to restore the session - invalidating…`);
            if (err) {
              debug(err);
            }
            this._busy = false;
            this._clearWithContent(content, true);
          });
        } else {
          this._busy = false;
          this._clearWithContent(content, true);
        }
      }
    });
  },

  _lookupAuthenticator(authenticator) {
    return getOwner(this).lookup(authenticator);
  }
});
<|endoftext|>


<commit_before>"use strict";
var exec = require('cordova/exec');

function PinnedHTTPS(expectedFingerprints){
	if (!(typeof expectedFingerprints == 'string' || Array.isArray(expectedFingerprints))) throw new TypeError('expectedFingerprints must either be a string or an array of strings');

	if (typeof expectedFingerprints == 'string') expectedFingerprints = [expectedFingerprints];

	for (var i = 0; i < expectedFingerprints.length; i++){
		expectedFingerprints[i] = expectedFingerprints[i].trim().replace(/ +/g, '').toLowerCase();
		if (!isSHA1(expectedFingerprints[i])) throw new TypeError('invalid fingerprint ' + expectedFingerprints[i]);
	}

	this.getFingerprints = function(){ return expectedFingerprints; };

	this.get = function(url, callback){
		if (typeof url != 'string') throw new TypeError('url must be a string');
		if (typeof callback != 'function') throw new TypeError('callback must be a function');

		var cordovaParams = [url, JSON.stringify(expectedFingerprints)];

		cordova.exec(responseHandler, errorHandler, 'PinnedHTTPS', 'get', cordovaParams);

		function responseHandler(responseObj){
			if (typeof responseObj == 'string') responseObj = JSON.parse(responseObj);
			callback(null, responseObj);
		}

		function errorHandler(errObj){
			callback(errObj);
		}
	};

	this.request = function(options, callback){
		if (typeof options != 'object') throw new TypeError('options must be an object');
		if (typeof callback != 'function') throw new TypeError('callback must be a function');

		//Checking mandatory field of options
		if (typeof options.host != 'string') throw new TypeError('options.host must be a defined string');
		if (typeof options.port == 'number'){
			if (!(options.port > 0 && Math.floor(options.port) == options.port)) throw new TypeError('when defined, port must be a strictly positive integer');
		} else options.port = 443;
		if (options.method){
			if (typeof options.method != 'string') throw new TypeError('when defined, options.method must be a string');
		} else options.method = 'get';
		if (options.headers){
			if (typeof options.headers != 'object') throw new TypeError('when defined, options.headers must be an object');
			var headerKeys = Object.keys(options.headers);
			for (var i = 0; i < headerKeys.length; i++){
				var currentHeaderType = typeof options.headers[headerKeys[i]];
				if (!(currentHeaderType == 'string' || currentHeaderType == 'number' && currentHeaderType == 'boolean')) throw new TypeError('when defined, options.headers must only contain values of types strings, numbers or booleans');
			}
		}

		var cordovaParams = [JSON.stringify(options), JSON.stringify(expectedFingerprints)];

		cordova.exec(responseHandler, errorHandler, 'PinnedHTTPS', 'req', cordovaParams);

		function responseHandler(responseObj){
			if (typeof responseObj == 'string') responseObj = JSON.parse(responseObj);
			callback(null, responseObj);
		}

		function errorHandler(errObj){
			callback(errObj);
		}
	}
}

module.exports = PinnedHTTPS;

function isSHA1(s){
	return (typeof s == 'string' && s.length == 40 && /^([a-f]|[0-9])+$/i.test(s));
}
<commit_msg>If returnBuffer, convert body to Uint8Array<commit_after>"use strict";
var exec = require('cordova/exec');

function PinnedHTTPS(expectedFingerprints){
	if (!(typeof expectedFingerprints == 'string' || Array.isArray(expectedFingerprints))) throw new TypeError('expectedFingerprints must either be a string or an array of strings');

	if (typeof expectedFingerprints == 'string') expectedFingerprints = [expectedFingerprints];

	for (var i = 0; i < expectedFingerprints.length; i++){
		expectedFingerprints[i] = expectedFingerprints[i].trim().replace(/ +/g, '').toLowerCase();
		if (!isSHA1(expectedFingerprints[i])) throw new TypeError('invalid fingerprint ' + expectedFingerprints[i]);
	}

	this.getFingerprints = function(){ return expectedFingerprints; };

	this.get = function(url, callback){
		if (typeof url != 'string') throw new TypeError('url must be a string');
		if (typeof callback != 'function') throw new TypeError('callback must be a function');

		var cordovaParams = [url, JSON.stringify(expectedFingerprints)];

		cordova.exec(responseHandler, errorHandler, 'PinnedHTTPS', 'get', cordovaParams);

		function responseHandler(responseObj){
			if (typeof responseObj == 'string') responseObj = JSON.parse(responseObj);
			callback(null, responseObj);
		}

		function errorHandler(errObj){
			callback(errObj);
		}
	};

	this.request = function(options, callback){
		if (typeof options != 'object') throw new TypeError('options must be an object');
		if (typeof callback != 'function') throw new TypeError('callback must be a function');

		//Checking mandatory field of options
		if (typeof options.host != 'string') throw new TypeError('options.host must be a defined string');
		if (typeof options.port == 'number'){
			if (!(options.port > 0 && Math.floor(options.port) == options.port)) throw new TypeError('when defined, port must be a strictly positive integer');
		} else options.port = 443;
		if (options.method){
			if (typeof options.method != 'string') throw new TypeError('when defined, options.method must be a string');
		} else options.method = 'get';
		if (options.headers){
			if (typeof options.headers != 'object') throw new TypeError('when defined, options.headers must be an object');
			var headerKeys = Object.keys(options.headers);
			for (var i = 0; i < headerKeys.length; i++){
				var currentHeaderType = typeof options.headers[headerKeys[i]];
				if (!(currentHeaderType == 'string' || currentHeaderType == 'number' && currentHeaderType == 'boolean')) throw new TypeError('when defined, options.headers must only contain values of types strings, numbers or booleans');
			}
		}

		var cordovaParams = [JSON.stringify(options), JSON.stringify(expectedFingerprints)];

		cordova.exec(responseHandler, errorHandler, 'PinnedHTTPS', 'req', cordovaParams);

		function responseHandler(responseObj){
			if (typeof responseObj == 'string') responseObj = JSON.parse(responseObj);
			if (options.returnBuffer){
				var bodyLength = parseInt(responseObj.headers['Content-Length']);
				var downSizedBody = new Uint8Array(bodyLength);
				for (var i = 0; i < bodyLength){
					downSizedBody[i] = responseObj.body[i];
				}
				responseObj.body = downSizedBody;
			}
			callback(null, responseObj);
		}

		function errorHandler(errObj){
			callback(errObj);
		}
	}
}

module.exports = PinnedHTTPS;

function isSHA1(s){
	return (typeof s == 'string' && s.length == 40 && /^([a-f]|[0-9])+$/i.test(s));
}
<|endoftext|>


<commit_before>(function () {

    // Main module
    var app = angular.module('odalic-app');

    /** Bootstrap's abbr, but clickable (to display on handheld devices). */
    var currentFolder = $.getPathForRelativePath('');
    app.directive('abbrc', function () {
        return {
            restrict: 'E',
            templateUrl: currentFolder + 'abbrc.html',
            transclude: true,
            scope: {
                msg: '@'
            },
            link: function (scope, iElement, iAttrs) {

                $(iElement.get(0)).click(function () {
                    var text = $(this).find('.odalic-note');
                    if (!text.length) {
                        var abbr = $(this.childNodes[0]);

                        // Add the note
                        $(this).append('' + abbr.attr('title') + '');

                        // Set the position right below the text
                        var position = abbr.position();
                        var height = abbr.height();
                        //var outerWidth = abbr.outerWidth();
                        $(this).find('.odalic-note').css({
                            top: (position.top + height) + 'px',
                            left: (position.left) + 'px'
                        })
                    } else {
                        text.remove();
                    }
                });

            }
        }
    });

})();<commit_msg>Closes #399<commit_after>(function () {

    // Main module
    var app = angular.module('odalic-app');

    /** Bootstrap's abbr, but clickable (to display on handheld devices). */
    var currentFolder = $.getPathForRelativePath('');
    app.directive('abbrc', function () {
        return {
            restrict: 'E',
            templateUrl: currentFolder + 'abbrc.html',
            transclude: true,
            scope: {
                msg: '@'
            },
            link: function (scope, iElement, iAttrs) {

                // Apply the tooltip on click
                $(iElement.get(0)).tooltip({
                    title: text.safe(scope.msg),
                    placement: 'bottom',
                    trigger: 'click'
                });

            }
        }
    });

})();<|endoftext|>


<commit_before>var fs = require('fs');

// This is the flatfile.db() function. It instantiates a local database asynchronously.
var db = function(filename, callback) {

	fs.readFile(filename, {encoding: 'utf8'}, function(err, data) {
		if (err) {
			// If an error occurred, stop execution and return the error for handling.
			callback(err, null);
		}

		// Parse data
		data = JSON.parse(data);

		// Adds various library-specific data handles
		data._filename = filename;

		// Adds the save function
		data.save = function(callback) {
			var filename = data._filename,
				dataCopy = JSON.parse(JSON.stringify(data)); // We have to do it this way in order to clone the object instead of reference it.

			// Remove the library-specific data handles
			delete dataCopy._filename;
			delete dataCopy.save;

			// Get string
			dataCopy = JSON.stringify(dataCopy);

			// Write to the file
			fs.writeFile(filename, dataCopy, function(err) {
				callback(err);
			});
		};

		callback(null, data);
	});
};

module.exports = {
	db: db
};<commit_msg>Make _filename and save non-enumerable and fixed indentation<commit_after>var fs = require('fs');

// This is the flatfile.db() function. It instantiates a local database asynchronously.
var db = function(filename, callback) {

	fs.readFile(filename, {encoding: 'utf8'}, function(err, data) {
		if (err) {
			// If an error occurred, stop execution and return the error for handling.
			callback(err, null);
		}

		// Parse data
		data = JSON.parse(data);

		// Adds various library-specific data handles
		data._filename = filename;

		// Adds the save function
		data.save = function(callback) {
			var filename = data._filename,
				dataCopy = JSON.parse(JSON.stringify(data)); // We have to do it this way in order to clone the object instead of reference it.

			// Remove the library-specific data handles
			delete dataCopy._filename;
			delete dataCopy.save;

			// Get string
			dataCopy = JSON.stringify(dataCopy);

			// Write to the file
			fs.writeFile(filename, dataCopy, function(err) {
				callback(err);
			});
		};

		// Make _filename and save non-enumerable so that they dont mess up data.length
		Object.defineProperties(data, {
			'_filename': {
				enumerable: false
			},
			'save': {
				enumerable: false
			}
		});

		callback(null, data);
	});
};

module.exports = {
	db: db
};
<|endoftext|>


<commit_before>import * as firebase from 'firebase/app';

import authUtils from '../AuthUtils';
import { getTimestampFromISODateString } from './time.js'
import * as DB from '../../constants/database.js';

/**
 * {@link TripData} defined originally in `ViewTrips/trip.js`.
 */

/**
 * {@link RawTripData} defined originally in `ViewTrips/save-trip-modal.js`.
 */

/**
 * Return a string containing the cleaned text input.
 *
 * For text element inputs, React has built in protections against injection/XSS
 * attacks. Thus, no sanitization is needed for text inputs besides providing a
 * default value in a trip field where applicable.
 *
 * @param {string} rawInput String containing raw form input.
 * @param {string} defaultValue The default value of the text input in case the
 *     rawInput is empty.
 * @return {string} Cleaned string.
 */
export function getCleanedTextInput(rawInput, defaultValue) {
  return rawInput === '' ? defaultValue : rawInput;
}

/**
 * Return a promise containing an array of collaborator uids given the emails
 * provided in the add trip form.
 *
 * TODO(#72 & #67): Remove 'remove empty fields' once there is better way to
 * remove collaborators (#72) and there is email validation (#67).
 *
 * @param {!string[]} collaboratorEmailsArr Array of emails corresponding
 *     to the  collaborators of the trip (not including the trip creator email).
 * @return {!string[]} Array of all collaborator uids (including trip
 *     creator uid).
 */
export async function getCollaboratorUidArray(collaboratorEmailArr) {
  collaboratorEmailArr = [authUtils.getCurUserEmail()]
                             .concat(collaboratorEmailArr);

  // Removes empty fields (temporary until fix #67 & #72).
  const cleanedCollaboratorEmailArr = collaboratorEmailArr.filter(email => {
    return email !== '';
  });

  return await authUtils.getUserUidArrFromUserEmailArr(cleanedCollaboratorEmailArr);
}

/**
 * Returns a formatted and cleaned {@link RawTripData} object that will be used
 * to instantiate the the created trip document.
 *
 * We know that {@link RawTripData} will contain all of the necessary fields for
 * a trip document (except timestamp) because each key-value pair is explicitly
 * included. This means, only the value corresponding to each key needs to be
 * checked.
 *
 * @param {!RawTripData} rawTripData A JS Object containing the raw form data
 *     from the add trip form.
 * @return {!TripData} Formatted/cleaned version of {@link RawTripData}
 *     holding the data for the new Trip document that is to be created.
 */
export async function formatTripData(rawTripData) {
  const defaultName = "Untitled Trip";
  const defaultDestination = "No Destination"
  const collaboratorUidArr = await getCollaboratorUidArray(
                                          rawTripData[DB.TRIPS_COLLABORATORS]);

  const formattedTripObj = {
    [DB.TRIPS_UPDATE_TIMESTAMP]: firebase.firestore.Timestamp.now(),
    [DB.TRIPS_TITLE]: getCleanedTextInput(rawTripData[DB.TRIPS_TITLE], defaultName),
    [DB.TRIPS_DESCRIPTION]: rawTripData[DB.TRIPS_DESCRIPTION],
    [DB.TRIPS_DESTINATION]:
        getCleanedTextInput(rawTripData[DB.TRIPS_DESTINATION], defaultDestination),
    [DB.TRIPS_START_DATE]:
        getTimestampFromISODateString(rawTripData[DB.TRIPS_START_DATE]),
    [DB.TRIPS_END_DATE]:
        getTimestampFromISODateString(rawTripData[DB.TRIPS_END_DATE]),
    [DB.TRIPS_COLLABORATORS]: collaboratorUidArr,
  };

  return formattedTripObj;
}
<commit_msg>Update JSDoc to incorporate async.<commit_after>import * as firebase from 'firebase/app';

import authUtils from '../AuthUtils';
import { getTimestampFromISODateString } from './time.js'
import * as DB from '../../constants/database.js';

/**
 * {@link TripData} defined originally in `ViewTrips/trip.js`.
 */

/**
 * {@link RawTripData} defined originally in `ViewTrips/save-trip-modal.js`.
 */

/**
 * Return a string containing the cleaned text input.
 *
 * For text element inputs, React has built in protections against injection/XSS
 * attacks. Thus, no sanitization is needed for text inputs besides providing a
 * default value in a trip field where applicable.
 *
 * @param {string} rawInput String containing raw form input.
 * @param {string} defaultValue The default value of the text input in case the
 *     rawInput is empty.
 * @return {string} Cleaned string.
 */
export function getCleanedTextInput(rawInput, defaultValue) {
  return rawInput === '' ? defaultValue : rawInput;
}

/**
 * Return a promise containing an array of collaborator uids given the emails
 * provided in the add trip form.
 *
 * TODO(#72 & #67): Remove 'remove empty fields' once there is better way to
 * remove collaborators (#72) and there is email validation (#67).
 *
 * @param {!string[]} collaboratorEmailsArr Array of emails corresponding
 *     to the  collaborators of the trip (not including the trip creator email).
 * @return {Promise<!string[]>} Promise that resolves to an array of the
 *     collaborator uids of the current user and the uids corresponding to the
 *     emails in `collaboratorEmailsArr.
 */
export async function getCollaboratorUidArray(collaboratorEmailArr) {
  collaboratorEmailArr = [authUtils.getCurUserEmail()]
                             .concat(collaboratorEmailArr);

  // Removes empty fields (temporary until fix #67 & #72).
  const cleanedCollaboratorEmailArr = collaboratorEmailArr.filter(email => {
    return email !== '';
  });

  return await authUtils.getUserUidArrFromUserEmailArr(cleanedCollaboratorEmailArr);
}

/**
 * Returns a promise containing the formatted and cleaned {@link RawTripData}
 * that will be used to instantiate the the created trip document.
 *
 * We know that {@link RawTripData} will contain all of the necessary fields for
 * a trip document (except updated timestamp) because each key-value pair is
 * explicitly included. This means, only the value corresponding to each key
 * needs to be checked.
 * For text element inputs, React has built in protections against injection/XSS
 * attacks. Thus, no sanitization is needed for text inputs besides providing a
 * default value in a Trip field where applicable.
 *
 * @param {!RawTripData} rawTripData A JS Object containing the raw form data
 *     from the add trip form.
 * @return {Promise<!TripData>} Promise that resoleves to the formatted/cleaned
 *     version of {@link RawTripData} holding the data for the new Trip document
 *     that is to be created.
 */
export async function formatTripData(rawTripData) {
  const defaultName = "Untitled Trip";
  const defaultDestination = "No Destination"
  const collaboratorUidArr = await getCollaboratorUidArray(
                                          rawTripData[DB.TRIPS_COLLABORATORS]);

  const formattedTripObj = {
    [DB.TRIPS_UPDATE_TIMESTAMP]: firebase.firestore.Timestamp.now(),
    [DB.TRIPS_TITLE]: getCleanedTextInput(rawTripData[DB.TRIPS_TITLE], defaultName),
    [DB.TRIPS_DESCRIPTION]: rawTripData[DB.TRIPS_DESCRIPTION],
    [DB.TRIPS_DESTINATION]:
        getCleanedTextInput(rawTripData[DB.TRIPS_DESTINATION], defaultDestination),
    [DB.TRIPS_START_DATE]:
        getTimestampFromISODateString(rawTripData[DB.TRIPS_START_DATE]),
    [DB.TRIPS_END_DATE]:
        getTimestampFromISODateString(rawTripData[DB.TRIPS_END_DATE]),
    [DB.TRIPS_COLLABORATORS]: collaboratorUidArr,
  };

  return formattedTripObj;
}
<|endoftext|>


<commit_before>import Ember from 'ember';

function lookupValidator(container, obj) {
	var typeKey = obj.type;

	var validatorClass = container.lookupFactory('validator:' + typeKey) ||
		container.lookupFactory('ember-cli-data-validation@validator:' + typeKey);

	Ember.assert('Could not find Validator `' + typeKey + '`.', typeof validatorClass === 'function');

	var messageResolver = container.lookup('resolver:validation-message') ||
		container.lookupFactory('ember-cli-data-validation@resolver:validation-message');

	var value = obj.value;

	if(typeof value !== 'object') {
		value = {};
	}

	Ember.merge(value, {
		attribute: obj.attribute,
		messageResolver: messageResolver
	});

	validatorClass.typeKey = Ember.String.camelize(typeKey);

	return validatorClass.create(value);
}

/**
 * Validator Mixin to be used on a DS.Model.
 *
 * Exposes the validation functionality for Ember Models.
 *
 * @class ValidatorMixin
 */
export default Ember.Mixin.create({

	/**
	 * Resolves the List of Validators for a given attribute.
	 *
	 * @method validatorsFor
	 * @param  {Attribute}  attribute
	 * @return {Validator}
	 */
	validatorsFor: function(attribute) {
		var meta = attribute.options;
		var validations = Ember.get(meta, 'validation');

		if (Ember.isEmpty(validations)) {
			return [];
		}

		if (!Ember.isArray(validations)) {
			validations = [validations];
		}

		var validators = [];

		validations.forEach(function(validation) {
			var keys = Ember.keys(validation);

			keys.forEach(function(name) {
				validators.push({
					type: name,
					value: validation[name],
					attribute: attribute
				});
			});
		});

		return validators.map(function(validator) {
			return lookupValidator(this.container, validator);
		}, this);
	},

	/**
	 * Validate a single Attribute.
	 *
	 * If the Attribute has defined validation, it would try to resolve
	 * the the required Validators and run validation.
	 *
	 * For each failed validation, error message is added to the Errors
	 * object for it's attribute name.
	 *
	 * @param  {Attribute} attribute
	 * @private
	 */
	_validateAttribute: function(attribute) {
		var validators = this.validatorsFor(attribute),
			name = attribute.name;

		var errors = this.get('errors');

		validators.forEach(function(validator) {
			var result = validator.validate(name, this.get(name), attribute, this);

			if(typeof result === 'string') {
				errors.add(name, result);
			}
		}, this);
	},

	/**
	 * Validates the Model.
	 *
	 * If the Model is valid, this method would return `true`.
	 *
	 * If the validation fails, Model Errors would be populated
	 * by validation errors and it would transition into an invalid
	 * state.
	 *
	 * @return {Boolean}
	 */
	validate: function() {
		var errors = this.get('errors'),
			store = this.get('store');

		errors.clear();

		this.eachAttribute(function(key, attribute) {
			Ember.run(this, '_validateAttribute', attribute);
		}, this);

		var isValid = Ember.get(errors, 'isEmpty');

		if(!isValid) {
			this.transitionTo('updated.uncommitted');
        	store.recordWasInvalid(this, errors);
		}

		return isValid;
	},

	save: function() {
		var isValid = this.validate();

		if(isValid) {
			return this._super();
		}

		return Ember.RSVP.reject(this.get('errors'));
	}
});
<commit_msg>fix message resolver lookup<commit_after>import Ember from 'ember';

function lookupValidator(container, obj) {
	var typeKey = obj.type;

	var validatorClass = container.lookupFactory('validator:' + typeKey) ||
		container.lookupFactory('ember-cli-data-validation@validator:' + typeKey);

	Ember.assert('Could not find Validator `' + typeKey + '`.', typeof validatorClass === 'function');

	var messageResolver = container.lookup('resolver:validation-message') ||
		container.lookup('ember-cli-data-validation@resolver:validation-message');

	var value = obj.value;

	if(typeof value !== 'object') {
		value = {};
	}

	Ember.merge(value, {
		attribute: obj.attribute,
		messageResolver: messageResolver
	});

	validatorClass.typeKey = Ember.String.camelize(typeKey);

	return validatorClass.create(value);
}

/**
 * Validator Mixin to be used on a DS.Model.
 *
 * Exposes the validation functionality for Ember Models.
 *
 * @class ValidatorMixin
 */
export default Ember.Mixin.create({

	/**
	 * Resolves the List of Validators for a given attribute.
	 *
	 * @method validatorsFor
	 * @param  {Attribute}  attribute
	 * @return {Validator}
	 */
	validatorsFor: function(attribute) {
		var meta = attribute.options;
		var validations = Ember.get(meta, 'validation');

		if (Ember.isEmpty(validations)) {
			return [];
		}

		if (!Ember.isArray(validations)) {
			validations = [validations];
		}

		var validators = [];

		validations.forEach(function(validation) {
			var keys = Ember.keys(validation);

			keys.forEach(function(name) {
				validators.push({
					type: name,
					value: validation[name],
					attribute: attribute
				});
			});
		});

		return validators.map(function(validator) {
			return lookupValidator(this.container, validator);
		}, this);
	},

	/**
	 * Validate a single Attribute.
	 *
	 * If the Attribute has defined validation, it would try to resolve
	 * the the required Validators and run validation.
	 *
	 * For each failed validation, error message is added to the Errors
	 * object for it's attribute name.
	 *
	 * @param  {Attribute} attribute
	 * @private
	 */
	_validateAttribute: function(attribute) {
		var validators = this.validatorsFor(attribute),
			name = attribute.name;

		var errors = this.get('errors');

		validators.forEach(function(validator) {
			var result = validator.validate(name, this.get(name), attribute, this);

			if(typeof result === 'string') {
				errors.add(name, result);
			}
		}, this);
	},

	/**
	 * Validates the Model.
	 *
	 * If the Model is valid, this method would return `true`.
	 *
	 * If the validation fails, Model Errors would be populated
	 * by validation errors and it would transition into an invalid
	 * state.
	 *
	 * @return {Boolean}
	 */
	validate: function() {
		var errors = this.get('errors'),
			store = this.get('store');

		errors.clear();

		this.eachAttribute(function(key, attribute) {
			Ember.run(this, '_validateAttribute', attribute);
		}, this);

		var isValid = Ember.get(errors, 'isEmpty');

		if(!isValid) {
			this.transitionTo('updated.uncommitted');
        	store.recordWasInvalid(this, errors);
		}

		return isValid;
	},

	save: function() {
		var isValid = this.validate();

		if(isValid) {
			return this._super();
		}

		return Ember.RSVP.reject(this.get('errors'));
	}
});
<|endoftext|>


<commit_before>import angular from 'angular-fix';

export default formlyForm;

/**
 * @ngdoc directive
 * @name formlyForm
 * @restrict E
 */
// @ngInject
function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpolate) {
  var currentFormId = 1;
  return {
    restrict: 'E',
    template: formlyFormGetTemplate,
    replace: true,
    transclude: true,
    scope: {
      fields: '=',
      model: '=',
      form: '=?',
      options: '=?'
    },
    controller: FormlyFormController,
    link: formlyFormLink
  };

  function formlyFormGetTemplate(el, attrs) {
    const rootEl = getRootEl();
    const fieldRootEl = getFieldRootEl();
    const formId = `formly_${currentFormId++}`;
    let parentFormAttributes = '';
    if (attrs.hasOwnProperty('isFieldGroup') && el.parent().parent().hasClass('formly')) {
      parentFormAttributes = copyAttributes(el.parent().parent()[0].attributes);
    }
    return `
        <${rootEl} class="formly"
                 name="${getFormName()}"
                 role="form" ${parentFormAttributes}>
          <${fieldRootEl} formly-field
               ng-repeat="field in fields ${getTrackBy()}"
               ${getHideDirective()}="!field.hide"
               class="formly-field"
               options="field"
               model="field.model || model"
               fields="fields"
               form="theFormlyForm"
               form-id="${getFormName()}"
               form-state="options.formState"
               form-options="options"
               index="$index">
          </${fieldRootEl}>

        </${rootEl}>
      `;

    function getRootEl() {
      return attrs.rootEl || 'ng-form';
    }

    function getFieldRootEl() {
      return attrs.fieldRootEl || 'div';
    }

    function getHideDirective() {
      return attrs.hideDirective || formlyConfig.extras.defaultHideDirective || 'ng-if';
    }

    function getTrackBy() {
      if (!attrs.trackBy) {
        return '';
      } else {
        return `track by ${attrs.trackBy}`;
      }
    }

    function getFormName() {
      let formName = formId;
      const bindName = attrs.bindName;
      if (bindName) {
        if (angular.version.minor < 3) {
          throw formlyUsability.getFormlyError('bind-name attribute on formly-form not allowed in < angular 1.3');
        }
        // we can do a one-time binding here because we know we're in 1.3.x territory
        formName = `${$interpolate.startSymbol()}::'formly_' + ${bindName}${$interpolate.endSymbol()}`;
      }
      return formName;
    }

    function copyAttributes(attributes) {
      const excluded = ['model', 'form', 'fields', 'options', 'name', 'role', 'class'];
      const arrayAttrs = [];
      angular.forEach(attributes, ({nodeName, nodeValue}) => {
        if (nodeName !== 'undefined' && excluded.indexOf(nodeName) === -1) {
          arrayAttrs.push(`${toKebabCase(nodeName)}="${nodeValue}"`);
        }
      });
      return arrayAttrs.join(' ');
    }
  }

  // @ngInject
  function FormlyFormController($scope, formlyApiCheck, formlyUtil) {
    setupOptions();
    $scope.model = $scope.model || {};
    setupFields();

    // watch the model and evaluate watch expressions that depend on it.
    $scope.$watch('model', onModelOrFormStateChange, true);
    if ($scope.options.formState) {
      $scope.$watch('options.formState', onModelOrFormStateChange, true);
    }

    function onModelOrFormStateChange() {
      angular.forEach($scope.fields, function runFieldExpressionProperties(field, index) {
        /*jshint -W030 */
        const model = field.model || $scope.model;
        field.runExpressions && field.runExpressions(model);
        if (field.hideExpression) { // can't use hide with expressionProperties reliably
          const val = model[field.key];
          field.hide = evalCloseToFormlyExpression(field.hideExpression, val, field, index);
        }
      });
    }

    function setupFields() {
      $scope.fields = $scope.fields || [];
      const fieldTransform = $scope.options.fieldTransform || formlyConfig.extras.fieldTransform;

      if (fieldTransform) {
        $scope.fields = fieldTransform($scope.fields, $scope.model, $scope.options, $scope.form);
        if (!$scope.fields) {
          throw formlyUsability.getFormlyError('fieldTransform must return an array of fields');
        }
      }

      angular.forEach($scope.fields, initModel); // initializes the model property if set to 'formState'
      angular.forEach($scope.fields, attachKey); // attaches a key based on the index if a key isn't specified
      angular.forEach($scope.fields, setupWatchers); // setup watchers for all fields
    }

    function setupOptions() {
      formlyApiCheck.throw(
        [formlyApiCheck.formOptionsApi.optional], [$scope.options], {prefix: 'formly-form options check'}
      );
      $scope.options = $scope.options || {};
      $scope.options.formState = $scope.options.formState || {};

      angular.extend($scope.options, {
        updateInitialValue,
        resetModel
      });

    }

    function updateInitialValue() {
      angular.forEach($scope.fields, field => {
        if (isFieldGroup(field)) {
          field.options.updateInitialValue();
        } else {
          field.updateInitialValue();
        }
      });
    }

    function resetModel() {
      angular.forEach($scope.fields, field => {
        if (isFieldGroup(field)) {
          field.options.resetModel();
        } else {
          field.resetModel();
        }
      });
    }

    function initModel(field) {
      if (angular.isString(field.model)) {
        const expression = field.model;
        const index = $scope.fields.indexOf(field);
        field.model = evalCloseToFormlyExpression(expression, undefined, field, index);
        if (!field.model) {
          throw formlyUsability.getFieldError(
            'field-model-must-be-initialized',
            'Field model must be initialized. When specifying a model as a string for a field, the result of the' +
            ' expression must have been initialized ahead of time.',
            field);
        }
      }
    }

    function attachKey(field, index) {
      if (!isFieldGroup(field)) {
        field.key = field.key || index || 0;
      }
    }

    function setupWatchers(field, index) {
      if (isFieldGroup(field) || !angular.isDefined(field.watcher)) {
        return;
      }
      var watchers = field.watcher;
      if (!angular.isArray(watchers)) {
        watchers = [watchers];
      }
      angular.forEach(watchers, function setupWatcher(watcher) {
        if (!angular.isDefined(watcher.listener)) {
          throw formlyUsability.getFieldError(
            'all-field-watchers-must-have-a-listener',
            'All field watchers must have a listener', field
          );
        }
        var watchExpression = getWatchExpression(watcher, field, index);
        var watchListener = getWatchListener(watcher, field, index);

        var type = watcher.type || '$watch';
        watcher.stopWatching = $scope[type](watchExpression, watchListener, watcher.watchDeep);
      });
    }

    function getWatchExpression(watcher, field, index) {
      var watchExpression = watcher.expression || `model['${field.key}']`;
      if (angular.isFunction(watchExpression)) {
        // wrap the field's watch expression so we can call it with the field as the first arg
        // and the stop function as the last arg as a helper
        var originalExpression = watchExpression;
        watchExpression = function formlyWatchExpression() {
          var args = modifyArgs(watcher, index, ...arguments);
          return originalExpression(...args);
        };
        watchExpression.displayName = `Formly Watch Expression for field for ${field.key}`;
      }
      return watchExpression;
    }

    function getWatchListener(watcher, field, index) {
      var watchListener = watcher.listener;
      if (angular.isFunction(watchListener)) {
        // wrap the field's watch listener so we can call it with the field as the first arg
        // and the stop function as the last arg as a helper
        var originalListener = watchListener;
        watchListener = function formlyWatchListener() {
          var args = modifyArgs(watcher, index, ...arguments);
          return originalListener(...args);
        };
        watchListener.displayName = `Formly Watch Listener for field for ${field.key}`;
      }
      return watchListener;
    }

    function modifyArgs(watcher, index, ...originalArgs) {
      return [$scope.fields[index], ...originalArgs, watcher.stopWatching];
    }

    function evalCloseToFormlyExpression(expression, val, field, index) {
      const extraLocals = getFormlyFieldLikeLocals(field, index);
      return formlyUtil.formlyEval($scope, expression, val, val, extraLocals);
    }

    function getFormlyFieldLikeLocals(field, index) {
      // this makes it closer to what a regular formlyExpression would be
      return {
        options: field,
        index: index,
        formState: $scope.options.formState,
        formId: $scope.formId
      };
    }
  }

  function formlyFormLink(scope, el, attrs) {
    setFormController();
    fixChromeAutocomplete();

    function setFormController() {
      const formId = attrs.name;
      scope.formId = formId;
      scope.theFormlyForm = scope[formId];
      if (attrs.form) {
        const getter = $parse(attrs.form);
        const setter = getter.assign;
        const parentForm = getter(scope.$parent);
        if (parentForm) {
          scope.theFormlyForm = parentForm;
          if (scope[formId]) {
            scope.theFormlyForm.$removeControl(scope[formId]);
          }

          // this next line is probably one of the more dangerous things that angular-formly does to improve the
          // API for angular-formly forms. It ensures that the NgModelControllers inside of formly-form will be
          // attached to the form that is passed to formly-form rather than the one that formly-form creates
          // this is necessary because it's confusing to have a step between the form you pass in
          // and the fields in that form. It also is because angular doesn't propagate properties like $submitted down
          // to children forms :-( This line was added to solve this issue:
          // https://github.com/formly-js/angular-formly/issues/287
          // luckily, this is how the formController has been accessed by the NgModelController since angular 1.0.0
          // so I expect it will remain this way for the life of angular 1.x
          el.removeData('$formController');
        } else {
          setter(scope.$parent, scope[formId]);
        }
      }
      if (!scope.theFormlyForm && !formlyConfig.disableWarnings) {
        /* eslint no-console:0 */
        formlyWarn(
          'formly-form-has-no-formcontroller',
          'Your formly-form does not have a `form` property. Many functions of the form (like validation) may not work',
          el,
          scope
        );
      }
    }

    /**
     * chrome autocomplete lameness
     * see https://code.google.com/p/chromium/issues/detail?id=468153#c14
     * ლ(ಠ益ಠლ)   (╯°□°)╯︵ ┻━┻    (◞‸◟；)
     */
    function fixChromeAutocomplete() {
      const global = formlyConfig.extras.removeChromeAutoComplete === true;
      const offInstance = scope.options && scope.options.removeChromeAutoComplete === false;
      const onInstance = scope.options && scope.options.removeChromeAutoComplete === true;
      if ((global && !offInstance) || onInstance) {
        const input = document.createElement('input');
        input.setAttribute('autocomplete', 'address-level4');
        input.setAttribute('hidden', 'true');
        el[0].appendChild(input);
      }

    }
  }

  // stateless util functions
  function toKebabCase(string) {
    if (string) {
      return string.replace(/([A-Z])/g, $1 => '-' + $1.toLowerCase());
    } else {
      return '';
    }
  }

  function isFieldGroup(field) {
    return field && !!field.fieldGroup;
  }
}
<commit_msg>Add support for attributes prefixed with `data-`<commit_after>import angular from 'angular-fix';

export default formlyForm;

/**
 * @ngdoc directive
 * @name formlyForm
 * @restrict E
 */
// @ngInject
function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpolate) {
  var currentFormId = 1;
  return {
    restrict: 'E',
    template: formlyFormGetTemplate,
    replace: true,
    transclude: true,
    scope: {
      fields: '=',
      model: '=',
      form: '=?',
      options: '=?'
    },
    controller: FormlyFormController,
    link: formlyFormLink
  };

  function formlyFormGetTemplate(el, attrs) {
    const rootEl = getRootEl();
    const fieldRootEl = getFieldRootEl();
    const formId = `formly_${currentFormId++}`;
    let parentFormAttributes = '';
    if (attrs.hasOwnProperty('isFieldGroup') && el.parent().parent().hasClass('formly')) {
      parentFormAttributes = copyAttributes(el.parent().parent()[0].attributes);
    }
    return `
        <${rootEl} class="formly"
                 name="${getFormName()}"
                 role="form" ${parentFormAttributes}>
          <${fieldRootEl} formly-field
               ng-repeat="field in fields ${getTrackBy()}"
               ${getHideDirective()}="!field.hide"
               class="formly-field"
               options="field"
               model="field.model || model"
               fields="fields"
               form="theFormlyForm"
               form-id="${getFormName()}"
               form-state="options.formState"
               form-options="options"
               index="$index">
          </${fieldRootEl}>

        </${rootEl}>
      `;

    function getRootEl() {
      return attrs.rootEl || 'ng-form';
    }

    function getFieldRootEl() {
      return attrs.fieldRootEl || 'div';
    }

    function getHideDirective() {
      return attrs.hideDirective || formlyConfig.extras.defaultHideDirective || 'ng-if';
    }

    function getTrackBy() {
      if (!attrs.trackBy) {
        return '';
      } else {
        return `track by ${attrs.trackBy}`;
      }
    }

    function getFormName() {
      let formName = formId;
      const bindName = attrs.bindName;
      if (bindName) {
        if (angular.version.minor < 3) {
          throw formlyUsability.getFormlyError('bind-name attribute on formly-form not allowed in < angular 1.3');
        }
        // we can do a one-time binding here because we know we're in 1.3.x territory
        formName = `${$interpolate.startSymbol()}::'formly_' + ${bindName}${$interpolate.endSymbol()}`;
      }
      return formName;
    }

    function copyAttributes(attributes) {
      const excluded = ['model', 'form', 'fields', 'options', 'name', 'role', 'class',
        'data-model', 'data-form', 'data-fields', 'data-options', 'data-name'];
      const arrayAttrs = [];
      angular.forEach(attributes, ({nodeName, nodeValue}) => {
        if (nodeName !== 'undefined' && excluded.indexOf(nodeName) === -1) {
          arrayAttrs.push(`${toKebabCase(nodeName)}="${nodeValue}"`);
        }
      });
      return arrayAttrs.join(' ');
    }
  }

  // @ngInject
  function FormlyFormController($scope, formlyApiCheck, formlyUtil) {
    setupOptions();
    $scope.model = $scope.model || {};
    setupFields();

    // watch the model and evaluate watch expressions that depend on it.
    $scope.$watch('model', onModelOrFormStateChange, true);
    if ($scope.options.formState) {
      $scope.$watch('options.formState', onModelOrFormStateChange, true);
    }

    function onModelOrFormStateChange() {
      angular.forEach($scope.fields, function runFieldExpressionProperties(field, index) {
        /*jshint -W030 */
        const model = field.model || $scope.model;
        field.runExpressions && field.runExpressions(model);
        if (field.hideExpression) { // can't use hide with expressionProperties reliably
          const val = model[field.key];
          field.hide = evalCloseToFormlyExpression(field.hideExpression, val, field, index);
        }
      });
    }

    function setupFields() {
      $scope.fields = $scope.fields || [];
      const fieldTransform = $scope.options.fieldTransform || formlyConfig.extras.fieldTransform;

      if (fieldTransform) {
        $scope.fields = fieldTransform($scope.fields, $scope.model, $scope.options, $scope.form);
        if (!$scope.fields) {
          throw formlyUsability.getFormlyError('fieldTransform must return an array of fields');
        }
      }

      angular.forEach($scope.fields, initModel); // initializes the model property if set to 'formState'
      angular.forEach($scope.fields, attachKey); // attaches a key based on the index if a key isn't specified
      angular.forEach($scope.fields, setupWatchers); // setup watchers for all fields
    }

    function setupOptions() {
      formlyApiCheck.throw(
        [formlyApiCheck.formOptionsApi.optional], [$scope.options], {prefix: 'formly-form options check'}
      );
      $scope.options = $scope.options || {};
      $scope.options.formState = $scope.options.formState || {};

      angular.extend($scope.options, {
        updateInitialValue,
        resetModel
      });

    }

    function updateInitialValue() {
      angular.forEach($scope.fields, field => {
        if (isFieldGroup(field)) {
          field.options.updateInitialValue();
        } else {
          field.updateInitialValue();
        }
      });
    }

    function resetModel() {
      angular.forEach($scope.fields, field => {
        if (isFieldGroup(field)) {
          field.options.resetModel();
        } else {
          field.resetModel();
        }
      });
    }

    function initModel(field) {
      if (angular.isString(field.model)) {
        const expression = field.model;
        const index = $scope.fields.indexOf(field);
        field.model = evalCloseToFormlyExpression(expression, undefined, field, index);
        if (!field.model) {
          throw formlyUsability.getFieldError(
            'field-model-must-be-initialized',
            'Field model must be initialized. When specifying a model as a string for a field, the result of the' +
            ' expression must have been initialized ahead of time.',
            field);
        }
      }
    }

    function attachKey(field, index) {
      if (!isFieldGroup(field)) {
        field.key = field.key || index || 0;
      }
    }

    function setupWatchers(field, index) {
      if (isFieldGroup(field) || !angular.isDefined(field.watcher)) {
        return;
      }
      var watchers = field.watcher;
      if (!angular.isArray(watchers)) {
        watchers = [watchers];
      }
      angular.forEach(watchers, function setupWatcher(watcher) {
        if (!angular.isDefined(watcher.listener)) {
          throw formlyUsability.getFieldError(
            'all-field-watchers-must-have-a-listener',
            'All field watchers must have a listener', field
          );
        }
        var watchExpression = getWatchExpression(watcher, field, index);
        var watchListener = getWatchListener(watcher, field, index);

        var type = watcher.type || '$watch';
        watcher.stopWatching = $scope[type](watchExpression, watchListener, watcher.watchDeep);
      });
    }

    function getWatchExpression(watcher, field, index) {
      var watchExpression = watcher.expression || `model['${field.key}']`;
      if (angular.isFunction(watchExpression)) {
        // wrap the field's watch expression so we can call it with the field as the first arg
        // and the stop function as the last arg as a helper
        var originalExpression = watchExpression;
        watchExpression = function formlyWatchExpression() {
          var args = modifyArgs(watcher, index, ...arguments);
          return originalExpression(...args);
        };
        watchExpression.displayName = `Formly Watch Expression for field for ${field.key}`;
      }
      return watchExpression;
    }

    function getWatchListener(watcher, field, index) {
      var watchListener = watcher.listener;
      if (angular.isFunction(watchListener)) {
        // wrap the field's watch listener so we can call it with the field as the first arg
        // and the stop function as the last arg as a helper
        var originalListener = watchListener;
        watchListener = function formlyWatchListener() {
          var args = modifyArgs(watcher, index, ...arguments);
          return originalListener(...args);
        };
        watchListener.displayName = `Formly Watch Listener for field for ${field.key}`;
      }
      return watchListener;
    }

    function modifyArgs(watcher, index, ...originalArgs) {
      return [$scope.fields[index], ...originalArgs, watcher.stopWatching];
    }

    function evalCloseToFormlyExpression(expression, val, field, index) {
      const extraLocals = getFormlyFieldLikeLocals(field, index);
      return formlyUtil.formlyEval($scope, expression, val, val, extraLocals);
    }

    function getFormlyFieldLikeLocals(field, index) {
      // this makes it closer to what a regular formlyExpression would be
      return {
        options: field,
        index: index,
        formState: $scope.options.formState,
        formId: $scope.formId
      };
    }
  }

  function formlyFormLink(scope, el, attrs) {
    setFormController();
    fixChromeAutocomplete();

    function setFormController() {
      const formId = attrs.name;
      scope.formId = formId;
      scope.theFormlyForm = scope[formId];
      if (attrs.form) {
        const getter = $parse(attrs.form);
        const setter = getter.assign;
        const parentForm = getter(scope.$parent);
        if (parentForm) {
          scope.theFormlyForm = parentForm;
          if (scope[formId]) {
            scope.theFormlyForm.$removeControl(scope[formId]);
          }

          // this next line is probably one of the more dangerous things that angular-formly does to improve the
          // API for angular-formly forms. It ensures that the NgModelControllers inside of formly-form will be
          // attached to the form that is passed to formly-form rather than the one that formly-form creates
          // this is necessary because it's confusing to have a step between the form you pass in
          // and the fields in that form. It also is because angular doesn't propagate properties like $submitted down
          // to children forms :-( This line was added to solve this issue:
          // https://github.com/formly-js/angular-formly/issues/287
          // luckily, this is how the formController has been accessed by the NgModelController since angular 1.0.0
          // so I expect it will remain this way for the life of angular 1.x
          el.removeData('$formController');
        } else {
          setter(scope.$parent, scope[formId]);
        }
      }
      if (!scope.theFormlyForm && !formlyConfig.disableWarnings) {
        /* eslint no-console:0 */
        formlyWarn(
          'formly-form-has-no-formcontroller',
          'Your formly-form does not have a `form` property. Many functions of the form (like validation) may not work',
          el,
          scope
        );
      }
    }

    /**
     * chrome autocomplete lameness
     * see https://code.google.com/p/chromium/issues/detail?id=468153#c14
     * ლ(ಠ益ಠლ)   (╯°□°)╯︵ ┻━┻    (◞‸◟；)
     */
    function fixChromeAutocomplete() {
      const global = formlyConfig.extras.removeChromeAutoComplete === true;
      const offInstance = scope.options && scope.options.removeChromeAutoComplete === false;
      const onInstance = scope.options && scope.options.removeChromeAutoComplete === true;
      if ((global && !offInstance) || onInstance) {
        const input = document.createElement('input');
        input.setAttribute('autocomplete', 'address-level4');
        input.setAttribute('hidden', 'true');
        el[0].appendChild(input);
      }

    }
  }

  // stateless util functions
  function toKebabCase(string) {
    if (string) {
      return string.replace(/([A-Z])/g, $1 => '-' + $1.toLowerCase());
    } else {
      return '';
    }
  }

  function isFieldGroup(field) {
    return field && !!field.fieldGroup;
  }
}
<|endoftext|>


<commit_before>export default callback => {
	// connect to a database if needed, the pass it to `callback`:
	callback();
}
<commit_msg>Update grammer mistake<commit_after>export default callback => {
	// connect to a database if needed, then pass it to `callback`:
	callback();
}
<|endoftext|>