<commit_before>import { LightningElement, api, track, wire } from 'lwc';
import { fireEvent } from 'c/pubsubNoPageRef';
import { showToast, constructErrorMessage, isNull } from 'c/utilCommon';

import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { getRecord } from 'lightning/uiRecordApi';

import RECURRING_DONATION_OBJECT from '@salesforce/schema/npe03__Recurring_Donation__c';

import FIELD_NAME from '@salesforce/schema/npe03__Recurring_Donation__c.Name';
import FIELD_CAMPAIGN from '@salesforce/schema/npe03__Recurring_Donation__c.npe03__Recurring_Donation_Campaign__c';
import FIELD_AMOUNT from '@salesforce/schema/npe03__Recurring_Donation__c.npe03__Amount__c';
import FIELD_PAYMENT_METHOD from '@salesforce/schema/npe03__Recurring_Donation__c.PaymentMethod__c';
import FIELD_STATUS from '@salesforce/schema/npe03__Recurring_Donation__c.Status__c';
import FIELD_STATUS_REASON from '@salesforce/schema/npe03__Recurring_Donation__c.ClosedReason__c';

import currencyFieldLabel from '@salesforce/label/c.lblCurrency';
import cancelButtonLabel from '@salesforce/label/c.stgBtnCancel';
import saveButtonLabel from '@salesforce/label/c.stgBtnSave';
import newHeaderLabel from '@salesforce/label/c.RD2_EntryFormHeader';
import editHeaderLabel from '@salesforce/label/c.commonEdit';
import donorSectionHeader from '@salesforce/label/c.RD2_EntryFormDonorSectionHeader';
import scheduleSectionHeader from '@salesforce/label/c.RD2_EntryFormDonationSectionHeader';
import otherSectionHeader from '@salesforce/label/c.RD2_EntryFormOtherSectionHeader';
import insertSuccessMessage from '@salesforce/label/c.RD2_EntryFormInsertSuccessMessage';
import updateSuccessMessage from '@salesforce/label/c.RD2_EntryFormUpdateSuccessMessage';

import getSetting from '@salesforce/apex/RD2_entryFormController.getRecurringSettings';

export default class rd2EntryForm extends LightningElement {

    listenerEvent = 'rd2EntryFormEvent';

    customLabels = Object.freeze({
        cancelButtonLabel,
        saveButtonLabel,
        donorSectionHeader,
        otherSectionHeader,
        scheduleSectionHeader,
        currencyFieldLabel
    });

    @api parentId;
    @api recordId;

    @track isEdit = false;
    @track record;
    @track isMultiCurrencyEnabled = false;
    @track fields = {};
    fieldInfos;

    @track header = newHeaderLabel;

    @track isAutoNamingEnabled;
    @track isLoading = true;
    @track isRecordReady = false;
    isSettingReady = false;

    @track hasError = false;
    @track errorMessage = {};

    /***
    * @description Dynamic render edit form CSS to show/hide the edit form
    */
    get cssEditForm() {
        //Note: all of these flags need to be checked before the sections are displayed.
        //If the isSettingReady is not checked, then the form on an error resets all fields
        //including the Schedule section LWC fields.
        //TODO: check why and how this expression can be simplifed.
        return (!this.isLoading && this.isSettingReady && this.isRecordReady)
            ? ''
            : 'slds-hide';
    }

    /***
    * @description Get settings required to enable or disable fields and populate their values
    */
    connectedCallback() {
        getSetting({ parentId: this.parentId })
            .then(response => {
                this.isAutoNamingEnabled = response.isAutoNamingEnabled;
                this.isMultiCurrencyEnabled = response.isMultiCurrencyEnabled;
            })
            .catch((error) => {
                this.handleError(error);
            })
            .finally(() => {
                this.isSettingReady = true;
                this.isLoading = false;
            });
    }

    /***
    * @description Retrieve Recurring Donation Object info
    */
    @wire(getObjectInfo, { objectApiName: RECURRING_DONATION_OBJECT.objectApiName })
    wiredRecurringDonationObjectInfo(response) {
        if (response.data) {
            let rdObjectInfo = response.data;
            this.setFields(rdObjectInfo.fields);
            this.fieldInfos = this.buildFieldDescribes(
                rdObjectInfo.fields,
                rdObjectInfo.apiName
            );

            if (isNull(this.recordId)) {
                this.isRecordReady = true;
            } else {
                this.isEdit = true;
            }
        }

        if (response.error) {
            this.isRecordReady = true;
            this.handleError(response.error);
        }
    }

    /***
    * @description Method converts field describe info into objects that the
    * getRecord method can accept into its 'fields' parameter.
    */
    buildFieldDescribes(fields, objectApiName) {
        return Object.keys(fields).map((fieldApiName) => {
            return {
                fieldApiName: fieldApiName,
                objectApiName: objectApiName
            }
        });
    }

    /***
    * @description Construct field describe info from the Recurring Donation SObject info
    */
    setFields(fieldInfos) {
        this.fields.name = this.extractFieldInfo(fieldInfos[FIELD_NAME.fieldApiName]);
        this.fields.campaign = this.extractFieldInfo(fieldInfos[FIELD_CAMPAIGN.fieldApiName]);
        this.fields.amount = this.extractFieldInfo(fieldInfos[FIELD_AMOUNT.fieldApiName]);
        this.fields.paymentMethod = this.extractFieldInfo(fieldInfos[FIELD_PAYMENT_METHOD.fieldApiName]);
        this.fields.status = this.extractFieldInfo(fieldInfos[FIELD_STATUS.fieldApiName]);
        this.fields.statusreason = this.extractFieldInfo(fieldInfos[FIELD_STATUS_REASON.fieldApiName]);
        this.fields.currency = { label: currencyFieldLabel, apiName: 'CurrencyIsoCode' };
    }

    /***
    * @description Method converts field describe info into a object that is easily accessible from the front end
    */
    extractFieldInfo(field) {
        return {
            apiName: field.apiName,
            label: field.label,
            inlineHelpText: field.inlineHelpText,
            dataType: field.dataType
        };
    }

    /***
    * @description Retrieve Recurring Donation record value
    */
    @wire(getRecord, { recordId: '$recordId', fields: '$fieldInfos' })
    wiredRecurringDonationRecord(response) {
        if (response.data) {
            this.record = response.data;
            this.header = editHeaderLabel + ' ' + this.record.fields.Name.value;
            this.isRecordReady = true;
        }

        if (response.error) {
            this.isRecordReady = true;
            this.handleError(response.error);
        }
    }

    /***
    * @description Overrides the standard submit.
    * Collects fields displayed on the form and any integrated LWC (for example Schedule section)
    * and submits them for the record insert or update.
    */
    handleSubmit(event) {
        this.isLoading = true;
        this.hasError = false;
        this.template.querySelector("[data-id='submitButton']").disabled = true;

        event.preventDefault();
        const fields = event.detail.fields;

        const scheduleSection = this.scheduleComponent;
        const scheduleFields = (scheduleSection === null || scheduleSection === undefined)
            ? {}
            : scheduleSection.returnValues();

        const donorSection = this.donorComponent;
        const donorFields = (donorSection === null || donorSection === undefined)
            ? {}
            : donorSection.returnValues();

        const allFields = {
            ...fields, ...scheduleFields, ...donorFields
        };

        let isValid = donorSection.isValid() && scheduleSection.isValid();

        if (isValid) {
            this.template.querySelector('[data-id="outerRecordEditForm"]').submit(allFields);
        } else {
            this.isLoading = false;
            this.hasError = false;
            this.template.querySelector("[data-id='submitButton']").disabled = false;
        }
    }

    /***
    * @description Handle component display when an error occurs
    */
    handleError(error) {
        this.errorMessage = constructErrorMessage(error);
        this.hasError = true;
        this.isLoading = false;

        this.template.querySelector("[data-id='submitButton']").disabled = false;
        this.template.querySelector(".slds-modal__header").scrollIntoView();
    }

    /***
    * @description Fires an event to utilDedicatedListener with the cancel action
    */
    handleCancel() {
        fireEvent(this.pageRef, this.listenerEvent, { action: 'cancel', recordId: this.recordId });
    }

    /***
    * @description Fires an event to utilDedicatedListener with the success action
    */
    handleSuccess(event) {
        const recordName = event.detail.fields.Name.value;
        const message = (this.recordId)
            ? updateSuccessMessage.replace("{0}", recordName)
            : insertSuccessMessage.replace("{0}", recordName);

        showToast(message, '', 'success', []);

        fireEvent(this.pageRef, this.listenerEvent, { action: 'success', recordId: event.detail.id });
    }

    /**
     * @description Returns the Schedule Child Component instance
     * @returns rd2EntryFormScheduleSection component dom
     */
    get scheduleComponent() {
        return this.template.querySelectorAll('[data-id="scheduleComponent"]')[0];
    }

    /**
     * @description Returns the Donor Child Component instance
     * @returns rd2EntryFormDonorSection component dom
     */
    get donorComponent() {
        return this.template.querySelectorAll('[data-id="donorComponent"]')[0];
    }

}<commit_msg>Ensure that Status and Status Reason show up properly in edit mode<commit_after>import { LightningElement, api, track, wire } from 'lwc';
import { fireEvent } from 'c/pubsubNoPageRef';
import { showToast, constructErrorMessage, isNull } from 'c/utilCommon';

import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { getRecord } from 'lightning/uiRecordApi';

import RECURRING_DONATION_OBJECT from '@salesforce/schema/npe03__Recurring_Donation__c';

import FIELD_NAME from '@salesforce/schema/npe03__Recurring_Donation__c.Name';
import FIELD_CAMPAIGN from '@salesforce/schema/npe03__Recurring_Donation__c.npe03__Recurring_Donation_Campaign__c';
import FIELD_AMOUNT from '@salesforce/schema/npe03__Recurring_Donation__c.npe03__Amount__c';
import FIELD_PAYMENT_METHOD from '@salesforce/schema/npe03__Recurring_Donation__c.PaymentMethod__c';
import FIELD_STATUS from '@salesforce/schema/npe03__Recurring_Donation__c.Status__c';
import FIELD_STATUS_REASON from '@salesforce/schema/npe03__Recurring_Donation__c.ClosedReason__c';

import currencyFieldLabel from '@salesforce/label/c.lblCurrency';
import cancelButtonLabel from '@salesforce/label/c.stgBtnCancel';
import saveButtonLabel from '@salesforce/label/c.stgBtnSave';
import newHeaderLabel from '@salesforce/label/c.RD2_EntryFormHeader';
import editHeaderLabel from '@salesforce/label/c.commonEdit';
import donorSectionHeader from '@salesforce/label/c.RD2_EntryFormDonorSectionHeader';
import scheduleSectionHeader from '@salesforce/label/c.RD2_EntryFormDonationSectionHeader';
import otherSectionHeader from '@salesforce/label/c.RD2_EntryFormOtherSectionHeader';
import insertSuccessMessage from '@salesforce/label/c.RD2_EntryFormInsertSuccessMessage';
import updateSuccessMessage from '@salesforce/label/c.RD2_EntryFormUpdateSuccessMessage';

import getSetting from '@salesforce/apex/RD2_entryFormController.getRecurringSettings';

export default class rd2EntryForm extends LightningElement {

    listenerEvent = 'rd2EntryFormEvent';

    customLabels = Object.freeze({
        cancelButtonLabel,
        saveButtonLabel,
        donorSectionHeader,
        otherSectionHeader,
        scheduleSectionHeader,
        currencyFieldLabel
    });

    @api parentId;
    @api recordId;

    @track isEdit = false;
    @track record;
    @track isMultiCurrencyEnabled = false;
    @track fields = {};
    fieldInfos;

    @track header = newHeaderLabel;

    @track isAutoNamingEnabled;
    @track isLoading = true;
    @track isRecordReady = false;
    isSettingReady = false;

    @track hasError = false;
    @track errorMessage = {};

    /***
    * @description Dynamic render edit form CSS to show/hide the edit form
    */
    get cssEditForm() {
        //Note: all of these flags need to be checked before the sections are displayed.
        //If the isSettingReady is not checked, then the form on an error resets all fields
        //including the Schedule section LWC fields.
        //TODO: check why and how this expression can be simplifed.
        return (!this.isLoading && this.isSettingReady && this.isRecordReady)
            ? ''
            : 'slds-hide';
    }

    /***
    * @description Get settings required to enable or disable fields and populate their values
    */
    connectedCallback() {
        getSetting({ parentId: this.parentId })
            .then(response => {
                this.isAutoNamingEnabled = response.isAutoNamingEnabled;
                this.isMultiCurrencyEnabled = response.isMultiCurrencyEnabled;
            })
            .catch((error) => {
                this.handleError(error);
            })
            .finally(() => {
                this.isSettingReady = true;
                this.isLoading = false;
            });
    }

    /***
    * @description Retrieve Recurring Donation Object info
    */
    @wire(getObjectInfo, { objectApiName: RECURRING_DONATION_OBJECT.objectApiName })
    wiredRecurringDonationObjectInfo(response) {
        if (response.data) {
            let rdObjectInfo = response.data;
            this.setFields(rdObjectInfo.fields);
            this.fieldInfos = this.buildFieldDescribes(
                rdObjectInfo.fields,
                rdObjectInfo.apiName
            );

            if (isNull(this.recordId)) {
                this.isRecordReady = true;
            }
        }

        if (response.error) {
            this.isRecordReady = true;
            this.handleError(response.error);
        }
    }

    /***
    * @description Method converts field describe info into objects that the
    * getRecord method can accept into its 'fields' parameter.
    */
    buildFieldDescribes(fields, objectApiName) {
        return Object.keys(fields).map((fieldApiName) => {
            return {
                fieldApiName: fieldApiName,
                objectApiName: objectApiName
            }
        });
    }

    /***
    * @description Construct field describe info from the Recurring Donation SObject info
    */
    setFields(fieldInfos) {
        this.fields.name = this.extractFieldInfo(fieldInfos[FIELD_NAME.fieldApiName]);
        this.fields.campaign = this.extractFieldInfo(fieldInfos[FIELD_CAMPAIGN.fieldApiName]);
        this.fields.amount = this.extractFieldInfo(fieldInfos[FIELD_AMOUNT.fieldApiName]);
        this.fields.paymentMethod = this.extractFieldInfo(fieldInfos[FIELD_PAYMENT_METHOD.fieldApiName]);
        this.fields.status = this.extractFieldInfo(fieldInfos[FIELD_STATUS.fieldApiName]);
        this.fields.statusreason = this.extractFieldInfo(fieldInfos[FIELD_STATUS_REASON.fieldApiName]);
        this.fields.currency = { label: currencyFieldLabel, apiName: 'CurrencyIsoCode' };
    }

    /***
    * @description Method converts field describe info into a object that is easily accessible from the front end
    */
    extractFieldInfo(field) {
        return {
            apiName: field.apiName,
            label: field.label,
            inlineHelpText: field.inlineHelpText,
            dataType: field.dataType
        };
    }

    /***
    * @description Retrieve Recurring Donation record value
    */
    @wire(getRecord, { recordId: '$recordId', fields: '$fieldInfos' })
    wiredRecurringDonationRecord(response) {
        if (response.data) {
            this.record = response.data;
            this.header = editHeaderLabel + ' ' + this.record.fields.Name.value;
            this.isRecordReady = true;
            this.isEdit = true;
        }

        if (response.error) {
            this.isRecordReady = true;
            this.handleError(response.error);
        }
    }

    /***
    * @description Overrides the standard submit.
    * Collects fields displayed on the form and any integrated LWC (for example Schedule section)
    * and submits them for the record insert or update.
    */
    handleSubmit(event) {
        this.isLoading = true;
        this.hasError = false;
        this.template.querySelector("[data-id='submitButton']").disabled = true;

        event.preventDefault();
        const fields = event.detail.fields;

        const scheduleSection = this.scheduleComponent;
        const scheduleFields = (scheduleSection === null || scheduleSection === undefined)
            ? {}
            : scheduleSection.returnValues();

        const donorSection = this.donorComponent;
        const donorFields = (donorSection === null || donorSection === undefined)
            ? {}
            : donorSection.returnValues();

        const allFields = {
            ...fields, ...scheduleFields, ...donorFields
        };

        let isValid = donorSection.isValid() && scheduleSection.isValid();

        if (isValid) {
            this.template.querySelector('[data-id="outerRecordEditForm"]').submit(allFields);
        } else {
            this.isLoading = false;
            this.hasError = false;
            this.template.querySelector("[data-id='submitButton']").disabled = false;
        }
    }

    /***
    * @description Handle component display when an error occurs
    */
    handleError(error) {
        this.errorMessage = constructErrorMessage(error);
        this.hasError = true;
        this.isLoading = false;

        this.template.querySelector("[data-id='submitButton']").disabled = false;
        this.template.querySelector(".slds-modal__header").scrollIntoView();
    }

    /***
    * @description Fires an event to utilDedicatedListener with the cancel action
    */
    handleCancel() {
        fireEvent(this.pageRef, this.listenerEvent, { action: 'cancel', recordId: this.recordId });
    }

    /***
    * @description Fires an event to utilDedicatedListener with the success action
    */
    handleSuccess(event) {
        const recordName = event.detail.fields.Name.value;
        const message = (this.recordId)
            ? updateSuccessMessage.replace("{0}", recordName)
            : insertSuccessMessage.replace("{0}", recordName);

        showToast(message, '', 'success', []);

        fireEvent(this.pageRef, this.listenerEvent, { action: 'success', recordId: event.detail.id });
    }

    /**
     * @description Returns the Schedule Child Component instance
     * @returns rd2EntryFormScheduleSection component dom
     */
    get scheduleComponent() {
        return this.template.querySelectorAll('[data-id="scheduleComponent"]')[0];
    }

    /**
     * @description Returns the Donor Child Component instance
     * @returns rd2EntryFormDonorSection component dom
     */
    get donorComponent() {
        return this.template.querySelectorAll('[data-id="donorComponent"]')[0];
    }

}<|endoftext|>


<commit_before>define("js!core", function(){
   var core;
   return core = {
      provideInnerElements : function(root){
         var
            collection,
            result = {};

         function _getCollection (root){
            var
               components = root.querySelectorAll("[data-component]"),
               deadCollection = [];

            for (var i = 0, l = components.length; i < l; i++){
               if (components[i].localName == "component"){
                  deadCollection.push(components[i]);
               }
               else{
                  var p = components[i];
                  while(p = p.parentNode){
                     if (p === root){
                        deadCollection.push(components[i]);
                        break;
                     }
                     else if(/sf-has-markup/.test(p.className)){
                        break;
                     }
                  }
               }
            }

            return deadCollection;
         }

         collection = _getCollection(root);

         for (var i = 0, l = collection.length; i < l; i++){
            var
               cName = collection[i].getAttribute("data-component"),
               inst = new (require("js!" + cName))(collection[i]),
               name = inst.name();
            if (name){
               result[name] = inst;
            }
         }
         return result;
      },
      bootUp : function(deps){
         require(deps, function(){
            core.provideInnerElements(document.body);
         });
      },
      /**
       * https://github.com/dansdom/extend
       * @returns {*|{}}
       */
      extend : function() {
         var options, name, src, copy, copyIsArray, clone,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false,
         // helper which replicates the jquery internal functions
            objectHelper = {
               hasOwn: Object.prototype.hasOwnProperty,
               class2type: {},
               type: function (obj) {
                  return obj == null ?
                     String(obj) :
                     objectHelper.class2type[ Object.prototype.toString.call(obj) ] || "object";
               },
               isPlainObject: function (obj) {
                  if (!obj || objectHelper.type(obj) !== "object" || obj.nodeType || objectHelper.isWindow(obj)) {
                     return false;
                  }

                  try {
                     if (obj.constructor && !objectHelper.hasOwn.call(obj, "constructor") && !objectHelper.hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
                        return false;
                     }
                  } catch (e) {
                     return false;
                  }

                  var key;
                  for (key in obj) {
                  }

                  return key === undefined || objectHelper.hasOwn.call(obj, key);
               },
               isArray: Array.isArray || function (obj) {
                  return objectHelper.type(obj) === "array";
               },
               isFunction: function (obj) {
                  return objectHelper.type(obj) === "function";
               },
               isWindow: function (obj) {
                  return obj != null && obj == obj.window;
               }
            };  // end of objectHelper

         // Handle a deep copy situation
         if (typeof target === "boolean") {
            deep = target;
            target = arguments[1] || {};
            // skip the boolean and the target
            i = 2;
         }

         // Handle case when target is a string or something (possible in deep copy)
         if (typeof target !== "object" && !objectHelper.isFunction(target)) {
            target = {};
         }

         // If no second argument is used then this can extend an object that is using this method
         if (length === i) {
            target = this;
            --i;
         }

         for (; i < length; i++) {
            // Only deal with non-null/undefined values
            if ((options = arguments[ i ]) != null) {
               // Extend the base object
               for (name in options) {
                  src = target[ name ];
                  copy = options[ name ];

                  // Prevent never-ending loop
                  if (target === copy) {
                     continue;
                  }

                  // Recurse if we're merging plain objects or arrays
                  if (deep && copy && ( objectHelper.isPlainObject(copy) || (copyIsArray = objectHelper.isArray(copy)) )) {
                     if (copyIsArray) {
                        copyIsArray = false;
                        clone = src && objectHelper.isArray(src) ? src : [];

                     } else {
                        clone = src && objectHelper.isPlainObject(src) ? src : {};
                     }

                     // Never move original objects, clone them
                     target[ name ] = core.extend(deep, clone, copy);

                     // Don't bring in undefined values
                  } else if (copy !== undefined) {
                     target[ name ] = copy;
                  }
               }
            }
         }

         // Return the modified object
         return target;
      }
   }
});
<commit_msg>проверка типа<commit_after>define("js!core", function(){
   var core;
   return core = {
      provideInnerElements : function(root){
         var
            collection,
            result = {};

         function _getCollection (root){
            var
               components = root.querySelectorAll("[data-component]"),
               deadCollection = [];

            for (var i = 0, l = components.length; i < l; i++){
               if (components[i].localName == "component"){
                  deadCollection.push(components[i]);
               }
               else{
                  var p = components[i];
                  while(p = p.parentNode){
                     if (p === root){
                        deadCollection.push(components[i]);
                        break;
                     }
                     else if(/sf-has-markup/.test(p.className)){
                        break;
                     }
                  }
               }
            }

            return deadCollection;
         }

         collection = _getCollection(root);

         for (var i = 0, l = collection.length; i < l; i++){
            var
               cName = collection[i].getAttribute("data-component"),
               inst = new (require("js!" + cName))(collection[i]),
               name = inst.name();
            if (name){
               result[name] = inst;
            }
         }
         return result;
      },
      bootUp : function(deps){
         require(deps, function(){
            core.provideInnerElements(document.body);
         });
      },
      /**
       * https://github.com/dansdom/extend
       * @returns {*|{}}
       */
      extend : function() {
         var options, name, src, copy, copyIsArray, clone,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false,
         // helper which replicates the jquery internal functions
            objectHelper = {
               hasOwn: Object.prototype.hasOwnProperty,
               class2type: {},
               type: function (obj) {
                  return obj == null ?
                     String(obj) :
                     objectHelper.class2type[ Object.prototype.toString.call(obj) ] || "object";
               },
               isPlainObject: function (obj) {
                  if (!obj || objectHelper.type(obj) !== "object" || obj.nodeType || objectHelper.isWindow(obj)) {
                     return false;
                  }

                  try {
                     if (obj.constructor && !objectHelper.hasOwn.call(obj, "constructor") && !objectHelper.hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
                        return false;
                     }
                  } catch (e) {
                     return false;
                  }

                  var key;
                  for (key in obj) {
                  }

                  return key === undefined || objectHelper.hasOwn.call(obj, key);
               },
               isArray: Array.isArray || function (obj) {
                  return objectHelper.type(obj) === "array";
               },
               isFunction: function (obj) {
                  return objectHelper.type(obj) === "function";
               },
               isWindow: function (obj) {
                  return obj != null && obj == obj.window;
               }
            };  // end of objectHelper

         // Handle a deep copy situation
         if (typeof target === "boolean") {
            deep = target;
            target = arguments[1] || {};
            // skip the boolean and the target
            i = 2;
         }

         // Handle case when target is a string or something (possible in deep copy)
         if (typeof target !== "object" && !objectHelper.isFunction(target)) {
            target = {};
         }

         // If no second argument is used then this can extend an object that is using this method
         if (length === i) {
            target = this;
            --i;
         }

         for (; i < length; i++) {
            // Only deal with non-null/undefined values
            if ((options = arguments[ i ]) != null) {
               // Extend the base object
               for (name in options) {
                  src = target[ name ];
                  copy = options[ name ];

                  // Prevent never-ending loop
                  if (target === copy) {
                     continue;
                  }

                  // Recurse if we're merging plain objects or arrays
                  if (deep && copy && ( objectHelper.isPlainObject(copy) || (copyIsArray = objectHelper.isArray(copy)) )) {
                     if (copyIsArray) {
                        copyIsArray = false;
                        clone = src && objectHelper.isArray(src) ? src : [];

                     } else {
                        clone = src && objectHelper.isPlainObject(src) ? src : {};
                     }

                     // Never move original objects, clone them
                     target[ name ] = core.extend(deep, clone, copy);

                     // Don't bring in undefined values
                  } else if (copy !== undefined) {
                     target[ name ] = copy;
                  }
               }
            }
         }

         // Return the modified object
         return target;
      },
      /**
       * type checking by Jon Bretman
       * @param {*} o
       * @return {String}
       */
      type: function(o){
         if (o && o.nodeType === 1){
            return 'element';
         }

         var match = Object.prototype.toString.call(o).match(/\[object (.*?)\]/);
         var _type = match[1].toLowerCase();

         if (_type === 'number' && isNaN(o)){
            return 'nan';
         }

         return _type;
      }
   }
});
<|endoftext|>


<commit_before>const { app, Menu } = require('electron');
const _ = require('underscore-plus');
const MenuHelpers = require('../menu-helpers');

// Used to manage the global application menu.
//
// It's created by {AtomApplication} upon instantiation and used to add, remove
// and maintain the state of all menu items.
module.exports = class ApplicationMenu {
  constructor(version, autoUpdateManager) {
    this.version = version;
    this.autoUpdateManager = autoUpdateManager;
    this.windowTemplates = new WeakMap();
    this.setActiveTemplate(this.getDefaultTemplate());
    this.autoUpdateManager.on('state-changed', state =>
      this.showUpdateMenuItem(state)
    );
  }

  // Public: Updates the entire menu with the given keybindings.
  //
  // window - The BrowserWindow this menu template is associated with.
  // template - The Object which describes the menu to display.
  // keystrokesByCommand - An Object where the keys are commands and the values
  //                       are Arrays containing the keystroke.
  update(window, template, keystrokesByCommand) {
    this.translateTemplate(template, keystrokesByCommand);
    this.substituteVersion(template);
    this.windowTemplates.set(window, template);
    if (window === this.lastFocusedWindow)
      return this.setActiveTemplate(template);
  }

  setActiveTemplate(template) {
    if (!_.isEqual(template, this.activeTemplate)) {
      this.activeTemplate = template;
      this.menu = Menu.buildFromTemplate(_.deepClone(template));
      Menu.setApplicationMenu(this.menu);
    }

    return this.showUpdateMenuItem(this.autoUpdateManager.getState());
  }

  // Register a BrowserWindow with this application menu.
  addWindow(window) {
    if (this.lastFocusedWindow == null) this.lastFocusedWindow = window;

    const focusHandler = () => {
      this.lastFocusedWindow = window;
      const template = this.windowTemplates.get(window);
      if (template) this.setActiveTemplate(template);
    };

    window.on('focus', focusHandler);
    window.once('closed', () => {
      if (window === this.lastFocusedWindow) this.lastFocusedWindow = null;
      this.windowTemplates.delete(window);
      window.removeListener('focus', focusHandler);
    });

    this.enableWindowSpecificItems(true);
  }

  // Flattens the given menu and submenu items into an single Array.
  //
  // menu - A complete menu configuration object for atom-shell's menu API.
  //
  // Returns an Array of native menu items.
  flattenMenuItems(menu) {
    const object = menu.items || {};
    let items = [];
    for (let index in object) {
      const item = object[index];
      items.push(item);
      if (item.submenu)
        items = items.concat(this.flattenMenuItems(item.submenu));
    }
    return items;
  }

  // Flattens the given menu template into an single Array.
  //
  // template - An object describing the menu item.
  //
  // Returns an Array of native menu items.
  flattenMenuTemplate(template) {
    let items = [];
    for (let item of template) {
      items.push(item);
      if (item.submenu)
        items = items.concat(this.flattenMenuTemplate(item.submenu));
    }
    return items;
  }

  // Public: Used to make all window related menu items are active.
  //
  // enable - If true enables all window specific items, if false disables all
  //          window specific items.
  enableWindowSpecificItems(enable) {
    for (let item of this.flattenMenuItems(this.menu)) {
      if (item.metadata && item.metadata.windowSpecific) item.enabled = enable;
    }
  }

  // Replaces VERSION with the current version.
  substituteVersion(template) {
    let item = this.flattenMenuTemplate(template).find(
      ({ label }) => label === 'VERSION'
    );
    if (item) item.label = `Version ${this.version}`;
  }

  // Sets the proper visible state the update menu items
  showUpdateMenuItem(state) {
    const items = this.flattenMenuItems(this.menu);
    const checkForUpdateItem = items.find(
      // ({ label }) => label === 'Check for Update'
      ({ id }) => id === 'Check for Update'
    );
    const checkingForUpdateItem = items.find(
      // ({ label }) => label === 'Checking for Update'
      ({ id }) => id === 'Checking for Update'
    );
    const downloadingUpdateItem = items.find(
      // ({ label }) => label === 'Downloading Update'
      ({ id }) => id === 'Downloading Update'
    );
    const installUpdateItem = items.find(
      // ({ label }) => label === 'Restart and Install Update'
      ({ id }) => id === 'Restart and Install Update'
    );

    if (
      !checkForUpdateItem ||
      !checkingForUpdateItem ||
      !downloadingUpdateItem ||
      !installUpdateItem
    )
      return;

    checkForUpdateItem.visible = false;
    checkingForUpdateItem.visible = false;
    downloadingUpdateItem.visible = false;
    installUpdateItem.visible = false;

    switch (state) {
      case 'idle':
      case 'error':
      case 'no-update-available':
        checkForUpdateItem.visible = true;
        break;
      case 'checking':
        checkingForUpdateItem.visible = true;
        break;
      case 'downloading':
        downloadingUpdateItem.visible = true;
        break;
      case 'update-available':
        installUpdateItem.visible = true;
        break;
    }
  }

  // Default list of menu items.
  //
  // Returns an Array of menu item Objects.
  getDefaultTemplate() {
    return [
      {
        label: 'Atom',
        id: 'Atom',
        submenu: [
          {
            label: 'Check for Update',
            id: 'Check for Update',
            metadata: { autoUpdate: true }
          },
          {
            label: 'Reload',
            id: 'Reload',
            accelerator: 'Command+R',
            click: () => {
              const window = this.focusedWindow();
              if (window) window.reload();
            }
          },
          {
            label: 'Close Window',
            id: 'Close Window',
            accelerator: 'Command+Shift+W',
            click: () => {
              const window = this.focusedWindow();
              if (window) window.close();
            }
          },
          {
            label: 'Toggle Dev Tools',
            id: 'Toggle Dev Tools',
            accelerator: 'Command+Alt+I',
            click: () => {
              const window = this.focusedWindow();
              if (window) window.toggleDevTools();
            }
          },
          {
            label: 'Quit',
            id: 'Quit',
            accelerator: 'Command+Q',
            click: () => app.quit()
          }
        ]
      }
    ];
  }

  focusedWindow() {
    return global.atomApplication
      .getAllWindows()
      .find(window => window.isFocused());
  }

  // Combines a menu template with the appropriate keystroke.
  //
  // template - An Object conforming to atom-shell's menu api but lacking
  //            accelerator and click properties.
  // keystrokesByCommand - An Object where the keys are commands and the values
  //                       are Arrays containing the keystroke.
  //
  // Returns a complete menu configuration object for atom-shell's menu API.
  translateTemplate(template, keystrokesByCommand) {
    template.forEach(item => {
      if (item.metadata == null) item.metadata = {};
      if (item.command) {
        const keystrokes = keystrokesByCommand[item.command];
        if (keystrokes && keystrokes.length > 0) {
          const keystroke = keystrokes[0];
          // Electron does not support multi-keystroke accelerators. Therefore,
          // when the command maps to a multi-stroke key binding, show the
          // keystrokes next to the item's label.
          if (keystroke.includes(' ')) {
            item.label += ` [${_.humanizeKeystroke(keystroke)}]`;
          } else {
            item.accelerator = MenuHelpers.acceleratorForKeystroke(keystroke);
          }
        }
        item.click = () =>
          global.atomApplication.sendCommand(item.command, item.commandDetail);
        if (!/^application:/.test(item.command)) {
          item.metadata.windowSpecific = true;
        }
      }
      if (item.submenu)
        this.translateTemplate(item.submenu, keystrokesByCommand);
    });
    return template;
  }
};
<commit_msg>Update src/main-process/application-menu.js<commit_after>const { app, Menu } = require('electron');
const _ = require('underscore-plus');
const MenuHelpers = require('../menu-helpers');

// Used to manage the global application menu.
//
// It's created by {AtomApplication} upon instantiation and used to add, remove
// and maintain the state of all menu items.
module.exports = class ApplicationMenu {
  constructor(version, autoUpdateManager) {
    this.version = version;
    this.autoUpdateManager = autoUpdateManager;
    this.windowTemplates = new WeakMap();
    this.setActiveTemplate(this.getDefaultTemplate());
    this.autoUpdateManager.on('state-changed', state =>
      this.showUpdateMenuItem(state)
    );
  }

  // Public: Updates the entire menu with the given keybindings.
  //
  // window - The BrowserWindow this menu template is associated with.
  // template - The Object which describes the menu to display.
  // keystrokesByCommand - An Object where the keys are commands and the values
  //                       are Arrays containing the keystroke.
  update(window, template, keystrokesByCommand) {
    this.translateTemplate(template, keystrokesByCommand);
    this.substituteVersion(template);
    this.windowTemplates.set(window, template);
    if (window === this.lastFocusedWindow)
      return this.setActiveTemplate(template);
  }

  setActiveTemplate(template) {
    if (!_.isEqual(template, this.activeTemplate)) {
      this.activeTemplate = template;
      this.menu = Menu.buildFromTemplate(_.deepClone(template));
      Menu.setApplicationMenu(this.menu);
    }

    return this.showUpdateMenuItem(this.autoUpdateManager.getState());
  }

  // Register a BrowserWindow with this application menu.
  addWindow(window) {
    if (this.lastFocusedWindow == null) this.lastFocusedWindow = window;

    const focusHandler = () => {
      this.lastFocusedWindow = window;
      const template = this.windowTemplates.get(window);
      if (template) this.setActiveTemplate(template);
    };

    window.on('focus', focusHandler);
    window.once('closed', () => {
      if (window === this.lastFocusedWindow) this.lastFocusedWindow = null;
      this.windowTemplates.delete(window);
      window.removeListener('focus', focusHandler);
    });

    this.enableWindowSpecificItems(true);
  }

  // Flattens the given menu and submenu items into an single Array.
  //
  // menu - A complete menu configuration object for atom-shell's menu API.
  //
  // Returns an Array of native menu items.
  flattenMenuItems(menu) {
    const object = menu.items || {};
    let items = [];
    for (let index in object) {
      const item = object[index];
      items.push(item);
      if (item.submenu)
        items = items.concat(this.flattenMenuItems(item.submenu));
    }
    return items;
  }

  // Flattens the given menu template into an single Array.
  //
  // template - An object describing the menu item.
  //
  // Returns an Array of native menu items.
  flattenMenuTemplate(template) {
    let items = [];
    for (let item of template) {
      items.push(item);
      if (item.submenu)
        items = items.concat(this.flattenMenuTemplate(item.submenu));
    }
    return items;
  }

  // Public: Used to make all window related menu items are active.
  //
  // enable - If true enables all window specific items, if false disables all
  //          window specific items.
  enableWindowSpecificItems(enable) {
    for (let item of this.flattenMenuItems(this.menu)) {
      if (item.metadata && item.metadata.windowSpecific) item.enabled = enable;
    }
  }

  // Replaces VERSION with the current version.
  substituteVersion(template) {
    let item = this.flattenMenuTemplate(template).find(
      ({ label }) => label === 'VERSION'
    );
    if (item) item.label = `Version ${this.version}`;
  }

  // Sets the proper visible state the update menu items
  showUpdateMenuItem(state) {
    const items = this.flattenMenuItems(this.menu);
    const checkForUpdateItem = items.find(
      ({ id }) => id === 'Check for Update'
    );
    const checkingForUpdateItem = items.find(
      // ({ label }) => label === 'Checking for Update'
      ({ id }) => id === 'Checking for Update'
    );
    const downloadingUpdateItem = items.find(
      // ({ label }) => label === 'Downloading Update'
      ({ id }) => id === 'Downloading Update'
    );
    const installUpdateItem = items.find(
      // ({ label }) => label === 'Restart and Install Update'
      ({ id }) => id === 'Restart and Install Update'
    );

    if (
      !checkForUpdateItem ||
      !checkingForUpdateItem ||
      !downloadingUpdateItem ||
      !installUpdateItem
    )
      return;

    checkForUpdateItem.visible = false;
    checkingForUpdateItem.visible = false;
    downloadingUpdateItem.visible = false;
    installUpdateItem.visible = false;

    switch (state) {
      case 'idle':
      case 'error':
      case 'no-update-available':
        checkForUpdateItem.visible = true;
        break;
      case 'checking':
        checkingForUpdateItem.visible = true;
        break;
      case 'downloading':
        downloadingUpdateItem.visible = true;
        break;
      case 'update-available':
        installUpdateItem.visible = true;
        break;
    }
  }

  // Default list of menu items.
  //
  // Returns an Array of menu item Objects.
  getDefaultTemplate() {
    return [
      {
        label: 'Atom',
        id: 'Atom',
        submenu: [
          {
            label: 'Check for Update',
            id: 'Check for Update',
            metadata: { autoUpdate: true }
          },
          {
            label: 'Reload',
            id: 'Reload',
            accelerator: 'Command+R',
            click: () => {
              const window = this.focusedWindow();
              if (window) window.reload();
            }
          },
          {
            label: 'Close Window',
            id: 'Close Window',
            accelerator: 'Command+Shift+W',
            click: () => {
              const window = this.focusedWindow();
              if (window) window.close();
            }
          },
          {
            label: 'Toggle Dev Tools',
            id: 'Toggle Dev Tools',
            accelerator: 'Command+Alt+I',
            click: () => {
              const window = this.focusedWindow();
              if (window) window.toggleDevTools();
            }
          },
          {
            label: 'Quit',
            id: 'Quit',
            accelerator: 'Command+Q',
            click: () => app.quit()
          }
        ]
      }
    ];
  }

  focusedWindow() {
    return global.atomApplication
      .getAllWindows()
      .find(window => window.isFocused());
  }

  // Combines a menu template with the appropriate keystroke.
  //
  // template - An Object conforming to atom-shell's menu api but lacking
  //            accelerator and click properties.
  // keystrokesByCommand - An Object where the keys are commands and the values
  //                       are Arrays containing the keystroke.
  //
  // Returns a complete menu configuration object for atom-shell's menu API.
  translateTemplate(template, keystrokesByCommand) {
    template.forEach(item => {
      if (item.metadata == null) item.metadata = {};
      if (item.command) {
        const keystrokes = keystrokesByCommand[item.command];
        if (keystrokes && keystrokes.length > 0) {
          const keystroke = keystrokes[0];
          // Electron does not support multi-keystroke accelerators. Therefore,
          // when the command maps to a multi-stroke key binding, show the
          // keystrokes next to the item's label.
          if (keystroke.includes(' ')) {
            item.label += ` [${_.humanizeKeystroke(keystroke)}]`;
          } else {
            item.accelerator = MenuHelpers.acceleratorForKeystroke(keystroke);
          }
        }
        item.click = () =>
          global.atomApplication.sendCommand(item.command, item.commandDetail);
        if (!/^application:/.test(item.command)) {
          item.metadata.windowSpecific = true;
        }
      }
      if (item.submenu)
        this.translateTemplate(item.submenu, keystrokesByCommand);
    });
    return template;
  }
};
<|endoftext|>


<commit_before>var states = [
    {
        name : 'app.course.assignment',
        url : '/assignments/{moduleId}',
        views : {
            'childContent' : {
                templateUrl : 'views/student/assignment/home.html',
                controller : 'Student.AssignmentDetailsCtrl',
                controllerAs : 'assignmentDetails'
            },
            'assignmentContent@app.course.assignment' : {
                templateUrl : 'views/student/assignment_details/home.html'
            }
        }
    },
    {
        name : 'app.course.assignment.group',
        url : '/group/{groupId}',
        views : {
            'assignmentContent' : {
                templateUrl : 'views/student/assignment_create_group/home.html',
                controller : 'Student.AssignmentGroupCtrl',
                controllerAs : 'assignmentGroup'
            }
        },
        resolve : {
            groupId : ['GroupService','$stateParams','$state', function(GroupService, $stateParams, $state){
                if(!$stateParams.groupId){
                    var courseId = $stateParams.courseId;
                    var moduleId = $stateParams.moduleId;
                    return GroupService.initialize(courseId, moduleId)
                        .then(function(payload){
                            $state.go($state.current, {groupId:payload.id});
                        return payload.id;
                    }, function(err){
                            $state.go('app.course.assignment', {moduleId : moduleId});
                        return err;
                    });
                }else{
                    return $stateParams.groupId;
                }
            }]
        }
    },
    {
        name :  'app.course.assignment.login',
        url : '/group/{groupId}/login',
        views : {
            'assignmentContent' : {
                templateUrl : 'views/student/assignment_group_login/home.html',
                controller : 'Student.AssignmentLoginCtrl',
                controllerAs : 'assignmentLogin'
            }
        },
        resolve : {
            lock : ['GroupService','$stateParams','$state', function(GroupService, $stateParams, $state){
                var courseId = $stateParams.courseId;
                var moduleId = $stateParams.moduleId;
                var groupId = $stateParams.groupId;
                return GroupService.getLock(courseId, moduleId, groupId)
                    .then(function(payload){
                        if(payload.hasLock) {
                            $state.go('app.course.assignment.questions', { groupId : groupId});
                        }
                    return payload;
                }, function(err){
                        $state.go('app.course.assignment', {moduleId : moduleId});
                    return err;
                });
            }]
        }
    },
    {
        name : 'app.course.assignment.questions',
        url : '/group/{groupId}/questions',
        views : {
            'assignmentContent' : {
                templateUrl : 'views/student/assignment_questions/home.html',
                controller : 'Student.AssignmentQuestionsCtrl',
                controllerAs : 'assignmentQuestions'
            }
        },
        resolve : {
            lock : ['GroupService','$stateParams','$state', function(GroupService, $stateParams, $state){
                var courseId = $stateParams.courseId;
                var moduleId = $stateParams.moduleId;
                var groupId = $stateParams.groupId;
                return GroupService.getLock(courseId, moduleId, groupId);
            }]
        }
    }
]

module.exports = states;
<commit_msg>CHNG: minor change that still might need fixing<commit_after>var states = [
    {
        name : 'app.course.assignment',
        url : '/assignments/{moduleId}',
        views : {
            'childContent' : {
                templateUrl : 'views/student/assignment/home.html',
                controller : 'Student.AssignmentDetailsCtrl',
                controllerAs : 'assignmentDetails'
            },
            'assignmentContent@app.course.assignment' : {
                templateUrl : 'views/student/assignment_details/home.html'
            }
        }
    },
    {
        name : 'app.course.assignment.group',
        url : '/group/{groupId}',
        views : {
            'assignmentContent' : {
                templateUrl : 'views/student/assignment_create_group/home.html',
                controller : 'Student.AssignmentGroupCtrl',
                controllerAs : 'assignmentGroup'
            }
        },
        resolve : {
            groupId : ['GroupService','$stateParams','$state', function(GroupService, $stateParams, $state){
                if(!$stateParams.groupId){
                    var courseId = $stateParams.courseId;
                    var moduleId = $stateParams.moduleId;
                    return GroupService.initialize(courseId, moduleId)
                        .then(function(payload){
                            $stateParams.groupId = payload.id;
                        return payload.id;
                    }, function(err){
                            $state.go('app.course.assignment', {moduleId : moduleId});
                        return err;
                    });
                }else{
                    return $stateParams.groupId;
                }
            }]
        }
    },
    {
        name :  'app.course.assignment.login',
        url : '/group/{groupId}/login',
        views : {
            'assignmentContent' : {
                templateUrl : 'views/student/assignment_group_login/home.html',
                controller : 'Student.AssignmentLoginCtrl',
                controllerAs : 'assignmentLogin'
            }
        },
        resolve : {
            lock : ['GroupService','$stateParams','$state', function(GroupService, $stateParams, $state){
                var courseId = $stateParams.courseId;
                var moduleId = $stateParams.moduleId;
                var groupId = $stateParams.groupId;
                return GroupService.getLock(courseId, moduleId, groupId)
                    .then(function(payload){
                        if(payload.hasLock) {
                            $state.go('app.course.assignment.questions', { groupId : groupId});
                        }
                    return payload;
                }, function(err){
                        $state.go('app.course.assignment', {moduleId : moduleId});
                    return err;
                });
            }]
        }
    },
    {
        name : 'app.course.assignment.questions',
        url : '/group/{groupId}/questions',
        views : {
            'assignmentContent' : {
                templateUrl : 'views/student/assignment_questions/home.html',
                controller : 'Student.AssignmentQuestionsCtrl',
                controllerAs : 'assignmentQuestions'
            }
        },
        resolve : {
            lock : ['GroupService','$stateParams','$state', function(GroupService, $stateParams, $state){
                var courseId = $stateParams.courseId;
                var moduleId = $stateParams.moduleId;
                var groupId = $stateParams.groupId;
                return GroupService.getLock(courseId, moduleId, groupId);
            }]
        }
    }
]

module.exports = states;
<|endoftext|>


<commit_before>/*jshint browserify: true */
"use strict";

var noop = require('./util/noop'),
  inherits = require('util').inherits,
  extend = require('extend'),
  ArrayCursor = require('./cursor');

module.exports = extend(
  function (connection, body) {
    var Ctor = (body.type === 3 ? EdgeCollection : DocumentCollection);
    return new Ctor(connection, body);
  }, {
    _BaseCollection: BaseCollection,
    DocumentCollection: DocumentCollection,
    EdgeCollection: EdgeCollection
  }
);

function BaseCollection(connection, body) {
  this._connection = connection;
  this._api = this._connection.route('_api');
  extend(this, body);
  delete this.code;
  delete this.error;
}

extend(BaseCollection.prototype, {
  _documentPath: function (documentHandle) {
    return (this.type === 3 ? 'edge/' : 'document/') + this._documentHandle(documentHandle);
  },
  _documentHandle: function (documentHandle) {
    if (documentHandle._id) {
      documentHandle = documentHandle._id;
    } else if (documentHandle._key) {
      documentHandle = documentHandle._key;
    }
    if (documentHandle.indexOf('/') === -1) {
      documentHandle = this.name + '/' + documentHandle;
    }
    return documentHandle;
  },
  _indexHandle: function (indexHandle) {
    if (indexHandle.id) {
      indexHandle = indexHandle.id;
    }
    if (indexHandle.indexOf('/') === -1) {
      indexHandle = this.name + '/' + indexHandle;
    }
    return indexHandle;
  },
  _get: function (path, update, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (!callback) callback = noop;
    var self = this;
    self._api.get('collection/' + self.name + '/' + path, opts, function (err, body) {
      if (err) callback(err);
      else {
        if (update) {
          extend(self, body);
          delete self.code;
          delete self.error;
        }
        callback(null, body);
      }
    });
  },
  _put: function (path, data, update, callback) {
    if (!callback) callback = noop;
    var self = this;
    self._api.put('collection/' + self.name + '/' + path, data, function (err, body) {
      if (err) callback(err);
      else {
        if (update) extend(self, body);
        callback(null, body);
      }
    });
  },
  properties: function (callback) {
    return this._get('properties', true, callback);
  },
  count: function (callback) {
    return this._get('count', true, callback);
  },
  figures: function (callback) {
    return this._get('figures', true, callback);
  },
  revision: function (callback) {
    return this._get('revision', true, callback);
  },
  checksum: function (opts, callback) {
    return this._get('checksum', true, opts, callback);
  },
  load: function (count, callback) {
    if (typeof count === 'function') {
      callback = count;
      count = undefined;
    }
    return this._put('load', (
      typeof count === 'boolean' ? {count: count} : undefined
    ), true, callback);
  },
  unload: function (callback) {
    return this._put('unload', undefined, true, callback);
  },
  setProperties: function (properties, callback) {
    return this._put('properties', properties, true, callback);
  },
  rename: function (name, callback) {
    return this._put('rename', {name: name}, true, callback);
  },
  rotate: function (callback) {
    return this._put('rotate', undefined, false, callback);
  },
  truncate: function (callback) {
    return this._put('truncate', undefined, true, callback);
  },
  drop: function (callback) {
    if (!callback) callback = noop;
    var self = this;
    self._api.delete('collection/' + self.name, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  replace: function (documentHandle, data, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (!callback) callback = noop;
    opts = extend({}, opts, {collection: this.name});
    this._api.put(this._documentPath(documentHandle), data, opts, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  update: function (documentHandle, data, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (!callback) callback = noop;
    opts = extend({}, opts, {collection: this.name});
    this._api.patch(this._documentPath(documentHandle), data, opts, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  remove: function (documentHandle, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (!callback) callback = noop;
    opts = extend({}, opts, {collection: this.name});
    this._api.delete(this._documentPath(documentHandle), opts, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  all: function (type, callback) {
    if (typeof type === 'function') {
      callback = type;
      type = undefined;
    }
    if (!callback) callback = noop;
    this._api.get('document', {
      type: type || 'id',
      collection: this.name
    }, function (err, body) {
      if (err) callback(err);
      else callback(null, body.documents);
    });
  },
  import: function (data, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (!callback) callback = noop;
    this._api.request({
      method: 'POST',
      path: 'import',
      body: data,
      ld: Boolean(opts.type !== 'array'),
      qs: extend({
        type: 'auto'
      }, opts, {
        collection: this.name
      })
    }, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  indexes: function (callback) {
    if (!callback) callback = noop;
    this._api.get('index', {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result.indexes);
    });
  },
  index: function (indexHandle, callback) {
    if (!callback) callback = noop;
    this._api.get(
      'index/' + this._indexHandle(indexHandle),
      function (err, result) {
        if (err) callback(err);
        else callback(null, result);
      }
    );
  },
  createIndex: function (details, callback) {
    if (!callback) callback = noop;
    this._api.post('index', details, {
      collection: this.name
    }, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  dropIndex: function (indexHandle, callback) {
    if (!callback) callback = noop;
    this._api.delete(
      'index/' + this._indexHandle(indexHandle),
      function (err, result) {
        if (err) callback(err);
        else callback(null, result);
      }
    );
  },
  createCapConstraint: function (size, callback) {
    if (typeof size === 'number') {
      size = {size: size};
    }
    if (!callback) callback = noop;
    this._api.post('index', extend({}, size, {
      type: 'cap'
    }), {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  createHashIndex: function (fields, unique, callback) {
    if (typeof unique === 'function') {
      callback = unique;
      unique = undefined;
    }
    if (typeof fields === 'string') {
      fields = [fields];
    }
    if (!callback) callback = noop;
    this._api.post('index', {
      type: 'hash',
      fields: fields,
      unique: Boolean(unique)
    }, {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  createSkipList: function (fields, unique, callback) {
    if (typeof unique === 'function') {
      callback = unique;
      unique = undefined;
    }
    if (typeof fields === 'string') {
      fields = [fields];
    }
    if (!callback) callback = noop;
    this._api.post('index', {
      type: 'skiplist',
      fields: fields,
      unique: Boolean(unique)
    }, {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  createGeoIndex: function (fields, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (typeof fields === 'string') {
      fields = [fields];
    }
    if (!callback) callback = noop;
    this._api.post('index', extend({}, opts, {
      type: 'geo',
      fields: fields
    }), {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  createFulltextIndex: function (fields, minLength, callback) {
    if (typeof minLength === 'function') {
      callback = minLength;
      minLength = undefined;
    }
    if (typeof fields === 'string') {
      fields = [fields];
    }
    if (!callback) callback = noop;
    this._api.post('index', {
      type: 'fulltext',
      fields: fields,
      minLength: minLength ? Number(minLength) : undefined
    }, {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  fulltext: function (field, query, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (opts) {
      opts = extend({}, opts);
      if (opts.index) opts.index = this._indexHandle(opts.index);
    }
    if (!callback) callback = noop;
    var self = this;
    self._api.put('simple/fulltext', extend(opts, {
      collection: this.name,
      attribute: field,
      query: query
    }), function (err, body) {
      if (err) callback(err);
      else callback(null, new ArrayCursor(self._connection, body));
    });
  },
  near: function (latitude, longitude, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (opts) {
      opts = extend({}, opts);
      if (opts.geo) opts.geo = this._indexHandle(opts.geo);
    }
    if (!callback) callback = noop;
    var self = this;
    self._api.put('simple/near', extend(opts, {
      collection: this.name,
      latitude: latitude,
      longitude: longitude
    }), function (err, body) {
      if (err) callback(err);
      else callback(null, new ArrayCursor(self._connection, body));
    });
  },
  within: function (latitude, longitude, radius, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (opts) {
      opts = extend({}, opts);
      if (opts.geo) opts.geo = this._indexHandle(opts.geo);
    }
    if (!callback) callback = noop;
    var self = this;
    self._api.put('simple/within', extend(opts, {
      collection: this.name,
      latitude: latitude,
      longitude: longitude,
      radius: Number(radius)
    }), function (err, body) {
      if (err) callback(err);
      else callback(null, new ArrayCursor(self._connection, body));
    });
  }
});

function DocumentCollection(connection, body) {
  BaseCollection.call(this, connection, body);
}

inherits(DocumentCollection, BaseCollection);

extend(DocumentCollection.prototype, {
  document: function (documentHandle, callback) {
    if (!callback) callback = noop;
    this._api.get('document/' + this._documentHandle(documentHandle), function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  save: function (data, callback) {
    if (!callback) callback = noop;
    this._api.post('document/', data, {
      collection: this.name
    }, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  }
});

function EdgeCollection(connection, body) {
  BaseCollection.call(this, connection, body);
}

inherits(EdgeCollection, BaseCollection);

extend(EdgeCollection.prototype, {
  edge: function (documentHandle, callback) {
    if (!callback) callback = noop;
    this._api.get('edge/' + this._documentHandle(documentHandle), function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  save: function (data, fromId, toId, callback) {
    if (!callback) callback = noop;
    this._api.post('edge/', data, {
      collection: this.name,
      from: this._documentHandle(fromId),
      to: this._documentHandle(toId)
    }, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  _edges: function (documentHandle, direction, callback) {
    if (!callback) callback = noop;
    this._api.get('edges/' + this.name, {
      vertex: this._documentHandle(documentHandle),
      direction: direction
    }, function (err, body) {
      if (err) callback(err);
      else callback(null, body.edges);
    });
  },
  edges: function (vertex, callback) {
    return this._edges(vertex, undefined, callback);
  },
  inEdges: function (vertex, callback) {
    return this._edges(vertex, 'in', callback);
  },
  outEdges: function (vertex, callback) {
    return this._edges(vertex, 'out', callback);
  },
  traversal: function (startVertex, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    this._api.post('traversal', extend({}, opts, {
      startVertex: startVertex,
      edgeCollection: this.name
    }), function (err, data) {
      if (err) callback(err);
      else callback(null, data.result);
    });
  }
});

<commit_msg>Fixed #27.<commit_after>/*jshint browserify: true */
"use strict";

var noop = require('./util/noop'),
  inherits = require('util').inherits,
  extend = require('extend'),
  ArrayCursor = require('./cursor');

module.exports = extend(
  function (connection, body) {
    var Ctor = (body.type === 3 ? EdgeCollection : DocumentCollection);
    return new Ctor(connection, body);
  }, {
    _BaseCollection: BaseCollection,
    DocumentCollection: DocumentCollection,
    EdgeCollection: EdgeCollection
  }
);

function BaseCollection(connection, body) {
  this._connection = connection;
  this._api = this._connection.route('_api');
  extend(this, body);
  delete this.code;
  delete this.error;
}

extend(BaseCollection.prototype, {
  _documentPath: function (documentHandle) {
    return (this.type === 3 ? 'edge/' : 'document/') + this._documentHandle(documentHandle);
  },
  _documentHandle: function (documentHandle) {
    if (documentHandle._id) {
      documentHandle = documentHandle._id;
    } else if (documentHandle._key) {
      documentHandle = documentHandle._key;
    }
    if (documentHandle.indexOf('/') === -1) {
      documentHandle = this.name + '/' + documentHandle;
    }
    return documentHandle;
  },
  _indexHandle: function (indexHandle) {
    if (indexHandle.id) {
      indexHandle = indexHandle.id;
    }
    if (indexHandle.indexOf('/') === -1) {
      indexHandle = this.name + '/' + indexHandle;
    }
    return indexHandle;
  },
  _get: function (path, update, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (!callback) callback = noop;
    var self = this;
    self._api.get('collection/' + self.name + '/' + path, opts, function (err, body) {
      if (err) callback(err);
      else {
        if (update) {
          extend(self, body);
          delete self.code;
          delete self.error;
        }
        callback(null, body);
      }
    });
  },
  _put: function (path, data, update, callback) {
    if (!callback) callback = noop;
    var self = this;
    self._api.put('collection/' + self.name + '/' + path, data, function (err, body) {
      if (err) callback(err);
      else {
        if (update) extend(self, body);
        callback(null, body);
      }
    });
  },
  properties: function (callback) {
    return this._get('properties', true, callback);
  },
  count: function (callback) {
    return this._get('count', true, callback);
  },
  figures: function (callback) {
    return this._get('figures', true, callback);
  },
  revision: function (callback) {
    return this._get('revision', true, callback);
  },
  checksum: function (opts, callback) {
    return this._get('checksum', true, opts, callback);
  },
  load: function (count, callback) {
    if (typeof count === 'function') {
      callback = count;
      count = undefined;
    }
    return this._put('load', (
      typeof count === 'boolean' ? {count: count} : undefined
    ), true, callback);
  },
  unload: function (callback) {
    return this._put('unload', undefined, true, callback);
  },
  setProperties: function (properties, callback) {
    return this._put('properties', properties, true, callback);
  },
  rename: function (name, callback) {
    return this._put('rename', {name: name}, true, callback);
  },
  rotate: function (callback) {
    return this._put('rotate', undefined, false, callback);
  },
  truncate: function (callback) {
    return this._put('truncate', undefined, true, callback);
  },
  drop: function (callback) {
    if (!callback) callback = noop;
    var self = this;
    self._api.delete('collection/' + self.name, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  replace: function (documentHandle, data, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (!callback) callback = noop;
    opts = extend({}, opts, {collection: this.name});
    this._api.put(this._documentPath(documentHandle), data, opts, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  update: function (documentHandle, data, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (!callback) callback = noop;
    opts = extend({}, opts, {collection: this.name});
    this._api.patch(this._documentPath(documentHandle), data, opts, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  remove: function (documentHandle, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (!callback) callback = noop;
    opts = extend({}, opts, {collection: this.name});
    this._api.delete(this._documentPath(documentHandle), opts, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  all: function (type, callback) {
    if (typeof type === 'function') {
      callback = type;
      type = undefined;
    }
    if (!callback) callback = noop;
    this._api.get('document', {
      type: type || 'id',
      collection: this.name
    }, function (err, body) {
      if (err) callback(err);
      else callback(null, body.documents);
    });
  },
  import: function (data, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (!callback) callback = noop;
    this._api.request({
      method: 'POST',
      path: 'import',
      body: data,
      ld: Boolean(!opts || opts.type !== 'array'),
      qs: extend({
        type: 'auto'
      }, opts, {
        collection: this.name
      })
    }, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  indexes: function (callback) {
    if (!callback) callback = noop;
    this._api.get('index', {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result.indexes);
    });
  },
  index: function (indexHandle, callback) {
    if (!callback) callback = noop;
    this._api.get(
      'index/' + this._indexHandle(indexHandle),
      function (err, result) {
        if (err) callback(err);
        else callback(null, result);
      }
    );
  },
  createIndex: function (details, callback) {
    if (!callback) callback = noop;
    this._api.post('index', details, {
      collection: this.name
    }, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  dropIndex: function (indexHandle, callback) {
    if (!callback) callback = noop;
    this._api.delete(
      'index/' + this._indexHandle(indexHandle),
      function (err, result) {
        if (err) callback(err);
        else callback(null, result);
      }
    );
  },
  createCapConstraint: function (size, callback) {
    if (typeof size === 'number') {
      size = {size: size};
    }
    if (!callback) callback = noop;
    this._api.post('index', extend({}, size, {
      type: 'cap'
    }), {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  createHashIndex: function (fields, unique, callback) {
    if (typeof unique === 'function') {
      callback = unique;
      unique = undefined;
    }
    if (typeof fields === 'string') {
      fields = [fields];
    }
    if (!callback) callback = noop;
    this._api.post('index', {
      type: 'hash',
      fields: fields,
      unique: Boolean(unique)
    }, {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  createSkipList: function (fields, unique, callback) {
    if (typeof unique === 'function') {
      callback = unique;
      unique = undefined;
    }
    if (typeof fields === 'string') {
      fields = [fields];
    }
    if (!callback) callback = noop;
    this._api.post('index', {
      type: 'skiplist',
      fields: fields,
      unique: Boolean(unique)
    }, {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  createGeoIndex: function (fields, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (typeof fields === 'string') {
      fields = [fields];
    }
    if (!callback) callback = noop;
    this._api.post('index', extend({}, opts, {
      type: 'geo',
      fields: fields
    }), {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  createFulltextIndex: function (fields, minLength, callback) {
    if (typeof minLength === 'function') {
      callback = minLength;
      minLength = undefined;
    }
    if (typeof fields === 'string') {
      fields = [fields];
    }
    if (!callback) callback = noop;
    this._api.post('index', {
      type: 'fulltext',
      fields: fields,
      minLength: minLength ? Number(minLength) : undefined
    }, {collection: this.name}, function (err, result) {
      if (err) callback(err);
      else callback(null, result);
    });
  },
  fulltext: function (field, query, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (opts) {
      opts = extend({}, opts);
      if (opts.index) opts.index = this._indexHandle(opts.index);
    }
    if (!callback) callback = noop;
    var self = this;
    self._api.put('simple/fulltext', extend(opts, {
      collection: this.name,
      attribute: field,
      query: query
    }), function (err, body) {
      if (err) callback(err);
      else callback(null, new ArrayCursor(self._connection, body));
    });
  },
  near: function (latitude, longitude, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (opts) {
      opts = extend({}, opts);
      if (opts.geo) opts.geo = this._indexHandle(opts.geo);
    }
    if (!callback) callback = noop;
    var self = this;
    self._api.put('simple/near', extend(opts, {
      collection: this.name,
      latitude: latitude,
      longitude: longitude
    }), function (err, body) {
      if (err) callback(err);
      else callback(null, new ArrayCursor(self._connection, body));
    });
  },
  within: function (latitude, longitude, radius, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    if (opts) {
      opts = extend({}, opts);
      if (opts.geo) opts.geo = this._indexHandle(opts.geo);
    }
    if (!callback) callback = noop;
    var self = this;
    self._api.put('simple/within', extend(opts, {
      collection: this.name,
      latitude: latitude,
      longitude: longitude,
      radius: Number(radius)
    }), function (err, body) {
      if (err) callback(err);
      else callback(null, new ArrayCursor(self._connection, body));
    });
  }
});

function DocumentCollection(connection, body) {
  BaseCollection.call(this, connection, body);
}

inherits(DocumentCollection, BaseCollection);

extend(DocumentCollection.prototype, {
  document: function (documentHandle, callback) {
    if (!callback) callback = noop;
    this._api.get('document/' + this._documentHandle(documentHandle), function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  save: function (data, callback) {
    if (!callback) callback = noop;
    this._api.post('document/', data, {
      collection: this.name
    }, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  }
});

function EdgeCollection(connection, body) {
  BaseCollection.call(this, connection, body);
}

inherits(EdgeCollection, BaseCollection);

extend(EdgeCollection.prototype, {
  edge: function (documentHandle, callback) {
    if (!callback) callback = noop;
    this._api.get('edge/' + this._documentHandle(documentHandle), function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  save: function (data, fromId, toId, callback) {
    if (!callback) callback = noop;
    this._api.post('edge/', data, {
      collection: this.name,
      from: this._documentHandle(fromId),
      to: this._documentHandle(toId)
    }, function (err, body) {
      if (err) callback(err);
      else callback(null, body);
    });
  },
  _edges: function (documentHandle, direction, callback) {
    if (!callback) callback = noop;
    this._api.get('edges/' + this.name, {
      vertex: this._documentHandle(documentHandle),
      direction: direction
    }, function (err, body) {
      if (err) callback(err);
      else callback(null, body.edges);
    });
  },
  edges: function (vertex, callback) {
    return this._edges(vertex, undefined, callback);
  },
  inEdges: function (vertex, callback) {
    return this._edges(vertex, 'in', callback);
  },
  outEdges: function (vertex, callback) {
    return this._edges(vertex, 'out', callback);
  },
  traversal: function (startVertex, opts, callback) {
    if (typeof opts === 'function') {
      callback = opts;
      opts = undefined;
    }
    this._api.post('traversal', extend({}, opts, {
      startVertex: startVertex,
      edgeCollection: this.name
    }), function (err, data) {
      if (err) callback(err);
      else callback(null, data.result);
    });
  }
});

<|endoftext|>


<commit_before>'use strict';

module.exports = (api) => {

  const collectorMethodNames = ['pick', 'fail', 'take', 'timeout', 'done'];

  api.metasync.collect = (
    expected // count or array of string
    // returns: collector functor
  ) => {
    const isCount = typeof(expected) === 'number';
    const isKeys = Array.isArray(expected);
    if (!(isCount || isKeys)) throw new TypeError('Unexpected type');
    let keys = null;
    if (isKeys) {
      keys = new Set(expected);
      expected = expected.length;
    }
    let count = 0;
    let takeCbCount = 0;
    let timer = null;
    let done = null;
    let isDistinct = false;
    let isDone = false;
    const data = {};
    const returnIfDone = (fn) => (...args) => {
      if (!isDone) return fn(...args);
    };

    const collector = returnIfDone((key, err, value) => {
      if (!isDistinct || !(key in data)) {
        if (!isCount && !keys.has(key)) return;
        count++;
      }
      if (err) return done(err, data);
      data[key] = value;
      if (expected === count) {
        if (timer) clearTimeout(timer);
        done(null, data);
      }
      return collector;
    });

    collector.pick = (key, value) => collector(key, null, value);

    collector.fail = (key, err) => collector(key, err);

    collector.take = (key, fn, ...args) => {
      if (count + takeCbCount < expected) {
        takeCbCount++;
        fn(...args, (...cbArgs) => {
          takeCbCount--;
          collector(key, ...cbArgs);
        });
      }
      return collector;
    };

    collector.timeout = (msec) => {
      if (msec) {
        timer = setTimeout(() => {
          const err = new Error('Collector timeout');
          done(err, data);
        }, msec);
        timer.unref();
      }
      return collector;
    };

    collector.done = (cb) => {
      done = api.common.cb((...args) => {
        isDone = true;
        let methodName;
        for (methodName of collectorMethodNames) {
          collector[methodName] = api.common.emptiness;
        }
        cb(...args);
      });
      return collector;
    };

    collector.distinct = (value = true) => {
      isDistinct = value;
      return collector;
    };

    let methodName;
    for (methodName of collectorMethodNames) {
      collector[methodName] = returnIfDone(collector[methodName]);
    }

    return collector;
  };

  function Collector() {}

  Collector.prototype.on = function(
    // Collector events:
    eventName,
    listener // handler function
    // on('error', function(err, key))
    // on('timeout', function(err, data))
    // on('done', function(errs, data))
  ) {
    if (eventName in this.events) {
      this.events[eventName] = listener;
    }
  };

  Collector.prototype.emit = function(
    // Emit Collector events
    eventName, err, data
  ) {
    const event = this.events[eventName];
    if (event) event(err, data);
  };

  api.metasync.DataCollector = function(
    expected, // number of collect() calls expected
    timeout // collect timeout (optional)
    // returns: instance of DataCollector
  ) {
    this.expected = expected;
    this.timeout = timeout;
    this.count = 0;
    this.data = {};
    this.errs = [];
    this.events = {
      error: null,
      timeout: null,
      done: null
    };
    const collector = this;
    if (this.timeout) {
      this.timer = setTimeout(() => {
        const err = new Error('DataCollector timeout');
        collector.emit('timeout', err, collector.data);
      }, timeout);
    }
  };

  api.util.inherits(api.metasync.DataCollector, Collector);

  api.metasync.DataCollector.prototype.collect = function(
    // Push data to collector
    key, // key in result data
    data // value or error instance
  ) {
    this.count++;
    if (data instanceof Error) {
      this.errs[key] = data;
      this.emit('error', data, key);
    } else {
      this.data[key] = data;
    }
    if (this.expected === this.count) {
      if (this.timer) clearTimeout(this.timer);
      const errs = this.errs.length ? this.errs : null;
      this.emit('done', errs, this.data);
    }
  };

  api.metasync.KeyCollector = function(
    // Key Collector
    keys, // array of keys, example: ['config', 'users', 'cities']
    timeout // collect timeout (optional)
    // returns: instance of DataCollector
  ) {
    this.isDone = false;
    this.keys = keys;
    this.expected = keys.length;
    this.count = 0;
    this.timeout = timeout;
    this.data = {};
    this.errs = [];
    this.events = {
      error: null,
      timeout: null,
      done: null
    };
    const collector = this;
    if (this.timeout) {
      this.timer = setTimeout(() => {
        const err = new Error('KeyCollector timeout');
        collector.emit('timeout', err, collector.data);
      }, timeout);
    }
  };

  api.util.inherits(api.metasync.KeyCollector, Collector);

  api.metasync.KeyCollector.prototype.collect = function(
    key,
    data
  ) {
    if (this.keys.includes(key)) {
      this.count++;
      if (data instanceof Error) {
        this.errs[key] = data;
        this.emit('error', data, key);
      } else {
        this.data[key] = data;
      }
      if (this.expected === this.count) {
        if (this.timer) clearTimeout(this.timer);
        const errs = this.errs.length ? this.errs : null;
        this.emit('done', errs, this.data);
      }
    }
  };

  api.metasync.KeyCollector.prototype.stop = function() {
  };

  api.metasync.KeyCollector.prototype.pause = function() {
  };

  api.metasync.KeyCollector.prototype.resume = function() {
  };

};
<commit_msg>Change mixin to Object.assign to avoid deopt<commit_after>'use strict';

module.exports = (api) => {

  api.metasync.collect = (
    expected // count or array of string
    // returns: collector functor
  ) => {
    const isCount = typeof(expected) === 'number';
    const isKeys = Array.isArray(expected);
    if (!(isCount || isKeys)) throw new TypeError('Unexpected type');
    let keys = null;
    if (isKeys) {
      keys = new Set(expected);
      expected = expected.length;
    }
    let count = 0;
    let timer = null;
    let done = null;
    let isDistinct = false;
    let isDone = false;
    const data = {};

    const collector = (key, err, value) => {
      if (isDone) return collector;
      if (!isDistinct || !(key in data)) {
        if (!isCount && !keys.has(key)) return;
        count++;
      }
      if (err) return collector.finalize(err, data);
      data[key] = value;
      if (expected === count) {
        if (timer) clearTimeout(timer);
        collector.finalize(null, data);
      }
      return collector;
    };

    const methods = {
      pick: (key, value) => collector(key, null, value),
      fail: (key, err) => collector(key, err),

      take: (key, fn, ...args) => {
        fn(...args, (err, data) => collector(key, err, data));
        return collector;
      },

      timeout: (msec) => {
        if (msec) {
          timer = setTimeout(() => {
            const err = new Error('Collector timeout');
            collector.finalize(err, data);
          }, msec);
          timer.unref();
        }
        return collector;
      },

      done: (callback) => {
        done = callback;
        return collector;
      },

      finalize: (err, data) => {
        if (isDone) return collector;
        isDone = true;
        if (done) done(err, data);
        return collector;
      },

      distinct: (value = true) => {
        isDistinct = value;
        return collector;
      }
    };

    Object.assign(collector, methods);

    return collector;
  };

  function Collector() {}

  Collector.prototype.on = function(
    // Collector events:
    eventName,
    listener // handler function
    // on('error', function(err, key))
    // on('timeout', function(err, data))
    // on('done', function(errs, data))
  ) {
    if (eventName in this.events) {
      this.events[eventName] = listener;
    }
  };

  Collector.prototype.emit = function(
    // Emit Collector events
    eventName, err, data
  ) {
    const event = this.events[eventName];
    if (event) event(err, data);
  };

  api.metasync.DataCollector = function(
    expected, // number of collect() calls expected
    timeout // collect timeout (optional)
    // returns: instance of DataCollector
  ) {
    this.expected = expected;
    this.timeout = timeout;
    this.count = 0;
    this.data = {};
    this.errs = [];
    this.events = {
      error: null,
      timeout: null,
      done: null
    };
    const collector = this;
    if (this.timeout) {
      this.timer = setTimeout(() => {
        const err = new Error('DataCollector timeout');
        collector.emit('timeout', err, collector.data);
      }, timeout);
    }
  };

  api.util.inherits(api.metasync.DataCollector, Collector);

  api.metasync.DataCollector.prototype.collect = function(
    // Push data to collector
    key, // key in result data
    data // value or error instance
  ) {
    this.count++;
    if (data instanceof Error) {
      this.errs[key] = data;
      this.emit('error', data, key);
    } else {
      this.data[key] = data;
    }
    if (this.expected === this.count) {
      if (this.timer) clearTimeout(this.timer);
      const errs = this.errs.length ? this.errs : null;
      this.emit('done', errs, this.data);
    }
  };

  api.metasync.KeyCollector = function(
    // Key Collector
    keys, // array of keys, example: ['config', 'users', 'cities']
    timeout // collect timeout (optional)
    // returns: instance of DataCollector
  ) {
    this.isDone = false;
    this.keys = keys;
    this.expected = keys.length;
    this.count = 0;
    this.timeout = timeout;
    this.data = {};
    this.errs = [];
    this.events = {
      error: null,
      timeout: null,
      done: null
    };
    const collector = this;
    if (this.timeout) {
      this.timer = setTimeout(() => {
        const err = new Error('KeyCollector timeout');
        collector.emit('timeout', err, collector.data);
      }, timeout);
    }
  };

  api.util.inherits(api.metasync.KeyCollector, Collector);

  api.metasync.KeyCollector.prototype.collect = function(
    key,
    data
  ) {
    if (this.keys.includes(key)) {
      this.count++;
      if (data instanceof Error) {
        this.errs[key] = data;
        this.emit('error', data, key);
      } else {
        this.data[key] = data;
      }
      if (this.expected === this.count) {
        if (this.timer) clearTimeout(this.timer);
        const errs = this.errs.length ? this.errs : null;
        this.emit('done', errs, this.data);
      }
    }
  };

  api.metasync.KeyCollector.prototype.stop = function() {
  };

  api.metasync.KeyCollector.prototype.pause = function() {
  };

  api.metasync.KeyCollector.prototype.resume = function() {
  };

};
<|endoftext|>