<commit_before>/*
  Copyright 2010 Riccardo Govoni battlehorse@gmail.com

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

// RHIZODEP=rhizo.layout,rhizo.ui
// Dependency on rhizo.ui is for rhizo.ui.reRender.

namespace("rhizo.layout");
namespace("rhizo.layout.treemap");

// TreeMap Layout.
// Based on the "Squarified Treemaps" algorithm, by Mark Bruls, Kees Huizing,
// and Jarke J. van Wijk (http://tinyurl.com/2eey2zn).
//
// TODO(battlehorse): When expanding and unexpanding a model in this layout, the
// model is re-rendered with its original size, not the one the treemap expects.
//
// TODO(battlehorse): This layout does not support nesting and hierarchies yet.
//

rhizo.layout.treemap.TreeMapNode = function(supermodel, areaMeta, areaRatio) {
  this.model_ = supermodel;
  this.area_ = parseFloat(this.model_.unwrap()[areaMeta]) * areaRatio;
  if (isNaN(this.area_)) {
    this.area_ = 0.0;
  }
};

rhizo.layout.treemap.TreeMapNode.prototype.area = function() {
  return this.area_;
};

rhizo.layout.treemap.TreeMapNode.prototype.model = function() {
  return this.model_;
};

rhizo.layout.treemap.TreeMapDirection = {
  HOR: 'h',
  VER: 'v'
};

rhizo.layout.treemap.TreeMapSlice = function(length, direction, anchorPoint) {
  this.length_ = length;
  this.direction_ = direction;
  this.anchorPoint_ = anchorPoint;
  this.nodes_ = [];
  this.sliceArea_ = 0.0;
  this.minArea_ = Number.MAX_VALUE;
};

rhizo.layout.treemap.TreeMapSlice.prototype.addNode = function(node) {
  this.nodes_.push(node);
  this.sliceArea_ += node.area();
  this.minArea_ = Math.min(this.minArea_, node.area());
};

rhizo.layout.treemap.TreeMapSlice.prototype.span = function(opt_newNode) {
  if (opt_newNode) {
    return (this.sliceArea_ + opt_newNode.area()) / this.length_;
  } else {
    return this.sliceArea_ / this.length_;
  }
};

rhizo.layout.treemap.TreeMapSlice.prototype.aspectRatio = function(
    opt_newNode) {
  var span = this.span(opt_newNode);
  var ratio = null;
  if (opt_newNode) {
    ratio = Math.min(this.minArea_, opt_newNode.area()) / (1.0 * span * span);
  } else {
    ratio = this.minArea_ / (1.0 * span * span);
  }
  if (ratio < 1) {
    ratio = 1.0 / ratio;
  }
  return ratio;
};

rhizo.layout.treemap.TreeMapSlice.prototype.length = function() {
  return this.length_;
};

rhizo.layout.treemap.TreeMapSlice.prototype.nodes = function() {
  return this.nodes_;
};

rhizo.layout.treemap.TreeMapSlice.prototype.direction = function() {
  return this.direction_;
};

rhizo.layout.treemap.TreeMapSlice.prototype.anchorPoint = function() {
  return this.anchorPoint_;
};

/**
 * A wrapper around a supermodel managed (i.e. actively laid out) by
 * TreeMapLayout.
 *
 * Since TreeMapLayout actively alters the rendering of the models it lays out,
 * we need to keep track of the ones we are currently managing, to be able to
 * restore them to their original shape/color once.
 *
 * @param {rhizo.model.SuperModel} model The model to wrap.
 * @constructor
 */
rhizo.layout.treemap.ManagedModel = function(model) {
  this.model_ = model;
  this.originalDimensions_ = jQuery.extend({}, model.getDimensions());
  this.originalBackground_ = model.nakedCss('background-color');
};

rhizo.layout.treemap.ManagedModel.prototype.restoreAll = function() {
  this.model_.setNakedCss({backgroundColor: this.originalBackground_});
  this.model_.rescaleRendering(this.originalDimensions_.width,
                               this.originalDimensions_.height);
};

rhizo.layout.TreeMapLayout = function(project) {
  this.project_ = project;
  this.areaSelector_ = null;
  this.colorSelector_ = null;
  this.managedModels_ = null;

  this.colorMin_ = {r: 237, g: 76, b: 95};
  this.colorMax_ = {r: 122, g: 255, b: 115};
};

rhizo.layout.TreeMapLayout.prototype.layout = function(container,
                                                       supermodels,
                                                       allmodels,
                                                       meta,
                                                       opt_options) {
  // The list of managed models is preserved through multiple consequent
  // applications of the TreeMapLayout:
  // - when two TreeMapLayouts are applied consequently only the delta of models
  //   between the two is restored (removed from managed models) or added to the
  //   set.
  // - when a TreeMapLayout is replaced by a different one, all managed models
  //   are restored (see cleanup()).
  // - when a TreeMapLayout replaces a previous (different) layout, the set of
  //   managed models is initially null and populated during the layout
  //   operation.
  if (this.managedModels_) {
    var survivingModelIds = {};
    for (var i = 0; i < supermodels.length; i++) {
      survivingModelIds[supermodels[i].id] = true;
    }
    var restorableModels = {};
    for (var modelId in this.managedModels_) {
      if (!(modelId in survivingModelIds)) {
        restorableModels[modelId] = this.managedModels_[modelId];
      }
    }
    this.restoreSizesAndColors_(restorableModels, opt_options);
    for (var modelId in restorableModels) {
      delete this.managedModels_[modelId];
    }
  }

  var areaMeta = this.areaSelector_.val();
  var colorMeta = this.colorSelector_.val();

  var area = 0.0;
  var colorRange = null;
  if (colorMeta.length > 0) {
    colorRange = {
      min: Number.MAX_VALUE,
      max: Number.MIN_VALUE,
      meta: colorMeta
    };
  }
  supermodels.sort(rhizo.meta.sortBy(areaMeta, meta[areaMeta].kind, true));
  for (var i = 0; i < supermodels.length; i++) {
    var model = supermodels[i];

    // Accumulate area
    area += parseFloat(model.unwrap()[areaMeta]);

    // Identify the minimum and maximum color ranges.
    if (colorMeta.length > 0) {
      var colorVal = parseFloat(model.unwrap()[colorMeta]);
      if (!isNaN(colorVal)) {
        colorRange.min = Math.min(colorRange.min, colorVal);
        colorRange.max = Math.max(colorRange.max, colorVal);
      }
    }

    if (model.expanded) {
      // Revert expanded items, since it messes up with treemapping.
      model.expanded = !model.expanded;
      rhizo.ui.reRender(model,
                        this.project_.renderer(),
                        opt_options);
    }
  }
  // Pointer to the container were new treemap nodes are added to. Initially
  // maps to the entire available rendering area.
  var remainderContainer = {
    width: container.width(),
    height: container.height()
  };

  // Compute the ratio between the treemap area as defined by the dimension we
  // are treemapping along and the available pixel region as defined by the
  // container we will render into.
  var areaRatio = container.width() * container.height() * 1.0 / area;

  var slices = [];
  if (remainderContainer.width < remainderContainer.height) {
    slices.push(new rhizo.layout.treemap.TreeMapSlice(
      remainderContainer.width,
      rhizo.layout.treemap.TreeMapDirection.HOR,
      {x:0, y:0}));
  } else {
    slices.push(new rhizo.layout.treemap.TreeMapSlice(
      remainderContainer.height,
      rhizo.layout.treemap.TreeMapDirection.VER,
      {x:0, y:0}));
  }
  var currentSlice = slices[0];
  var modelsCount = 0;
  if (supermodels.length > 0) {
    var node = new rhizo.layout.treemap.TreeMapNode(supermodels[modelsCount++],
                                                    areaMeta,
                                                    areaRatio);
    if (node.area() <= 0.0) {
      // Nodes are sorted by decreasing areas. It means that all the models have
      // a zero (or less) area. There's nothing to layout.
      return;
    } else {
      currentSlice.addNode(node);
    }
  }

  var numModelsToHide = 0;
  while (modelsCount < supermodels.length) {
    // create a TreeMapNode that wraps the currently inspected supermodel.
    var node = new rhizo.layout.treemap.TreeMapNode(supermodels[modelsCount++],
                                                    areaMeta,
                                                    areaRatio);
    if (node.area() <= 0.0) {
      node.model().filter('__treemap__');  // hard-coded filter key
      numModelsToHide++;
      continue;
    }

    // compute the worst aspect ratio the slice would have if the node were
    // added to the current slice.
    var withAspectRatio = currentSlice.aspectRatio(node);
    var withoutAspectRatio = currentSlice.aspectRatio();

    if (withAspectRatio > withoutAspectRatio) {
      // Create a new slice, in the opposite direction from the current one

      // Update the remainder container.
      remainderContainer = this.getRemainderContainer_(remainderContainer,
                                                       currentSlice);

      var containerSpan =
        currentSlice.direction() == rhizo.layout.treemap.TreeMapDirection.HOR ?
        remainderContainer.height : remainderContainer.width;

      if (currentSlice.direction() == rhizo.layout.treemap.TreeMapDirection.HOR) {
        currentSlice = new rhizo.layout.treemap.TreeMapSlice(
          containerSpan,
          rhizo.layout.treemap.TreeMapDirection.VER,
          {x:currentSlice.anchorPoint().x,
           y: currentSlice.anchorPoint().y + currentSlice.span()});
      } else {
        currentSlice = new rhizo.layout.treemap.TreeMapSlice(
          containerSpan,
          rhizo.layout.treemap.TreeMapDirection.HOR,
          {x: currentSlice.anchorPoint().x + currentSlice.span(),
           y: currentSlice.anchorPoint().y});
      }
      slices.push(currentSlice);
    }
    currentSlice.addNode(node);

  }
  if (slices.length > 0) {
    this.managedModels_ = this.managedModels_ || {};
    numModelsToHide += this.draw_(container, slices, colorRange);
  }
  if (numModelsToHide > 0) {
    this.project_.alignVisibility();
  }
};

rhizo.layout.TreeMapLayout.prototype.draw_ = function(container,
                                                      slices,
                                                      colorRange) {
  var numModelsToHide = 0;
  for (var i = 0; i < slices.length; i++) {
    numModelsToHide += this.drawSlice_(container, slices[i], colorRange);
  }
  return numModelsToHide;
};

rhizo.layout.TreeMapLayout.prototype.drawSlice_ = function(container,
                                                           slice,
                                                           colorRange) {
  var numModelsToHide = 0;
  var t = slice.anchorPoint().y;
  var l = slice.anchorPoint().x;
  for (var i = 0; i < slice.nodes().length; i++) {
    var length = slice.nodes()[i].area() / slice.span();
    var model = slice.nodes()[i].model();

    if (Math.round(length) == 0 || Math.round(slice.span()) == 0) {
      // Hide items that are too small to be displayed on screen
      model.filter('__treemap__');
      numModelsToHide++;
    } else {
      var renderingSize = {};
      if (slice.direction() == rhizo.layout.treemap.TreeMapDirection.HOR) {
        renderingSize['width'] = Math.round(length);
        renderingSize['height'] = Math.round(slice.span());
      } else {
        renderingSize['width'] = Math.round(slice.span());
        renderingSize['height'] = Math.round(length);
      }
      if (!(model.id in this.managedModels_)) {
        this.managedModels_[model.id] =
            new rhizo.layout.treemap.ManagedModel(model);
      }
      if (!model.rescaleRendering(renderingSize['width'], renderingSize['height'])) {
        model.filter('__treemap__');
        delete this.managedModels_[model.id];
        numModelsToHide++;
      } else {
        if (colorRange) {
          var colorVal = parseFloat(model.unwrap()[colorRange.meta]);
          if (!isNaN(colorVal)) {
            model.setNakedCss({backgroundColor:
                               this.getBackgroundColor_(colorVal,
                                                        colorRange)});
          }
        }
        model.rendering.move(Math.round(t), Math.round(l));
      }
    }
    if (slice.direction() == rhizo.layout.treemap.TreeMapDirection.HOR) {
      l += length;
    } else {
      t += length;
    }
  }
  return numModelsToHide;
};

rhizo.layout.TreeMapLayout.prototype.getBackgroundColor_ = function(colorVal,
                                                                    colorRange) {
  var channels = ['r', 'g', 'b'];
  var outputColor = {};
  for (var i = 0; i < channels.length; i++) {
    var channel = channels[i];
    outputColor[channel] = this.colorMin_[channel] +
      (this.colorMax_[channel] - this.colorMin_[channel])*
      (colorVal - colorRange.min)/(colorRange.max - colorRange.min);
  }
  return 'rgb(' + Math.round(outputColor.r) + ',' +
      Math.round(outputColor.g) + ',' +
      Math.round(outputColor.b) + ')';
};

rhizo.layout.TreeMapLayout.prototype.getRemainderContainer_ = function(
    remainderContainer, slice) {
  var sliceSpan = slice.span();
  if (slice.direction() == rhizo.layout.treemap.TreeMapDirection.HOR) {
    return {width: remainderContainer.width,
            height: remainderContainer.height - sliceSpan};
  } else {
    return {width: remainderContainer.width - sliceSpan,
            height: remainderContainer.height};
  }
};

rhizo.layout.TreeMapLayout.prototype.cleanup = function(sameEngine,
                                                        opt_options) {
  // Restore all models to their original sizes, if we are moving to a different
  // layout engine.
  if (!sameEngine) {
    this.restoreSizesAndColors_(this.managedModels_, opt_options);
    this.managedModels_ = null;
  }
  this.project_.resetAllFilter('__treemap__');
  this.project_.alignVisibility();
};

rhizo.layout.TreeMapLayout.prototype.restoreSizesAndColors_ = function(
    managedModelsMap, opt_options) {
  for (var modelId in managedModelsMap) {
    managedModelsMap[modelId].restoreAll();
  }
};

rhizo.layout.TreeMapLayout.prototype.details = function() {
  this.areaSelector_ = rhizo.layout.metaModelKeySelector(
    this.project_, 'rhizo-treemaplayout-area');
  this.colorSelector_ = rhizo.layout.metaModelKeySelector(
    this.project_, 'rhizo-treemaplayout-color');
  this.colorSelector_.append("<option value='' selected>-</option>");
  return $("<div />").
      append("Area: ").
      append(this.areaSelector_).
      append(" Color:").
      append(this.colorSelector_);
};

rhizo.layout.TreeMapLayout.prototype.toString = function() {
  return "TreeMap";
};

// register the treemaplayout in global layout list
rhizo.layout.layouts.treemap = rhizo.layout.TreeMapLayout;
<commit_msg>Bugfix. A treemap would not restore original colors when choosing not to use any color, after an initial coloring was selected<commit_after>/*
  Copyright 2010 Riccardo Govoni battlehorse@gmail.com

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

// RHIZODEP=rhizo.layout,rhizo.ui
// Dependency on rhizo.ui is for rhizo.ui.reRender.

namespace("rhizo.layout");
namespace("rhizo.layout.treemap");

// TreeMap Layout.
// Based on the "Squarified Treemaps" algorithm, by Mark Bruls, Kees Huizing,
// and Jarke J. van Wijk (http://tinyurl.com/2eey2zn).
//
// TODO(battlehorse): When expanding and unexpanding a model in this layout, the
// model is re-rendered with its original size, not the one the treemap expects.
//
// TODO(battlehorse): This layout does not support nesting and hierarchies yet.
//
// TODO(battlehorse): Should offer the possibility to color items via a
// logarithmic scale.

rhizo.layout.treemap.TreeMapNode = function(supermodel, areaMeta, areaRatio) {
  this.model_ = supermodel;
  this.area_ = parseFloat(this.model_.unwrap()[areaMeta]) * areaRatio;
  if (isNaN(this.area_)) {
    this.area_ = 0.0;
  }
};

rhizo.layout.treemap.TreeMapNode.prototype.area = function() {
  return this.area_;
};

rhizo.layout.treemap.TreeMapNode.prototype.model = function() {
  return this.model_;
};

rhizo.layout.treemap.TreeMapDirection = {
  HOR: 'h',
  VER: 'v'
};

rhizo.layout.treemap.TreeMapSlice = function(length, direction, anchorPoint) {
  this.length_ = length;
  this.direction_ = direction;
  this.anchorPoint_ = anchorPoint;
  this.nodes_ = [];
  this.sliceArea_ = 0.0;
  this.minArea_ = Number.MAX_VALUE;
};

rhizo.layout.treemap.TreeMapSlice.prototype.addNode = function(node) {
  this.nodes_.push(node);
  this.sliceArea_ += node.area();
  this.minArea_ = Math.min(this.minArea_, node.area());
};

rhizo.layout.treemap.TreeMapSlice.prototype.span = function(opt_newNode) {
  if (opt_newNode) {
    return (this.sliceArea_ + opt_newNode.area()) / this.length_;
  } else {
    return this.sliceArea_ / this.length_;
  }
};

rhizo.layout.treemap.TreeMapSlice.prototype.aspectRatio = function(
    opt_newNode) {
  var span = this.span(opt_newNode);
  var ratio = null;
  if (opt_newNode) {
    ratio = Math.min(this.minArea_, opt_newNode.area()) / (1.0 * span * span);
  } else {
    ratio = this.minArea_ / (1.0 * span * span);
  }
  if (ratio < 1) {
    ratio = 1.0 / ratio;
  }
  return ratio;
};

rhizo.layout.treemap.TreeMapSlice.prototype.length = function() {
  return this.length_;
};

rhizo.layout.treemap.TreeMapSlice.prototype.nodes = function() {
  return this.nodes_;
};

rhizo.layout.treemap.TreeMapSlice.prototype.direction = function() {
  return this.direction_;
};

rhizo.layout.treemap.TreeMapSlice.prototype.anchorPoint = function() {
  return this.anchorPoint_;
};

/**
 * A wrapper around a supermodel managed (i.e. actively laid out) by
 * TreeMapLayout.
 *
 * Since TreeMapLayout actively alters the rendering of the models it lays out,
 * we need to keep track of the ones we are currently managing, to be able to
 * restore them to their original shape/color once.
 *
 * @param {rhizo.model.SuperModel} model The model to wrap.
 * @constructor
 */
rhizo.layout.treemap.ManagedModel = function(model) {
  this.model_ = model;
  this.originalDimensions_ = jQuery.extend({}, model.getDimensions());
  this.originalBackground_ = model.nakedCss('background-color');
};

rhizo.layout.treemap.ManagedModel.prototype.restore = function(restoreSizes,
                                                               restoreColors) {
  if (restoreColors) {
    this.model_.setNakedCss({backgroundColor: this.originalBackground_});
  }
  if (restoreSizes) {
    this.model_.rescaleRendering(this.originalDimensions_.width,
                                 this.originalDimensions_.height);
  }
};

rhizo.layout.TreeMapLayout = function(project) {
  this.project_ = project;
  this.areaSelector_ = null;
  this.colorSelector_ = null;
  this.prevColorMeta_ = '';
  this.managedModels_ = null;

  this.colorMin_ = {r: 237, g: 76, b: 95};
  this.colorMax_ = {r: 122, g: 255, b: 115};
};

rhizo.layout.TreeMapLayout.prototype.layout = function(container,
                                                       supermodels,
                                                       allmodels,
                                                       meta,
                                                       opt_options) {
  var areaMeta = this.areaSelector_.val();
  var colorMeta = this.colorSelector_.val();

  // The list of managed models is preserved through multiple consequent
  // applications of the TreeMapLayout:
  // - when two TreeMapLayouts are applied consequently only the delta of models
  //   between the two is restored (removed from managed models) or added to the
  //   set.
  // - when a TreeMapLayout is replaced by a different one, all managed models
  //   are restored (see cleanup()).
  // - when a TreeMapLayout replaces a previous (different) layout, the set of
  //   managed models is initially null and populated during the layout
  //   operation.
  if (this.managedModels_) {
    var survivingModelIds = {};
    for (var i = 0; i < supermodels.length; i++) {
      survivingModelIds[supermodels[i].id] = true;
    }
    var restorableModels = {};
    for (var modelId in this.managedModels_) {
      if (!(modelId in survivingModelIds)) {
        restorableModels[modelId] = this.managedModels_[modelId];
      }
    }
    this.restore_(restorableModels, true, true);
    for (var modelId in restorableModels) {
      delete this.managedModels_[modelId];
    }

    if (this.prevColorMeta_ != '' && colorMeta == '') {
      this.restore_(this.managedModels_, /*sizes=*/ false, /*colors=*/ true);
    }
  }

  this.prevColorMeta_ = colorMeta;
  var area = 0.0;
  var colorRange = null;
  if (colorMeta.length > 0) {
    colorRange = {
      min: Number.MAX_VALUE,
      max: Number.MIN_VALUE,
      meta: colorMeta
    };
  }
  supermodels.sort(rhizo.meta.sortBy(areaMeta, meta[areaMeta].kind, true));
  for (var i = 0; i < supermodels.length; i++) {
    var model = supermodels[i];

    // Accumulate area
    area += parseFloat(model.unwrap()[areaMeta]);

    // Identify the minimum and maximum color ranges.
    if (colorMeta.length > 0) {
      var colorVal = parseFloat(model.unwrap()[colorMeta]);
      if (!isNaN(colorVal)) {
        colorRange.min = Math.min(colorRange.min, colorVal);
        colorRange.max = Math.max(colorRange.max, colorVal);
      }
    }

    if (model.expanded) {
      // Revert expanded items, since it messes up with treemapping.
      model.expanded = !model.expanded;
      rhizo.ui.reRender(model,
                        this.project_.renderer(),
                        opt_options);
    }
  }
  // Pointer to the container were new treemap nodes are added to. Initially
  // maps to the entire available rendering area.
  var remainderContainer = {
    width: container.width(),
    height: container.height()
  };

  // Compute the ratio between the treemap area as defined by the dimension we
  // are treemapping along and the available pixel region as defined by the
  // container we will render into.
  var areaRatio = container.width() * container.height() * 1.0 / area;

  var slices = [];
  if (remainderContainer.width < remainderContainer.height) {
    slices.push(new rhizo.layout.treemap.TreeMapSlice(
      remainderContainer.width,
      rhizo.layout.treemap.TreeMapDirection.HOR,
      {x:0, y:0}));
  } else {
    slices.push(new rhizo.layout.treemap.TreeMapSlice(
      remainderContainer.height,
      rhizo.layout.treemap.TreeMapDirection.VER,
      {x:0, y:0}));
  }
  var currentSlice = slices[0];
  var modelsCount = 0;
  if (supermodels.length > 0) {
    var node = new rhizo.layout.treemap.TreeMapNode(supermodels[modelsCount++],
                                                    areaMeta,
                                                    areaRatio);
    if (node.area() <= 0.0) {
      // Nodes are sorted by decreasing areas. It means that all the models have
      // a zero (or less) area. There's nothing to layout.
      return;
    } else {
      currentSlice.addNode(node);
    }
  }

  var numModelsToHide = 0;
  while (modelsCount < supermodels.length) {
    // create a TreeMapNode that wraps the currently inspected supermodel.
    var node = new rhizo.layout.treemap.TreeMapNode(supermodels[modelsCount++],
                                                    areaMeta,
                                                    areaRatio);
    if (node.area() <= 0.0) {
      node.model().filter('__treemap__');  // hard-coded filter key
      numModelsToHide++;
      continue;
    }

    // compute the worst aspect ratio the slice would have if the node were
    // added to the current slice.
    var withAspectRatio = currentSlice.aspectRatio(node);
    var withoutAspectRatio = currentSlice.aspectRatio();

    if (withAspectRatio > withoutAspectRatio) {
      // Create a new slice, in the opposite direction from the current one

      // Update the remainder container.
      remainderContainer = this.getRemainderContainer_(remainderContainer,
                                                       currentSlice);

      var containerSpan =
        currentSlice.direction() == rhizo.layout.treemap.TreeMapDirection.HOR ?
        remainderContainer.height : remainderContainer.width;

      if (currentSlice.direction() == rhizo.layout.treemap.TreeMapDirection.HOR) {
        currentSlice = new rhizo.layout.treemap.TreeMapSlice(
          containerSpan,
          rhizo.layout.treemap.TreeMapDirection.VER,
          {x:currentSlice.anchorPoint().x,
           y: currentSlice.anchorPoint().y + currentSlice.span()});
      } else {
        currentSlice = new rhizo.layout.treemap.TreeMapSlice(
          containerSpan,
          rhizo.layout.treemap.TreeMapDirection.HOR,
          {x: currentSlice.anchorPoint().x + currentSlice.span(),
           y: currentSlice.anchorPoint().y});
      }
      slices.push(currentSlice);
    }
    currentSlice.addNode(node);

  }
  if (slices.length > 0) {
    this.managedModels_ = this.managedModels_ || {};
    numModelsToHide += this.draw_(container, slices, colorRange);
  }
  if (numModelsToHide > 0) {
    this.project_.alignVisibility();
  }
};

rhizo.layout.TreeMapLayout.prototype.draw_ = function(container,
                                                      slices,
                                                      colorRange) {
  var numModelsToHide = 0;
  for (var i = 0; i < slices.length; i++) {
    numModelsToHide += this.drawSlice_(container, slices[i], colorRange);
  }
  return numModelsToHide;
};

rhizo.layout.TreeMapLayout.prototype.drawSlice_ = function(container,
                                                           slice,
                                                           colorRange) {
  var numModelsToHide = 0;
  var t = slice.anchorPoint().y;
  var l = slice.anchorPoint().x;
  for (var i = 0; i < slice.nodes().length; i++) {
    var length = slice.nodes()[i].area() / slice.span();
    var model = slice.nodes()[i].model();

    if (Math.round(length) == 0 || Math.round(slice.span()) == 0) {
      // Hide items that are too small to be displayed on screen
      model.filter('__treemap__');
      numModelsToHide++;
    } else {
      var renderingSize = {};
      if (slice.direction() == rhizo.layout.treemap.TreeMapDirection.HOR) {
        renderingSize['width'] = Math.round(length);
        renderingSize['height'] = Math.round(slice.span());
      } else {
        renderingSize['width'] = Math.round(slice.span());
        renderingSize['height'] = Math.round(length);
      }
      if (!(model.id in this.managedModels_)) {
        this.managedModels_[model.id] =
            new rhizo.layout.treemap.ManagedModel(model);
      }
      if (!model.rescaleRendering(renderingSize['width'], renderingSize['height'])) {
        model.filter('__treemap__');
        delete this.managedModels_[model.id];
        numModelsToHide++;
      } else {
        if (colorRange) {
          var colorVal = parseFloat(model.unwrap()[colorRange.meta]);
          if (!isNaN(colorVal)) {
            model.setNakedCss({backgroundColor:
                               this.getBackgroundColor_(colorVal,
                                                        colorRange)});
          }
        }
        model.rendering.move(Math.round(t), Math.round(l));
      }
    }
    if (slice.direction() == rhizo.layout.treemap.TreeMapDirection.HOR) {
      l += length;
    } else {
      t += length;
    }
  }
  return numModelsToHide;
};

rhizo.layout.TreeMapLayout.prototype.getBackgroundColor_ = function(colorVal,
                                                                    colorRange) {
  var channels = ['r', 'g', 'b'];
  var outputColor = {};
  for (var i = 0; i < channels.length; i++) {
    var channel = channels[i];
    outputColor[channel] = this.colorMin_[channel] +
      (this.colorMax_[channel] - this.colorMin_[channel])*
      (colorVal - colorRange.min)/(colorRange.max - colorRange.min);
  }
  return 'rgb(' + Math.round(outputColor.r) + ',' +
      Math.round(outputColor.g) + ',' +
      Math.round(outputColor.b) + ')';
};

rhizo.layout.TreeMapLayout.prototype.getRemainderContainer_ = function(
    remainderContainer, slice) {
  var sliceSpan = slice.span();
  if (slice.direction() == rhizo.layout.treemap.TreeMapDirection.HOR) {
    return {width: remainderContainer.width,
            height: remainderContainer.height - sliceSpan};
  } else {
    return {width: remainderContainer.width - sliceSpan,
            height: remainderContainer.height};
  }
};

rhizo.layout.TreeMapLayout.prototype.cleanup = function(sameEngine,
                                                        opt_options) {
  // Restore all models to their original sizes, if we are moving to a different
  // layout engine.
  if (!sameEngine) {
    this.restore_(this.managedModels_, true, true);
    this.managedModels_ = null;
  }
  this.project_.resetAllFilter('__treemap__');
  this.project_.alignVisibility();
};

rhizo.layout.TreeMapLayout.prototype.restore_ = function(managedModelsMap,
                                                         restoreSizes,
                                                         restoreColors) {
  for (var modelId in managedModelsMap) {
    managedModelsMap[modelId].restore(restoreSizes, restoreColors);
  }
};

rhizo.layout.TreeMapLayout.prototype.details = function() {
  this.areaSelector_ = rhizo.layout.metaModelKeySelector(
    this.project_, 'rhizo-treemaplayout-area');
  this.colorSelector_ = rhizo.layout.metaModelKeySelector(
    this.project_, 'rhizo-treemaplayout-color');
  this.colorSelector_.append("<option value='' selected>-</option>");
  return $("<div />").
      append("Area: ").
      append(this.areaSelector_).
      append(" Color:").
      append(this.colorSelector_);
};

rhizo.layout.TreeMapLayout.prototype.toString = function() {
  return "TreeMap";
};

// register the treemaplayout in global layout list
rhizo.layout.layouts.treemap = rhizo.layout.TreeMapLayout;
<|endoftext|>


<commit_before>import { register, waitFor } from '../dispatcher/Dispatcher';
import { createStore, mergeIntoBag, isInBag } from '../utils/StoreUtils';
import ActionTypes from '../constants/ActionTypes';
import selectn from 'selectn';
import UserStore from './UserStore';
import { getValidationErrors } from '../utils/StoreUtils';

const _questions = {};
const _pagination = {};
let _answerQuestion = {};
let _errors = '';
let _goToQuestionStats = false;

const QuestionStore = createStore({
    contains(userId, questionId) {
        let question = selectn(userId+'.'+questionId, _questions);
        return question && question.hasOwnProperty('question');
    },

    get(userId) {
        return _questions[userId];
    },

    isFirstQuestion(userId) {
        return !_questions.hasOwnProperty(userId)  ||
            _questions.hasOwnProperty(userId) && Object.keys(_questions[userId]).length === 0;
    },

    getPagination(userId) {
        return _pagination[userId];
    },

    getQuestion() {
        return this.first(_answerQuestion);
    },

    getErrors() {
        return _errors;
    },

    getUserAnswer(userId, questionId) {
        return _questions[userId] ? selectn('userAnswer', _questions[userId][questionId]) : null;
    },

    mustGoToQuestionStats() {
        return _goToQuestionStats;
    },

    first(obj) {
        for (let a in obj) {
            if (obj.hasOwnProperty(a)) {
                return obj[a];
            }
        }
    }
});

QuestionStore.dispatchToken = register(action => {
    waitFor([UserStore.dispatchToken]);

    const items = selectn('response.entities.items', action);
    const pagination = selectn('response.result.pagination', action);
    const question = selectn('response.entities.question', action);
    const userAnswer = selectn('response.userAnswer', action);
    const userAnswerQuestion = selectn('response.question', action);
    const error = selectn('error', action);
    const userId = selectn('userId', action);
    const otherUserId = selectn('otherUserId', action);
    let newItems = {};
    _goToQuestionStats = false;

    if (typeof _questions[userId] === "undefined") {
        _questions[userId] = {};
    }
    if (typeof _pagination[userId] === "undefined") {
        _pagination[userId] = {};
    }
    if (error) {
        _errors = getValidationErrors(error);
        QuestionStore.emitChange();
    }
    else if (items) {
        if (action.type === 'REQUEST_COMPARED_QUESTIONS_SUCCESS') {
            for (let index in items) {
                if (items.hasOwnProperty(index)) {
                    newItems[index] = items[index].questions;
                }
            }
            _pagination[otherUserId] = pagination;
        } else {
            newItems[userId] = items;
            _pagination[userId] = pagination;
        }
        mergeIntoBag(_questions, newItems);

        QuestionStore.emitChange();
    }
    else if (question) {
        _answerQuestion = question;
        QuestionStore.emitChange();
    }
    else if (userAnswer && userAnswerQuestion) {
        // TODO: mergeIntoBag does not work here (maybe should)
        //let userAnswerAndQuestion = { question: userAnswerQuestion, userAnswer: userAnswer };
        //mergeIntoBag(_questions[userId], userAnswerAndQuestion);
        _questions[userId][userAnswer.questionId] = { question: userAnswerQuestion, userAnswer: userAnswer };
        _goToQuestionStats = true;
        _pagination[userId]++;
        QuestionStore.emitChange();
    }
    else if (action.type === 'REQUEST_EXISTING_QUESTION') {
        _answerQuestion = {};
        _answerQuestion[action.questionId] = _questions[userId][action.questionId].question;
        QuestionStore.emitChange();
    }
});

export default QuestionStore;
<commit_msg>QS-1001: Fix repeated validation errors when registering<commit_after>import { register, waitFor } from '../dispatcher/Dispatcher';
import { createStore, mergeIntoBag, isInBag } from '../utils/StoreUtils';
import ActionTypes from '../constants/ActionTypes';
import selectn from 'selectn';
import UserStore from './UserStore';
import { getValidationErrors } from '../utils/StoreUtils';

const _questions = {};
const _pagination = {};
let _answerQuestion = {};
let _errors = '';
let _goToQuestionStats = false;

const QuestionStore = createStore({
    contains(userId, questionId) {
        let question = selectn(userId+'.'+questionId, _questions);
        return question && question.hasOwnProperty('question');
    },

    get(userId) {
        return _questions[userId];
    },

    isFirstQuestion(userId) {
        return !_questions.hasOwnProperty(userId)  ||
            _questions.hasOwnProperty(userId) && Object.keys(_questions[userId]).length === 0;
    },

    getPagination(userId) {
        return _pagination[userId];
    },

    getQuestion() {
        return this.first(_answerQuestion);
    },

    getErrors() {
        return _errors;
    },

    getUserAnswer(userId, questionId) {
        return _questions[userId] ? selectn('userAnswer', _questions[userId][questionId]) : null;
    },

    mustGoToQuestionStats() {
        return _goToQuestionStats;
    },

    first(obj) {
        for (let a in obj) {
            if (obj.hasOwnProperty(a)) {
                return obj[a];
            }
        }
    }
});

QuestionStore.dispatchToken = register(action => {
    waitFor([UserStore.dispatchToken]);

    const items = selectn('response.entities.items', action);
    const pagination = selectn('response.result.pagination', action);
    const question = selectn('response.entities.question', action);
    const userAnswer = selectn('response.userAnswer', action);
    const userAnswerQuestion = selectn('response.question', action);
    const error = selectn('error', action);
    const userId = selectn('userId', action);
    const otherUserId = selectn('otherUserId', action);
    let newItems = {};
    _goToQuestionStats = false;

    if (typeof _questions[userId] === "undefined") {
        _questions[userId] = {};
    }
    if (typeof _pagination[userId] === "undefined") {
        _pagination[userId] = {};
    }
    //TODO: Change all to action types switch
    if (error && action.type === ActionTypes.ANSWER_QUESTION_ERROR) {
        _errors = getValidationErrors(error);
        QuestionStore.emitChange();
    }
    else if (items) {
        if (action.type === 'REQUEST_COMPARED_QUESTIONS_SUCCESS') {
            for (let index in items) {
                if (items.hasOwnProperty(index)) {
                    newItems[index] = items[index].questions;
                }
            }
            _pagination[otherUserId] = pagination;
        } else {
            newItems[userId] = items;
            _pagination[userId] = pagination;
        }
        mergeIntoBag(_questions, newItems);

        QuestionStore.emitChange();
    }
    else if (question) {
        _answerQuestion = question;
        QuestionStore.emitChange();
    }
    else if (userAnswer && userAnswerQuestion) {
        // TODO: mergeIntoBag does not work here (maybe should)
        //let userAnswerAndQuestion = { question: userAnswerQuestion, userAnswer: userAnswer };
        //mergeIntoBag(_questions[userId], userAnswerAndQuestion);
        _questions[userId][userAnswer.questionId] = { question: userAnswerQuestion, userAnswer: userAnswer };
        _goToQuestionStats = true;
        _pagination[userId]++;
        QuestionStore.emitChange();
    }
    else if (action.type === 'REQUEST_EXISTING_QUESTION') {
        _answerQuestion = {};
        _answerQuestion[action.questionId] = _questions[userId][action.questionId].question;
        QuestionStore.emitChange();
    }
});

export default QuestionStore;
<|endoftext|>


<commit_before>module.exports = Eris => {
	Eris.Channel.prototype.createEmbed = function(embed) {
		const client = this.guild ? this.guild.shard.client : this._client;
		return client.createEmbed(this.id, embed);
	};
};

module.exports.deps = ["Client.createEmbed"];
<commit_msg>Update createEmbed.js<commit_after>module.exports = Eris => {
	Eris.Channel.prototype.createEmbed = function(embed) {
		const client = this.guild ? this.guild.shard.client : this.client;
		return client.createEmbed(this.id, embed);
	};
};

module.exports.deps = ["Client.createEmbed"];
<|endoftext|>


<commit_before><commit_msg>issue<commit_after><|endoftext|>


<commit_before>/*
 Copyright 2016 - 2018 by Jan Dockx

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

'use strict'

const ContractError = require('./ContractError')
const is = require('../_private/is')
const property = require('../_private/property')
const stack = require('../_private/stack')
const report = require('../_private/report')
const assert = require('assert')

const namePrefix = '𝕋⚖'

/**
 * Function that always returns false.
 */
const falseCondition = function () { return false }

/**
 * Singleton array of {@linkplain AbstractContract#falseCondition}. Can be used the clearly signal
 * that a function should never throw exceptions, or never end nominally, or should never be called,
 * because the conditions will always fail.
 */
const mustNotHappen = Object.freeze([falseCondition])

// noinspection ParameterNamingConventionJS
/**
 * Abstract definition of a function contract.
 *
 * An AbstractContract consists of an array of preconditions, an array of nominal postconditions,
 * and an array of exceptional postconditions.
 *
 * The conditions are functions whose result is interpreted as a booleany value.
 * The conditions are called with the same `this` and arguments as the functional call in which they are
 * verified. When verifying nominal postconditions, the result of the function call is added as extra argument.
 * When verifying exceptional postconditions, the exception thrown by the function call is added as extra argument.
 * Finally, a version of the contract function bound to `this` is supplied as final parameter when verifying
 * nominal and exceptional postconditions. This function reference can be used in contracts that use recursion.
 *
 * The default preconditions and postconditions are the empty array (anything goes). For exceptions, the default
 * condition is `AbstractContract.mustNotHappen` (any exception is a violation).
 *
 * Furthermore, an instance contains a `location` property, which is a line of text
 * that refers to the source code where the contract was created. For internal contracts, this is
 * `AbstractContract.internalLocation.
 *
 * If `verify` and `verifyPostconditions` are both truthy, contract function verification will verify preconditions,
 * postconditions and exception conditions. If `verify` is truthy, but `verifyPostconditions` is falsy, contract
 * function verification will verify preconditions, but not postconditions and exception conditions. This is the
 * default. If `verify` is falsy, contract function executions will not be verified.
 *
 * The `contract` of a contract function is a separate object that has the contract it is an implementation of as a
 * prototype. Therefor, the `verify` and `verifyPostconditions` properties can be overridden for all
 * `AbstractContracts` by changing the properties of `AbstractContracts.prototype`, for one contract by changing its
 * properties, or for a particular contract function `cf` by changing the properties of `cf.contract`. The values
 * the properties hold at the moment of the call are used (this is relevant for asynchronous functions).
 *
 * With well-tested code, the default settings should be used in production. When the code is extremely stable,
 * `verify` can be set to `false` to gain additional speed. In tests, `verify` should be truthy and
 * `verifyPostconditions` should be `true`, at least for the function under test.
 *
 * The `_location` argument is for internal use, and might be removed.
 *
 * @constructor
 */
function AbstractContract (kwargs, /* Object? */ _location) {
  assert.ok(kwargs)
  assert(!kwargs.pre || Array.isArray(kwargs.pre), 'optional kwargs.pre is an array')
  assert(!kwargs.post || Array.isArray(kwargs.post), 'optional kwargs.post is an array')
  assert(!kwargs.exception || Array.isArray(kwargs.exception), 'optional kwargs.exception is an array')
  assert(!_location || _location === AbstractContract.internalLocation || typeof _location === 'string', 'optional private _location is internal or a string')

  const self = this

  /* This cannot be defined in the prototype. The `self` is the contract, not the `this`. When this function is called
     as a method of a random object, that random object is the `this`, not this contract. */
  function abstract () { throw new AbstractContract.AbstractError(self, stack.raw()) }

  const location = _location || stack.location(1)
  AbstractContract.bless(abstract, self, abstract, location)
  property.setAndFreeze(self, '_pre', Object.freeze(kwargs.pre ? kwargs.pre.slice() : []))
  property.setAndFreeze(self, '_post', Object.freeze(kwargs.post ? kwargs.post.slice() : []))
  property.setAndFreeze(self, '_exception', Object.freeze(kwargs.exception ? kwargs.exception.slice() : mustNotHappen))
  property.setAndFreeze(self, 'location', Object.freeze(location))
  property.setAndFreeze(self, 'abstract', abstract)
}

/**
 * Object to be used as location for contracts and implementations that are generated inside this library.
 */
AbstractContract.internalLocation = Object.freeze({
  toString: function () { return 'INTERNAL' }
})

AbstractContract.prototype = {
  constructor: AbstractContract,
  _pre: null,
  _post: null,
  _exception: null,
  isImplementedBy: function (f) {
    return AbstractContract.isAGeneralContractFunction(f) && Object.getPrototypeOf(f.contract) === this
  }
}
property.frozenReadOnlyArray(AbstractContract.prototype, 'pre', '_pre')
property.frozenReadOnlyArray(AbstractContract.prototype, 'post', '_post')
property.frozenReadOnlyArray(AbstractContract.prototype, 'exception', '_exception')
property.setAndFreeze(AbstractContract.prototype, 'location', AbstractContract.internalLocation)
property.setAndFreeze(AbstractContract.prototype, 'abstract', null)
AbstractContract.prototype.verify = true
AbstractContract.prototype.verifyPostconditions = false

AbstractContract.namePrefix = namePrefix

/**
 * This function is intended to be used as the bind function of contract functions. It makes sure
 * that, when applied to a contract function, the result
 * [is also a contract function]{@linkplain AbstractContract#isAContractFunction}.
 * The bind aspect of the functionality is the same as {@link Function#prototype#bind}.
 * The implementation of the resulting contract function is also bound in the same
 * way as the resulting contract function itself.
 */
AbstractContract.bindContractFunction = function bind () {
  assert(AbstractContract.isAGeneralContractFunction(this), 'this is a general contract function')

  const bound = Function.prototype.bind.apply(this, arguments)
  const boundImplementation = Function.prototype.bind.apply(this.implementation, arguments)
  AbstractContract.bless(bound, this.contract, boundImplementation, this.location)
  return bound
}

/**
 * A General Contract Function is an implementation of an AbstractContract. This function verifies whether a function
 * given as a parameter is a General Contract Function.
 *
 * To be a General Contract Function, the subject must
 *

 *
be a function,

 *
have a frozen `contract` property that refers to a AbstractContract,

 *
have a frozen `implementation` property that refers to a function (which realizes the contract),

 *
have a frozen `location` property, that has a value,

 *
have a frozen `bind` property, which is {@link AbstractContract.bindContractFunction}, and

 *
have a `name`, which is a string that gives information for a programmer to understand which contract
 *     function this is, and

 *
if the `implementation` function has a `prototype`, have a `prototype` property,
 *

 *
that is an object,

 *
that has a `constructor` property that is the contract function, and

 *
that has `f.implementation.prototype` in its prototype chain, or is equal to it.
 *

 *

 */
AbstractContract.isAGeneralContractFunction = function (f) {
  // Apart from this, we expect f to have a name. But it is controlled by the JavaScript engine, and we cannot
  // freeze it, and not guaranteed in all engines.
  return typeof f === 'function' &&
         is.frozenOwnProperty(f, 'contract') &&
         f.contract instanceof AbstractContract &&
         is.frozenOwnProperty(f, 'implementation') &&
         typeof f.implementation === 'function' &&
         is.frozenOwnProperty(f, 'location') &&
         f.location &&
         (f === f.implementation ||
            f.name === report.conciseCondition(AbstractContract.namePrefix, f.implementation)) &&
         is.frozenOwnProperty(f, 'bind') &&
         f.bind === AbstractContract.bindContractFunction &&
         (!f.implementation.hasOwnProperty('prototype') ||
             (typeof f.prototype === 'object' &&
                 f.prototype.constructor === f &&
                 (f.prototype === f.implementation.prototype || f.prototype instanceof f.implementation)))
}

/**
 * A Contract Function is an implementation of a Contract. This function verifies whether a function
 * given as a parameter is a Contract Function.
 *
 * To be a Contract Function, the subject must
 *

 *
be a [general contract function]{@linkplain #isAGeneralContractFunction()},

 *
have a frozen `location` property, which is a string that represents a location in source code,
 *     outside this library.

 *

 */
AbstractContract.isAContractFunction = function (f) {
  return AbstractContract.isAGeneralContractFunction(f) && is.stackLocation(f.location)
}

/**
 * Helper function that transforms any function given as contractFunction
 * into a [contract function]{@linkplain AbstractContract#isAContractFunction}
 * for the given parameters.
 * If {@code implFunction.prototype} exists, the {@code contractFunction.prototype} is changed to
 * an object that refers to {@code contractFunction} as {@code contractFunction.prototype.constructor},
 * is otherwise empty, and has {@code implFunction.prototype} as prototype.
 *
 * @param contractFunction {Function} the regular {Function} to be transformed into a contract function
 * @param contract {AbstractContract} the contract contractFunction is a realisation of
 * @param implFunction {Function} the function that is used in contractFunction
 *                     to realize the postconditions of contract under its preconditions
 * @param location {String} the location outside this library that the resulting
 *                          [contract function]{@linkplain AbstractContract#isAContractFunction} will carry,
 *                          that says where it is defined.
 */
AbstractContract.bless = function bless (contractFunction, contract, implFunction, location) {
  assert.equal(typeof contractFunction, 'function')
  // noinspection JSUnresolvedVariable
  assert.ok(!contractFunction.contract)
  // noinspection JSUnresolvedVariable
  assert.ok(!contractFunction.implementation)
  // noinspection JSUnresolvedVariable
  assert.ok(!contractFunction.location)
  assert.strictEqual(contractFunction.bind, Function.prototype.bind)
  assert(contract instanceof AbstractContract, 'contract is an AbstractContract')
  assert.equal(typeof implFunction, 'function')
  assert(location === AbstractContract.internalLocation || is.stackLocation(location), 'location is internal, or a stack location')

  property.setAndFreeze(contractFunction, 'contract', Object.create(contract))
  property.setAndFreeze(contractFunction, 'implementation', implFunction)
  property.setAndFreeze(contractFunction, 'location', location)
  property.setAndFreeze(contractFunction, 'bind', AbstractContract.bindContractFunction)
  if (implFunction.hasOwnProperty('prototype')) {
    contractFunction.prototype = Object.create(implFunction.prototype)
    property.setAndFreeze(contractFunction.prototype, 'constructor', contractFunction)
  }
  /* The name of the contract function will always be 'contractFunction', because we need to define it in
    `Contract.implementation`, because we need to refer to the contract function internally. We would like the result
    of `Contract.implementation` to get a name inferred from its syntactic position, but cannot happen: before
    we reach the 'syntactic position' (a.k.a, we assign the contract function to a variable or property with
    a name), it will already have the name `contractFunction` we need internally. Therefor, we will explicitly set
    the name, based on the name of implementation function.
    The Firefox feature `displayName` will not be used.  */
  // IDEA we might also add a name property to a Contract, and combine it with that
  if (contractFunction !== implFunction) {
    /* `abstract` refers to itself as implementation; we do not change its name (it would create a circular name
       definition) */
    // IDEA defend code against more complex circular structure
    property.frozenDerived(
      contractFunction,
      'name',
      report.conciseCondition.bind(null, namePrefix, implFunction)
    )
  }
}

AbstractContract.falseCondition = falseCondition
AbstractContract.mustNotHappen = mustNotHappen

/**
 * The most general function AbstractContract. This has the most strict preconditions (nothing is allowed), which can
 * be weakened by specializations, and the most general nominal and exceptional postconditions (anything goes),
 * which can be strengthened by specializations.
 */
AbstractContract.root = new AbstractContract(
  {
    pre: AbstractContract.mustNotHappen,
    post: [],
    exception: []
  },
  AbstractContract.internalLocation
)

const message = 'an abstract function cannot be executed'

/**
 * Thrown when an abstract method is called. You shouldn't.
 *
 * @constructor
 */
function AbstractError (contract, rawStack) {
  assert(contract instanceof AbstractContract, 'contract is an AbstractContract')
  assert(is.stack(rawStack), 'rawStack is a stack')

  ContractError.call(this, rawStack)
  property.setAndFreeze(this, 'name', AbstractError.name)
  property.setAndFreeze(this, 'message', message)
  property.setAndFreeze(this, 'contract', contract)
}

AbstractError.prototype = new ContractError(stack.raw())
AbstractError.prototype.constructor = AbstractError
property.setAndFreeze(AbstractError.prototype, 'name', AbstractError.name)
property.setAndFreeze(AbstractError.prototype, 'message', message)
property.setAndFreeze(AbstractError.prototype, 'contract', null)

AbstractError.message = message

AbstractContract.AbstractError = AbstractError

module.exports = AbstractContract
<commit_msg>Fix name of bound contract function<commit_after>/*
 Copyright 2016 - 2018 by Jan Dockx

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

'use strict'

const ContractError = require('./ContractError')
const is = require('../_private/is')
const property = require('../_private/property')
const stack = require('../_private/stack')
const report = require('../_private/report')
const assert = require('assert')

const namePrefix = '𝕋⚖'

/**
 * Function that always returns false.
 */
const falseCondition = function () { return false }

/**
 * Singleton array of {@linkplain AbstractContract#falseCondition}. Can be used the clearly signal
 * that a function should never throw exceptions, or never end nominally, or should never be called,
 * because the conditions will always fail.
 */
const mustNotHappen = Object.freeze([falseCondition])

// noinspection ParameterNamingConventionJS
/**
 * Abstract definition of a function contract.
 *
 * An AbstractContract consists of an array of preconditions, an array of nominal postconditions,
 * and an array of exceptional postconditions.
 *
 * The conditions are functions whose result is interpreted as a booleany value.
 * The conditions are called with the same `this` and arguments as the functional call in which they are
 * verified. When verifying nominal postconditions, the result of the function call is added as extra argument.
 * When verifying exceptional postconditions, the exception thrown by the function call is added as extra argument.
 * Finally, a version of the contract function bound to `this` is supplied as final parameter when verifying
 * nominal and exceptional postconditions. This function reference can be used in contracts that use recursion.
 *
 * The default preconditions and postconditions are the empty array (anything goes). For exceptions, the default
 * condition is `AbstractContract.mustNotHappen` (any exception is a violation).
 *
 * Furthermore, an instance contains a `location` property, which is a line of text
 * that refers to the source code where the contract was created. For internal contracts, this is
 * `AbstractContract.internalLocation.
 *
 * If `verify` and `verifyPostconditions` are both truthy, contract function verification will verify preconditions,
 * postconditions and exception conditions. If `verify` is truthy, but `verifyPostconditions` is falsy, contract
 * function verification will verify preconditions, but not postconditions and exception conditions. This is the
 * default. If `verify` is falsy, contract function executions will not be verified.
 *
 * The `contract` of a contract function is a separate object that has the contract it is an implementation of as a
 * prototype. Therefor, the `verify` and `verifyPostconditions` properties can be overridden for all
 * `AbstractContracts` by changing the properties of `AbstractContracts.prototype`, for one contract by changing its
 * properties, or for a particular contract function `cf` by changing the properties of `cf.contract`. The values
 * the properties hold at the moment of the call are used (this is relevant for asynchronous functions).
 *
 * With well-tested code, the default settings should be used in production. When the code is extremely stable,
 * `verify` can be set to `false` to gain additional speed. In tests, `verify` should be truthy and
 * `verifyPostconditions` should be `true`, at least for the function under test.
 *
 * The `_location` argument is for internal use, and might be removed.
 *
 * @constructor
 */
function AbstractContract (kwargs, /* Object? */ _location) {
  assert.ok(kwargs)
  assert(!kwargs.pre || Array.isArray(kwargs.pre), 'optional kwargs.pre is an array')
  assert(!kwargs.post || Array.isArray(kwargs.post), 'optional kwargs.post is an array')
  assert(!kwargs.exception || Array.isArray(kwargs.exception), 'optional kwargs.exception is an array')
  assert(!_location || _location === AbstractContract.internalLocation || typeof _location === 'string', 'optional private _location is internal or a string')

  const self = this

  /* This cannot be defined in the prototype. The `self` is the contract, not the `this`. When this function is called
     as a method of a random object, that random object is the `this`, not this contract. */
  function abstract () { throw new AbstractContract.AbstractError(self, stack.raw()) }

  const location = _location || stack.location(1)
  AbstractContract.bless(abstract, self, abstract, location)
  property.setAndFreeze(self, '_pre', Object.freeze(kwargs.pre ? kwargs.pre.slice() : []))
  property.setAndFreeze(self, '_post', Object.freeze(kwargs.post ? kwargs.post.slice() : []))
  property.setAndFreeze(self, '_exception', Object.freeze(kwargs.exception ? kwargs.exception.slice() : mustNotHappen))
  property.setAndFreeze(self, 'location', Object.freeze(location))
  property.setAndFreeze(self, 'abstract', abstract)
}

/**
 * Object to be used as location for contracts and implementations that are generated inside this library.
 */
AbstractContract.internalLocation = Object.freeze({
  toString: function () { return 'INTERNAL' }
})

AbstractContract.prototype = {
  constructor: AbstractContract,
  _pre: null,
  _post: null,
  _exception: null,
  isImplementedBy: function (f) {
    return AbstractContract.isAGeneralContractFunction(f) && Object.getPrototypeOf(f.contract) === this
  }
}
property.frozenReadOnlyArray(AbstractContract.prototype, 'pre', '_pre')
property.frozenReadOnlyArray(AbstractContract.prototype, 'post', '_post')
property.frozenReadOnlyArray(AbstractContract.prototype, 'exception', '_exception')
property.setAndFreeze(AbstractContract.prototype, 'location', AbstractContract.internalLocation)
property.setAndFreeze(AbstractContract.prototype, 'abstract', null)
AbstractContract.prototype.verify = true
AbstractContract.prototype.verifyPostconditions = false

AbstractContract.namePrefix = namePrefix

/**
 * This function is intended to be used as the bind function of contract functions. It makes sure
 * that, when applied to a contract function, the result
 * [is also a contract function]{@linkplain AbstractContract#isAContractFunction}.
 * The bind aspect of the functionality is the same as {@link Function#prototype#bind}.
 * The implementation of the resulting contract function is also bound in the same
 * way as the resulting contract function itself.
 */
AbstractContract.bindContractFunction = function bind () {
  assert(AbstractContract.isAGeneralContractFunction(this), 'this is a general contract function')

  const bound = Function.prototype.bind.apply(this, arguments)
  const boundImplementation = Function.prototype.bind.apply(this.implementation, arguments)
  property.frozenDerived(
    boundImplementation,
    'name',
    () => report.conciseCondition('bound', this.implementation)
  )
  AbstractContract.bless(bound, this.contract, boundImplementation, this.location)
  return bound
}

/**
 * A General Contract Function is an implementation of an AbstractContract. This function verifies whether a function
 * given as a parameter is a General Contract Function.
 *
 * To be a General Contract Function, the subject must
 *

 *
be a function,

 *
have a frozen `contract` property that refers to a AbstractContract,

 *
have a frozen `implementation` property that refers to a function (which realizes the contract),

 *
have a frozen `location` property, that has a value,

 *
have a frozen `bind` property, which is {@link AbstractContract.bindContractFunction}, and

 *
have a `name`, which is a string that gives information for a programmer to understand which contract
 *     function this is, and

 *
if the `implementation` function has a `prototype`, have a `prototype` property,
 *

 *
that is an object,

 *
that has a `constructor` property that is the contract function, and

 *
that has `f.implementation.prototype` in its prototype chain, or is equal to it.
 *

 *

 */
AbstractContract.isAGeneralContractFunction = function (f) {
  // Apart from this, we expect f to have a name. But it is controlled by the JavaScript engine, and we cannot
  // freeze it, and not guaranteed in all engines.
  return typeof f === 'function' &&
         is.frozenOwnProperty(f, 'contract') &&
         f.contract instanceof AbstractContract &&
         is.frozenOwnProperty(f, 'implementation') &&
         typeof f.implementation === 'function' &&
         is.frozenOwnProperty(f, 'location') &&
         f.location &&
         (f === f.implementation ||
            f.name === report.conciseCondition(AbstractContract.namePrefix, f.implementation)) &&
         is.frozenOwnProperty(f, 'bind') &&
         f.bind === AbstractContract.bindContractFunction &&
         (!f.implementation.hasOwnProperty('prototype') ||
             (typeof f.prototype === 'object' &&
                 f.prototype.constructor === f &&
                 (f.prototype === f.implementation.prototype || f.prototype instanceof f.implementation)))
}

/**
 * A Contract Function is an implementation of a Contract. This function verifies whether a function
 * given as a parameter is a Contract Function.
 *
 * To be a Contract Function, the subject must
 *

 *
be a [general contract function]{@linkplain #isAGeneralContractFunction()},

 *
have a frozen `location` property, which is a string that represents a location in source code,
 *     outside this library.

 *

 */
AbstractContract.isAContractFunction = function (f) {
  return AbstractContract.isAGeneralContractFunction(f) && is.stackLocation(f.location)
}

/**
 * Helper function that transforms any function given as contractFunction
 * into a [contract function]{@linkplain AbstractContract#isAContractFunction}
 * for the given parameters.
 * If {@code implFunction.prototype} exists, the {@code contractFunction.prototype} is changed to
 * an object that refers to {@code contractFunction} as {@code contractFunction.prototype.constructor},
 * is otherwise empty, and has {@code implFunction.prototype} as prototype.
 *
 * @param contractFunction {Function} the regular {Function} to be transformed into a contract function
 * @param contract {AbstractContract} the contract contractFunction is a realisation of
 * @param implFunction {Function} the function that is used in contractFunction
 *                     to realize the postconditions of contract under its preconditions
 * @param location {String} the location outside this library that the resulting
 *                          [contract function]{@linkplain AbstractContract#isAContractFunction} will carry,
 *                          that says where it is defined.
 */
AbstractContract.bless = function bless (contractFunction, contract, implFunction, location) {
  assert.equal(typeof contractFunction, 'function')
  // noinspection JSUnresolvedVariable
  assert.ok(!contractFunction.contract)
  // noinspection JSUnresolvedVariable
  assert.ok(!contractFunction.implementation)
  // noinspection JSUnresolvedVariable
  assert.ok(!contractFunction.location)
  assert.strictEqual(contractFunction.bind, Function.prototype.bind)
  assert(contract instanceof AbstractContract, 'contract is an AbstractContract')
  assert.equal(typeof implFunction, 'function')
  assert(location === AbstractContract.internalLocation || is.stackLocation(location), 'location is internal, or a stack location')

  property.setAndFreeze(contractFunction, 'contract', Object.create(contract))
  property.setAndFreeze(contractFunction, 'implementation', implFunction)
  property.setAndFreeze(contractFunction, 'location', location)
  property.setAndFreeze(contractFunction, 'bind', AbstractContract.bindContractFunction)
  if (implFunction.hasOwnProperty('prototype')) {
    contractFunction.prototype = Object.create(implFunction.prototype)
    property.setAndFreeze(contractFunction.prototype, 'constructor', contractFunction)
  }
  /* The name of the contract function will always be 'contractFunction', because we need to define it in
    `Contract.implementation`, because we need to refer to the contract function internally. We would like the result
    of `Contract.implementation` to get a name inferred from its syntactic position, but cannot happen: before
    we reach the 'syntactic position' (a.k.a, we assign the contract function to a variable or property with
    a name), it will already have the name `contractFunction` we need internally. Therefor, we will explicitly set
    the name, based on the name of implementation function.
    The Firefox feature `displayName` will not be used.  */
  // IDEA we might also add a name property to a Contract, and combine it with that
  if (contractFunction !== implFunction) {
    /* `abstract` refers to itself as implementation; we do not change its name (it would create a circular name
       definition) */
    // IDEA defend code against more complex circular structure
    property.frozenDerived(
      contractFunction,
      'name',
      report.conciseCondition.bind(null, namePrefix, implFunction)
    )
  }
}

AbstractContract.falseCondition = falseCondition
AbstractContract.mustNotHappen = mustNotHappen

/**
 * The most general function AbstractContract. This has the most strict preconditions (nothing is allowed), which can
 * be weakened by specializations, and the most general nominal and exceptional postconditions (anything goes),
 * which can be strengthened by specializations.
 */
AbstractContract.root = new AbstractContract(
  {
    pre: AbstractContract.mustNotHappen,
    post: [],
    exception: []
  },
  AbstractContract.internalLocation
)

const message = 'an abstract function cannot be executed'

/**
 * Thrown when an abstract method is called. You shouldn't.
 *
 * @constructor
 */
function AbstractError (contract, rawStack) {
  assert(contract instanceof AbstractContract, 'contract is an AbstractContract')
  assert(is.stack(rawStack), 'rawStack is a stack')

  ContractError.call(this, rawStack)
  property.setAndFreeze(this, 'name', AbstractError.name)
  property.setAndFreeze(this, 'message', message)
  property.setAndFreeze(this, 'contract', contract)
}

AbstractError.prototype = new ContractError(stack.raw())
AbstractError.prototype.constructor = AbstractError
property.setAndFreeze(AbstractError.prototype, 'name', AbstractError.name)
property.setAndFreeze(AbstractError.prototype, 'message', message)
property.setAndFreeze(AbstractError.prototype, 'contract', null)

AbstractError.message = message

AbstractContract.AbstractError = AbstractError

module.exports = AbstractContract
<|endoftext|>


<commit_before>import { AttrAssembler } from './AttrAssembler'
import { ChildNodeAssembler } from './ChildNodeAssembler'
import { NodeAssembler } from './NodeAssembler'

const { prototype : { map } } = Array
const { MutationObserver, Node } = window
const { ATTRIBUTE_NODE, DOCUMENT_FRAGMENT_NODE } = Node
const { getNodeOf } = NodeAssembler
const { flatten } = ChildNodeAssembler
const CHILDREN_PROPERTY_NAME = 'children'
const TYPE_FUNCTION = 'function'
const TYPE_STRING = 'string'
const key = Symbol()

/**
 * @see https://www.w3.org/TR/dom/#interface-parentnode
 * @abstract
 */
export class ParentNodeAssembler extends ChildNodeAssembler {
    /**
     * Append child nodes to the node
     * @param {string|Node|ChildNodeAssembler|array|*} childNodes
     */
    append(...childNodes) {
        const node = this.node
        flatten(childNodes).forEach(child => {
            if(child instanceof ChildNodeAssembler || child instanceof AttrAssembler) {
                const parentNode = child.node.parentNode
                if(parentNode && parentNode.nodeType === DOCUMENT_FRAGMENT_NODE) {
                    node.append(parentNode)
                }
                else child.parentNode = node
            }
            else node.append(child)
        })
    }

    /**
     * @param {ChildNodeAssembler|Node|*} object
     * @returns {boolean}
     */
    contains(object) {
        return this.node.contains(getNodeOf(object))
    }

    /**
     * @param {class|string|*} subject - ElementAssembler or AttrAssembler subclass or selector
     * @param {function} [subject.getAttrOf]
     * @param {function} [subject.getInstanceOf]
     * @param {function|string} [filter]
     * @returns {ElementAssembler|AttrAssembler|*|null}
     */
    find(subject, filter) {
        const isString = typeof subject === TYPE_STRING
        if(typeof filter === TYPE_FUNCTION) {
            const instances = this.findAll(subject, filter)
            return instances[0] || null
        }
        let selector = isString?
            subject :
            subject.selector
        if(typeof filter === TYPE_STRING) {
            selector += filter
        }
        const node = this.node.querySelector(selector)
        if(node) {
            if(AttrAssembler.isPrototypeOf(subject)) {
                return subject.getAttrOf(node)
            }
            else {
                const assembler = isString?
                    this.constructor.getAssemblerOf(node) :
                    subject
                return assembler.getInstanceOf(node)
            }
        }
        return null
    }

    /**
     * @param {class|string|*} subject - ElementAssembler or AttrAssembler subclass or selector
     * @param {function} [subject.getAttrOf]
     * @param {function} [subject.getInstanceOf]
     * @param {function|string} [filter]
     * @returns {array.ElementAssembler|array.AttrAssembler|*}
     */
    findAll(subject, filter) {
        const isString = typeof subject === TYPE_STRING
        let selector = isString?
            subject :
            subject.selector
        if(typeof filter === TYPE_STRING) {
            selector += filter
        }
        const nodeList = this.node.querySelectorAll(selector)
        const isAttr = AttrAssembler.isPrototypeOf(subject)
        const results = map.call(nodeList, node => {
            if(isAttr) {
                return subject.getAttrOf(node)
            }
            else {
                const assembler = isString?
                    this.constructor.getAssemblerOf(node) :
                    subject
                return assembler.getInstanceOf(node)
            }
        })
        return typeof filter === TYPE_FUNCTION?
            results.filter(filter) :
            results
    }

    /**
     * Prepend child nodes to the node
     * @param {string|Node|ChildNodeAssembler|array|*} childNodes
     */
    prepend(...childNodes) {
        this.node.prepend(...flatten(childNodes).map(child => {
            const node = getNodeOf(child) || child
            return node.nodeType === ATTRIBUTE_NODE?
                node.ownerElement :
                node
        }))
    }

    /**
     * @param {class|string} type
     * @param {function} callback
     * @param {{attributeOldValue,subtree}|{}|boolean|EventTargetAssembler|*} [options]
     */
    on(type, callback, options) {
        if(AttrAssembler.isPrototypeOf(type)) {
            const { context = this, subtree } = options = getOptions(options)
            const name = type.localName
            const storage = context[key] || (context[key] = { true : {}, false : {} })
            const handlers = storage[Boolean(subtree)]
            const map = handlers[name] || (handlers[name] = new Map)
            if(!map.has(callback)) {
                const observer = new MutationObserver(records => {
                    records.forEach(record => callback.call(context, record))
                })
                map.set(callback, observer)
                options.attributeFilter = [name]
                delete options.context
                observer.observe(this.node, options)
            }
        }
        else super.on(type, callback, options)
    }

    /**
     * @param {class|string} type
     * @param {function} callback
     * @param {{}|boolean|EventTargetAssembler|*} [options]
     */
    un(type, callback, options) {
        if(AttrAssembler.isPrototypeOf(type)) {
            const { context = this, subtree } = getOptions(options)
            const storage = context[key]
            const map = storage && storage[Boolean(subtree)][type.localName]
            const observer = map && map.get(callback)
            if(observer) {
                observer.takeRecords().forEach(record => callback.call(context, record))
                observer.disconnect()
                map.delete(callback)
            }
        }
        else super.un(type, callback, options)
    }

    /**
     * Get an array of child nodes
     * @returns {*} {array}
     */
    get childNodes() {
        return map.call(this.node.childNodes, node => this.getInstanceOf(node))
    }

    /**
     * Append children to the element
     * @param {*} children
     */
    set children(children) {
        if(this.node.hasChildNodes()) {
            Array.from(this.node.childNodes).forEach(child => child.remove())
        }
        this.append(children)
    }

    /**
     * Get all children of the element as an array
     * @returns {ElementAssembler[]}
     */
    get children() {
        return map.call(this.node.children, node => this.getInstanceOf(node))
    }

    /**
     * @param {ChildNodeAssembler|ChildNode|*} firstChild
     */
    set firstChild(firstChild) {
        this.prepend(firstChild)
    }

    /**
     * @returns {ChildNodeAssembler|*|null}
     */
    get firstChild() {
        return this.getInstanceOf(this.node.firstChild)
    }

    /**
     * @returns {ElementAssembler|*|null}
     */
    get firstElementChild() {
        return this.getInstanceOf(this.node.firstElementChild)
    }

    /**
     * @param {ChildNodeAssembler|ChildNode|*} lastChild
     */
    set lastChild(lastChild) {
        this.append(lastChild)
    }

    /**
     * @returns {ChildNodeAssembler|*|null}
     */
    get lastChild() {
        return this.getInstanceOf(this.node.lastChild)
    }

    /**
     * @returns {ChildNodeAssembler|*|null}
     */
    get lastElementChild() {
        return this.getInstanceOf(this.node.lastElementChild)
    }

    /**
     * Set a text content of the node
     * @param {string} textContent
     */
    set textContent(textContent) {
        this.node.textContent = textContent
    }

    /**
     * Get a text content of the node
     * @returns {string}
     */
    get textContent() {
        return this.node.textContent
    }

    /**
     * @returns {string}
     * @override
     */
    static get valuePropertyName() {
        return CHILDREN_PROPERTY_NAME
    }
}

/**
 * @param {{context,attributeOldValue,subtree}|ParentNodeAssembler|*} [options]
 * @param {ParentNodeAssembler|*} [options.context]
 * @param {boolean} [options.attributeOldValue]
 * @param {boolean} [options.subtree]
 * @returns {{context,attributeOldValue,subtree}}
 */
function getOptions(options) {
    const result = {}
    if(options) {
        if(options instanceof NodeAssembler) {
            result.context = options
        }
        else {
            result.context = options.context
            result.attributeOldValue = options.attributeOldValue || false
            result.subtree = options.subtree || false
        }
    }
    return result
}

Object.defineProperty(NodeAssembler, 'ParentNodeAssembler', { value : ParentNodeAssembler })
<commit_msg>ParentNodeAssembler: use duck typing instead of isPrototypeOf method<commit_after>import { AttrAssembler } from './AttrAssembler'
import { ChildNodeAssembler } from './ChildNodeAssembler'
import { NodeAssembler } from './NodeAssembler'

const { prototype : { map } } = Array
const { MutationObserver, Node } = window
const { ATTRIBUTE_NODE, DOCUMENT_FRAGMENT_NODE } = Node
const { getNodeOf } = NodeAssembler
const { flatten } = ChildNodeAssembler
const CHILDREN_PROPERTY_NAME = 'children'
const TYPE_FUNCTION = 'function'
const TYPE_STRING = 'string'
const key = Symbol()

/**
 * @see https://www.w3.org/TR/dom/#interface-parentnode
 * @abstract
 */
export class ParentNodeAssembler extends ChildNodeAssembler {
    /**
     * Append child nodes to the node
     * @param {string|Node|ChildNodeAssembler|array|*} childNodes
     */
    append(...childNodes) {
        const node = this.node
        flatten(childNodes).forEach(child => {
            if(child instanceof ChildNodeAssembler || child instanceof AttrAssembler) {
                const parentNode = child.node.parentNode
                if(parentNode && parentNode.nodeType === DOCUMENT_FRAGMENT_NODE) {
                    node.append(parentNode)
                }
                else child.parentNode = node
            }
            else node.append(child)
        })
    }

    /**
     * @param {ChildNodeAssembler|Node|*} object
     * @returns {boolean}
     */
    contains(object) {
        return this.node.contains(getNodeOf(object))
    }

    /**
     * @param {class|string|*} subject - ElementAssembler or AttrAssembler subclass or selector
     * @param {function} [subject.getAttrOf]
     * @param {function} [subject.getInstanceOf]
     * @param {function|string} [filter]
     * @returns {ElementAssembler|AttrAssembler|*|null}
     */
    find(subject, filter) {
        const isString = typeof subject === TYPE_STRING
        if(typeof filter === TYPE_FUNCTION) {
            const instances = this.findAll(subject, filter)
            return instances[0] || null
        }
        let selector = isString?
            subject :
            subject.selector
        if(typeof filter === TYPE_STRING) {
            selector += filter
        }
        const node = this.node.querySelector(selector)
        if(node) {
            if(typeof subject.getAttrOf === TYPE_FUNCTION) {
                return subject.getAttrOf(node)
            }
            else {
                const assembler = isString?
                    this.constructor.getAssemblerOf(node) :
                    subject
                return assembler.getInstanceOf(node)
            }
        }
        return null
    }

    /**
     * @param {class|string|*} subject - ElementAssembler or AttrAssembler subclass or selector
     * @param {function} [subject.getAttrOf]
     * @param {function} [subject.getInstanceOf]
     * @param {function|string} [filter]
     * @returns {array.ElementAssembler|array.AttrAssembler|*}
     */
    findAll(subject, filter) {
        const isString = typeof subject === TYPE_STRING
        let selector = isString?
            subject :
            subject.selector
        if(typeof filter === TYPE_STRING) {
            selector += filter
        }
        const nodeList = this.node.querySelectorAll(selector)
        const results = map.call(nodeList, node => {
            if(typeof subject.getAttrOf === TYPE_FUNCTION) {
                return subject.getAttrOf(node)
            }
            else {
                const assembler = isString?
                    this.constructor.getAssemblerOf(node) :
                    subject
                return assembler.getInstanceOf(node)
            }
        })
        return typeof filter === TYPE_FUNCTION?
            results.filter(filter) :
            results
    }

    /**
     * Prepend child nodes to the node
     * @param {string|Node|ChildNodeAssembler|array|*} childNodes
     */
    prepend(...childNodes) {
        this.node.prepend(...flatten(childNodes).map(child => {
            const node = getNodeOf(child) || child
            return node.nodeType === ATTRIBUTE_NODE?
                node.ownerElement :
                node
        }))
    }

    /**
     * @param {class|string} type
     * @param {function} callback
     * @param {{attributeOldValue,subtree}|{}|boolean|EventTargetAssembler|*} [options]
     */
    on(type, callback, options) {
        if(typeof type === TYPE_FUNCTION && 'localName' in type) {
            const { context = this, subtree } = options = getOptions(options)
            const name = type.localName
            const storage = context[key] || (context[key] = { true : {}, false : {} })
            const handlers = storage[Boolean(subtree)]
            const map = handlers[name] || (handlers[name] = new Map)
            if(!map.has(callback)) {
                const observer = new MutationObserver(records => {
                    records.forEach(record => callback.call(context, record))
                })
                map.set(callback, observer)
                options.attributeFilter = [name]
                delete options.context
                observer.observe(this.node, options)
            }
        }
        else super.on(type, callback, options)
    }

    /**
     * @param {class|string} type
     * @param {function} callback
     * @param {{}|boolean|EventTargetAssembler|*} [options]
     */
    un(type, callback, options) {
        if(typeof type === TYPE_FUNCTION && 'localName' in type) {
            const { context = this, subtree } = getOptions(options)
            const storage = context[key]
            const map = storage && storage[Boolean(subtree)][type.localName]
            const observer = map && map.get(callback)
            if(observer) {
                observer.takeRecords().forEach(record => callback.call(context, record))
                observer.disconnect()
                map.delete(callback)
            }
        }
        else super.un(type, callback, options)
    }

    /**
     * Get an array of child nodes
     * @returns {*} {array}
     */
    get childNodes() {
        return map.call(this.node.childNodes, node => this.getInstanceOf(node))
    }

    /**
     * Append children to the element
     * @param {*} children
     */
    set children(children) {
        if(this.node.hasChildNodes()) {
            Array.from(this.node.childNodes).forEach(child => child.remove())
        }
        this.append(children)
    }

    /**
     * Get all children of the element as an array
     * @returns {ElementAssembler[]}
     */
    get children() {
        return map.call(this.node.children, node => this.getInstanceOf(node))
    }

    /**
     * @param {ChildNodeAssembler|ChildNode|*} firstChild
     */
    set firstChild(firstChild) {
        this.prepend(firstChild)
    }

    /**
     * @returns {ChildNodeAssembler|*|null}
     */
    get firstChild() {
        return this.getInstanceOf(this.node.firstChild)
    }

    /**
     * @returns {ElementAssembler|*|null}
     */
    get firstElementChild() {
        return this.getInstanceOf(this.node.firstElementChild)
    }

    /**
     * @param {ChildNodeAssembler|ChildNode|*} lastChild
     */
    set lastChild(lastChild) {
        this.append(lastChild)
    }

    /**
     * @returns {ChildNodeAssembler|*|null}
     */
    get lastChild() {
        return this.getInstanceOf(this.node.lastChild)
    }

    /**
     * @returns {ChildNodeAssembler|*|null}
     */
    get lastElementChild() {
        return this.getInstanceOf(this.node.lastElementChild)
    }

    /**
     * Set a text content of the node
     * @param {string} textContent
     */
    set textContent(textContent) {
        this.node.textContent = textContent
    }

    /**
     * Get a text content of the node
     * @returns {string}
     */
    get textContent() {
        return this.node.textContent
    }

    /**
     * @returns {string}
     * @override
     */
    static get valuePropertyName() {
        return CHILDREN_PROPERTY_NAME
    }
}

/**
 * @param {{context,attributeOldValue,subtree}|ParentNodeAssembler|*} [options]
 * @param {ParentNodeAssembler|*} [options.context]
 * @param {boolean} [options.attributeOldValue]
 * @param {boolean} [options.subtree]
 * @returns {{context,attributeOldValue,subtree}}
 */
function getOptions(options) {
    const result = {}
    if(options) {
        if(options instanceof NodeAssembler) {
            result.context = options
        }
        else {
            result.context = options.context
            result.attributeOldValue = options.attributeOldValue || false
            result.subtree = options.subtree || false
        }
    }
    return result
}

Object.defineProperty(NodeAssembler, 'ParentNodeAssembler', { value : ParentNodeAssembler })
<|endoftext|>


<commit_before>var _ = require('underscore');
var Mountpoint = require('mountpoint');

function PluginRtpbroadcast(server) {
  this.server = server;
  this.mountpoints = [];
  this.infoSession = null;
  this.mountpointsSession = null;
  this.watchSession = null;
}

PluginRtpbroadcast.prototype.getUpstreamPlugin = function() {
  return _.find(this.server.upstream.plugins, function(plugin) {
    return plugin instanceof this.constructor;
  }.bind(this));
};

PluginRtpbroadcast.prototype.onRegister = function() {
  return this.listenForMountpointInfo();
};

PluginRtpbroadcast.prototype.onUnregister = function() {
  return Promise.all([
    this.closeInfoSession(),
    this.closeMountpointsSession(),
    this.closeWatchSession()
  ]);
};

PluginRtpbroadcast.prototype.onSetUpstream = function() {
  if (null === this.upstream) {
    this.upstream.off('mountpoint-change');
    return Promise.all([
      this.closeMountpointsSession(),
      this.closeWatchSession()
    ]);
  } else {
    this.upstream.on('mountpoint-change', this.replicateUpstreamMountpoints.bind(this));
    return Promise.all([
      this.openMountpointsSession(),
      this.openWatchSession()
    ]).then(function() {
      this.replicateUpstreamMountpoints();
    }.bind(this))
  }
};

PluginRtpbroadcast.prototype.listenForMountpointInfo = function() {
  var self = this;
  return this.server.getConnection()
    .then(function(connection) {
      return self.infoSession = connection.createSession();
    })
    .then(function(session) {
      return session.attachPlugin('rtpbroadcast');
    })
    .then(function(plugin) {
      plugin.on('message', function(message) {
        if (message['body']['request'] === 'mountpoint-info') {
          self.updateMountpoints(message['body']['data']);
        }
      });
      // add transaction
      return plugin.sendTransaction('upgrade connection');
    });
};

PluginRtpbroadcast.prototype.closeInfoSession = function() {
  return this.infoSession.destroy().finally(function() {
    this.infoSession = null;
  }.bind(this));
};

PluginRtpbroadcast.prototype.replicateUpstreamMountpoints = function() {
  var upstreamMountpointsIds = _.pluck(this.getUpstreamPlugin().mountpoints, 'id');
  var mountpointsIds = _.pluck(this.mountpoints, 'id');

  _.difference(upstreamMountpointsIds, mountpointsIds).forEach(function(mountpointId) {
    this.removeMountpoint(mountpointId);
  }.bind(this));

  this.differentMountpoints(mountpointsIds, upstreamMountpointsIds).forEach(function(mountpointId) {
    this.createMountpoint(mountpointId);
  }.bind(this));
};

PluginRtpbroadcast.prototype.createMountpoint = function(mountpointId) {
  return this.getMountpointsSession()
    .then(function(session) {
      return session.attachPlugin('rtpbroadcast');
    })
    .then(function(plugin) {
      // probably should check transaction response
      return plugin.send('create-mountpoint', mountpointId);
    });
};

PluginRtpbroadcast.prototype.removeMountpoint = function(mountpoint) {
  this.getMountpointsSession().detachPlugin(mountpoint.pluginId);
};

PluginRtpbroadcast.prototype.openMountpointsSession = function() {
  if (this.mountpointsSession) {
    throw new Error('Mountpoints session already present');
  }
  return this.server.getConnection()
    .then(function(connection) {
      return this.mountpointsSession = connection.createSession();
    }.bind(this));
};

PluginRtpbroadcast.prototype.getMountpointsSession = function() {
  return this.mountpointsSession;
};

PluginRtpbroadcast.prototype.closeMountpointsSession = function() {
  return this.mountpointsSession.destroy().finally(function() {
    this.mountpointsSession = null;
  }.bind(this));
};

PluginRtpbroadcast.prototype.openWatchSession = function() {
  if (this.watchSession) {
    throw new Error('Watch session already present');
  }
  return this.server.upstream.getConnection()
    .then(function(connection) {
      return this.watchSession = connection.createSession();
    }.bind(this));
};

PluginRtpbroadcast.prototype.getWatchSession = function() {
  return this.watchSession;
};

PluginRtpbroadcast.prototype.closeWatchSession = function() {
  return this.watchSession.destroy().finally(function() {
    this.watchSession = null;
  }.bind(this));
};

PluginRtpbroadcast.prototype.updateMountpoints = function(mountpointsData) {
  var realMountpoints = mountpointsData.map(function(mountpointInfo) {
    return new Mountpoint(this, mountpointInfo['id'], mountpointInfo['pluginId']);
  }.bind(this));

  this.differentMountpoints(realMountpoints, this.mountpoints).forEach(function(mountpoint) {
    this.watchUpstream(mountpoint);
  }.bind(this));

  this.differentMountpoints(this.mountpoints, realMountpoints).forEach(function(mountpoint) {
    this.stopWatchUpstream(mountpoint);
  }.bind(this));

  this.mountpoints = realMountpoints;
  this.emit('mountpoint-change');
};

PluginRtpbroadcast.prototype.watchUpstream = function() {
  return this.getWatchSession()
    .then(function(session) {
      return session.attachPlugin('rtpbroadcast');
    })
    .then(function(plugin) {
      return plugin.sendTransaction('watch-udp');
    });
};

PluginRtpbroadcast.prototype.stopWatchUpstream = function(mountpoint) {
  return this.getWatchSession().detachPlugin(mountpoint.pluginId);
};

PluginRtpbroadcast.prototype.differentMountpoints = function(mountpoints1, mountpoints2) {
  return _.reject(this.mountpoints, function(mountpoint) {
    return _.find(realMountpoints, function(realMountpoint) {
      return realMountpoint.id === mountpoint.id;
    });
  });
};

module.exports = PluginRtpbroadcast;
<commit_msg>Rename mountpointsSession to createSession<commit_after>var _ = require('underscore');
var Mountpoint = require('mountpoint');

function PluginRtpbroadcast(server) {
  this.server = server;
  this.mountpoints = [];
  this.infoSession = null;
  this.createSession = null;
  this.watchSession = null;
}

PluginRtpbroadcast.prototype.getUpstreamPlugin = function() {
  return _.find(this.server.upstream.plugins, function(plugin) {
    return plugin instanceof this.constructor;
  }.bind(this));
};

PluginRtpbroadcast.prototype.onRegister = function() {
  return this.listenForMountpointInfo();
};

PluginRtpbroadcast.prototype.onUnregister = function() {
  return Promise.all([
    this.closeInfoSession(),
    this.closeCreateSession(),
    this.closeWatchSession()
  ]);
};

PluginRtpbroadcast.prototype.onSetUpstream = function() {
  if (null === this.upstream) {
    this.upstream.off('mountpoint-change');
    return Promise.all([
      this.closeCreateSession(),
      this.closeWatchSession()
    ]);
  } else {
    this.upstream.on('mountpoint-change', this.replicateUpstreamMountpoints.bind(this));
    return Promise.all([
      this.openCreateSession(),
      this.openWatchSession()
    ]).then(function() {
      this.replicateUpstreamMountpoints();
    }.bind(this))
  }
};

PluginRtpbroadcast.prototype.listenForMountpointInfo = function() {
  var self = this;
  return this.server.getConnection()
    .then(function(connection) {
      return self.infoSession = connection.createSession();
    })
    .then(function(session) {
      return session.attachPlugin('rtpbroadcast');
    })
    .then(function(plugin) {
      plugin.on('message', function(message) {
        if (message['body']['request'] === 'mountpoint-info') {
          self.updateMountpoints(message['body']['data']);
        }
      });
      // add transaction
      return plugin.sendTransaction('upgrade connection');
    });
};

PluginRtpbroadcast.prototype.closeInfoSession = function() {
  return this.infoSession.destroy().finally(function() {
    this.infoSession = null;
  }.bind(this));
};

PluginRtpbroadcast.prototype.replicateUpstreamMountpoints = function() {
  var upstreamMountpointsIds = _.pluck(this.getUpstreamPlugin().mountpoints, 'id');
  var mountpointsIds = _.pluck(this.mountpoints, 'id');

  _.difference(upstreamMountpointsIds, mountpointsIds).forEach(function(mountpointId) {
    this.removeMountpoint(mountpointId);
  }.bind(this));

  this.differentMountpoints(mountpointsIds, upstreamMountpointsIds).forEach(function(mountpointId) {
    this.createMountpoint(mountpointId);
  }.bind(this));
};

PluginRtpbroadcast.prototype.createMountpoint = function(mountpointId) {
  return this.getCreateSession()
    .then(function(session) {
      return session.attachPlugin('rtpbroadcast');
    })
    .then(function(plugin) {
      // probably should check transaction response
      return plugin.send('create-mountpoint', mountpointId);
    });
};

PluginRtpbroadcast.prototype.removeMountpoint = function(mountpoint) {
  this.getCreateSession().detachPlugin(mountpoint.pluginId);
};

PluginRtpbroadcast.prototype.openCreateSession = function() {
  if (this.createSession) {
    throw new Error('Mountpoints session already present');
  }
  return this.server.getConnection()
    .then(function(connection) {
      return this.createSession = connection.createSession();
    }.bind(this));
};

PluginRtpbroadcast.prototype.getCreateSession = function() {
  return this.createSession;
};

PluginRtpbroadcast.prototype.closeCreateSession = function() {
  return this.createSession.destroy().finally(function() {
    this.createSession = null;
  }.bind(this));
};

PluginRtpbroadcast.prototype.openWatchSession = function() {
  if (this.watchSession) {
    throw new Error('Watch session already present');
  }
  return this.server.upstream.getConnection()
    .then(function(connection) {
      return this.watchSession = connection.createSession();
    }.bind(this));
};

PluginRtpbroadcast.prototype.getWatchSession = function() {
  return this.watchSession;
};

PluginRtpbroadcast.prototype.closeWatchSession = function() {
  return this.watchSession.destroy().finally(function() {
    this.watchSession = null;
  }.bind(this));
};

PluginRtpbroadcast.prototype.updateMountpoints = function(mountpointsData) {
  var realMountpoints = mountpointsData.map(function(mountpointInfo) {
    return new Mountpoint(this, mountpointInfo['id'], mountpointInfo['pluginId']);
  }.bind(this));

  this.differentMountpoints(realMountpoints, this.mountpoints).forEach(function(mountpoint) {
    this.watchUpstream(mountpoint);
  }.bind(this));

  this.differentMountpoints(this.mountpoints, realMountpoints).forEach(function(mountpoint) {
    this.stopWatchUpstream(mountpoint);
  }.bind(this));

  this.mountpoints = realMountpoints;
  this.emit('mountpoint-change');
};

PluginRtpbroadcast.prototype.watchUpstream = function() {
  return this.getWatchSession()
    .then(function(session) {
      return session.attachPlugin('rtpbroadcast');
    })
    .then(function(plugin) {
      return plugin.sendTransaction('watch-udp');
    });
};

PluginRtpbroadcast.prototype.stopWatchUpstream = function(mountpoint) {
  return this.getWatchSession().detachPlugin(mountpoint.pluginId);
};

PluginRtpbroadcast.prototype.differentMountpoints = function(mountpoints1, mountpoints2) {
  return _.reject(this.mountpoints, function(mountpoint) {
    return _.find(realMountpoints, function(realMountpoint) {
      return realMountpoint.id === mountpoint.id;
    });
  });
};

module.exports = PluginRtpbroadcast;
<|endoftext|>