<commit_before>
angular.module('os.biospecimen.specimen.addedit', [])
  .controller('AddEditSpecimenCtrl', function(
    $scope, $state, $parse, cp, cpr, visit, specimen, extensionCtxt,
    aliquotQtyReq, barcodingEnabled, spmnBarcodesAutoGen, hasDict, sysDict,
    cpDict, layout, onValueChangeCb, spmnReq, defSpmns, createDerived, imagingEnabled,
    Alerts, CpConfigSvc, PluginReg, Util, ParticipantSpecimensViewState,
    Specimen, CollectSpecimensSvc) {

    var inputCtxts;

    function init() {
      $scope.opts = {
        cp: cp, cpr: cpr, visit: visit, spmnReq: spmnReq,
        extensionCtxt: extensionCtxt, hasDict: hasDict,
        sysDict: sysDict, cpDict: cpDict,
        barcodingEnabled: barcodingEnabled, spmnBarcodesAutoGen: spmnBarcodesAutoGen,
        editMode: !!specimen.id, reqId: specimen.reqId, aliquotQtyReq: aliquotQtyReq,
        layout: layout, onValueChange: onValueChangeCb, mdInput: false,
        hasInfo: PluginReg.getTmpls('specimen-addedit', 'info').length > 0,
        createDerived: createDerived, imagingEnabled: imagingEnabled
      }

      CpConfigSvc.getCommonCfg(cp.id, 'addSpecimen').then(
        function(cfg) {
          angular.extend($scope.opts, cfg || {});
        }
      );

      if (!specimen.id && !specimen.reqId && defSpmns.length > 0) {
        inputCtxts = $scope.inputCtxts = getInputCtxts(defSpmns);
      } else {
        inputCtxts = $scope.inputCtxts = [{specimen: angular.copy(specimen), form: {}, open: true}]
      }
    }

    function getInputCtxts(defSpmns) {
      return (defSpmns || []).map(
        function(defSpmn, idx) {
          var spmn = new Specimen({lineage: 'New', visitId: visit.id, labelFmt: cpr.specimenLabelFmt});
          angular.forEach(defSpmn.fields,
            function(field) {
              try {
                $parse(field.name).assign({specimen: spmn}, field.value);
              } catch (e) {
                // meant for devs / support staff to debug
                Alerts.errorText("Invalid field definition: " + (field && JSON.stringify(field)));
              }
            }
          );

          var aliquots = (defSpmn.aliquots || []).map(
            function(spec) {
              return new Specimen({
                lineage: 'Aliquot', type: spec.type,
                noOfAliquots: spec.count, qtyPerAliquot: spec.quantity,
                pathology: spec.pathology
              });
            }
          );

          return {specimen: spmn, form: {}, open: idx == 0, aliquots: aliquots.length > 0 ? aliquots : undefined};
        }
      );
    }

    function getState() {
      if (cp.specimenCentric) {
        return {state: $state.get('cp-specimens'), params: {cpId: cp.id}};
      } else {
        return {state: $state.get('visit-detail.overview'), params: {visitId: visit.id}};
      }
    };

    $scope.addAnother = function() {
      inputCtxts.push({
        specimen: new Specimen({lineage: 'New', visitId: visit.id, labelFmt: cpr.specimenLabelFmt}),
        form: {},
        open: true
      });
    }

    $scope.remove = function(event, index) {
      event.stopPropagation();

      Util.showConfirm({
        title: 'specimens.delete_q',
        confirmMsg: 'specimens.confirm_q',
        isWarning: true,
        ok: function() {
          inputCtxts.splice(index, 1);
          if (inputCtxts.length == 0) {
            $scope.addAnother();
          }
        }
      });
    }

    $scope.addCopyOfLast = function(reqAliquots) {
      var lastCtrl = inputCtxts[inputCtxts.length - 1].form.ctrl;

      var lastSpmn = lastCtrl.getSpecimens()[0];
      if (!!lastSpmn) {
        var spmn = angular.copy(lastSpmn);
        var location = spmn.storageLocation;
        if (location) {
          spmn.storageLocation = { name: location.name, mode: location.mode };
        }

        delete spmn.label;
        delete spmn.barcode;
        delete spmn.$$count;
        delete spmn.$$labels;

        var aliquots = reqAliquots ? angular.copy(lastCtrl.getAliquotSpec()) : undefined;
        inputCtxts.push({
          specimen: spmn,
          aliquots: aliquots,
          form: {},
          open: true
        });
      }
    }

    $scope.next = function() {
      var error = false, specimensToCollect = [];
      angular.forEach(inputCtxts,
        function(ctxt) {
          var spmns = ctxt.form.ctrl.getSpecimens(true);

          if (spmns) {
            spmns[0].selected = true;
            spmns[0].status = 'Pending';

            var labels = Util.splitStr(spmns[0].$$labels || '', /,|\t|\n/);
            var count = labels.length || spmns[0].$$count || 1;
            for (var i = 0; i < +count; ++i) {
              if (i != 0) {
                spmns = angular.copy(spmns);
                var location = spmns[0].storageLocation;
                if (location) {
                  spmns[0].storageLocation = { name: location.name, mode: location.mode };
                }

                delete spmns[0].label;
                delete spmns[0].barcode;
                delete spmns[0].$$count;
                delete spmns[0].$$labels;
              }

              if (labels.length > 0) {
                spmns[0].label = labels[i];
              }

              Array.prototype.push.apply(specimensToCollect, spmns);
            }
          } else {
            error = true;
          }
        }
      );

      if (error) {
        return;
      }

      var opts = {showCollVisitDetails: false};
      CollectSpecimensSvc.collect(getState(), visit, specimensToCollect, opts);
    }

    $scope.update = function() {
      var input = inputCtxts[0].form.ctrl.getSpecimens();
      if (!input || input.length == 0) {
        return;
      }

      input[0].$saveOrUpdate().then(
        function(result) {
          angular.extend(specimen, result);
          ParticipantSpecimensViewState.specimensUpdated($scope);

          var params = {specimenId: result.id, cprId: result.cprId, visitId: result.visitId, srId: result.reqId};
          $state.go('specimen-detail.overview', params);
        }
      );
    }

    init();
  })
  .directive('osSpecimenAddeditForm', function($rootScope, Specimen, SpecimenUtil, Util, PvManager, ExtensionsUtil) {

    function loadPvs(scope) {
      scope.biohazards       = PvManager.getPvs('specimen-biohazard');
      scope.specimenStatuses = PvManager.getPvs('specimen-status');
    };

    function updateParent(parent, children) {
      angular.forEach(children, function(child) { child.parent = parent; });
      parent.children = parent.children || [];
      parent.children = parent.children.concat(children);
    }

    function getAliquots(opts, primarySpmn, types, typeSpecs) {
      var result = [], children = [], derived = undefined;

      angular.forEach(types,
        function(type) {
          derived = undefined;
          angular.forEach(typeSpecs[type],
            function(aliquotSpec) {
              var detail = angular.copy({aliquotSpec: aliquotSpec});
              angular.extend(detail, {parentSpecimen: primarySpmn, deFormCtrl: {}, cpr: opts.cpr});
              detail.aliquotSpec.createDerived = opts.createDerived;

              var tree = SpecimenUtil.collectAliquots(detail);
              tree.splice(0, 1); // remove parent as it is already in our final list
              if (type != primarySpmn.type) {
                if (derived) {
                  if (!isNaN(derived.initialQty) && !isNaN(tree[0].initialQty)) {
                    derived.initialQty = derived.initialQty + tree[0].initialQty;
                  } else {
                    derived.initialQty = undefined;
                  }

                  tree.splice(0, 1);         // remove derived
                  primarySpmn.children = []; // remove derived as child of primary specimen;
                  updateParent(derived, tree);
                } else {
                  derived = tree[0];
                }
              }

              Array.prototype.push.apply(result, tree);
              Array.prototype.push.apply(children, primarySpmn.children); // accumulate direct children of primary spmns
            }
          );
        }
      );

      primarySpmn.children = children;
      return result;
    }

    return {
      restrict: 'E',

      templateUrl: 'modules/biospecimen/participant/specimen/addedit-form.html',

      scope: {
        opts    : '=',
        specimen: '=',
        aliquots: '=',
        ctrl    : '='
      },

      controller: function($scope) {
        this.getSpecimens = function(reqAliquots) {
          var formCtrl = $scope.deFormCtrl.ctrl;
          if (formCtrl && !formCtrl.validate()) {
            return null;
          }

          var primarySpmn = $scope.inputSpmn;
          if (formCtrl) {
            primarySpmn.extensionDetail = formCtrl.getFormData();
          }

          var spmnCtx = $scope.spmnCtx, result = [primarySpmn];
          if (reqAliquots && spmnCtx.createAliquots) {
            var types = [], typeSpecs = {};
            angular.forEach(spmnCtx.aliquots,
              function(spec) {
                if (types.indexOf(spec.type) == -1) {
                  types.push(spec.type);
                }

                typeSpecs[spec.type] = typeSpecs[spec.type] || [];
                typeSpecs[spec.type].push(spec);
              }
            );

            var tree = getAliquots($scope.opts, primarySpmn, types, typeSpecs);
            Array.prototype.push.apply(result, tree);
          } else {
            primarySpmn.children = [];
          }

          return result;
        }

        this.getAliquotSpec = function() {
          return ($scope.spmnCtx.createAliquots && $scope.spmnCtx.aliquots) || [];
        }
      },

      link: function(scope, element, attrs, ctrl) {
        var opts = scope.opts, specimen = scope.specimen;
        scope.ctrl.ctrl = ctrl;

        var inputSpmn = scope.inputSpmn = specimen; //angular.copy(specimen);
        delete inputSpmn.children;

        inputSpmn.cpId = inputSpmn.cpId || opts.cp.id;
        inputSpmn.visitId = opts.visit && opts.visit.id;
        inputSpmn.createdOn = inputSpmn.createdOn || new Date();

        if (inputSpmn.lineage == 'Aliquot') {
          inputSpmn.anatomicSite = inputSpmn.laterality = undefined;
        }

        if (inputSpmn.status != 'Collected') {
          if (!inputSpmn.id) {
            inputSpmn.status = 'Collected';
          }

          inputSpmn.availableQty = inputSpmn.initialQty;
        }

        if (!inputSpmn.labelFmt) {
          if (specimen.lineage == 'New') {
            inputSpmn.labelFmt = opts.cpr.specimenLabelFmt;
          } else if (specimen.lineage == 'Aliquot') {
            inputSpmn.labelFmt = opts.cpr.aliquotLabelFmt;
          } else if (specimen.lineage == 'Derived') {
            inputSpmn.labelFmt = opts.cpr.derivativeLabelFmt;
          }
        }

        var exObjs = ['specimen.lineage', 'specimen.parentLabel', 'specimen.events'];
        if (!inputSpmn.id && !inputSpmn.reqId) {
          var ce = inputSpmn.collectionEvent = inputSpmn.collectionEvent || {};
          ce.user = (!ce.user || !ce.user.id) ? $rootScope.currentUser : ce.user;
          ce.container = ce.container || 'Not Specified';
          ce.procedure = ce.procedure || 'Not Specified';

          var re = inputSpmn.receivedEvent = inputSpmn.receivedEvent || {};
          re.user = (!re.user || !re.user.id) ? $rootScope.currentUser : re.user;
          re.receivedQuality = re.receivedQuality || 'Acceptable';
        }

        if (inputSpmn.lineage != 'New') {
          exObjs.push('specimen.collectionEvent', 'specimen.receivedEvent');
        }

        inputSpmn.initialQty    = Util.getNumberInScientificNotation(inputSpmn.initialQty);
        inputSpmn.availableQty  = Util.getNumberInScientificNotation(inputSpmn.availableQty);
        inputSpmn.concentration = Util.getNumberInScientificNotation(inputSpmn.concentration);

        var spmnCtx = scope.spmnCtx = {
          currentDate: new Date(),
          obj: {specimen: inputSpmn, cpr: opts.cpr, visit: opts.visit, cp: opts.cp},
          opts: angular.extend({viewCtx: scope}, opts),
          inObjs: ['specimen'], exObjs: exObjs,
          isVirtual: inputSpmn.showVirtual(),
          manualSpecLabelReq: !!inputSpmn.label || !inputSpmn.labelFmt || opts.cp.manualSpecLabelEnabled,
          aliquots: scope.aliquots || [new Specimen({lineage: 'Aliquot'})],
          createAliquots: (scope.aliquots || []).length > 0
        };

        scope.deFormCtrl = {};
        scope.extnOpts = ExtensionsUtil.getExtnOpts(inputSpmn, opts.extensionCtxt);

        if (!opts.hasDict) {
          loadPvs(scope);
        }

        scope.toggleAliquots = function() {
          spmnCtx.aliquots = [new Specimen({lineage: 'Aliquot'})];
        }

        scope.addAnotherAliquot = function() {
          var aliquot = new Specimen({lineage: 'Aliquot'});
          spmnCtx.aliquots.push(aliquot);

          if (spmnCtx.opts.$$sdeFormFields) {
            spmnCtx.opts.$$sdeFormFields.valueChanged(spmnCtx.obj, aliquot, 'aliquots.$onAdd', 'aliquots');
          }
        }

        scope.removeAliquot = function(idx) {
          var aliquots = spmnCtx.aliquots.splice(idx, 1);
          if (spmnCtx.opts.$$sdeFormFields && aliquots.length > 0) {
            spmnCtx.opts.$$sdeFormFields.valueChanged(spmnCtx.obj, aliquots[0], 'aliquots.$onRemove', 'aliquots');
          }

          if (spmnCtx.aliquots.length == 0) {
            scope.addAnotherAliquot();
          }
        }

        scope.addIfLastAliquot = function(idx) {
          if (idx == (spmnCtx.aliquots.length - 1)) {
            scope.addAnotherAliquot();
          }
        }
      }
    }
  });
<commit_msg>OPSMN-4534: Fixed the 'Add Specimen' workflow to allow creating primary specimen and aliquots of same type.<commit_after>
angular.module('os.biospecimen.specimen.addedit', [])
  .controller('AddEditSpecimenCtrl', function(
    $scope, $state, $parse, cp, cpr, visit, specimen, extensionCtxt,
    aliquotQtyReq, barcodingEnabled, spmnBarcodesAutoGen, hasDict, sysDict,
    cpDict, layout, onValueChangeCb, spmnReq, defSpmns, createDerived, imagingEnabled,
    Alerts, CpConfigSvc, PluginReg, Util, ParticipantSpecimensViewState,
    Specimen, CollectSpecimensSvc) {

    var inputCtxts;

    function init() {
      $scope.opts = {
        cp: cp, cpr: cpr, visit: visit, spmnReq: spmnReq,
        extensionCtxt: extensionCtxt, hasDict: hasDict,
        sysDict: sysDict, cpDict: cpDict,
        barcodingEnabled: barcodingEnabled, spmnBarcodesAutoGen: spmnBarcodesAutoGen,
        editMode: !!specimen.id, reqId: specimen.reqId, aliquotQtyReq: aliquotQtyReq,
        layout: layout, onValueChange: onValueChangeCb, mdInput: false,
        hasInfo: PluginReg.getTmpls('specimen-addedit', 'info').length > 0,
        createDerived: createDerived, imagingEnabled: imagingEnabled
      }

      CpConfigSvc.getCommonCfg(cp.id, 'addSpecimen').then(
        function(cfg) {
          angular.extend($scope.opts, cfg || {});
        }
      );

      if (!specimen.id && !specimen.reqId && defSpmns.length > 0) {
        inputCtxts = $scope.inputCtxts = getInputCtxts(defSpmns);
      } else {
        inputCtxts = $scope.inputCtxts = [{specimen: angular.copy(specimen), form: {}, open: true}]
      }
    }

    function getInputCtxts(defSpmns) {
      return (defSpmns || []).map(
        function(defSpmn, idx) {
          var spmn = new Specimen({lineage: 'New', visitId: visit.id, labelFmt: cpr.specimenLabelFmt});
          angular.forEach(defSpmn.fields,
            function(field) {
              try {
                $parse(field.name).assign({specimen: spmn}, field.value);
              } catch (e) {
                // meant for devs / support staff to debug
                Alerts.errorText("Invalid field definition: " + (field && JSON.stringify(field)));
              }
            }
          );

          var aliquots = (defSpmn.aliquots || []).map(
            function(spec) {
              return new Specimen({
                lineage: 'Aliquot', type: spec.type,
                noOfAliquots: spec.count, qtyPerAliquot: spec.quantity,
                pathology: spec.pathology
              });
            }
          );

          return {specimen: spmn, form: {}, open: idx == 0, aliquots: aliquots.length > 0 ? aliquots : undefined};
        }
      );
    }

    function getState() {
      if (cp.specimenCentric) {
        return {state: $state.get('cp-specimens'), params: {cpId: cp.id}};
      } else {
        return {state: $state.get('visit-detail.overview'), params: {visitId: visit.id}};
      }
    };

    $scope.addAnother = function() {
      inputCtxts.push({
        specimen: new Specimen({lineage: 'New', visitId: visit.id, labelFmt: cpr.specimenLabelFmt}),
        form: {},
        open: true
      });
    }

    $scope.remove = function(event, index) {
      event.stopPropagation();

      Util.showConfirm({
        title: 'specimens.delete_q',
        confirmMsg: 'specimens.confirm_q',
        isWarning: true,
        ok: function() {
          inputCtxts.splice(index, 1);
          if (inputCtxts.length == 0) {
            $scope.addAnother();
          }
        }
      });
    }

    $scope.addCopyOfLast = function(reqAliquots) {
      var lastCtrl = inputCtxts[inputCtxts.length - 1].form.ctrl;

      var lastSpmn = lastCtrl.getSpecimens()[0];
      if (!!lastSpmn) {
        var spmn = angular.copy(lastSpmn);
        var location = spmn.storageLocation;
        if (location) {
          spmn.storageLocation = { name: location.name, mode: location.mode };
        }

        delete spmn.label;
        delete spmn.barcode;
        delete spmn.$$count;
        delete spmn.$$labels;

        var aliquots = reqAliquots ? angular.copy(lastCtrl.getAliquotSpec()) : undefined;
        inputCtxts.push({
          specimen: spmn,
          aliquots: aliquots,
          form: {},
          open: true
        });
      }
    }

    $scope.next = function() {
      var error = false, specimensToCollect = [];
      angular.forEach(inputCtxts,
        function(ctxt) {
          var spmns = ctxt.form.ctrl.getSpecimens(true);

          if (spmns) {
            spmns[0].selected = true;
            spmns[0].status = 'Pending';

            var labels = Util.splitStr(spmns[0].$$labels || '', /,|\t|\n/);
            var count = labels.length || spmns[0].$$count || 1;
            for (var i = 0; i < +count; ++i) {
              if (i != 0) {
                spmns = angular.copy(spmns);
                var location = spmns[0].storageLocation;
                if (location) {
                  spmns[0].storageLocation = { name: location.name, mode: location.mode };
                }

                delete spmns[0].label;
                delete spmns[0].barcode;
                delete spmns[0].$$count;
                delete spmns[0].$$labels;
              }

              if (labels.length > 0) {
                spmns[0].label = labels[i];
              }

              Array.prototype.push.apply(specimensToCollect, spmns);
            }
          } else {
            error = true;
          }
        }
      );

      if (error) {
        return;
      }

      var opts = {showCollVisitDetails: false};
      CollectSpecimensSvc.collect(getState(), visit, specimensToCollect, opts);
    }

    $scope.update = function() {
      var input = inputCtxts[0].form.ctrl.getSpecimens();
      if (!input || input.length == 0) {
        return;
      }

      input[0].$saveOrUpdate().then(
        function(result) {
          angular.extend(specimen, result);
          ParticipantSpecimensViewState.specimensUpdated($scope);

          var params = {specimenId: result.id, cprId: result.cprId, visitId: result.visitId, srId: result.reqId};
          $state.go('specimen-detail.overview', params);
        }
      );
    }

    init();
  })
  .directive('osSpecimenAddeditForm', function($rootScope, Specimen, SpecimenUtil, Util, PvManager, ExtensionsUtil) {

    function loadPvs(scope) {
      scope.biohazards       = PvManager.getPvs('specimen-biohazard');
      scope.specimenStatuses = PvManager.getPvs('specimen-status');
    };

    function updateParent(parent, children) {
      angular.forEach(children, function(child) { child.parent = parent; });
      parent.children = parent.children || [];
      parent.children = parent.children.concat(children);
    }

    function getAliquots(opts, primarySpmn, types, typeSpecs) {
      var result = [], children = [], derived = undefined;

      angular.forEach(types,
        function(type) {
          derived = undefined;
          angular.forEach(typeSpecs[type],
            function(aliquotSpec) {
              var detail = angular.copy({aliquotSpec: aliquotSpec});
              angular.extend(detail, {parentSpecimen: primarySpmn, deFormCtrl: {}, cpr: opts.cpr});
              detail.aliquotSpec.createDerived = opts.createDerived;

              var tree = SpecimenUtil.collectAliquots(detail);
              tree.splice(0, 1); // remove parent as it is already in our final list
              if (type != primarySpmn.type) {
                if (derived) {
                  if (!isNaN(derived.initialQty) && !isNaN(tree[0].initialQty)) {
                    derived.initialQty = derived.initialQty + tree[0].initialQty;
                  } else {
                    derived.initialQty = undefined;
                  }

                  tree.splice(0, 1);         // remove derived
                  primarySpmn.children = []; // remove derived as child of primary specimen;
                  updateParent(derived, tree);
                } else {
                  derived = tree[0];
                }
              }

              Array.prototype.push.apply(result, tree);
              Array.prototype.push.apply(children, primarySpmn.children); // accumulate direct children of primary spmns
            }
          );
        }
      );

      primarySpmn.children = children;
      return result;
    }

    return {
      restrict: 'E',

      templateUrl: 'modules/biospecimen/participant/specimen/addedit-form.html',

      scope: {
        opts    : '=',
        specimen: '=',
        aliquots: '=',
        ctrl    : '='
      },

      controller: function($scope) {
        this.getSpecimens = function(reqAliquots) {
          var formCtrl = $scope.deFormCtrl.ctrl;
          if (formCtrl && !formCtrl.validate()) {
            return null;
          }

          var primarySpmn = $scope.inputSpmn;
          if (formCtrl) {
            primarySpmn.extensionDetail = formCtrl.getFormData();
          }

          var spmnCtx = $scope.spmnCtx, result = [primarySpmn];
          if (reqAliquots && spmnCtx.createAliquots) {
            var types = [], typeSpecs = {};
            angular.forEach(spmnCtx.aliquots,
              function(spec) {
                if (!spec.type) {
                  return;
                }

                if (types.indexOf(spec.type) == -1) {
                  types.push(spec.type);
                }

                typeSpecs[spec.type] = typeSpecs[spec.type] || [];
                typeSpecs[spec.type].push(spec);
              }
            );

            var tree = getAliquots($scope.opts, primarySpmn, types, typeSpecs);
            Array.prototype.push.apply(result, tree);
          } else {
            primarySpmn.children = [];
          }

          return result;
        }

        this.getAliquotSpec = function() {
          return ($scope.spmnCtx.createAliquots && $scope.spmnCtx.aliquots) || [];
        }
      },

      link: function(scope, element, attrs, ctrl) {
        var opts = scope.opts, specimen = scope.specimen;
        scope.ctrl.ctrl = ctrl;

        var inputSpmn = scope.inputSpmn = specimen; //angular.copy(specimen);
        delete inputSpmn.children;

        inputSpmn.cpId = inputSpmn.cpId || opts.cp.id;
        inputSpmn.visitId = opts.visit && opts.visit.id;
        inputSpmn.createdOn = inputSpmn.createdOn || new Date();

        if (inputSpmn.lineage == 'Aliquot') {
          inputSpmn.anatomicSite = inputSpmn.laterality = undefined;
        }

        if (inputSpmn.status != 'Collected') {
          if (!inputSpmn.id) {
            inputSpmn.status = 'Collected';
          }

          inputSpmn.availableQty = inputSpmn.initialQty;
        }

        if (!inputSpmn.labelFmt) {
          if (specimen.lineage == 'New') {
            inputSpmn.labelFmt = opts.cpr.specimenLabelFmt;
          } else if (specimen.lineage == 'Aliquot') {
            inputSpmn.labelFmt = opts.cpr.aliquotLabelFmt;
          } else if (specimen.lineage == 'Derived') {
            inputSpmn.labelFmt = opts.cpr.derivativeLabelFmt;
          }
        }

        var exObjs = ['specimen.lineage', 'specimen.parentLabel', 'specimen.events'];
        if (!inputSpmn.id && !inputSpmn.reqId) {
          var ce = inputSpmn.collectionEvent = inputSpmn.collectionEvent || {};
          ce.user = (!ce.user || !ce.user.id) ? $rootScope.currentUser : ce.user;
          ce.container = ce.container || 'Not Specified';
          ce.procedure = ce.procedure || 'Not Specified';

          var re = inputSpmn.receivedEvent = inputSpmn.receivedEvent || {};
          re.user = (!re.user || !re.user.id) ? $rootScope.currentUser : re.user;
          re.receivedQuality = re.receivedQuality || 'Acceptable';
        }

        if (inputSpmn.lineage != 'New') {
          exObjs.push('specimen.collectionEvent', 'specimen.receivedEvent');
        }

        inputSpmn.initialQty    = Util.getNumberInScientificNotation(inputSpmn.initialQty);
        inputSpmn.availableQty  = Util.getNumberInScientificNotation(inputSpmn.availableQty);
        inputSpmn.concentration = Util.getNumberInScientificNotation(inputSpmn.concentration);

        var spmnCtx = scope.spmnCtx = {
          currentDate: new Date(),
          obj: {specimen: inputSpmn, cpr: opts.cpr, visit: opts.visit, cp: opts.cp},
          opts: angular.extend({viewCtx: scope}, opts),
          inObjs: ['specimen'], exObjs: exObjs,
          isVirtual: inputSpmn.showVirtual(),
          manualSpecLabelReq: !!inputSpmn.label || !inputSpmn.labelFmt || opts.cp.manualSpecLabelEnabled,
          aliquots: scope.aliquots || [new Specimen({lineage: 'Aliquot'})],
          createAliquots: (scope.aliquots || []).length > 0
        };

        scope.deFormCtrl = {};
        scope.extnOpts = ExtensionsUtil.getExtnOpts(inputSpmn, opts.extensionCtxt);

        if (!opts.hasDict) {
          loadPvs(scope);
        }

        scope.toggleAliquots = function() {
          spmnCtx.aliquots = [new Specimen({lineage: 'Aliquot'})];
        }

        scope.addAnotherAliquot = function() {
          var aliquot = new Specimen({lineage: 'Aliquot'});
          spmnCtx.aliquots.push(aliquot);

          if (spmnCtx.opts.$$sdeFormFields) {
            spmnCtx.opts.$$sdeFormFields.valueChanged(spmnCtx.obj, aliquot, 'aliquots.$onAdd', 'aliquots');
          }
        }

        scope.removeAliquot = function(idx) {
          var aliquots = spmnCtx.aliquots.splice(idx, 1);
          if (spmnCtx.opts.$$sdeFormFields && aliquots.length > 0) {
            spmnCtx.opts.$$sdeFormFields.valueChanged(spmnCtx.obj, aliquots[0], 'aliquots.$onRemove', 'aliquots');
          }

          if (spmnCtx.aliquots.length == 0) {
            scope.addAnotherAliquot();
          }
        }

        scope.addIfLastAliquot = function(idx) {
          if (idx == (spmnCtx.aliquots.length - 1)) {
            scope.addAnotherAliquot();
          }
        }
      }
    }
  });
<|endoftext|>


<commit_before>var mapzen = mapzen || {};
mapzen.whosonfirst = mapzen.whosonfirst || {};
mapzen.whosonfirst.boundaryissues = mapzen.whosonfirst.boundaryissues || {};

// for the time being this assumes jQuery is present
// that decision may change some day but not today
// (20160121/thisisaaronland)

mapzen.whosonfirst.boundaryissues.results = (function() {

	var map,
	    batch_update_ids = [];

	var VenueIcon = L.Icon.extend({
		options: {
			iconUrl: '/images/marker-icon.png',
			iconRetinaUrl: '/images/marker-icon-2x.png',
			shadowUrl: null,
			iconAnchor: new L.Point(13, 42),
			iconSize: new L.Point(25, 42),
			popupAnchor: new L.Point(0, -42)
		}
	});
	var poi_icon_base = '/images/categories/';

	var self = {

		setup_map: function() {
			var lat = 40.73581157695217;
			var lon = -73.9815902709961;
			var zoom = 12;
			mapzen.whosonfirst.leaflet.tangram.scenefile('/tangram/refill.yaml');
			map = mapzen.whosonfirst.leaflet.tangram.map_with_latlon(
				'map',
				lat, lon, zoom
			);
			L.control.geocoder('search-o3YYmTI', {
				markers: {
					icon: new VenueIcon()
				}
			}).addTo(map);
			var hash = new L.Hash(map);

			var markers = [];
			$('#search-results > li').each(function(i, result) {
				if ($(result).data('icon')) {
					var marker = self.setup_icon_marker(result);
				} else {
					var marker = self.setup_circle_marker(result);
				}

				markers.push(marker);
				marker.on('mouseover', function() {
					this.setStyle(mapzen.whosonfirst.leaflet.styles.venue_hover());
				});
				marker.on('mouseout', function() {
					this.setStyle(this._style);
				});
				marker.on('click', function() {
					location.href = '/id/' + parseInt(id) + '/';
				});
				marker.bindLabel($(result).find('a').html());
			});
			var group = L.featureGroup(markers).addTo(map);
			map.fitBounds(group.getBounds());
		},

		setup_circle_marker: function(result) {
			var marker_style = self.get_venue_marker_style(result);
			var id = $(result).data('id');
			var marker = L.circleMarker({
				lat: parseFloat($(result).data('lat')),
				lng: parseFloat($(result).data('lng'))
			}, marker_style);
			marker._style = marker_style;
			return marker;
		},

		setup_icon_marker: function(result) {
			var id = $(result).data('id');
			var icon_id = $(result).data('icon');
			var options = {
				icon: L.icon({
					iconRetinaUrl: poi_icon_base + icon_id + '.png',
					iconSize: [19, 19],
					iconAnchor: [9, 9],
					popupAnchor: [-3, -38]
				})
			}
			var marker = L.marker({
				lat: parseFloat($(result).data('lat')),
				lng: parseFloat($(result).data('lng'))
			}, options);
			return marker;
		},

		setup_drawing: function() {
			var drawControl = new L.Control.Draw({
				draw: {
					polyline: false,
					polygon: false,
					rectangle: false,
					circle: false,
					marker: {
						icon: new VenueIcon()
					}
				},
				edit: false
			});
			map.addControl(drawControl);

			map.on('draw:created', function(e){
				var ll = e.layer.getLatLng();
				var lat = parseFloat(ll.lat).toFixed(6);
				var lng = parseFloat(ll.lng).toFixed(6);
				var zoom = 16;
				var url = '/add/#' + zoom + '/' + lat + '/' + lng;
				location.href = url;
			});
		},

		setup_results: function() {
			$('#search-results > li > input[type=checkbox]').each(function(i, checkbox) {
				// Check for initial checked state (i.e., reload after selection)
				self.update_batch(checkbox);
				$(checkbox).change(function(e) {
					// Check for change to checked state
					self.update_batch(e.target);
				});
			});
			$('#batch-update-status a').click(function(e) {
				e.preventDefault();
				if (batch_update_ids.length == 0) {
					return;
				}
				var data = {
					crumb: $('#batch-update').data('crumb-save-batch'),
					ids: batch_update_ids.join(',')
				};
				if (e.target.nodeName == 'A') {
					var $link = $(e.target);
				} else {
					var $link = $(e.target).closest('a');
				}
				var status = $link.data('status');

				var today = mapzen.whosonfirst.boundaryissues.edit.get_edtf_date(new Date());
				if (status == 'current') {
					data.properties = {
						"mz:is_current": 1,
						"edtf:cessation": 'uuuu',
						"edtf:deprecated": 'uuuu'
					};
				} else if (status == 'closed') {
					data.properties = {
						"mz:is_current": 0,
						"edtf:cessation": today
					};
				} else if (status == 'deprecated') {
					data.properties = {
						"mz:is_current": 0,
						"edtf:deprecated": today
					};
				} else if (status == 'funky') {
					data.properties = {
						"mz:is_funky": 1
					};
				}
				data.properties = JSON.stringify(data.properties);

				var onsuccess = function(rsp) {
					var properties = rsp.properties;
					$.each(rsp.saved, function(i, wof) {
						var id = wof.properties['wof:id'];
						console.log(id);
						var $checkbox = $('input[name="select-' + id + '"]');
						var $item = $checkbox.closest('li');
						$checkbox.attr('checked', false);
						$item.removeClass('iscurrent-yes');
						$item.removeClass('iscurrent-no');
						$item.removeClass('iscurrent-unknown');
						$item.removeClass('deprecated');
						if (properties['mz:is_current']) {
							$item.addClass('iscurrent-yes');
						} else if (properties['mz:is_funky']) {
							// We don't do anything with is_funky yet
						} else if (properties['edtf:deprecated']) {
							$item.addClass('iscurrent-no');
							$item.addClass('deprected');
						} else if (properties['edtf:cessation']) {
							$item.addClass('iscurrent-no');
						} else {
							// In theory we should never be uncertain at this point
							$item.addClass('iscurrent-unknown');
						}
					});
				};
				var onerror = function(rsp) {
					mapzen.whosonfirst.log.debug("error with batch saving.");
					console.log(rsp);
				};

				mapzen.whosonfirst.boundaryissues.api.api_call("wof.save_batch", data, onsuccess, onerror);
			});

			$('#batch-update-category a').click(function(e) {
				e.preventDefault();
				if (batch_update_ids.length == 0) {
					return;
				}
				var data = {
					crumb: $('#batch-update').data('crumb-save-batch'),
					ids: batch_update_ids.join(',')
				};
				if (e.target.nodeName == 'A') {
					var $link = $(e.target);
				} else {
					var $link = $(e.target).closest('a');
				}
				var category = $link.data('category');

				var today = mapzen.whosonfirst.boundaryissues.edit.get_edtf_date(new Date());
				data.properties = {
					"wof:category": category
				};
				data.properties = JSON.stringify(data.properties);

				var onsuccess = function(rsp) {
					var properties = rsp.properties;
					$.each(rsp.saved, function(i, wof) {
						var id = wof.properties['wof:id'];
						var $checkbox = $('input[name="select-' + id + '"]');
						var $item = $checkbox.closest('li');
						$checkbox.attr('checked', false);
					});
				};
				var onerror = function(rsp) {
					mapzen.whosonfirst.log.debug("error with batch saving.");
				};
				mapzen.whosonfirst.boundaryissues.api.api_call("wof.save_batch", data, onsuccess, onerror);
			});

			$('#toggle-all').change(function(e) {
				$('.search-result > input[type=checkbox]').each(function(i, input) {
					input.checked = $('#toggle-all').get(0).checked;
				});
			});

			$('#per-page a').click(function(e) {
				e.preventDefault();
				if (e.target.nodeName == 'A') {
					var $link = $(e.target);
				} else {
					var $link = $(e.target).closest('a');
				}
				var per_page = $link.data('per-page');
				if (location.search.match(/per_page=\d+/)) {
					var q = location.search.replace(/per_page=\d+/, 'per_page=' + per_page);
				} else if (location.search) {
					var q = location.search + '&per_page=' + per_page;
				} else {
					var q = '?per_page=' + per_page;
				}
				window.location = location.pathname + q;
			});

			$('#batch-download').click(function(e) {
				e.preventDefault();
				if (batch_update_ids.length == 0) {
					return;
				}
				var ids = batch_update_ids.join(',');
				var form = document.createElement('form');
				var input = document.createElement('input');
				input.name = 'ids';
				input.value = ids;
				form.appendChild(input.cloneNode());
				form.method = 'POST';
				form.action = '/geojson.php';
				document.body.appendChild(form);
				form.submit();
			});
		},

		get_venue_marker_style: function(item) {
			if ($(item).hasClass('iscurrent-yes')) {
				return mapzen.whosonfirst.leaflet.styles.venue_current();
			} else if ($(item).hasClass('deprecated')) {
				return mapzen.whosonfirst.leaflet.styles.venue_deprecated();
			} else if ($(item).hasClass('iscurrent-no')) {
				return mapzen.whosonfirst.leaflet.styles.venue_not_current();
			} else {
				return mapzen.whosonfirst.leaflet.styles.venue_unknown();
			}
		},

		update_batch: function(checkbox) {
			var id = $(checkbox).closest('li').data('id');
			id = parseInt(id);
			var index = batch_update_ids.indexOf(id);
			if (checkbox.checked) {
				if (index == -1) {
					batch_update_ids.push(id);
				}
			} else {
				if (index != -1) {
					batch_update_ids.splice(index, 1);
				}
			}
			if (batch_update_ids.length > 0) {
				$('#batch-update li').removeClass('disabled');
				$('#batch-download').removeClass('disabled');
			} else {
				$('#batch-update li').addClass('disabled');
				$('#batch-download').addClass('disabled');
			}
		}

	};

	$(document).ready(function() {
		if ($('#search-results').length == 0) {
			return;
		}
		self.setup_map();
		self.setup_drawing();
		self.setup_results();
	});

	return self;
})();
<commit_msg>Select ranges of records using shift key<commit_after>var mapzen = mapzen || {};
mapzen.whosonfirst = mapzen.whosonfirst || {};
mapzen.whosonfirst.boundaryissues = mapzen.whosonfirst.boundaryissues || {};

// for the time being this assumes jQuery is present
// that decision may change some day but not today
// (20160121/thisisaaronland)

mapzen.whosonfirst.boundaryissues.results = (function() {

	var map,
	    batch_update_ids = [];

	var VenueIcon = L.Icon.extend({
		options: {
			iconUrl: '/images/marker-icon.png',
			iconRetinaUrl: '/images/marker-icon-2x.png',
			shadowUrl: null,
			iconAnchor: new L.Point(13, 42),
			iconSize: new L.Point(25, 42),
			popupAnchor: new L.Point(0, -42)
		}
	});
	var poi_icon_base = '/images/categories/';

	var self = {

		setup_map: function() {
			var lat = 40.73581157695217;
			var lon = -73.9815902709961;
			var zoom = 12;
			mapzen.whosonfirst.leaflet.tangram.scenefile('/tangram/refill.yaml');
			map = mapzen.whosonfirst.leaflet.tangram.map_with_latlon(
				'map',
				lat, lon, zoom
			);
			L.control.geocoder('search-o3YYmTI', {
				markers: {
					icon: new VenueIcon()
				}
			}).addTo(map);
			var hash = new L.Hash(map);

			var markers = [];
			$('#search-results > li').each(function(i, result) {
				if ($(result).data('icon')) {
					var marker = self.setup_icon_marker(result);
				} else {
					var marker = self.setup_circle_marker(result);
				}

				markers.push(marker);
				marker.on('mouseover', function() {
					this.setStyle(mapzen.whosonfirst.leaflet.styles.venue_hover());
				});
				marker.on('mouseout', function() {
					this.setStyle(this._style);
				});
				marker.on('click', function() {
					location.href = '/id/' + parseInt(id) + '/';
				});
				marker.bindLabel($(result).find('a').html());
			});
			var group = L.featureGroup(markers).addTo(map);
			map.fitBounds(group.getBounds());
		},

		setup_circle_marker: function(result) {
			var marker_style = self.get_venue_marker_style(result);
			var id = $(result).data('id');
			var marker = L.circleMarker({
				lat: parseFloat($(result).data('lat')),
				lng: parseFloat($(result).data('lng'))
			}, marker_style);
			marker._style = marker_style;
			return marker;
		},

		setup_icon_marker: function(result) {
			var id = $(result).data('id');
			var icon_id = $(result).data('icon');
			var options = {
				icon: L.icon({
					iconRetinaUrl: poi_icon_base + icon_id + '.png',
					iconSize: [19, 19],
					iconAnchor: [9, 9],
					popupAnchor: [-3, -38]
				})
			}
			var marker = L.marker({
				lat: parseFloat($(result).data('lat')),
				lng: parseFloat($(result).data('lng'))
			}, options);
			return marker;
		},

		setup_drawing: function() {
			var drawControl = new L.Control.Draw({
				draw: {
					polyline: false,
					polygon: false,
					rectangle: false,
					circle: false,
					marker: {
						icon: new VenueIcon()
					}
				},
				edit: false
			});
			map.addControl(drawControl);

			map.on('draw:created', function(e){
				var ll = e.layer.getLatLng();
				var lat = parseFloat(ll.lat).toFixed(6);
				var lng = parseFloat(ll.lng).toFixed(6);
				var zoom = 16;
				var url = '/add/#' + zoom + '/' + lat + '/' + lng;
				location.href = url;
			});
		},

		setup_results: function() {
			var lastCheckbox;
			$('#search-results > li > input[type=checkbox]').each(function(i, checkbox) {
				// Check for initial checked state (i.e., reload after selection)
				self.update_batch(checkbox);
				$(checkbox).data('checkbox-num', i);
				$(checkbox).change(function(e) {
					// Check for change to checked state
					self.update_batch(e.target);
				});
				$(checkbox).click(function(e) {
					if (typeof lastCheckbox == 'undefined') {
						lastCheckbox = $(e.target).data('checkbox-num');
						return;
					}
					if (! e.shiftKey) {
						lastCheckbox = $(e.target).data('checkbox-num');
						return;
					}
					var currCheckbox = $(e.target).data('checkbox-num');
					if (currCheckbox > lastCheckbox) {
						var min = lastCheckbox;
						var max = currCheckbox;
					} else {
						var min = currCheckbox;
						var max = lastCheckbox;
					}
					$('#search-results > li > input[type=checkbox]').each(function(j, cb) {
						var num = $(cb).data('checkbox-num');
						if (num > min && num < max) {
							cb.checked = e.target.checked;
						}
					});
					lastCheckbox = currCheckbox;
				});
			});
			$('#batch-update-status a').click(function(e) {
				e.preventDefault();
				if (batch_update_ids.length == 0) {
					return;
				}
				var data = {
					crumb: $('#batch-update').data('crumb-save-batch'),
					ids: batch_update_ids.join(',')
				};
				if (e.target.nodeName == 'A') {
					var $link = $(e.target);
				} else {
					var $link = $(e.target).closest('a');
				}
				var status = $link.data('status');

				var today = mapzen.whosonfirst.boundaryissues.edit.get_edtf_date(new Date());
				if (status == 'current') {
					data.properties = {
						"mz:is_current": 1,
						"edtf:cessation": 'uuuu',
						"edtf:deprecated": 'uuuu'
					};
				} else if (status == 'closed') {
					data.properties = {
						"mz:is_current": 0,
						"edtf:cessation": today
					};
				} else if (status == 'deprecated') {
					data.properties = {
						"mz:is_current": 0,
						"edtf:deprecated": today
					};
				} else if (status == 'funky') {
					data.properties = {
						"mz:is_funky": 1
					};
				}
				data.properties = JSON.stringify(data.properties);

				var onsuccess = function(rsp) {
					var properties = rsp.properties;
					$.each(rsp.saved, function(i, wof) {
						var id = wof.properties['wof:id'];
						console.log(id);
						var $checkbox = $('input[name="select-' + id + '"]');
						var $item = $checkbox.closest('li');
						$checkbox.attr('checked', false);
						$item.removeClass('iscurrent-yes');
						$item.removeClass('iscurrent-no');
						$item.removeClass('iscurrent-unknown');
						$item.removeClass('deprecated');
						if (properties['mz:is_current']) {
							$item.addClass('iscurrent-yes');
						} else if (properties['mz:is_funky']) {
							// We don't do anything with is_funky yet
						} else if (properties['edtf:deprecated']) {
							$item.addClass('iscurrent-no');
							$item.addClass('deprected');
						} else if (properties['edtf:cessation']) {
							$item.addClass('iscurrent-no');
						} else {
							// In theory we should never be uncertain at this point
							$item.addClass('iscurrent-unknown');
						}
					});
				};
				var onerror = function(rsp) {
					mapzen.whosonfirst.log.debug("error with batch saving.");
					console.log(rsp);
				};

				mapzen.whosonfirst.boundaryissues.api.api_call("wof.save_batch", data, onsuccess, onerror);
			});

			$('#batch-update-category a').click(function(e) {
				e.preventDefault();
				if (batch_update_ids.length == 0) {
					return;
				}
				var data = {
					crumb: $('#batch-update').data('crumb-save-batch'),
					ids: batch_update_ids.join(',')
				};
				if (e.target.nodeName == 'A') {
					var $link = $(e.target);
				} else {
					var $link = $(e.target).closest('a');
				}
				var category = $link.data('category');

				var today = mapzen.whosonfirst.boundaryissues.edit.get_edtf_date(new Date());
				data.properties = {
					"wof:category": category
				};
				data.properties = JSON.stringify(data.properties);

				var onsuccess = function(rsp) {
					var properties = rsp.properties;
					$.each(rsp.saved, function(i, wof) {
						var id = wof.properties['wof:id'];
						var $checkbox = $('input[name="select-' + id + '"]');
						var $item = $checkbox.closest('li');
						$checkbox.attr('checked', false);
					});
				};
				var onerror = function(rsp) {
					mapzen.whosonfirst.log.debug("error with batch saving.");
				};
				mapzen.whosonfirst.boundaryissues.api.api_call("wof.save_batch", data, onsuccess, onerror);
			});

			$('#toggle-all').change(function(e) {
				$('.search-result > input[type=checkbox]').each(function(i, input) {
					input.checked = $('#toggle-all').get(0).checked;
				});
			});

			$('#per-page a').click(function(e) {
				e.preventDefault();
				if (e.target.nodeName == 'A') {
					var $link = $(e.target);
				} else {
					var $link = $(e.target).closest('a');
				}
				var per_page = $link.data('per-page');
				if (location.search.match(/per_page=\d+/)) {
					var q = location.search.replace(/per_page=\d+/, 'per_page=' + per_page);
				} else if (location.search) {
					var q = location.search + '&per_page=' + per_page;
				} else {
					var q = '?per_page=' + per_page;
				}
				window.location = location.pathname + q;
			});

			$('#batch-download').click(function(e) {
				e.preventDefault();
				if (batch_update_ids.length == 0) {
					return;
				}
				var ids = batch_update_ids.join(',');
				var form = document.createElement('form');
				var input = document.createElement('input');
				input.name = 'ids';
				input.value = ids;
				form.appendChild(input.cloneNode());
				form.method = 'POST';
				form.action = '/geojson.php';
				document.body.appendChild(form);
				form.submit();
			});
		},

		get_venue_marker_style: function(item) {
			if ($(item).hasClass('iscurrent-yes')) {
				return mapzen.whosonfirst.leaflet.styles.venue_current();
			} else if ($(item).hasClass('deprecated')) {
				return mapzen.whosonfirst.leaflet.styles.venue_deprecated();
			} else if ($(item).hasClass('iscurrent-no')) {
				return mapzen.whosonfirst.leaflet.styles.venue_not_current();
			} else {
				return mapzen.whosonfirst.leaflet.styles.venue_unknown();
			}
		},

		update_batch: function(checkbox) {
			var id = $(checkbox).closest('li').data('id');
			id = parseInt(id);
			var index = batch_update_ids.indexOf(id);
			if (checkbox.checked) {
				if (index == -1) {
					batch_update_ids.push(id);
				}
			} else {
				if (index != -1) {
					batch_update_ids.splice(index, 1);
				}
			}
			if (batch_update_ids.length > 0) {
				$('#batch-update li').removeClass('disabled');
				$('#batch-download').removeClass('disabled');
			} else {
				$('#batch-update li').addClass('disabled');
				$('#batch-download').addClass('disabled');
			}
		}

	};

	$(document).ready(function() {
		if ($('#search-results').length == 0) {
			return;
		}
		self.setup_map();
		self.setup_drawing();
		self.setup_results();
	});

	return self;
})();
<|endoftext|>


<commit_before>import koa from 'koa';

import vhost, {filterHost as filter} from './vhost';

describe('when filtering virtual hosts', () => {
  describe('by string', () => {
    const filterHost = 'yes.server.my';

    test('host should match when the strings match', () => {
      const reqHost = 'yes.server.my';
      expect(filter(filterHost, reqHost)).toBeTruthy();
    });

    test('host should not match when the strings do not match', () => {
      const reqHost = 'no.server.my';
      expect(filter(filterHost, reqHost)).toBeFalsy();
    });
  });

  describe('by regex', () => {
    const filterHost = /.*.server.my/;

    test('host should match when the regex matches', () => {
      const reqHost = 'regex.server.my';
      expect(filter(filterHost, reqHost)).toBeTruthy();
    });

    test('host should not match when the regex does not match', () => {
      const reqHost = 'regex.server.yours';
      expect(filter(filterHost, reqHost)).toBeFalsy();
    });
  });

  describe('by array', () => {
    const filterHosts = ['string.server.my', 'string.server.yours'];

    test('host should match when at least one vhost matches', () => {
      const reqHost = 'string.server.my';
      expect(filter(filterHosts, reqHost)).toBeTruthy();
    });

    test('host should not match when no vhost matches', () => {
      const reqHost = 'string.server.theirs';
      expect(filter(filterHosts, reqHost)).toBeFalsy();
    });
  });
});
<commit_msg>Added more tests for the custom virtual hosts middleware<commit_after>import Koa from 'koa';

import vhost, {filterHost as filter} from './vhost';

describe('when filtering virtual hosts', () => {
  describe('by string', () => {
    const filterHost = 'yes.server.my';

    test('host should match when the strings match', () => {
      const reqHost = 'yes.server.my';
      expect(filter(filterHost, reqHost)).toBeTruthy();
    });

    test('host should not match when the strings do not match', () => {
      const reqHost = 'no.server.my';
      expect(filter(filterHost, reqHost)).toBeFalsy();
    });
  });

  describe('by regex', () => {
    const filterHost = /.*.server.my/;

    test('host should match when the regex matches', () => {
      const reqHost = 'regex.server.my';
      expect(filter(filterHost, reqHost)).toBeTruthy();
    });

    test('host should not match when the regex does not match', () => {
      const reqHost = 'regex.server.yours';
      expect(filter(filterHost, reqHost)).toBeFalsy();
    });
  });

  describe('by array', () => {
    const filterHosts = ['string.server.my', 'string.server.yours'];

    test('host should match when at least one vhost matches', () => {
      const reqHost = 'string.server.my';
      expect(filter(filterHosts, reqHost)).toBeTruthy();
    });

    test('host should not match when no vhost matches', () => {
      const reqHost = 'string.server.theirs';
      expect(filter(filterHosts, reqHost)).toBeFalsy();
    });
  });
});

describe('virtual host middleware', () => {
  const request = require('request');

  const usedAfterMw = jest.fn();

  let koa;
  let server;

  beforeEach(() => {
    koa = new Koa();
    server = koa.listen(3000);
  });

  afterEach(() => {
    usedAfterMw.mockClear();
    server.close();
  });

  test('should defer to other middleware when the domain matches', () => {
    expect.assertions(1);
    koa.use(vhost('localhost'))
      .use(afterMw);

    const testReq = new Promise(resolve =>
      request('http://localhost:3000', () => resolve()));

    return testReq.then(() => expect(usedAfterMw.mock.calls.length).toBe(1));
  });

  test('should not defer to other middleware when the domain does not match', () => {
    expect.assertions(1);
    koa.use(vhost('nonexistent.domain.my'))
      .use(afterMw);

    const testReq = new Promise(resolve =>
      request('http://localhost:3000', () => resolve()));

    return testReq.then(() => expect(usedAfterMw.mock.calls.length).toBe(0));
  });

  function afterMw() {
    usedAfterMw();
  }
});
<|endoftext|>


<commit_before>/*jslint browser: true, sloppy: true, white: true */
/*global Ext: true, VideoJS: true */

/**
 * A panel implementing an HTML5 video player.
 * Use the `video` config option to add the file references.
 * VideoJS is needed for this component to work.
 *
 * Add this to your page to load VideoJS:
 *
 *   <script src="http://vjs.zencdn.net/c/video.js" type="text/javascript"></script>
 *   <link href="http://vjs.zencdn.net/c/video-js.css" rel="stylesheet">
 *
 * Example:
 *
 *      Ext.create('Ext.ux.VideoPanel', {
 *          title: 'HTML5 video panel',
 *          video: [ { url: 'big_buck_bunny.webm', type: 'video/webm' }, 'big_buck_bunny.ogg', 'big_buck_bunny.mp4' ],
 *          videoWidth: 854,
 *          videoHeight: 480
 *      });
 */
Ext.define('Ext.ux.VideoPanel', {
    extend: 'Ext.panel.Panel',
    xtype: 'videopanel',
    downloadCls: 'x-ux-videopanel-download-button',
    downloadMenuEntryCls: 'x-ux-videopanel-download-menuEntry',
    defaultSkin: 'vjs-default-skin',
    showControls: true,
    autoPlay: false,
    loop: false,
    poster: '',
    constructor: function() {

        // throw an exception if the VideoJS library is not available
        if (Ext.isEmpty(window.VideoJS)) {
            throw "VideoJS library not loaded";
        }

        // determine the skin
        this.skin = this.skin || this.defaultSkin;

        this.callParent(arguments);
    },
    onRender: function() {

        this.callParent(arguments);

        this.body.addCls('x-ux-videopanel-body');

        // create the video tag
        this.videoEl = this.body.insertFirst({
            tag: 'video',
            cls: 'video-js ' + this.skin,
            width: this.videoWidth,
            height: this.videoHeight
        });

        // add a toolbar containing the download menu
        if (this.allowDownload) {

            // create the toolbar
            this.addDocked({
                xtype: 'toolbar',
                dock: 'top',
                items: [
                    {
                        itemId: 'downloadMenu',
                        xtype: 'button',
                        text: 'Download',
                        iconCls: this.downloadCls,
                        // create a menu, it will be filled below
                        menu: []
                    }
                ]
            });

            // save a reference to the download menu
            this.downloadMenu = this.getDockedItems('toolbar[dock="top"]')[0].items.get('downloadMenu').menu;
        }

        // save a reference to the panel, because the VideoJS.ready() function does not permit scoping
        var cmp = this;

        // apply VideoJS to the video
        this.videoControl = new VideoJS(
            this.videoEl.id,
            {
                controls: this.showControls,
                preload: 'auto',
                autoplay: this.autoPlay,
                loop: this.loop,
                poster: this.poster
            },
            // on player ready
            function() {

                Ext.fly(this.el).addCls('x-ux-videopanel-video');

                // add ech video to the list of sources for this video element
                Ext.each(cmp.video, function(source) {

                    var url, type;

                    // determine the file url
                    if (Ext.isObject(source)) {
                        url = source.url;
                    }
                    else {
                        url = source;
                    }

                    // read the mime type, if given
                    if (Ext.isObject(source) && !Ext.isEmpty(source.type)) {
                        type = source.type;
                    }
                    // or try to determine the mime type from the file name
                    else if (/\.(ogg)$/i.test(url)) {
                        type = 'video/ogg';
                    }
                    else if (/\.(mp4)$/i.test(url)) {
                        type = 'video/mp4';
                    }
                    else if (/\.(webm)$/i.test(url)) {
                        type = 'video/webm';
                    }
                    // otherwise leave the type attribute empty
                    else {
                        type = '';
                    }

                    this.src({ src: url , type: type });

                    if (cmp.allowDownload) {

                        // add the menu entry
                        cmp.downloadMenu.add({
                            text: url,
                            href: url,
                            tooltip: 'Right-click and select "Save as..." to download',
                            iconCls: cmp.downloadMenuEntryCls
                        });
                    }
                }, this);
        });

    }
});
<commit_msg>Fixed Ogg mime type recognition from file extension<commit_after>/*jslint browser: true, sloppy: true, white: true */
/*global Ext: true, VideoJS: true */

/**
 * A panel implementing an HTML5 video player.
 * Use the `video` config option to add the file references.
 * VideoJS is needed for this component to work.
 *
 * Add this to your page to load VideoJS:
 *
 *   <script src="http://vjs.zencdn.net/c/video.js" type="text/javascript"></script>
 *   <link href="http://vjs.zencdn.net/c/video-js.css" rel="stylesheet">
 *
 * Example:
 *
 *      Ext.create('Ext.ux.VideoPanel', {
 *          title: 'HTML5 video panel',
 *          video: [ { url: 'big_buck_bunny.webm', type: 'video/webm' }, 'big_buck_bunny.ogg', 'big_buck_bunny.mp4' ],
 *          videoWidth: 854,
 *          videoHeight: 480
 *      });
 */
Ext.define('Ext.ux.VideoPanel', {
    extend: 'Ext.panel.Panel',
    xtype: 'videopanel',
    downloadCls: 'x-ux-videopanel-download-button',
    downloadMenuEntryCls: 'x-ux-videopanel-download-menuEntry',
    defaultSkin: 'vjs-default-skin',
    showControls: true,
    autoPlay: false,
    loop: false,
    poster: '',
    constructor: function() {

        // throw an exception if the VideoJS library is not available
        if (Ext.isEmpty(window.VideoJS)) {
            throw "VideoJS library not loaded";
        }

        // determine the skin
        this.skin = this.skin || this.defaultSkin;

        this.callParent(arguments);
    },
    onRender: function() {

        this.callParent(arguments);

        this.body.addCls('x-ux-videopanel-body');

        // create the video tag
        this.videoEl = this.body.insertFirst({
            tag: 'video',
            cls: 'video-js ' + this.skin,
            width: this.videoWidth,
            height: this.videoHeight
        });

        // add a toolbar containing the download menu
        if (this.allowDownload) {

            // create the toolbar
            this.addDocked({
                xtype: 'toolbar',
                dock: 'top',
                items: [
                    {
                        itemId: 'downloadMenu',
                        xtype: 'button',
                        text: 'Download',
                        iconCls: this.downloadCls,
                        // create a menu, it will be filled below
                        menu: []
                    }
                ]
            });

            // save a reference to the download menu
            this.downloadMenu = this.getDockedItems('toolbar[dock="top"]')[0].items.get('downloadMenu').menu;
        }

        // save a reference to the panel, because the VideoJS.ready() function does not permit scoping
        var cmp = this;

        // apply VideoJS to the video
        this.videoControl = new VideoJS(
            this.videoEl.id,
            {
                controls: this.showControls,
                preload: 'auto',
                autoplay: this.autoPlay,
                loop: this.loop,
                poster: this.poster
            },
            // on player ready
            function() {

                Ext.fly(this.el).addCls('x-ux-videopanel-video');

                // add ech video to the list of sources for this video element
                Ext.each(cmp.video, function(source) {

                    var url, type;

                    // determine the file url
                    if (Ext.isObject(source)) {
                        url = source.url;
                    }
                    else {
                        url = source;
                    }

                    // read the mime type, if given
                    if (Ext.isObject(source) && !Ext.isEmpty(source.type)) {
                        type = source.type;
                    }
                    // or try to determine the mime type from the file name
                    else if (/\.(ogg|ogv)$/i.test(url)) {
                        type = 'video/ogg';
                    }
                    else if (/\.(mp4)$/i.test(url)) {
                        type = 'video/mp4';
                    }
                    else if (/\.(webm)$/i.test(url)) {
                        type = 'video/webm';
                    }
                    // otherwise leave the type attribute empty
                    else {
                        type = '';
                    }

                    this.src({ src: url , type: type });

                    if (cmp.allowDownload) {

                        // add the menu entry
                        cmp.downloadMenu.add({
                            text: url,
                            href: url,
                            tooltip: 'Right-click and select "Save as..." to download',
                            iconCls: cmp.downloadMenuEntryCls
                        });
                    }
                }, this);
        });

    }
});
<|endoftext|>


<commit_before>/**
 * @param confirm function(text): boolean    ie window.confirm
 * @param addPresentCommand function(present): JQueryPromise
 * */

function ViewModel(confirm, addPresentCommand, editPresentCommand) {
	this.confirm = confirm;
	this.addPresentCommand = addPresentCommand;
	this.editPresentCommand = editPresentCommand;
	this.users = ko.observable({
		'idNicolas': {
			id: 'idNicolas',
			name: 'Nicolas'
		},
		'idOlivier': {
			id: 'idOlivier',
			name: 'Olivier'
		},
		'idElisa': {
			id: 'idElisa',
			name: 'Elisa'
		},
	});
	this.loggedInUser = ko.observable('idOlivier');
	this.selectedList = ko.observable('idElisa');
	this.presents = ko.observable([{
			id: "1",
			title: "Des mugs",
			description: "Des jolis mugs aux motifs africains",
			to: "idElisa",
			createdBy: "idElisa",
			creationDate: new Date(),
			offeredBy: "idOlivier",
			offeredDate: new Date(),
			deletedBy: null
		}, {
			id: "2",
			title: "Le prix Goncourt",
			description: "C'est le \"serment de josette\" de Gaston Serpette aux édition boiron",
			to: "idElisa",
			createdBy: "idElisa",
			creationDate: new Date(),
			offeredBy: null,
			offeredDate: null,
			deletedBy: null
		}
	]);
	//present edition
	this.editing = ko.observable(false);
	this.editedPresent = ko.observable(null);
	this.edition = {
		title: ko.observable(),
		description: ko.observable()
	};
	this.successMessage = ko.observable();
	this.errorMessage = ko.observable();
	this.undoAction = ko.observable();
	this.loadingMessage = ko.observable();

	var self = this;
	var throttledHasLoading = ko.computed(function() {
		    return self.loadingMessage() != null;
	}).extend({ throttle: 400 });
	//display null immediatly but wait a few ms before displaying non null
	this.slowShowingLoadingMessage = ko.computed(function() {
		var loadingMessage = self.loadingMessage();
		var hasLoading = throttledHasLoading();
		return hasLoading ? loadingMessage : null;
	});
}

ViewModel.prototype = {
	nextId: 1,
	vowels: {
		a: true,
		e: true,
		i: true,
		o: true,
		u: true,
		y: true
	},
	lists: function() {
		var users = this.users();
		var loggedInUser = this.loggedInUser();
		var ids = Object.keys(users);
		ids.sort(function(a, b) {
			if (a === loggedInUser) {
				return -1;
			}
			if (b === loggedInUser) {
				return 1;
			}
			return users[a].name < users[b].name ? -1 : 1;
		});
		return ids.map(function(id) {
			var userName = users[id].name;
			var beginByVowel = ViewModel.prototype.vowels[userName.slice(0, 1).toLowerCase()];
			var label = 'Liste ' + (beginByVowel ? "d'" : "de ") + userName;
			return {
				id: id,
				label: label
			};
		});
	},
	_comparePresents: function(a, b) {
		return a.creationDate.getTime() - b.creationDate.getTime();
	},
	displayedPresents: function() {
		var selectedList = this.selectedList();
		var loggedInUser = this.loggedInUser();
		var self = this;
		return this.presents().filter(function(p) {
			if (p.to != selectedList) {
				return false;
			}
			if (p.deletedBy != null && !self.displayPresentAsOffered(p)) {
				return false;
			}
			if (p.to == loggedInUser && p.createdBy != loggedInUser) {
				return false;
			}
			return true;
		}).sort(function(a, b) {
			return self._comparePresents(a, b);
		});
	},
	/**Returns a string or null*/
	displayPresentAsOffered: function(present) {
		var loggedInUser = this.loggedInUser();
		if (present.offeredBy == null) {
			return null;
		}
		if (present.to == loggedInUser) {
			return null;
		}
		var username = this.users()[present.offeredBy].name;
		return '(Rayé par ' + username + ')';
	},
	isEditedPresentModified: function() {
		var beforeModification = this._isCreating() ? {
			title: '',
			description: ''
		} : this.editedPresent();
		var hasChanges = false;
		if (this.edition.title() != beforeModification.title) {
			hasChanges = true;
		}
		if (this.edition.description() != beforeModification.description) {
			hasChanges = true;
		}
		return hasChanges;
	},
	editPresent: function(present) {
		this.editedPresent(present);
		this.edition.title(present.title);
		this.edition.description(present.description);
		this.editing(true);
	},
	cancelEdition: function() {
		if (this.isEditedPresentModified()) {
			var changeAnyways = this.confirm('Vous avez modifi\u00e9 ce cadeau. OK pour perdre vos modifications ?');
			if (!changeAnyways) {
				return;
			}
		}
		this.editing(false);
	},
	_isCreating: function() {
		return this.editedPresent() == null;
	},
	editPopupText: function() {
		return this._isCreating() ? 'Ajouter un cadeau' : 'Modifier ' + this.editedPresent().title;
	},
	_addPresent: function(present) {
		this.presents(this.presents().concat([present]));
		this.discardConfirm();
		this.loadingMessage('Ajout de "' + present.title + '" en cours...');
		var self = this;
		this.addPresentCommand(present)
			.always(function() {
				self.loadingMessage(null);
			})
			.fail(function() {
				self.errorMessage('Erreur pendant la sauvegarde de ' + present.title);
				var presents = self.presents();
				var index = presents.indexOf(present);
				presents.splice(index, 1);
				self.presents(presents);
			}).done(function(newPresent) {
				var presents = self.presents();
				var index = presents.indexOf(present);
				presents[index] = newPresent;
				self.presents(presents);
				self.successMessage('"' + newPresent.title + '" a bien été créé');
				self.undoAction(function() {
					self.deletePresent(newPresent);
				});
			});
	},
	_savePresent: function(oldPresent, newPresent) {
		var presents = this.presents();
		var index = presents.indexOf(oldPresent);
		if (index == -1) {
			throw new Error('present not found');
		}
		presents[index] = newPresent;
//		this.presents([]); //force redisplay
		this.presents(presents);
		this.discardConfirm();
		this.loadingMessage('Modification de "' + newPresent.title + '" en cours...');
		var self = this;
		this.editPresentCommand(oldPresent, newPresent)
			.always(function() {
				self.loadingMessage(null);
			})
			.fail(function() {
				self.errorMessage('Erreur pendant la sauvegarde de ' + newPresent.title);
				var presents = self.presents();
				var index = presents.indexOf(newPresent);
				if (index == -1) {
					throw new Error('present not found');
				}
				presents[index] = oldPresent;
		//		this.presents([]); //force redisplay
				self.presents(presents);
			}).done(function(savedPresent) {
				var presents = self.presents();
				var index = presents.indexOf(newPresent);
				presents[index] = savedPresent;
				self.presents(presents);
				self.successMessage('"' + savedPresent.title + '" a bien été modifié');
				self.undoAction(function() {
					self._savePresent(savedPresent, oldPresent);
				});
			});
	},
	togglePresentOffered: function(present) {
		var clone = $.extend({}, present);
		if (!present.offeredBy) {
			clone.offeredBy = this.loggedInUser();
			clone.offeredDate = new Date();
		} else {
			if (present.offeredBy != this.loggedInUser()) {
				var offeredByName = this.users()[present.offeredBy].name;
				var ok = this.confirm("Ce cadeau a \u00e9t\u00e9 offert par " + offeredByName + ". Voulez-vous le marquer comme non encore offert ?");
				if (!ok) {
					return;
				}
			}
			clone.offeredBy = null;
			clone.offeredDate = null;
		}
		this._savePresent(present, clone);
	},
	saveEditedPresent: function() {
		var title = this.edition.title();
		var description = this.edition.description();
		if (this._isCreating()) {
			var id = "tempId" + this.nextId++;
			var present = {
				id: id,
				title: title,
				description: description,
				to: this.selectedList(),
				createdBy: this.loggedInUser(),
				creationDate: new Date(),
				givenBy: null,
				givenDate: null,
				deletedBy: null
			};
			this._addPresent(present);
		} else {
			var selected = this.editedPresent();
			var clone = $.extend({}, selected);
			clone.title = title;
			clone.description = description;
			this._savePresent(selected, clone);
		}
		this.editing(false);
	},
	addPresent: function() {
		this.editedPresent(null);
		this.edition.title('');
		this.edition.description('');
		this.editing(true);
	},
	deletePresent: function(present) {
		if (present.createdBy != this.loggedInUser()) {
			var createdByName = this.users()[present.createdBy].name;
			var ok = this.confirm('Ce cadeau a \u00e9t\u00e9 cr\u00e9\u00e9 par ' + createdByName + '. Supprimer ?');
			if (!ok) {
				return;
			}
		}
		var clone = $.extend({}, present);
		clone.deletedBy = this.loggedInUser();
		this._savePresent(present, clone);
	},
	discardConfirm: function() {
		this.successMessage(null);
		this.errorMessage(null);
		this.undoAction(null);
	}
};
<commit_msg>offered presents first<commit_after>/**
 * @param confirm function(text): boolean    ie window.confirm
 * @param addPresentCommand function(present): JQueryPromise
 * */

function ViewModel(confirm, addPresentCommand, editPresentCommand) {
	this.confirm = confirm;
	this.addPresentCommand = addPresentCommand;
	this.editPresentCommand = editPresentCommand;
	this.users = ko.observable({
		'idNicolas': {
			id: 'idNicolas',
			name: 'Nicolas'
		},
		'idOlivier': {
			id: 'idOlivier',
			name: 'Olivier'
		},
		'idElisa': {
			id: 'idElisa',
			name: 'Elisa'
		},
	});
	this.loggedInUser = ko.observable('idOlivier');
	this.selectedList = ko.observable('idElisa');
	this.presents = ko.observable([{
			id: "1",
			title: "Des mugs",
			description: "Des jolis mugs aux motifs africains",
			to: "idElisa",
			createdBy: "idElisa",
			creationDate: new Date(),
			offeredBy: "idOlivier",
			offeredDate: new Date(),
			deletedBy: null
		}, {
			id: "2",
			title: "Le prix Goncourt",
			description: "C'est le \"serment de josette\" de Gaston Serpette aux édition boiron",
			to: "idElisa",
			createdBy: "idElisa",
			creationDate: new Date(),
			offeredBy: null,
			offeredDate: null,
			deletedBy: null
		}
	]);
	//present edition
	this.editing = ko.observable(false);
	this.editedPresent = ko.observable(null);
	this.edition = {
		title: ko.observable(),
		description: ko.observable()
	};
	this.successMessage = ko.observable();
	this.errorMessage = ko.observable();
	this.undoAction = ko.observable();
	this.loadingMessage = ko.observable();

	var self = this;
	var throttledHasLoading = ko.computed(function() {
		    return self.loadingMessage() != null;
	}).extend({ throttle: 400 });
	//display null immediatly but wait a few ms before displaying non null
	this.slowShowingLoadingMessage = ko.computed(function() {
		var loadingMessage = self.loadingMessage();
		var hasLoading = throttledHasLoading();
		return hasLoading ? loadingMessage : null;
	});
}

ViewModel.prototype = {
	nextId: 1,
	vowels: {
		a: true,
		e: true,
		i: true,
		o: true,
		u: true,
		y: true
	},
	lists: function() {
		var users = this.users();
		var loggedInUser = this.loggedInUser();
		var ids = Object.keys(users);
		ids.sort(function(a, b) {
			if (a === loggedInUser) {
				return -1;
			}
			if (b === loggedInUser) {
				return 1;
			}
			return users[a].name < users[b].name ? -1 : 1;
		});
		return ids.map(function(id) {
			var userName = users[id].name;
			var beginByVowel = ViewModel.prototype.vowels[userName.slice(0, 1).toLowerCase()];
			var label = 'Liste ' + (beginByVowel ? "d'" : "de ") + userName;
			return {
				id: id,
				label: label
			};
		});
	},
	_comparePresents: function(a, b) {
		var aAsOffered = this.displayPresentAsOffered(a);
		var bAsOffered = this.displayPresentAsOffered(b);
		if (aAsOffered != bAsOffered) {
			return aAsOffered ? 1 : -1;
		}
		return a.creationDate.getTime() - b.creationDate.getTime();
	},
	displayedPresents: function() {
		var selectedList = this.selectedList();
		var loggedInUser = this.loggedInUser();
		var self = this;
		return this.presents().filter(function(p) {
			if (p.to != selectedList) {
				return false;
			}
			if (p.deletedBy != null && !self.displayPresentAsOffered(p)) {
				return false;
			}
			if (p.to == loggedInUser && p.createdBy != loggedInUser) {
				return false;
			}
			return true;
		}).sort(function(a, b) {
			return self._comparePresents(a, b);
		});
	},
	/**Returns a string or null*/
	displayPresentAsOffered: function(present) {
		var loggedInUser = this.loggedInUser();
		if (present.offeredBy == null) {
			return null;
		}
		if (present.to == loggedInUser) {
			return null;
		}
		var username = this.users()[present.offeredBy].name;
		return '(Rayé par ' + username + ')';
	},
	isEditedPresentModified: function() {
		var beforeModification = this._isCreating() ? {
			title: '',
			description: ''
		} : this.editedPresent();
		var hasChanges = false;
		if (this.edition.title() != beforeModification.title) {
			hasChanges = true;
		}
		if (this.edition.description() != beforeModification.description) {
			hasChanges = true;
		}
		return hasChanges;
	},
	editPresent: function(present) {
		this.editedPresent(present);
		this.edition.title(present.title);
		this.edition.description(present.description);
		this.editing(true);
	},
	cancelEdition: function() {
		if (this.isEditedPresentModified()) {
			var changeAnyways = this.confirm('Vous avez modifi\u00e9 ce cadeau. OK pour perdre vos modifications ?');
			if (!changeAnyways) {
				return;
			}
		}
		this.editing(false);
	},
	_isCreating: function() {
		return this.editedPresent() == null;
	},
	editPopupText: function() {
		return this._isCreating() ? 'Ajouter un cadeau' : 'Modifier ' + this.editedPresent().title;
	},
	_addPresent: function(present) {
		this.presents(this.presents().concat([present]));
		this.discardConfirm();
		this.loadingMessage('Ajout de "' + present.title + '" en cours...');
		var self = this;
		this.addPresentCommand(present)
			.always(function() {
				self.loadingMessage(null);
			})
			.fail(function() {
				self.errorMessage('Erreur pendant la sauvegarde de ' + present.title);
				var presents = self.presents();
				var index = presents.indexOf(present);
				presents.splice(index, 1);
				self.presents(presents);
			}).done(function(newPresent) {
				var presents = self.presents();
				var index = presents.indexOf(present);
				presents[index] = newPresent;
				self.presents(presents);
				self.successMessage('"' + newPresent.title + '" a bien été créé');
				self.undoAction(function() {
					self.deletePresent(newPresent);
				});
			});
	},
	_savePresent: function(oldPresent, newPresent) {
		var presents = this.presents();
		var index = presents.indexOf(oldPresent);
		if (index == -1) {
			throw new Error('present not found');
		}
		presents[index] = newPresent;
//		this.presents([]); //force redisplay
		this.presents(presents);
		this.discardConfirm();
		this.loadingMessage('Modification de "' + newPresent.title + '" en cours...');
		var self = this;
		this.editPresentCommand(oldPresent, newPresent)
			.always(function() {
				self.loadingMessage(null);
			})
			.fail(function() {
				self.errorMessage('Erreur pendant la sauvegarde de ' + newPresent.title);
				var presents = self.presents();
				var index = presents.indexOf(newPresent);
				if (index == -1) {
					throw new Error('present not found');
				}
				presents[index] = oldPresent;
		//		this.presents([]); //force redisplay
				self.presents(presents);
			}).done(function(savedPresent) {
				var presents = self.presents();
				var index = presents.indexOf(newPresent);
				presents[index] = savedPresent;
				self.presents(presents);
				self.successMessage('"' + savedPresent.title + '" a bien été modifié');
				self.undoAction(function() {
					self._savePresent(savedPresent, oldPresent);
				});
			});
	},
	togglePresentOffered: function(present) {
		var clone = $.extend({}, present);
		if (!present.offeredBy) {
			clone.offeredBy = this.loggedInUser();
			clone.offeredDate = new Date();
		} else {
			if (present.offeredBy != this.loggedInUser()) {
				var offeredByName = this.users()[present.offeredBy].name;
				var ok = this.confirm("Ce cadeau a \u00e9t\u00e9 offert par " + offeredByName + ". Voulez-vous le marquer comme non encore offert ?");
				if (!ok) {
					return;
				}
			}
			clone.offeredBy = null;
			clone.offeredDate = null;
		}
		this._savePresent(present, clone);
	},
	saveEditedPresent: function() {
		var title = this.edition.title();
		var description = this.edition.description();
		if (this._isCreating()) {
			var id = "tempId" + this.nextId++;
			var present = {
				id: id,
				title: title,
				description: description,
				to: this.selectedList(),
				createdBy: this.loggedInUser(),
				creationDate: new Date(),
				givenBy: null,
				givenDate: null,
				deletedBy: null
			};
			this._addPresent(present);
		} else {
			var selected = this.editedPresent();
			var clone = $.extend({}, selected);
			clone.title = title;
			clone.description = description;
			this._savePresent(selected, clone);
		}
		this.editing(false);
	},
	addPresent: function() {
		this.editedPresent(null);
		this.edition.title('');
		this.edition.description('');
		this.editing(true);
	},
	deletePresent: function(present) {
		if (present.createdBy != this.loggedInUser()) {
			var createdByName = this.users()[present.createdBy].name;
			var ok = this.confirm('Ce cadeau a \u00e9t\u00e9 cr\u00e9\u00e9 par ' + createdByName + '. Supprimer ?');
			if (!ok) {
				return;
			}
		}
		var clone = $.extend({}, present);
		clone.deletedBy = this.loggedInUser();
		this._savePresent(present, clone);
	},
	discardConfirm: function() {
		this.successMessage(null);
		this.errorMessage(null);
		this.undoAction(null);
	}
};
<|endoftext|>


<commit_before>/*
 * Views
 */

Array.destroyAll = function(items) {
    for (var i in items)
        items[i].destroy();
}

LzNode.prototype.set = function(name, value) {
    if (typeof name == 'object') {
        var hash = name;
        for (name in hash)
            this.setAttribute(name, hash[name]);
    } else
        this.setAttribute(name, value);
}

LzNode.prototype.to = function(name, value, duration, relative, rest) {
    if (typeof name == 'object') {
        var hash = name;
        for (name in hash)
            // shifted by one
            this.animate(name, hash[name], value, duration, relative);
        return;
    }
    var durationNames = {slow: 600, normal: 400, fast: 200};
    if (arguments.length < 3)
        duration = 'normal';
    if (durationNames[duration])
        duration = durationNames[duration];
    this.animate(name, value, duration, relative, rest);
}

LzNode.prototype.animators = function(hash, duration) {
    var self = this;
    Hash.each(hash, function(name, value) {
        self.animate(name, value, duration);
    });
}

// LFC version is off by one
LzView.prototype.containsPt = function(x, y) {
    return 0 <= x && x < this.width &&
        0 <= y && y < this.height;
}

LzView.prototype.containsMouse = function() {
    return this.containsPt(this.getMouse('x'), this.getMouse('y'));
}

LzView.prototype.destroyDirectInstances = function(klass) {
    for (var i in this.subviews) {
        var e = this.subviews[i];
        if (e.class == klass)
            e.destroy();
        e.destroyDirectInstances(klass);
    }
}

LzView.prototype.moveTo = function(x, y) {
    this.setX(x);
    this.setY(y);
}

LzView.prototype.setFilter = function(filter) {
    var filters = [];
    if (filter)
        filters = [filter];
    this.getMCRef().filters = filters;
}

LzNode.prototype.show = function() {
    this.setVisible(true);
}

LzNode.prototype.hide = function() {
    this.setVisible(false);
}

LzNode.prototype.toggle = function(name) {
    if (!arguments.length) name = 'visible';
    this.set(name, !this[name]);
}

/*
 * Iterators
 */

LzView.prototype.eachDirectInstance = function(klass, fn) {
    for (var i in this.subviews) {
        var e = this.subviews[i];
        if (e.class == klass)
            fn(e);
    }
}

LzView.prototype.eachPath = function(klass, fn) {
    for (var i in this.subviews) {
        var e = this.subviews[i];
        if (e instanceof klass)
            fn(e);
        else
            e.eachPath(klass, fn);
    }
}

// only in this class
LzView.prototype.eachSibling = function(fn, klass) {
    var children = this.parent.subviews;
    for (var i in children) {
        var e = children[i];
        if (e != this && e instanceof klass)
            fn(e);
    }
}<commit_msg>add set/1, unset<commit_after>/*
 * Views
 */

Array.destroyAll = function(items) {
    for (var i in items)
        items[i].destroy();
}

LzNode.prototype.set = function(name, value) {
    if (arguments.length < 2) value = true;
    if (typeof name == 'object') {
        var hash = name;
        for (name in hash)
            this.setAttribute(name, hash[name]);
    } else
        this.setAttribute(name, value);
}

LzNode.prototype.unset = function(name) {
    this.set(name, false);
}

LzNode.prototype.to = function(name, value, duration, relative, rest) {
    if (typeof name == 'object') {
        var hash = name;
        for (name in hash)
            // shifted by one
            this.animate(name, hash[name], value, duration, relative);
        return;
    }
    var durationNames = {slow: 600, normal: 400, fast: 200};
    if (arguments.length < 3)
        duration = 'normal';
    if (durationNames[duration])
        duration = durationNames[duration];
    this.animate(name, value, duration, relative, rest);
}

LzNode.prototype.animators = function(hash, duration) {
    var self = this;
    Hash.each(hash, function(name, value) {
        self.animate(name, value, duration);
    });
}

// LFC version is off by one
LzView.prototype.containsPt = function(x, y) {
    return 0 <= x && x < this.width &&
        0 <= y && y < this.height;
}

LzView.prototype.containsMouse = function() {
    return this.containsPt(this.getMouse('x'), this.getMouse('y'));
}

LzView.prototype.destroyDirectInstances = function(klass) {
    for (var i in this.subviews) {
        var e = this.subviews[i];
        if (e.class == klass)
            e.destroy();
        e.destroyDirectInstances(klass);
    }
}

LzView.prototype.moveTo = function(x, y) {
    this.setX(x);
    this.setY(y);
}

LzView.prototype.setFilter = function(filter) {
    var filters = [];
    if (filter)
        filters = [filter];
    this.getMCRef().filters = filters;
}

LzNode.prototype.show = function() {
    this.setVisible(true);
}

LzNode.prototype.hide = function() {
    this.setVisible(false);
}

LzNode.prototype.toggle = function(name) {
    if (!arguments.length) name = 'visible';
    this.set(name, !this[name]);
}

/*
 * Iterators
 */

LzView.prototype.eachDirectInstance = function(klass, fn) {
    for (var i in this.subviews) {
        var e = this.subviews[i];
        if (e.class == klass)
            fn(e);
    }
}

LzView.prototype.eachPath = function(klass, fn) {
    for (var i in this.subviews) {
        var e = this.subviews[i];
        if (e instanceof klass)
            fn(e);
        else
            e.eachPath(klass, fn);
    }
}

// only in this class
LzView.prototype.eachSibling = function(fn, klass) {
    var children = this.parent.subviews;
    for (var i in children) {
        var e = children[i];
        if (e != this && e instanceof klass)
            fn(e);
    }
}<|endoftext|>


<commit_before>'use strict';
var path = require('path');
var util = require('util');
var yeoman = require('yeoman-generator');

var ViewGenerator = module.exports = function ModelGenerator(args, options, config) {
  // By calling `NamedBase` here, we get the argument to the subgenerator call
  // as `this.name`.
  yeoman.generators.NamedBase.apply(this, arguments);

  console.log('You called the model subgenerator with the argument ' + this.name + '.');
};

util.inherits(ViewGenerator, yeoman.generators.NamedBase);

// ModelGenerator.prototype.files = function files() {
//   this.copy('somefile.js', 'somefile.js');
// };

ViewGenerator.prototype.createFiles = function createModelFiles() {
  var destFile = path.join('app/views', this.name + '.js');
  var jsName = this._.classify(this.name);
  var className = this.name.replace('/','-', 'gi');
  var template = [
    'define([',
    '  \'app\'',
    '],',
    'function(',
    '  app',
    ') {',
    '  \'use strict\';',
    '',
    '  var '+ jsName +'View = Backbone.View.extend({',
    '    className: \''+ className +'\'',
    '    template: app.fetchTemplate(\''+ this.name +'\'),',
    '',
    '    events: {',
    '    },',
    '',
    '    render: function() {',
    '      this.$el.html(this.template());',
    '      return this;',
    '    }',
    '',
    '    cleanup: function() {',
    '      this.off();',
    '    }',
    '  });',
    '',
    '  return '+ jsName +'View;',
    '});'
  ].join('\n');

  this.write(destFile, template);
  this.write(path.join('app/templates', this.name +'.html'), '');
};<commit_msg>added missing commas.<commit_after>'use strict';
var path = require('path');
var util = require('util');
var yeoman = require('yeoman-generator');

var ViewGenerator = module.exports = function ModelGenerator(args, options, config) {
  // By calling `NamedBase` here, we get the argument to the subgenerator call
  // as `this.name`.
  yeoman.generators.NamedBase.apply(this, arguments);

  console.log('You called the model subgenerator with the argument ' + this.name + '.');
};

util.inherits(ViewGenerator, yeoman.generators.NamedBase);

// ModelGenerator.prototype.files = function files() {
//   this.copy('somefile.js', 'somefile.js');
// };

ViewGenerator.prototype.createFiles = function createModelFiles() {
  var destFile = path.join('app/views', this.name + '.js');
  var jsName = this._.classify(this.name);
  var className = this.name.replace('/','-', 'gi');
  var template = [
    'define([',
    '  \'app\'',
    '],',
    'function(',
    '  app',
    ') {',
    '  \'use strict\';',
    '',
    '  var '+ jsName +'View = Backbone.View.extend({',
    '    className: \''+ className +'\'',,
    '    template: app.fetchTemplate(\''+ this.name +'\'),',
    '',
    '    events: {',
    '    },',
    '',
    '    render: function() {',
    '      this.$el.html(this.template());',
    '      return this;',
    '    },',
    '',
    '    cleanup: function() {',
    '      this.off();',
    '    }',
    '  });',
    '',
    '  return '+ jsName +'View;',
    '});'
  ].join('\n');

  this.write(destFile, template);
  this.write(path.join('app/templates', this.name +'.html'), '');
};
<|endoftext|>