<commit_before>/*
---

script: Uploader.js

description: Add your widget have a real form value.

license: Public domain (http://unlicense.org).

requires:
  - LSD.Mixin
  - Widgets/LSD.Widget.Button
  - Widgets/LSD.Widget.Progress
  - Uploader/*
  - LSD.Mixin.List
  - Core/JSON

provides:
  - LSD.Mixin.Uploader

...
*/

LSD.Mixin.Uploader = new Class({
  options: {
    actions: {
      uploader: {
        enable: function() {
          if (this.attributes.multiple) this.options.uploader.multiple = true;
          var uploader = this.getUploader();
          this.properties.set('uploader', uploader);
          var target = this.getUploaderTarget();
          if (target) uploader.attach(target);
          this.getStoredBlobs().each(this.addFile, this);
        },
        disable: function() {
          var uploader = this.getUploader();
          uploader.detach(this.getUploaderTarget())
          this.properties.unset('uploader', uploader);
        }
      }
    },
    events: {
      uploader: {
        fileComplete: 'onFileComplete',
        fileRemove: 'onFileRemove',
        fileProgress: 'onFileProgress',
        beforeSelect: 'onBeforeFileSelect'
      }
    },
    has: {
      many: {
        targets: {
          selector: ':uploading',
          as: 'uploader',
          relay: {
            mouseover: function() {
              this.uploader.getUploader().attach(this.toElement());
            }
          }
        }
      }
    },
    states: Array.object('empty'),
    uploader: {
      instantStart: true,
      timeLimit: 36000,
      queued: false,
      multiple: false,
      allowDuplicates: true
    }
  },

  constructors: {
    uploader: function(options, state) {
      if (state) this.blobs = {};
      else delete this.blobs;
    }
  },

  getUploader: function() {
    if (this.uploader) return this.uploader;
    var options = Object.append({}, this.options.uploader);
    if (!options.fileClass) options.fileClass = this.getUploaderFileClass(Uploader.getAdapter());
    this.uploader = new Uploader(options);
    this.uploader.widget = this;
    return this.uploader;
  },

  onBeforeFileSelect: function() {
    this.lastUploaderTarget =  this.getUploader().target;
  },

  getUploaderTarget: function() {
    var target = this.options.uploader.target;
    if (target) {
      return target;
    } else if (target !== false) {
      return this.element
    }
  },

  getUploaderFileClass: function(adapter) {
    if (!adapter) adapter = Uploader.getAdapter();
    if (adapter.indexOf) adapter = Uploader[LSD.toClassName(adapter)];
    if (!adapter.File.Widget) {
      var Klass = new Class({
        Implements: [adapter.File, this.getUploaderFileClassBase()]
      });
      if (Klass.prototype.setProperties == adapter.File.prototype.setProperties)
        delete Klass.prototype.setProperties;
      adapter.File.Widget = function() {
        return new LSD.Widget().mixin(Klass, true);
      }
    }
    return adapter.File.Widget;
  },

  getUploaderFileClassBase: function() {
    return LSD.Widget.Filelist.File
  },

  onFileComplete: function(file) {
    var blob = this.processStoredBlob(file.response.text);
    if (blob && !blob.errors) {
      this.onFileSuccess(file, blob);
    } else {
      this.onFileFailure(file, blob || response);
    }
  },

  processValue: function(blob) {
    return blob.id || blob.uid || blob._id;
  },

  onFileSuccess: function(file, blob) {
    this.addBlob(file, blob);
    file.fireEvent('success', blob);
  },

  onFileFailure: function(file, blob) {
    file.fireEvent('failure', blob);
  },

  onFileRemove: function(file) {
    this.removeBlob(file);
  },

  getBlob: function(file) {
    return this.blobs[file.id];
  },

  addBlob: function(file, blob) {
    if (this.options.uploader.value !== false) this.setValue(blob);
    this.blobs[file.id] = blob;
  },

  removeBlob: function(file) {
    if (this.options.uploader.value !== false) this.setValue(this.blobs[file.id], true);
    delete this.blobs[file.id];
  },

  retrieveStoredBlobs: function() {
    var attrs = this.attributes;
    return attrs.file || attrs.files || attrs.blobs || attrs.blob;
  },

  processStoredBlob: function(response) {
    if (response.indexOf) response = JSON.decode(response);
    if (response && Object.getLength(response) == 1) response = response[Object.keys(response)[0]];
    return response;
  },

  getStoredBlobs: function() {
    var files = this.retrieveStoredBlobs();
    return files ? Array.from(JSON.decode(files)).map(this.processStoredBlob.bind(this)) : [];
  },

  addFile: function(blob) {
    var widget = new (this.getUploaderFileClass());
    widget.widget = this;
    widget.states.include('complete');
    widget.build();
    widget.setBase(this.getUploader());
    widget.setFile(blob);
    this.addBlob(widget, blob);
  }
});

LSD.Mixin.Upload = new Class({
  options: {
    has: {
      one: {
        progress: {
          source: 'progress',
          selector: 'progress'
        }
      }
    },
    events: {
      setBase: function() {
        if (this.target) this.targetWidget = this.target.retrieve('widget');
        this.inject(this.getWidget());
      },
      progress: function(progress) {
        if (this.progress) this.progress.set(progress.percentLoaded);
      },
      start: function() {
        this.states.include('started');
      },
      fail: function() {
        this.states.erase('started');
        this.states.include('failed');
      },
      complete: function() {
        if (this.progress) this.progress.set(100);
        this.states.erase('started');
        this.states.include('complete');
      },
      stop: function() {
        this.states.erase('started');
      },
      remove: 'dispose'
    }
  },

  getParentWidget: function(widget) {
    return widget;
  },

  getWidget: function() {
    return (this.widget || this.base.widget);
  },

  setProperties: function(properties) {
    for (var name in properties) {
      this[name] = properties[name];
      this.dataset.set(name, properties[name]);
    }
  }
});

LSD.Widget.Filelist = new Class({
  options: {
    tag: 'filelist',
    inline: 'ul',
    pseudos: Array.from(':items'),
    has: {
      many: {
        items: {
          selector: 'file',
          source: 'filelist-file'
        }
      }
    }
  }
});

LSD.Widget.Filelist.File = new Class({
  options: {
    tag: 'file',
    inline: 'li',
    layout: {
      'span::container': '{name}',
      '::progress': true,
      '::canceller': 'Cancel'
    },
    pseudos: Array.object('upload'),
    has: {
      one: {
        canceller: {
          selector: 'a.cancel',
          source: 'a.cancel',
          events: {
            click: 'cancel'
          }
        }
      }
    }
  }
});

LSD.Behavior.define(':uploader', 'uploader');
LSD.Behavior.define(':upload', 'upload');<commit_msg>Make uploader not verbose by default. Fixes large file "progress" log spam<commit_after>/*
---

script: Uploader.js

description: Add your widget have a real form value.

license: Public domain (http://unlicense.org).

requires:
  - LSD.Mixin
  - Widgets/LSD.Widget.Button
  - Widgets/LSD.Widget.Progress
  - Uploader/*
  - LSD.Mixin.List
  - Core/JSON

provides:
  - LSD.Mixin.Uploader

...
*/

LSD.Mixin.Uploader = new Class({
  options: {
    actions: {
      uploader: {
        enable: function() {
          if (this.attributes.multiple) this.options.uploader.multiple = true;
          var uploader = this.getUploader();
          this.properties.set('uploader', uploader);
          var target = this.getUploaderTarget();
          if (target) uploader.attach(target);
          this.getStoredBlobs().each(this.addFile, this);
        },
        disable: function() {
          var uploader = this.getUploader();
          uploader.detach(this.getUploaderTarget())
          this.properties.unset('uploader', uploader);
        }
      }
    },
    events: {
      uploader: {
        fileComplete: 'onFileComplete',
        fileRemove: 'onFileRemove',
        fileProgress: 'onFileProgress',
        beforeSelect: 'onBeforeFileSelect'
      }
    },
    has: {
      many: {
        targets: {
          selector: ':uploading',
          as: 'uploader',
          relay: {
            mouseover: function() {
              this.uploader.getUploader().attach(this.toElement());
            }
          }
        }
      }
    },
    states: Array.object('empty'),
    uploader: {
      instantStart: true,
      timeLimit: 36000,
      queued: false,
      multiple: false,
      verbose: false,
      allowDuplicates: true
    }
  },

  constructors: {
    uploader: function(options, state) {
      if (state) this.blobs = {};
      else delete this.blobs;
    }
  },

  getUploader: function() {
    if (this.uploader) return this.uploader;
    var options = Object.append({}, this.options.uploader);
    if (!options.fileClass) options.fileClass = this.getUploaderFileClass(Uploader.getAdapter());
    this.uploader = new Uploader(options);
    this.uploader.widget = this;
    return this.uploader;
  },

  onBeforeFileSelect: function() {
    this.lastUploaderTarget =  this.getUploader().target;
  },

  getUploaderTarget: function() {
    var target = this.options.uploader.target;
    if (target) {
      return target;
    } else if (target !== false) {
      return this.element
    }
  },

  getUploaderFileClass: function(adapter) {
    if (!adapter) adapter = Uploader.getAdapter();
    if (adapter.indexOf) adapter = Uploader[LSD.toClassName(adapter)];
    if (!adapter.File.Widget) {
      var Klass = new Class({
        Implements: [adapter.File, this.getUploaderFileClassBase()]
      });
      if (Klass.prototype.setProperties == adapter.File.prototype.setProperties)
        delete Klass.prototype.setProperties;
      adapter.File.Widget = function() {
        return new LSD.Widget().mixin(Klass, true);
      }
    }
    return adapter.File.Widget;
  },

  getUploaderFileClassBase: function() {
    return LSD.Widget.Filelist.File
  },

  onFileComplete: function(file) {
    var blob = this.processStoredBlob(file.response.text);
    if (blob && !blob.errors) {
      this.onFileSuccess(file, blob);
    } else {
      this.onFileFailure(file, blob || response);
    }
  },

  processValue: function(blob) {
    return blob.id || blob.uid || blob._id;
  },

  onFileSuccess: function(file, blob) {
    this.addBlob(file, blob);
    file.fireEvent('success', blob);
  },

  onFileFailure: function(file, blob) {
    file.fireEvent('failure', blob);
  },

  onFileRemove: function(file) {
    this.removeBlob(file);
  },

  getBlob: function(file) {
    return this.blobs[file.id];
  },

  addBlob: function(file, blob) {
    if (this.options.uploader.value !== false) this.setValue(blob);
    this.blobs[file.id] = blob;
  },

  removeBlob: function(file) {
    if (this.options.uploader.value !== false) this.setValue(this.blobs[file.id], true);
    delete this.blobs[file.id];
  },

  retrieveStoredBlobs: function() {
    var attrs = this.attributes;
    return attrs.file || attrs.files || attrs.blobs || attrs.blob;
  },

  processStoredBlob: function(response) {
    if (response.indexOf) response = JSON.decode(response);
    if (response && Object.getLength(response) == 1) response = response[Object.keys(response)[0]];
    return response;
  },

  getStoredBlobs: function() {
    var files = this.retrieveStoredBlobs();
    return files ? Array.from(JSON.decode(files)).map(this.processStoredBlob.bind(this)) : [];
  },

  addFile: function(blob) {
    var widget = new (this.getUploaderFileClass());
    widget.widget = this;
    widget.states.include('complete');
    widget.build();
    widget.setBase(this.getUploader());
    widget.setFile(blob);
    this.addBlob(widget, blob);
  }
});

LSD.Mixin.Upload = new Class({
  options: {
    has: {
      one: {
        progress: {
          source: 'progress',
          selector: 'progress'
        }
      }
    },
    events: {
      setBase: function() {
        if (this.target) this.targetWidget = this.target.retrieve('widget');
        this.inject(this.getWidget());
      },
      progress: function(progress) {
        if (this.progress) this.progress.set(progress.percentLoaded);
      },
      start: function() {
        this.states.include('started');
      },
      fail: function() {
        this.states.erase('started');
        this.states.include('failed');
      },
      complete: function() {
        if (this.progress) this.progress.set(100);
        this.states.erase('started');
        this.states.include('complete');
      },
      stop: function() {
        this.states.erase('started');
      },
      remove: 'dispose'
    }
  },

  getParentWidget: function(widget) {
    return widget;
  },

  getWidget: function() {
    return (this.widget || this.base.widget);
  },

  setProperties: function(properties) {
    for (var name in properties) {
      this[name] = properties[name];
      this.dataset.set(name, properties[name]);
    }
  }
});

LSD.Widget.Filelist = new Class({
  options: {
    tag: 'filelist',
    inline: 'ul',
    pseudos: Array.from(':items'),
    has: {
      many: {
        items: {
          selector: 'file',
          source: 'filelist-file'
        }
      }
    }
  }
});

LSD.Widget.Filelist.File = new Class({
  options: {
    tag: 'file',
    inline: 'li',
    layout: {
      'span::container': '{name}',
      '::progress': true,
      '::canceller': 'Cancel'
    },
    pseudos: Array.object('upload'),
    has: {
      one: {
        canceller: {
          selector: 'a.cancel',
          source: 'a.cancel',
          events: {
            click: 'cancel'
          }
        }
      }
    }
  }
});

LSD.Behavior.define(':uploader', 'uploader');
LSD.Behavior.define(':upload', 'upload');<|endoftext|>


<commit_before>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2014 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {

	var module = angular.module('axelor.ng', []);

	module.config(['$provide', function($provide) {

		$provide.decorator('$rootScope', ['$delegate', '$exceptionHandler', '$injector', function ($rootScope, $exceptionHandler, $injector) {

			var __proto__ = Object.getPrototypeOf($rootScope),
				__super__ = {},
				__custom__ = {};

			for (var name in __proto__) {
				if (angular.isFunction(__proto__[name])) {
					__super__[name] = __proto__[name];
				}
			}

			var $http = null,
				$timeout = null;

			__custom__.ajaxStop = function ajaxStop(callback, context) {
				var count, wait;

				if ($http === null) {
					$http = $injector.get('$http');
				}

				count = _.size($http.pendingRequests || []);
				wait = _.last(arguments) || 10;

				if (_.isNumber(context)) {
					context = undefined;
				}

				if (count > 0) {
					return _.delay(ajaxStop.bind(this), wait, callback, context);
				}
				if (_.isFunction(callback)) {
					return this.$timeout(callback.bind(context), wait);
				}
			};

			__custom__.waitForActions = function waitForActions(callback) {
				var that = this;
				this.$timeout(function () {
					that.ajaxStop(function () {
						that.$timeout(callback, 100);
					}, 200);
				}, 100);
			};

			__custom__.applyLater = function applyLater(func, wait) {
				return this.$timeout(func ||angular.noop, wait);
			};

			__custom__.$callWhen = function (predicate, callback, wait) {
				var count = wait || 100;

				function later() {
					if (count-- === 0 || (_.isFunction(predicate) && predicate())) {
						return callback();
					}
					return _.delay(later, count);
				}

				this.$timeout(later, wait);
			};

			__custom__.$timeout = function(func, wait, invokeApply) {
				if ($timeout === null) {
					$timeout = $injector.get('$timeout');
				}
				if (arguments.length === 0) {
					return $timeout();
				}
				return $timeout.apply(null, arguments);
			};

			__custom__.$new = function $new() {
				var inst = __super__.$new.apply(this, arguments);
				inst.$$watchChecker = this.$$watchChecker;
				inst.$$watchInitialized = false;
				inst.$$childCanWatch = true;
				inst.$$shouldWatch = false;
				return inst;
			};

			// make sure to patch $rootScope.$digest with
			// if ((!current.$$canWatch || current.$$canWatch(current)) && (watchers = current.$$watchers)) {
			//   ...
			// }

			__custom__.$$canWatch = function () {
				if (!this.$$watchInitialized || !this.$$watchChecker) {
					return this.$$watchInitialized = true;
				}
				if (this.$$shouldWatch === true) {
					return true;
				}
				var parent = this.$parent || {};
				if (parent.$$childCanWatch !== undefined && !parent.$$childCanWatch) {
					return false;
				}
				return this.$$childCanWatch = this.$$watchChecker(this);
			};

			__custom__.$watchChecker = function (checker) {

				var self = this,
					previous = this.$$watchChecker;

				if (this.$$watchChecker === null) {
					this.$$watchChecker = checker;
				} else {
					this.$$watchChecker = function() {
						return previous(self) && checker(self);
					};
				}
			};

			function debouncedApply(wait) {
				return _.debounce(__super__.$apply, wait);
			}

			var $apply1 = _.debounce(__super__.$apply);
			var $apply2 = _.debounce(__super__.$apply, 100);

			var $debouncedApply = _.debounce(function $debouncedApply() {
				if ($http === null) {
					$http = $injector.get('$http');
				}
				if ($http.pendingRequests.length) {
					return $apply2.call(this);
				}
				return $apply1.call(this);
			});

			__custom__.$apply = function $apply() {
				if (arguments.length === 0) {
					return $debouncedApply.call(this);
				}
				return __super__.$apply.apply(this, arguments);
			};

			angular.extend(__proto__, __custom__);
			angular.extend($rootScope, __custom__);

			$rootScope.$$watchChecker = null;

			return $rootScope;
		}]);
	}]);

}());<commit_msg>Added scope.$applyNow<commit_after>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2014 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {

	var module = angular.module('axelor.ng', []);

	module.config(['$provide', function($provide) {

		$provide.decorator('$rootScope', ['$delegate', '$exceptionHandler', '$injector', function ($rootScope, $exceptionHandler, $injector) {

			var __proto__ = Object.getPrototypeOf($rootScope),
				__super__ = {},
				__custom__ = {};

			for (var name in __proto__) {
				if (angular.isFunction(__proto__[name])) {
					__super__[name] = __proto__[name];
				}
			}

			var $http = null,
				$timeout = null;

			__custom__.ajaxStop = function ajaxStop(callback, context) {
				var count, wait;

				if ($http === null) {
					$http = $injector.get('$http');
				}

				count = _.size($http.pendingRequests || []);
				wait = _.last(arguments) || 10;

				if (_.isNumber(context)) {
					context = undefined;
				}

				if (count > 0) {
					return _.delay(ajaxStop.bind(this), wait, callback, context);
				}
				if (_.isFunction(callback)) {
					return this.$timeout(callback.bind(context), wait);
				}
			};

			__custom__.waitForActions = function waitForActions(callback) {
				var that = this;
				this.$timeout(function () {
					that.ajaxStop(function () {
						that.$timeout(callback, 100);
					}, 200);
				}, 100);
			};

			__custom__.applyLater = function applyLater(func, wait) {
				return this.$timeout(func ||angular.noop, wait);
			};

			__custom__.$callWhen = function (predicate, callback, wait) {
				var count = wait || 100;

				function later() {
					if (count-- === 0 || (_.isFunction(predicate) && predicate())) {
						return callback();
					}
					return _.delay(later, count);
				}

				this.$timeout(later, wait);
			};

			__custom__.$timeout = function(func, wait, invokeApply) {
				if ($timeout === null) {
					$timeout = $injector.get('$timeout');
				}
				if (arguments.length === 0) {
					return $timeout();
				}
				return $timeout.apply(null, arguments);
			};

			__custom__.$new = function $new() {
				var inst = __super__.$new.apply(this, arguments);
				inst.$$watchChecker = this.$$watchChecker;
				inst.$$watchInitialized = false;
				inst.$$childCanWatch = true;
				inst.$$shouldWatch = false;
				return inst;
			};

			// make sure to patch $rootScope.$digest with
			// if ((!current.$$canWatch || current.$$canWatch(current)) && (watchers = current.$$watchers)) {
			//   ...
			// }

			__custom__.$$canWatch = function () {
				if (!this.$$watchInitialized || !this.$$watchChecker) {
					return this.$$watchInitialized = true;
				}
				if (this.$$shouldWatch === true) {
					return true;
				}
				var parent = this.$parent || {};
				if (parent.$$childCanWatch !== undefined && !parent.$$childCanWatch) {
					return false;
				}
				return this.$$childCanWatch = this.$$watchChecker(this);
			};

			__custom__.$watchChecker = function (checker) {

				var self = this,
					previous = this.$$watchChecker;

				if (this.$$watchChecker === null) {
					this.$$watchChecker = checker;
				} else {
					this.$$watchChecker = function() {
						return previous(self) && checker(self);
					};
				}
			};

			function debouncedApply(wait) {
				return _.debounce(__super__.$apply, wait);
			}

			var $apply1 = _.debounce(__super__.$apply);
			var $apply2 = _.debounce(__super__.$apply, 100);

			var $debouncedApply = _.debounce(function $debouncedApply() {
				if ($http === null) {
					$http = $injector.get('$http');
				}
				if ($http.pendingRequests.length) {
					return $apply2.call(this);
				}
				return $apply1.call(this);
			});

			__custom__.$apply = function $apply() {
				if (arguments.length === 0) {
					return $debouncedApply.call(this);
				}
				return __super__.$apply.apply(this, arguments);
			};

			__custom__.$applyNow = function $applyNow() {
				return __super__.$apply.apply(this, arguments);
			};

			angular.extend(__proto__, __custom__);
			angular.extend($rootScope, __custom__);

			$rootScope.$$watchChecker = null;

			return $rootScope;
		}]);
	}]);

}());<|endoftext|>


<commit_before>// (C) Copyright 2014-2015 Hewlett Packard Enterprise Development LP

var React = require('react');
var Form = require('grommet/components/Form');
var FormFields = require('grommet/components/FormFields');
var FormField = require('grommet/components/FormField');
var Header = require('grommet/components/Header');
var Menu = require('grommet/components/Menu');
var CheckBox = require('grommet/components/CheckBox');
var RadioButton = require('grommet/components/RadioButton');
var SearchInput = require('grommet/components/SearchInput');
var Table = require('grommet/components/Table');
var Footer = require('grommet/components/Footer');
var Button = require('grommet/components/Button');
var Calendar = require('grommet/components/Calendar');

var FullForm = React.createClass({

  propTypes: {
    compact: React.PropTypes.bool,
    onCancel: React.PropTypes.func,
    onSubmit: React.PropTypes.func,
    prefix: React.PropTypes.string
  },

  getDefaultProps: function () {
    return {prefix: 'ff'};
  },

  getInitialState: function () {
    return {
      rangeValue: 10,
      searchInput: {suggestions: this._searchInputSuggestions},
      calendarDate: (new Date()).toISOString().slice(0, 10)
    };
  },

  _searchInputSuggestions: ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight'],

  _onChangeRange: function (event) {
    this.setState({rangeValue: event.target.value});
  },

  _onChange: function (event) {
    console.log('!!! FullForm changed', event.target, 'to', event.target.value);
  },

  _onSearchInputChange: function (value) {
    console.log('!!! FullForm _onSearchInputChange', value);
    this.setState({
      searchInput: {
        value: value,
        suggestions: this._searchInputSuggestions
      }
    });
  },

  _onCalendarChange: function (value) {
    console.log('!!! FullForm _onCalendarChange', value);
    this.setState({calendarDate: value});
  },

  _onSearch: function (text) {
    var searchInput = this.state.searchInput;
    var regexp = new RegExp('^' + text);
    searchInput.suggestions =
      this._searchInputSuggestions.filter(function (value) {
        return regexp.test(value);
      });
    this.setState({searchInput: searchInput});
  },

  render: function() {
    var p = this.props.prefix;

    return (
      <Form onSubmit={this.props.onSubmit} compact={this.props.compact}>

Edit

        <FormFields>
          <fieldset>
            <legend>First section</legend>
            <FormField label="Item 1" htmlFor={p + "item1"} help="something helpful">
              <input id={p + "item1"} name="item-1" type="text" onChange={this._onChange} />
            </FormField>
            <FormField>
              <CheckBox id={p + "item2"} name="item-2" label="Item 2"
                onChange={this._onChange} />
            </FormField>
            <FormField label="Item 3">
              <RadioButton id={p + "item3-1"} name="item-3" label="first"
                onChange={this._onChange} />
              <RadioButton id={p + "item3-2"} name="item-3" label="second"
                onChange={this._onChange} />
            </FormField>
            <FormField label="Item 4" htmlFor={p + "item4"}
              error="something's wrong">
              <textarea id={p + "item4"} name="item-4"></textarea>
            </FormField>
            <FormField label="Item 5" htmlFor={p + "item5"}>
              <SearchInput id={p + "item5"} name="item-5"
                value={this.state.searchInput.value}
                suggestions={this.state.searchInput.suggestions}
                onChange={this._onSearchInputChange}
                onSearch={this._onSearchInputSearch} />
            </FormField>
            <FormField label="Item 6" htmlFor={p + "item6"}
              help={learn more ...}>
              <select id={p + "item6"} name="item-6">
                <option>first</option>
                <option>second</option>
                <option>third</option>
              </select>
            </FormField>
            <FormField label="Item 10" htmlFor={p + "item10"}>
              <Calendar id={p + "item10"} name="item-10"
                value={this.state.calendarDate}
                onChange={this._onCalendarChange} />
            </FormField>
          </fieldset>
          <fieldset>
            <legend>Another section</legend>

Some informational text.

            <FormField label="Item 7">

                    first
                    123

                    second
                    456

                    third
                    789

            </FormField>
            <FormField label="Item 8" htmlFor={p + "item8"}>
              <input id={p + "item8"} name="item-8" type="number"
                min="1" max="20" step="1" defaultValue="10" />
            </FormField>
            <FormField label="Item 9" htmlFor={p + "item9"} help={this.state.rangeValue}>
              <input id={p + "item9"} name="item-9" type="range"
                min="1" max="20" defaultValue="10"
                onChange={this._onChangeRange}/>
            </FormField>
          </fieldset>
        </FormFields>

          <Menu direction="right">
            <Button label="OK" primary={true} strong={true} onClick={this.props.onSubmit} />
            <Button label="Cancel" onClick={this.props.onCancel} />
          </Menu>

      </Form>
    );
  }
});

module.exports = FullForm;
<commit_msg>Removed cancel button from footer of sample full form.<commit_after>// (C) Copyright 2014-2015 Hewlett Packard Enterprise Development LP

var React = require('react');
var Form = require('grommet/components/Form');
var FormFields = require('grommet/components/FormFields');
var FormField = require('grommet/components/FormField');
var Header = require('grommet/components/Header');
var Menu = require('grommet/components/Menu');
var CheckBox = require('grommet/components/CheckBox');
var RadioButton = require('grommet/components/RadioButton');
var SearchInput = require('grommet/components/SearchInput');
var Table = require('grommet/components/Table');
var Footer = require('grommet/components/Footer');
var Button = require('grommet/components/Button');
var Calendar = require('grommet/components/Calendar');

var FullForm = React.createClass({

  propTypes: {
    compact: React.PropTypes.bool,
    onCancel: React.PropTypes.func,
    onSubmit: React.PropTypes.func,
    prefix: React.PropTypes.string
  },

  getDefaultProps: function () {
    return {prefix: 'ff'};
  },

  getInitialState: function () {
    return {
      rangeValue: 10,
      searchInput: {suggestions: this._searchInputSuggestions},
      calendarDate: (new Date()).toISOString().slice(0, 10)
    };
  },

  _searchInputSuggestions: ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight'],

  _onChangeRange: function (event) {
    this.setState({rangeValue: event.target.value});
  },

  _onChange: function (event) {
    console.log('!!! FullForm changed', event.target, 'to', event.target.value);
  },

  _onSearchInputChange: function (value) {
    console.log('!!! FullForm _onSearchInputChange', value);
    this.setState({
      searchInput: {
        value: value,
        suggestions: this._searchInputSuggestions
      }
    });
  },

  _onCalendarChange: function (value) {
    console.log('!!! FullForm _onCalendarChange', value);
    this.setState({calendarDate: value});
  },

  _onSearch: function (text) {
    var searchInput = this.state.searchInput;
    var regexp = new RegExp('^' + text);
    searchInput.suggestions =
      this._searchInputSuggestions.filter(function (value) {
        return regexp.test(value);
      });
    this.setState({searchInput: searchInput});
  },

  render: function() {
    var p = this.props.prefix;

    return (
      <Form onSubmit={this.props.onSubmit} compact={this.props.compact}>

Edit

        <FormFields>
          <fieldset>
            <legend>First section</legend>
            <FormField label="Item 1" htmlFor={p + "item1"} help="something helpful">
              <input id={p + "item1"} name="item-1" type="text" onChange={this._onChange} />
            </FormField>
            <FormField>
              <CheckBox id={p + "item2"} name="item-2" label="Item 2"
                onChange={this._onChange} />
            </FormField>
            <FormField label="Item 3">
              <RadioButton id={p + "item3-1"} name="item-3" label="first"
                onChange={this._onChange} />
              <RadioButton id={p + "item3-2"} name="item-3" label="second"
                onChange={this._onChange} />
            </FormField>
            <FormField label="Item 4" htmlFor={p + "item4"}
              error="something's wrong">
              <textarea id={p + "item4"} name="item-4"></textarea>
            </FormField>
            <FormField label="Item 5" htmlFor={p + "item5"}>
              <SearchInput id={p + "item5"} name="item-5"
                value={this.state.searchInput.value}
                suggestions={this.state.searchInput.suggestions}
                onChange={this._onSearchInputChange}
                onSearch={this._onSearchInputSearch} />
            </FormField>
            <FormField label="Item 6" htmlFor={p + "item6"}
              help={learn more ...}>
              <select id={p + "item6"} name="item-6">
                <option>first</option>
                <option>second</option>
                <option>third</option>
              </select>
            </FormField>
            <FormField label="Item 10" htmlFor={p + "item10"}>
              <Calendar id={p + "item10"} name="item-10"
                value={this.state.calendarDate}
                onChange={this._onCalendarChange} />
            </FormField>
          </fieldset>
          <fieldset>
            <legend>Another section</legend>

Some informational text.

            <FormField label="Item 7">

                    first
                    123

                    second
                    456

                    third
                    789

            </FormField>
            <FormField label="Item 8" htmlFor={p + "item8"}>
              <input id={p + "item8"} name="item-8" type="number"
                min="1" max="20" step="1" defaultValue="10" />
            </FormField>
            <FormField label="Item 9" htmlFor={p + "item9"} help={this.state.rangeValue}>
              <input id={p + "item9"} name="item-9" type="range"
                min="1" max="20" defaultValue="10"
                onChange={this._onChangeRange}/>
            </FormField>
          </fieldset>
        </FormFields>

          <Menu direction="right">
            <Button label="OK" primary={true} strong={true} onClick={this.props.onSubmit} />
          </Menu>

      </Form>
    );
  }
});

module.exports = FullForm;
<|endoftext|>


<commit_before>/**
 * dirPagination - AngularJS module for paginating (almost) anything.
 *
 *
 * Credits
 * =======
 *
 * Daniel Tabuenca: https://groups.google.com/d/msg/angular/an9QpzqIYiM/r8v-3W1X5vcJ
 * for the idea on how to dynamically invoke the ng-repeat directive.
 *
 * I borrowed a couple of lines and a few attribute names from the AngularUI Bootstrap project:
 * https://github.com/angular-ui/bootstrap/blob/master/src/pagination/pagination.js
 *
 * Copyright 2014 Michael Bromley <michael@michaelbromley.co.uk>
 */

(function() {

    /**
     * Config
     */
    var moduleName = 'angularUtils.directives.dirPagination';
    var templatePath = 'directives/pagination/dirPagination.tpl.html';

    /**
     * Module
     */
    var module;
    try {
        module = angular.module(moduleName);
    } catch(err) {
        // named module does not exist, so create one
        module = angular.module(moduleName, []);
    }

    module.directive('dirPaginate', ['$compile', '$parse', '$timeout', 'paginationService', function($compile, $parse, $timeout, paginationService) {
        return  {
            terminal: true,
            multiElement: true,
            priority: 5000, // This setting is used in conjunction with the later call to $compile() to prevent infinite recursion of compilation
            compile: function dirPaginationCompileFn(tElement, tAttrs){

                // Add ng-repeat to the dom element
                if (tElement[0].hasAttribute('dir-paginate-start') || tElement[0].hasAttribute('data-dir-paginate-start')) {
                    // using multiElement mode (dir-paginate-start, dir-paginate-end)
                    tAttrs.$set('ngRepeatStart', tAttrs.dirPaginate);
                    tElement.eq(tElement.length - 1).attr('ng-repeat-end', true);
                } else {
                    tAttrs.$set('ngRepeat', tAttrs.dirPaginate);
                }

                var expression = tAttrs.dirPaginate;
                // regex taken directly from https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js#L211
                var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);

                var filterPattern = /\|\s*itemsPerPage\s*:[^|]*/;
                if (match[2].match(filterPattern) === null) {
                    throw 'pagination directive: the \'itemsPerPage\' filter must be set.';
                }
                var itemsPerPageFilterRemoved = match[2].replace(filterPattern, '');
                var collectionGetter = $parse(itemsPerPageFilterRemoved);

                var paginationId = tAttrs.paginationId || '__default';
                paginationService.registerInstance(paginationId);

                return function dirPaginationLinkFn(scope, element, attrs){
                    var compiled =  $compile(element, false, 5000); // we manually compile the element again, as we have now added ng-repeat. Priority less than 5000 prevents infinite recursion of compiling dirPaginate

                    var currentPageGetter;
                    if (attrs.currentPage) {
                        currentPageGetter = $parse(attrs.currentPage);
                    } else {
                        // if the current-page attribute was not set, we'll make our own
                        var defaultCurrentPage = paginationId + '__currentPage';
                        scope[defaultCurrentPage] = 1;
                        currentPageGetter = $parse(defaultCurrentPage);
                    }
                    paginationService.setCurrentPageParser(paginationId, currentPageGetter, scope);

                    if (typeof attrs.totalItems !== 'undefined') {
                        paginationService.setAsyncModeTrue(paginationId);
                        scope.$watch(function() {
                            return $parse(attrs.totalItems)(scope);
                        }, function (result) {
                            if (0 <= result) {
                                paginationService.setCollectionLength(paginationId, result);
                            }
                        });
                    } else {
                        scope.$watchCollection(function() {
                            return collectionGetter(scope);
                        }, function(collection) {
                            if (collection) {
                                paginationService.setCollectionLength(paginationId, collection.length);
                            }
                        });
                    }

                    // Delegate to the link function returned by the new compilation of the ng-repeat
                    compiled(scope);
                };
            }
        };
    }]);

    module.directive('dirPaginationControls', ['paginationService', function(paginationService) {
        var numberRegex = /^\d+$/;
        /**
         * Generate an array of page numbers (or the '...' string) which is used in an ng-repeat to generate the
         * links used in pagination
         *
         * @param currentPage
         * @param rowsPerPage
         * @param paginationRange
         * @param collectionLength
         * @returns {Array}
         */
        function generatePagesArray(currentPage, collectionLength, rowsPerPage, paginationRange) {
            var pages = [];
            var totalPages = Math.ceil(collectionLength / rowsPerPage);
            var halfWay = Math.ceil(paginationRange / 2);
            var position;

            if (currentPage <= halfWay) {
                position = 'start';
            } else if (totalPages - halfWay < currentPage) {
                position = 'end';
            } else {
                position = 'middle';
            }

            var ellipsesNeeded = paginationRange < totalPages;
            var i = 1;
            while (i <= totalPages && i <= paginationRange) {
                var pageNumber = calculatePageNumber(i, currentPage, paginationRange, totalPages);

                var openingEllipsesNeeded = (i === 2 && (position === 'middle' || position === 'end'));
                var closingEllipsesNeeded = (i === paginationRange - 1 && (position === 'middle' || position === 'start'));
                if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) {
                    pages.push('...');
                } else {
                    pages.push(pageNumber);
                }
                i ++;
            }
            return pages;
        }

        /**
         * Given the position in the sequence of pagination links [i], figure out what page number corresponds to that position.
         *
         * @param i
         * @param currentPage
         * @param paginationRange
         * @param totalPages
         * @returns {*}
         */
        function calculatePageNumber(i, currentPage, paginationRange, totalPages) {
            var halfWay = Math.ceil(paginationRange/2);
            if (i === paginationRange) {
                return totalPages;
            } else if (i === 1) {
                return i;
            } else if (paginationRange < totalPages) {
                if (totalPages - halfWay < currentPage) {
                    return totalPages - paginationRange + i;
                } else if (halfWay < currentPage) {
                    return currentPage - halfWay + i;
                } else {
                    return i;
                }
            } else {
                return i;
            }
        }

        return {
            restrict: 'AE',
            templateUrl: function(elem, attrs) {
                return attrs.templateUrl || templatePath;
            },
            scope: {
                maxSize: '=?',
                onPageChange: '&?'
            },
            link: function(scope, element, attrs) {
                var paginationId;
                paginationId = attrs.paginationId || '__default';
                if (!scope.maxSize) { scope.maxSize = 9; }
                scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true;
                scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false;

                if (!paginationService.isRegistered(paginationId)) {
                    var idMessage = (paginationId !== '__default') ? ' (id: ' + paginationId + ') ' : ' ';
                    throw 'pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive.';
                }

                var paginationRange = Math.max(scope.maxSize, 5);
                scope.pages = [];
                scope.pagination = {
                    last: 1,
                    current: 1
                };

                scope.$watch(function() {
                    return (paginationService.getCollectionLength(paginationId) + 1) * paginationService.getItemsPerPage(paginationId);
                }, function(length) {
                    if (0 < length) {
                        generatePagination();
                    }
                });

                scope.$watch(function() {
                    return (paginationService.getItemsPerPage(paginationId));
                }, function(prev,cur) {
                    if (prev != cur) {
                        goToPage(scope.pagination.current);
                    }
                });

                scope.$watch(function() {
                    return paginationService.getCurrentPage(paginationId);
                }, function(currentPage, previousPage) {
                    if (currentPage != previousPage) {
                        goToPage(currentPage);
                    }
                });

                scope.setCurrent = function(num) {
                    if (isValidPageNumber(num)) {
                        paginationService.setCurrentPage(paginationId, num);
                    }
                };

                function goToPage(num) {
                    if (isValidPageNumber(num)) {
                        scope.pages = generatePagesArray(num, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
                        scope.pagination.current = num;

                        // if a callback has been set, then call it with the page number as an argument
                        if (scope.onPageChange) {
                            scope.onPageChange({ newPageNumber : num });
                        }
                    }
                }

                function generatePagination() {
                    var page = parseInt(paginationService.getCurrentPage(paginationId)) || 1;

                    scope.pages = generatePagesArray(page, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
                    scope.pagination.current = page;
                    scope.pagination.last = scope.pages[scope.pages.length - 1];
                    if (scope.pagination.last < scope.pagination.current) {
                        scope.setCurrent(scope.pagination.last);
                    }
                }

                function isValidPageNumber(num) {
                    return (numberRegex.test(num) && (0 < num && num <= scope.pagination.last));
                }
            }
        };
    }]);

    module.filter('itemsPerPage', ['paginationService', function(paginationService) {
        return function(collection, itemsPerPage, paginationId) {
            if (typeof (paginationId) === 'undefined') {
                paginationId = '__default';
            }
            if (!paginationService.isRegistered(paginationId)) {
                throw 'pagination directive: the itemsPerPage id argument (id: ' + paginationId + ') does not match a registered pagination-id.';
            }
            var end;
            var start;
            if (collection instanceof Array) {
                itemsPerPage = parseInt(itemsPerPage) || 9999999999;
                if (paginationService.isAsyncMode(paginationId)) {
                    start = 0;
                } else {
                    start = (paginationService.getCurrentPage(paginationId) - 1) * itemsPerPage;
                }
                end = start + itemsPerPage;
                paginationService.setItemsPerPage(paginationId, itemsPerPage);

                return collection.slice(start, end);
            } else {
                return collection;
            }
        };
    }]);

    module.service('paginationService', function() {
        var instances = {};
        var lastRegisteredInstance;

        this.registerInstance = function(instanceId) {
            if (typeof instances[instanceId] === 'undefined') {
                instances[instanceId] = {
                    asyncMode: false
                };
                lastRegisteredInstance = instanceId;
            }
        };

        this.isRegistered = function(instanceId) {
            return (typeof instances[instanceId] !== 'undefined');
        };

        this.getLastInstanceId = function() {
            return lastRegisteredInstance;
        };

        this.setCurrentPageParser = function(instanceId, val, scope) {
            instances[instanceId].currentPageParser = val;
            instances[instanceId].context = scope;
        };
        this.setCurrentPage = function(instanceId, val) {
            instances[instanceId].currentPageParser.assign(instances[instanceId].context, val);
        };
        this.getCurrentPage = function(instanceId) {
            var parser = instances[instanceId].currentPageParser;
            return parser ? parser(instances[instanceId].context) : 1;
        };

        this.setItemsPerPage = function(instanceId, val) {
            instances[instanceId].itemsPerPage = val;
        };
        this.getItemsPerPage = function(instanceId) {
            return instances[instanceId].itemsPerPage;
        };

        this.setCollectionLength = function(instanceId, val) {
            instances[instanceId].collectionLength = val;
        };
        this.getCollectionLength = function(instanceId) {
            return instances[instanceId].collectionLength;
        };

        this.setAsyncModeTrue = function(instanceId) {
            instances[instanceId].asyncMode = true;
        };

        this.isAsyncMode = function(instanceId) {
            return instances[instanceId].asyncMode;
        };
    });
})();
<commit_msg>Make templatePath globally configurable<commit_after>/**
 * dirPagination - AngularJS module for paginating (almost) anything.
 *
 *
 * Credits
 * =======
 *
 * Daniel Tabuenca: https://groups.google.com/d/msg/angular/an9QpzqIYiM/r8v-3W1X5vcJ
 * for the idea on how to dynamically invoke the ng-repeat directive.
 *
 * I borrowed a couple of lines and a few attribute names from the AngularUI Bootstrap project:
 * https://github.com/angular-ui/bootstrap/blob/master/src/pagination/pagination.js
 *
 * Copyright 2014 Michael Bromley <michael@michaelbromley.co.uk>
 */

(function() {

    /**
     * Config
     */
    var moduleName = 'angularUtils.directives.dirPagination';

    /**
     * Module
     */
    var module;
    try {
        module = angular.module(moduleName);
    } catch(err) {
        // named module does not exist, so create one
        module = angular.module(moduleName, []);
    }

    module.directive('dirPaginate', ['$compile', '$parse', '$timeout', 'paginationService', function($compile, $parse, $timeout, paginationService) {
        return  {
            terminal: true,
            multiElement: true,
            priority: 5000, // This setting is used in conjunction with the later call to $compile() to prevent infinite recursion of compilation
            compile: function dirPaginationCompileFn(tElement, tAttrs){

                // Add ng-repeat to the dom element
                if (tElement[0].hasAttribute('dir-paginate-start') || tElement[0].hasAttribute('data-dir-paginate-start')) {
                    // using multiElement mode (dir-paginate-start, dir-paginate-end)
                    tAttrs.$set('ngRepeatStart', tAttrs.dirPaginate);
                    tElement.eq(tElement.length - 1).attr('ng-repeat-end', true);
                } else {
                    tAttrs.$set('ngRepeat', tAttrs.dirPaginate);
                }

                var expression = tAttrs.dirPaginate;
                // regex taken directly from https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js#L211
                var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);

                var filterPattern = /\|\s*itemsPerPage\s*:[^|]*/;
                if (match[2].match(filterPattern) === null) {
                    throw 'pagination directive: the \'itemsPerPage\' filter must be set.';
                }
                var itemsPerPageFilterRemoved = match[2].replace(filterPattern, '');
                var collectionGetter = $parse(itemsPerPageFilterRemoved);

                var paginationId = tAttrs.paginationId || '__default';
                paginationService.registerInstance(paginationId);

                return function dirPaginationLinkFn(scope, element, attrs){
                    var compiled =  $compile(element, false, 5000); // we manually compile the element again, as we have now added ng-repeat. Priority less than 5000 prevents infinite recursion of compiling dirPaginate

                    var currentPageGetter;
                    if (attrs.currentPage) {
                        currentPageGetter = $parse(attrs.currentPage);
                    } else {
                        // if the current-page attribute was not set, we'll make our own
                        var defaultCurrentPage = paginationId + '__currentPage';
                        scope[defaultCurrentPage] = 1;
                        currentPageGetter = $parse(defaultCurrentPage);
                    }
                    paginationService.setCurrentPageParser(paginationId, currentPageGetter, scope);

                    if (typeof attrs.totalItems !== 'undefined') {
                        paginationService.setAsyncModeTrue(paginationId);
                        scope.$watch(function() {
                            return $parse(attrs.totalItems)(scope);
                        }, function (result) {
                            if (0 <= result) {
                                paginationService.setCollectionLength(paginationId, result);
                            }
                        });
                    } else {
                        scope.$watchCollection(function() {
                            return collectionGetter(scope);
                        }, function(collection) {
                            if (collection) {
                                paginationService.setCollectionLength(paginationId, collection.length);
                            }
                        });
                    }

                    // Delegate to the link function returned by the new compilation of the ng-repeat
                    compiled(scope);
                };
            }
        };
    }]);

    module.directive('dirPaginationControls', ['paginationService', 'paginationTemplate', function(paginationService, paginationTemplate) {
        var numberRegex = /^\d+$/;
        /**
         * Generate an array of page numbers (or the '...' string) which is used in an ng-repeat to generate the
         * links used in pagination
         *
         * @param currentPage
         * @param rowsPerPage
         * @param paginationRange
         * @param collectionLength
         * @returns {Array}
         */
        function generatePagesArray(currentPage, collectionLength, rowsPerPage, paginationRange) {
            var pages = [];
            var totalPages = Math.ceil(collectionLength / rowsPerPage);
            var halfWay = Math.ceil(paginationRange / 2);
            var position;

            if (currentPage <= halfWay) {
                position = 'start';
            } else if (totalPages - halfWay < currentPage) {
                position = 'end';
            } else {
                position = 'middle';
            }

            var ellipsesNeeded = paginationRange < totalPages;
            var i = 1;
            while (i <= totalPages && i <= paginationRange) {
                var pageNumber = calculatePageNumber(i, currentPage, paginationRange, totalPages);

                var openingEllipsesNeeded = (i === 2 && (position === 'middle' || position === 'end'));
                var closingEllipsesNeeded = (i === paginationRange - 1 && (position === 'middle' || position === 'start'));
                if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) {
                    pages.push('...');
                } else {
                    pages.push(pageNumber);
                }
                i ++;
            }
            return pages;
        }

        /**
         * Given the position in the sequence of pagination links [i], figure out what page number corresponds to that position.
         *
         * @param i
         * @param currentPage
         * @param paginationRange
         * @param totalPages
         * @returns {*}
         */
        function calculatePageNumber(i, currentPage, paginationRange, totalPages) {
            var halfWay = Math.ceil(paginationRange/2);
            if (i === paginationRange) {
                return totalPages;
            } else if (i === 1) {
                return i;
            } else if (paginationRange < totalPages) {
                if (totalPages - halfWay < currentPage) {
                    return totalPages - paginationRange + i;
                } else if (halfWay < currentPage) {
                    return currentPage - halfWay + i;
                } else {
                    return i;
                }
            } else {
                return i;
            }
        }

        return {
            restrict: 'AE',
            templateUrl: function(elem, attrs) {
                return attrs.templateUrl || paginationTemplate.getPath();
            },
            scope: {
                maxSize: '=?',
                onPageChange: '&?'
            },
            link: function(scope, element, attrs) {
                var paginationId;
                paginationId = attrs.paginationId || '__default';
                if (!scope.maxSize) { scope.maxSize = 9; }
                scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true;
                scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false;

                if (!paginationService.isRegistered(paginationId)) {
                    var idMessage = (paginationId !== '__default') ? ' (id: ' + paginationId + ') ' : ' ';
                    throw 'pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive.';
                }

                var paginationRange = Math.max(scope.maxSize, 5);
                scope.pages = [];
                scope.pagination = {
                    last: 1,
                    current: 1
                };

                scope.$watch(function() {
                    return (paginationService.getCollectionLength(paginationId) + 1) * paginationService.getItemsPerPage(paginationId);
                }, function(length) {
                    if (0 < length) {
                        generatePagination();
                    }
                });

                scope.$watch(function() {
                    return (paginationService.getItemsPerPage(paginationId));
                }, function(prev,cur) {
                    if (prev != cur) {
                        goToPage(scope.pagination.current);
                    }
                });

                scope.$watch(function() {
                    return paginationService.getCurrentPage(paginationId);
                }, function(currentPage, previousPage) {
                    if (currentPage != previousPage) {
                        goToPage(currentPage);
                    }
                });

                scope.setCurrent = function(num) {
                    if (isValidPageNumber(num)) {
                        paginationService.setCurrentPage(paginationId, num);
                    }
                };

                function goToPage(num) {
                    if (isValidPageNumber(num)) {
                        scope.pages = generatePagesArray(num, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
                        scope.pagination.current = num;

                        // if a callback has been set, then call it with the page number as an argument
                        if (scope.onPageChange) {
                            scope.onPageChange({ newPageNumber : num });
                        }
                    }
                }

                function generatePagination() {
                    var page = parseInt(paginationService.getCurrentPage(paginationId)) || 1;

                    scope.pages = generatePagesArray(page, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
                    scope.pagination.current = page;
                    scope.pagination.last = scope.pages[scope.pages.length - 1];
                    if (scope.pagination.last < scope.pagination.current) {
                        scope.setCurrent(scope.pagination.last);
                    }
                }

                function isValidPageNumber(num) {
                    return (numberRegex.test(num) && (0 < num && num <= scope.pagination.last));
                }
            }
        };
    }]);

    module.filter('itemsPerPage', ['paginationService', function(paginationService) {
        return function(collection, itemsPerPage, paginationId) {
            if (typeof (paginationId) === 'undefined') {
                paginationId = '__default';
            }
            if (!paginationService.isRegistered(paginationId)) {
                throw 'pagination directive: the itemsPerPage id argument (id: ' + paginationId + ') does not match a registered pagination-id.';
            }
            var end;
            var start;
            if (collection instanceof Array) {
                itemsPerPage = parseInt(itemsPerPage) || 9999999999;
                if (paginationService.isAsyncMode(paginationId)) {
                    start = 0;
                } else {
                    start = (paginationService.getCurrentPage(paginationId) - 1) * itemsPerPage;
                }
                end = start + itemsPerPage;
                paginationService.setItemsPerPage(paginationId, itemsPerPage);

                return collection.slice(start, end);
            } else {
                return collection;
            }
        };
    }]);

    module.service('paginationService', function() {
        var instances = {};
        var lastRegisteredInstance;

        this.registerInstance = function(instanceId) {
            if (typeof instances[instanceId] === 'undefined') {
                instances[instanceId] = {
                    asyncMode: false
                };
                lastRegisteredInstance = instanceId;
            }
        };

        this.isRegistered = function(instanceId) {
            return (typeof instances[instanceId] !== 'undefined');
        };

        this.getLastInstanceId = function() {
            return lastRegisteredInstance;
        };

        this.setCurrentPageParser = function(instanceId, val, scope) {
            instances[instanceId].currentPageParser = val;
            instances[instanceId].context = scope;
        };
        this.setCurrentPage = function(instanceId, val) {
            instances[instanceId].currentPageParser.assign(instances[instanceId].context, val);
        };
        this.getCurrentPage = function(instanceId) {
            var parser = instances[instanceId].currentPageParser;
            return parser ? parser(instances[instanceId].context) : 1;
        };

        this.setItemsPerPage = function(instanceId, val) {
            instances[instanceId].itemsPerPage = val;
        };
        this.getItemsPerPage = function(instanceId) {
            return instances[instanceId].itemsPerPage;
        };

        this.setCollectionLength = function(instanceId, val) {
            instances[instanceId].collectionLength = val;
        };
        this.getCollectionLength = function(instanceId) {
            return instances[instanceId].collectionLength;
        };

        this.setAsyncModeTrue = function(instanceId) {
            instances[instanceId].asyncMode = true;
        };

        this.isAsyncMode = function(instanceId) {
            return instances[instanceId].asyncMode;
        };
    });

    module.provider('paginationTemplate', function() {
        var templatePath = 'directives/pagination/dirPagination.tpl.html';

        this.setPath = function(path) {
            templatePath = path;
        };

        this.$get = function() {
            return {
                getPath: function() {
                    return templatePath;
                }
            };
        };
    });
})();<|endoftext|>


<commit_before>'use strict';

var getModelValues = require( '../dispatchers/get-model-values' );
var publish = require( '../dispatchers/publish-update' );
var stringToNum = require( '../utils/handle-string-input' );
var formatUSD = require( 'format-usd' );

var financialView = {
  $elements: $( '[data-financial]' ),
  $review: $( '[data-section="review"]' ),
  $programLength: $( '#estimated-years-attending' ),
  $addPrivateButton: $( '.private-loans_add-btn' ),
  $privateContainer: $( '.private-loans' ),
  $privateLoanClone: $( '[data-private-loan]:first' ).clone(),
  privateLoanKeys: [ 'baseAmount', 'fees', 'rate', 'deferPeriod' ],
  keyupDelay: null,
  currentInput: null,

  init: function() {
    var values = getModelValues.financial();
    this.keyupListener();
    this.focusoutListener();
    this.estimatedYearsListener();
    this.addPrivateListener();
    this.removePrivateListener();
    this.resetPrivateLoanView();
    this.updateView( values );
  },

  updateElement: function ( $ele, value, currency ) {
    if ( $ele.prop( 'tagName' ) === 'INPUT' ) {
      if ( currency === true ) {
        value = formatUSD( value, { decimalPlaces: 0 } );
      }
      $ele.val( value );
    } else {
      $ele.text( value );
    }
  },

  updateView: function( values ) {
    // handle non-private-loan fields
    var $nonPrivate = this.$elements.not( '[data-private-loan_key]' ),
        $percents = $nonPrivate.filter( '[data-percentage_value]' ),
        $leftovers = $nonPrivate.not( '[data-percentage_value]' ),
        $privateLoans = $( '[data-private-loan]' );
    $percents.each( function() {
      var $ele = $( this ),
          name = $ele.attr( 'data-financial' ),
          value = values[name] * 100;
      financialView.updateElement( $ele, value, false );
    } );
    $leftovers.each( function() {
      var $ele = $( this ),
          currency = true,
          name = $ele.attr( 'data-financial' );
      if ( financialView.currentInput === $( this ).attr( 'id' ) ) {
        currency = false;
      }
      financialView.updateElement( $ele, values[name], currency );
    } );
    $privateLoans.each( function() {
      var index = $( this ).index(),
          $fields = $( this ).find( '[data-private-loan_key]' );
      $fields.each( function() {
        var key = $( this ).attr( 'data-private-loan_key' ),
            val = values.privateLoanMulti[index][key],
            isntCurrentInput = ( $( this ).attr( 'id' ) !== financialView.currentInput );
        if ( $( this ).is( '[data-percentage_value="true"]' ) ) {
          val *= 100;
          $( this ).val( val );
        } else if ( isntCurrentInput && key === 'baseAmount' ) {
          $( this ).val( formatUSD( val, { decimalPlaces: 0 } ) );
        } else {
          $( this ).val( val );
        }
      } );
    } );
  },

  addPrivateListener: function() {
    this.$addPrivateButton.click( function() {
      var $container = $( '.private-loans' ),
          $button = $( '[data-add-loan-button]' );
      financialView.$privateLoanClone.clone().insertBefore( $button );
      financialView.enumeratePrivateLoanIDs();
      $container.find( '[data-private-loan]:last .aid-form_input' ).val( '0' );
      publish.addPrivateLoan();
    } );
  },

  removePrivateListener: function() {
    var buttonClass = '.private-loans_remove-btn';
    this.$privateContainer.on( 'click', buttonClass, function() {
      var $ele = $( this ).closest( '[data-private-loan]' ),
          index = $ele.index();
      $ele.remove();
      financialView.enumeratePrivateLoanIDs();
      publish.dropPrivateLoan( index );
      var values = getModelValues.financial();
      financialView.updateView( values );
    } );
  },

  resetPrivateLoanView: function() {
    // remove the 2 excess private loans (3 exist initially as a NoJS fallback)
    $( '[data-private-loan]' ).each( function() {
      var index = $( this ).index();
      if ( index > 0 ) {
        $( this ).remove();
        publish.dropPrivateLoan( index );
      }
    } );
  },

  enumeratePrivateLoanIDs: function() {
    // renumber private loan ids to prevent duplicate IDs
    $( '[data-private-loan' ).each( function() {
      var index = $( this ).index(),
          $ele = $( this ),
          $fields = $ele.find( '[data-private-loan_key]' );
      $fields.each( function() {
        var name = $( this ).attr( 'name' ),
            newID = name + '_' + index.toString();
        $( this ).attr( 'id', newID );
      } );
    } );
  },

  inputHandler: function( id ) {
    var $ele = $( '#' + id ),
        value = stringToNum( $ele.val() ),
        key = $ele.attr( 'data-financial' ),
        privateLoanKey = $ele.attr( 'data-private-loan_key' ),
        percentage = $ele.attr( 'data-percentage_value' );
    if ( percentage === 'true' ) {
      value /= 100;
    }
    if ( typeof privateLoanKey !== 'undefined' ) {
      var index = $ele.closest( '[data-private-loan]' ).index(),
          privLoanKey = $ele.attr( 'data-private-loan_key' );
      publish.updatePrivateLoan( index, privLoanKey, value );
    } else {
      publish.financialData( key, value );
    }
    var values = getModelValues.financial();
    financialView.updateView( values );
  },

  keyupListener: function() {
    this.$review.on( 'keyup', '[data-financial]', function() {
      clearTimeout( financialView.keyupDelay );
      financialView.currentInput = $( this ).attr( 'id' );
      financialView.keyupDelay = setTimeout( function() {
        financialView.inputHandler( financialView.currentInput );
      }, 500 );
    } );
  },

  focusoutListener: function() {
    this.$review.on( 'focusout', '[data-financial]', function() {
      clearTimeout( financialView.keyupDelay );
      financialView.currentInput = $( this ).attr( 'id' );
      financialView.inputHandler( financialView.currentInput );
    } );
  },

  estimatedYearsListener: function() {
    this.$programLength.on( 'change', function() {
      var programLength = $( this ).val(),
          values = getModelValues.financial();
      publish.financialData( 'programLength', programLength );
      financialView.updateView( values );
    });
  }

};

module.exports = financialView;
<commit_msg>updateView refactor<commit_after>'use strict';

var getModelValues = require( '../dispatchers/get-model-values' );
var publish = require( '../dispatchers/publish-update' );
var stringToNum = require( '../utils/handle-string-input' );
var formatUSD = require( 'format-usd' );

var financialView = {
  $elements: $( '[data-financial]' ),
  $review: $( '[data-section="review"]' ),
  $programLength: $( '#estimated-years-attending' ),
  $addPrivateButton: $( '.private-loans_add-btn' ),
  $privateContainer: $( '.private-loans' ),
  $privateLoanClone: $( '[data-private-loan]:first' ).clone(),
  privateLoanKeys: [ 'baseAmount', 'fees', 'rate', 'deferPeriod' ],
  keyupDelay: null,
  currentInput: null,

  init: function() {
    var values = getModelValues.financial();
    this.keyupListener();
    this.focusoutListener();
    this.estimatedYearsListener();
    this.addPrivateListener();
    this.removePrivateListener();
    this.resetPrivateLoanView();
    this.updateView( values );
  },

  updateElement: function ( $ele, value, currency ) {
    if ( $ele.prop( 'tagName' ) === 'INPUT' ) {
      if ( currency === true ) {
        value = formatUSD( value, { decimalPlaces: 0 } );
      }
      $ele.val( value );
    } else {
      $ele.text( value );
    }
  },

  updatePercentages: function( values, $percents ) {
    $percents.each( function() {
      var $ele = $( this ),
          name = $ele.attr( 'data-financial' ),
          value = values[name] * 100;
      financialView.updateElement( $ele, value, false );
    } );
  },

  updateLeftovers: function( values, $leftovers ) {
    $leftovers.each( function() {
      var $ele = $( this ),
          currency = true,
          name = $ele.attr( 'data-financial' );
      if ( financialView.currentInput === $( this ).attr( 'id' ) ) {
        currency = false;
      }
      financialView.updateElement( $ele, values[name], currency );
    } );
  },

  updatePrivateLoans: function( values, $privateLoans ) {
    $privateLoans.each( function() {
      var index = $( this ).index(),
          $fields = $( this ).find( '[data-private-loan_key]' );
      $fields.each( function() {
        var key = $( this ).attr( 'data-private-loan_key' ),
            val = values.privateLoanMulti[index][key],
            isntCurrentInput = $( this ).attr( 'id' ) !== financialView.currentInput;
        if ( $( this ).is( '[data-percentage_value="true"]' ) ) {
          val *= 100;
          $( this ).val( val );
        } else if ( isntCurrentInput && key === 'baseAmount' ) {
          $( this ).val( formatUSD( val, { decimalPlaces: 0 } ) );
        } else {
          $( this ).val( val );
        }
      } );
    } );
  },

  updateView: function( values ) {
    // handle non-private-loan fields
    var $nonPrivate = this.$elements.not( '[data-private-loan_key]' ),
        $percents = $nonPrivate.filter( '[data-percentage_value]' ),
        $leftovers = $nonPrivate.not( '[data-percentage_value]' ),
        $privateLoans = $( '[data-private-loan]' );
    this.updatePercentages( values, $percents );
    this.updateLeftovers( values, $leftovers );
    this.updatePrivateLoans( values, $privateLoans );
  },

  addPrivateListener: function() {
    this.$addPrivateButton.click( function() {
      var $container = $( '.private-loans' ),
          $button = $( '[data-add-loan-button]' );
      financialView.$privateLoanClone.clone().insertBefore( $button );
      financialView.enumeratePrivateLoanIDs();
      $container.find( '[data-private-loan]:last .aid-form_input' ).val( '0' );
      publish.addPrivateLoan();
    } );
  },

  removePrivateListener: function() {
    var buttonClass = '.private-loans_remove-btn';
    this.$privateContainer.on( 'click', buttonClass, function() {
      var $ele = $( this ).closest( '[data-private-loan]' ),
          index = $ele.index();
      $ele.remove();
      financialView.enumeratePrivateLoanIDs();
      publish.dropPrivateLoan( index );
      var values = getModelValues.financial();
      financialView.updateView( values );
    } );
  },

  resetPrivateLoanView: function() {
    // remove the 2 excess private loans (3 exist initially as a NoJS fallback)
    $( '[data-private-loan]' ).each( function() {
      var index = $( this ).index();
      if ( index > 0 ) {
        $( this ).remove();
        publish.dropPrivateLoan( index );
      }
    } );
  },

  enumeratePrivateLoanIDs: function() {
    // renumber private loan ids to prevent duplicate IDs
    $( '[data-private-loan' ).each( function() {
      var index = $( this ).index(),
          $ele = $( this ),
          $fields = $ele.find( '[data-private-loan_key]' );
      $fields.each( function() {
        var name = $( this ).attr( 'name' ),
            newID = name + '_' + index.toString();
        $( this ).attr( 'id', newID );
      } );
    } );
  },

  inputHandler: function( id ) {
    var $ele = $( '#' + id ),
        value = stringToNum( $ele.val() ),
        key = $ele.attr( 'data-financial' ),
        privateLoanKey = $ele.attr( 'data-private-loan_key' ),
        percentage = $ele.attr( 'data-percentage_value' );
    if ( percentage === 'true' ) {
      value /= 100;
    }
    if ( typeof privateLoanKey !== 'undefined' ) {
      var index = $ele.closest( '[data-private-loan]' ).index(),
          privLoanKey = $ele.attr( 'data-private-loan_key' );
      publish.updatePrivateLoan( index, privLoanKey, value );
    } else {
      publish.financialData( key, value );
    }
    var values = getModelValues.financial();
    financialView.updateView( values );
  },

  keyupListener: function() {
    this.$review.on( 'keyup', '[data-financial]', function() {
      clearTimeout( financialView.keyupDelay );
      financialView.currentInput = $( this ).attr( 'id' );
      financialView.keyupDelay = setTimeout( function() {
        financialView.inputHandler( financialView.currentInput );
      }, 500 );
    } );
  },

  focusoutListener: function() {
    this.$review.on( 'focusout', '[data-financial]', function() {
      clearTimeout( financialView.keyupDelay );
      financialView.currentInput = $( this ).attr( 'id' );
      financialView.inputHandler( financialView.currentInput );
    } );
  },

  estimatedYearsListener: function() {
    this.$programLength.on( 'change', function() {
      var programLength = $( this ).val(),
          values = getModelValues.financial();
      publish.financialData( 'programLength', programLength );
      financialView.updateView( values );
    } );
  }

};

module.exports = financialView;
<|endoftext|>


<commit_before>var UserAgent = require('inlet/http/ua')
var cadence = require('cadence')
var Legislator = require('paxos/legislator')
var Client = require('paxos/client')
var middleware = require('inlet/http/middleware')
var crypto = require('crypto')
var assert = require('assert')
var turnstile = require('turnstile')
var serializer = require('paxos/serializer')
var Id = require('paxos/id')
var RBTree = require('bintrees').RBTree

function Kibitzer (options) {
    this.waits = new RBTree(function (a, b) { return Id.compare(a.promise, b.promise) })
    this.preferred = !!options.preferred
    this.ua = new UserAgent
    this.brief = options.brief
    this.url = options.url
    this.play = options.play
    this.discover = middleware.handle(this.discover.bind(this))
    this.receive = middleware.handle(this.receive.bind(this))
    this.enqueue = middleware.handle(this.enqueue.bind(this))
    this.sync = middleware.handle(this.sync.bind(this))
    this.participants = {}
    this.legislator = new Legislator('0')
    this.cookies = {}
    this.logger = options.logger || function () { console.log(arguments) }

    this.subscriber = turnstile(function () {
        var outbox = this.client.outbox()
        return outbox.length ? outbox : null
    }.bind(this), cadence([function (async, value) {
        async(function () {
            this.ua.fetch({
                url: this.leader()
            }, {
                url: '/enqueue',
                payload: {
                    entries: value
                }
            }, async())
        }, function (body, response) {
            if (!response.okay || !body.posted) {
                this.client.published(null)
                setTimeout(async(), 250)
            } else {
                this.client.published(body.entries)
            }
        })
    }, this.catcher('subscriber')
    ]).bind(this))

    this.publisher = turnstile(function () {
        var outbox = this.legislator.outbox()
        return outbox.length ? outbox : null
    }.bind(this), cadence([function (async, outbox) {
        async(function (route) {
            var forwards = this.legislator.forwards(route.path, 0)
            async(function () {
                var serialized = {
                    route:route,
                    index: 1,
                    messages: serializer.flatten(forwards)
                }
                this.ua.fetch({
                    url: this.legislator.location[route.path[1]]
                }, {
                    url: '/receive',
                    payload: serialized
                }, async())
            }, function (body, response) {
                if (!response.okay) {
                    setTimeout(async(), 2500)
                }
                var returns = response.okay ? body.returns : []
                this.legislator.inbox(route, returns)
                this.legislator.sent(route, forwards, returns)
            })
        })(outbox)
    }, this.catcher('publisher')
    ]).bind(this))

    this.consumer = turnstile(function () {
        var entries = this.legislator.since(this.client.uniform)
        return entries.length ? entries : null
    }.bind(this), cadence([function (async, entries) {
        async(function () {
            setImmediate(async())
        }, function () {
            var promise = this.client.receive(entries)
            async(function (entry) {
                var callback = this.cookies[entry.cookie]
                if (callback) {
                    callback(null, entry)
                    delete this.cookies[entry.cookie]
                }
                async([function () {
                    this.play(entry, async())
                }, function (_, error) {
                    this.logger('error', 'play', error)
                }], function () {
                    var wait
                    while ((wait = this.waits.min()) && Id.compare(wait.promise, entry.promise) <= 0) {
                        wait.callbacks.forEach(function (callback) {
                            callback(null, wait.promise)
                        })
                        this.waits.remove(wait)
                    }
                })
            })(this.client.since(promise))
        })
    }, this.catcher('consumer')
    ]).bind(this))
}

//Error.stackTraceLimit = Infinity
Kibitzer.prototype.discover = cadence(function (async) {
    var urls = []
    for (var key in this.legislator.location) {
        urls.push(this.legislator.location[key])
    }
    return { urls: urls }
})

Kibitzer.prototype.receive = cadence(function (async, request) {
    var work = request.body
    var route = work.route, index = work.index, expanded = serializer.expand(work.messages)
    async(function () {
        this.legislator.inbox(route, expanded)
        route = this.legislator.routeOf(route.path)
        if (index + 1 < route.path.length) {
            async(function () {
                var forwards = this.legislator.forwards(route.path, index)
                this.ua.fetch(function () {
                    url: this.addresses[route.path[index + 1]]
                }, {
                    url: '/receive',
                    messages: serializer.flatten(forwards)
                })
            }, function (body, response) {
                if (response.okay) {
                    this.legislator.inbox(body.returns)
                }
            })
        }
    }, function () {
        this.consumer.nudge()
        return { returns: this.legislator.returns(route.path, index) }
    })
})

Kibitzer.prototype.createIdentifier = function (location) {
    var hash = crypto.createHash('md5')
    hash.update(crypto.pseudoRandomBytes(1024))
    var preferred = this.preferred ? 'a' : '7'
    return hash.digest('hex') + preferred
}

Kibitzer.prototype.bootstrap = function (location) {
    var id = this.createIdentifier()
    this.legislator = new Legislator(id, {
        peferred: function () { return this.id[this.id.length - 1] == 'a' }
    })
    this.legislator.bootstrap()
    this.client = new Client(this.legislator.id)
    this.client.prime(this.legislator.prime('1/0'))
    this.legislator.location[id] = location
}

Kibitzer.prototype.address = function (url) {
    this.send({
        meta: true,
        type: 'address',
        id: this.legislator.id,
        url: url
    })
}

Kibitzer.prototype.send = function (message) {
    if (this.legislator.government.majority[0] == this.legislator.id) {
        throw new Error
    }
}

Kibitzer.prototype.join = cadence(function (async, url) {
    async(function () {
        this.ua.fetch({ url: url, timeout: 2500 }, async())
    }, function (body, response) {
        var urls = body.urls
        if (urls.length == 0) {
            throw new Error('no other participants')
        }
        var index = 0, dataset = 'log', previous = null
        var sync = async(function () {
            this.ua.fetch({
                url: body.urls[index]
            }, {
                url: '/sync',
                payload: {
                    dataset: dataset,
                    previous: previous
                }
            }, async())
        }, function (body, response) {
            if (!response.okay) {
                if (++index == urls.length) {
                    throw new Error('cannot find a participant to sync with')
                } else {
                    return [ sync() ]
                }
            }
            switch (dataset) {
            case 'log':
                this.legislator.inject(body.entries)
                if (body.next == null) {
                    dataset = 'meta'
                }
                break
            case 'meta':
                this.legislator.location = body.location
                this.since = body.promise
                dataset = 'user'
                previous = null
                break
            case 'user':
                previous = body.previous
                body.entries.forEach(function (entry) {
                    this.play(entry)
                }, this)
                if (previous == null) {
                    return [ async, true ]
                }
                break
            }
        })()
    }, function () {
        this.legislator.immigrate(this.createIdentifier())
        this.client = new Client(this.legislator.id)
        this.legislator.initialize()
        this.client.prime(this.legislator.prime(this.since))
        assert(this.client.length, 'no entries in client')
        this.consumer.nudge()
        var cookie = this.publish({
            type: 'naturalize',
            id: this.legislator.id,
            location: this.url
        }, true, async())
    }, function () {
        console.log("WOOT!")
    })
})

Kibitzer.prototype.publish = cadence(function (async, entry, internal) {
    var cookie = this.client.publish(entry, internal)
    this.cookies[cookie] = async()
    this.subscriber.nudge()
})

Kibitzer.prototype.sync = cadence(function (async, request) {
    var body = request.body
    switch (body.dataset) {
    case 'log':
        return this.legislator.extract('reverse', 24, body.previous)
    case 'meta':
        return {
            promise: this.legislator.greatestOf(this.legislator.id).uniform,
            location: this.legislator.location
        }
    case 'user':
        return this.brief(body.previous)
    }
})

Kibitzer.prototype.enqueue = cadence(function (async, request) {
    var response = { entries: [] }
    request.body.entries.forEach(function (entry) {
        var outcome = this.legislator.post(entry.cookie, entry.value, entry.internal)
        // todo: I expect the cookie to be in the outcome.
        if (outcome.posted) {
            response.posted = true
            response.entries.push({ cookie: entry.cookie, promise: outcome.promise })
        }
    }, this)
    this.consumer.nudge()
    this.publisher.nudge()
    return response
})

Kibitzer.prototype.leader = function () {
    var id = this.legislator.government.majority[0]
    return this.legislator.location[id]
}

Kibitzer.prototype.catcher = function (context) {
    return function (_, error) {
        this.logger('error', context, error)
    }.bind(this)
}

Kibitzer.prototype.wait = function (promise, callback) {
    var wait = this.waits.find({ promise: promise })
    if (!wait) {
        wait = { promise: promise, callbacks: [] }
        this.waits.insert(wait)
    }
    wait.callbacks.push(callback)
}

module.exports = Kibitzer
<commit_msg>Remove `address` event handler.<commit_after>var UserAgent = require('inlet/http/ua')
var cadence = require('cadence')
var Legislator = require('paxos/legislator')
var Client = require('paxos/client')
var middleware = require('inlet/http/middleware')
var crypto = require('crypto')
var assert = require('assert')
var turnstile = require('turnstile')
var serializer = require('paxos/serializer')
var Id = require('paxos/id')
var RBTree = require('bintrees').RBTree

function Kibitzer (options) {
    this.waits = new RBTree(function (a, b) { return Id.compare(a.promise, b.promise) })
    this.preferred = !!options.preferred
    this.ua = new UserAgent
    this.brief = options.brief
    this.url = options.url
    this.play = options.play
    this.discover = middleware.handle(this.discover.bind(this))
    this.receive = middleware.handle(this.receive.bind(this))
    this.enqueue = middleware.handle(this.enqueue.bind(this))
    this.sync = middleware.handle(this.sync.bind(this))
    this.participants = {}
    this.legislator = new Legislator('0')
    this.cookies = {}
    this.logger = options.logger || function () { console.log(arguments) }

    this.subscriber = turnstile(function () {
        var outbox = this.client.outbox()
        return outbox.length ? outbox : null
    }.bind(this), cadence([function (async, value) {
        async(function () {
            this.ua.fetch({
                url: this.leader()
            }, {
                url: '/enqueue',
                payload: {
                    entries: value
                }
            }, async())
        }, function (body, response) {
            if (!response.okay || !body.posted) {
                this.client.published(null)
                setTimeout(async(), 250)
            } else {
                this.client.published(body.entries)
            }
        })
    }, this.catcher('subscriber')
    ]).bind(this))

    this.publisher = turnstile(function () {
        var outbox = this.legislator.outbox()
        return outbox.length ? outbox : null
    }.bind(this), cadence([function (async, outbox) {
        async(function (route) {
            var forwards = this.legislator.forwards(route.path, 0)
            async(function () {
                var serialized = {
                    route:route,
                    index: 1,
                    messages: serializer.flatten(forwards)
                }
                this.ua.fetch({
                    url: this.legislator.location[route.path[1]]
                }, {
                    url: '/receive',
                    payload: serialized
                }, async())
            }, function (body, response) {
                if (!response.okay) {
                    setTimeout(async(), 2500)
                }
                var returns = response.okay ? body.returns : []
                this.legislator.inbox(route, returns)
                this.legislator.sent(route, forwards, returns)
            })
        })(outbox)
    }, this.catcher('publisher')
    ]).bind(this))

    this.consumer = turnstile(function () {
        var entries = this.legislator.since(this.client.uniform)
        return entries.length ? entries : null
    }.bind(this), cadence([function (async, entries) {
        async(function () {
            setImmediate(async())
        }, function () {
            var promise = this.client.receive(entries)
            async(function (entry) {
                var callback = this.cookies[entry.cookie]
                if (callback) {
                    callback(null, entry)
                    delete this.cookies[entry.cookie]
                }
                async([function () {
                    this.play(entry, async())
                }, function (_, error) {
                    this.logger('error', 'play', error)
                }], function () {
                    var wait
                    while ((wait = this.waits.min()) && Id.compare(wait.promise, entry.promise) <= 0) {
                        wait.callbacks.forEach(function (callback) {
                            callback(null, wait.promise)
                        })
                        this.waits.remove(wait)
                    }
                })
            })(this.client.since(promise))
        })
    }, this.catcher('consumer')
    ]).bind(this))
}

//Error.stackTraceLimit = Infinity
Kibitzer.prototype.discover = cadence(function (async) {
    var urls = []
    for (var key in this.legislator.location) {
        urls.push(this.legislator.location[key])
    }
    return { urls: urls }
})

Kibitzer.prototype.receive = cadence(function (async, request) {
    var work = request.body
    var route = work.route, index = work.index, expanded = serializer.expand(work.messages)
    async(function () {
        this.legislator.inbox(route, expanded)
        route = this.legislator.routeOf(route.path)
        if (index + 1 < route.path.length) {
            async(function () {
                var forwards = this.legislator.forwards(route.path, index)
                this.ua.fetch(function () {
                    url: this.addresses[route.path[index + 1]]
                }, {
                    url: '/receive',
                    messages: serializer.flatten(forwards)
                })
            }, function (body, response) {
                if (response.okay) {
                    this.legislator.inbox(body.returns)
                }
            })
        }
    }, function () {
        this.consumer.nudge()
        return { returns: this.legislator.returns(route.path, index) }
    })
})

Kibitzer.prototype.createIdentifier = function (location) {
    var hash = crypto.createHash('md5')
    hash.update(crypto.pseudoRandomBytes(1024))
    var preferred = this.preferred ? 'a' : '7'
    return hash.digest('hex') + preferred
}

Kibitzer.prototype.bootstrap = function (location) {
    var id = this.createIdentifier()
    this.legislator = new Legislator(id, {
        peferred: function () { return this.id[this.id.length - 1] == 'a' }
    })
    this.legislator.bootstrap()
    this.client = new Client(this.legislator.id)
    this.client.prime(this.legislator.prime('1/0'))
    this.legislator.location[id] = location
}

Kibitzer.prototype.send = function (message) {
    if (this.legislator.government.majority[0] == this.legislator.id) {
        throw new Error
    }
}

Kibitzer.prototype.join = cadence(function (async, url) {
    async(function () {
        this.ua.fetch({ url: url, timeout: 2500 }, async())
    }, function (body, response) {
        var urls = body.urls
        if (urls.length == 0) {
            throw new Error('no other participants')
        }
        var index = 0, dataset = 'log', previous = null
        var sync = async(function () {
            this.ua.fetch({
                url: body.urls[index]
            }, {
                url: '/sync',
                payload: {
                    dataset: dataset,
                    previous: previous
                }
            }, async())
        }, function (body, response) {
            if (!response.okay) {
                if (++index == urls.length) {
                    throw new Error('cannot find a participant to sync with')
                } else {
                    return [ sync() ]
                }
            }
            switch (dataset) {
            case 'log':
                this.legislator.inject(body.entries)
                if (body.next == null) {
                    dataset = 'meta'
                }
                break
            case 'meta':
                this.legislator.location = body.location
                this.since = body.promise
                dataset = 'user'
                previous = null
                break
            case 'user':
                previous = body.previous
                body.entries.forEach(function (entry) {
                    this.play(entry)
                }, this)
                if (previous == null) {
                    return [ async, true ]
                }
                break
            }
        })()
    }, function () {
        this.legislator.immigrate(this.createIdentifier())
        this.client = new Client(this.legislator.id)
        this.legislator.initialize()
        this.client.prime(this.legislator.prime(this.since))
        assert(this.client.length, 'no entries in client')
        this.consumer.nudge()
        var cookie = this.publish({
            type: 'naturalize',
            id: this.legislator.id,
            location: this.url
        }, true, async())
    }, function () {
        console.log("WOOT!")
    })
})

Kibitzer.prototype.publish = cadence(function (async, entry, internal) {
    var cookie = this.client.publish(entry, internal)
    this.cookies[cookie] = async()
    this.subscriber.nudge()
})

Kibitzer.prototype.sync = cadence(function (async, request) {
    var body = request.body
    switch (body.dataset) {
    case 'log':
        return this.legislator.extract('reverse', 24, body.previous)
    case 'meta':
        return {
            promise: this.legislator.greatestOf(this.legislator.id).uniform,
            location: this.legislator.location
        }
    case 'user':
        return this.brief(body.previous)
    }
})

Kibitzer.prototype.enqueue = cadence(function (async, request) {
    var response = { entries: [] }
    request.body.entries.forEach(function (entry) {
        var outcome = this.legislator.post(entry.cookie, entry.value, entry.internal)
        // todo: I expect the cookie to be in the outcome.
        if (outcome.posted) {
            response.posted = true
            response.entries.push({ cookie: entry.cookie, promise: outcome.promise })
        }
    }, this)
    this.consumer.nudge()
    this.publisher.nudge()
    return response
})

Kibitzer.prototype.leader = function () {
    var id = this.legislator.government.majority[0]
    return this.legislator.location[id]
}

Kibitzer.prototype.catcher = function (context) {
    return function (_, error) {
        this.logger('error', context, error)
    }.bind(this)
}

Kibitzer.prototype.wait = function (promise, callback) {
    var wait = this.waits.find({ promise: promise })
    if (!wait) {
        wait = { promise: promise, callbacks: [] }
        this.waits.insert(wait)
    }
    wait.callbacks.push(callback)
}

module.exports = Kibitzer
<|endoftext|>