<commit_before>import Ember from 'ember';
import DS from 'ember-data';

export default DS.RESTSerializer.extend({

  primaryKey: 'objectId',

  extractArray(store, primaryType, payload) {
    var namespacedPayload = {};
    namespacedPayload[Ember.String.pluralize(primaryType.typeKey)] = payload.results;

    return this._super(store, primaryType, namespacedPayload);
  },

  extractSingle(store, primaryType, payload, recordId) {
    var namespacedPayload = {};
    namespacedPayload[primaryType.typeKey] = payload; // this.normalize(primaryType, payload);

    return this._super(store, primaryType, namespacedPayload, recordId);
  },

  typeForRoot(key) {
    return Ember.String.dasherize(Ember.String.singularize(key));
  },

  /**
  * Because Parse only returns the updatedAt/createdAt values on updates
  * we have to intercept it here to assure that the adapter knows which
  * record ID we are dealing with (using the primaryKey).
  */
  extract(store, type, payload, id, requestType) {
    if (id !== null && ('updateRecord' === requestType || 'deleteRecord' === requestType)) {
      payload[this.get('primaryKey')] = id;
    }

    return this._super(store, type, payload, id, requestType);
  },

  /**
  * Extracts count from the payload so that you can get the total number
  * of records in Parse if you're using skip and limit.
  */
  extractMeta(store, type, payload) {
    if (payload && payload.count) {
      store.metaForType(type, {count: payload.count});
      delete payload.count;
    }
  },

  /**
  * Special handling for the Date objects inside the properties of
  * Parse responses.
  */
  normalizeAttributes(type, hash) {
    type.eachAttribute(function(key, meta) {
      if ('date' === meta.type && 'object' === Ember.typeOf(hash[key]) && hash[key].iso) {
        hash[key] = hash[key].iso; //new Date(hash[key].iso).toISOString();
      }
    });

    this._super(type, hash);
  },

  /*
   * Here we set the links property to a serialized version
   * of key and className. This info will be passed to adapter.findHasMany
   * where we can deserialize to create the needed Parse query.
   */
  normalizeRelationships: function(type, hash) {
    if (this.keyForRelationship) {
      type.eachRelationship(function(key, relationship) {
        if (hash[key] && 'belongsTo' === relationship.kind) {
          hash[key] = hash[key].objectId;
        }

        if (hash[key] && 'hasMany' === relationship.kind) {
          hash.links = {};
          hash.links[key] = JSON.stringify({key: key, className: hash[key].className});
          delete hash[key].__type;
          delete hash[key].className;
          hash[key] = [];
        }
      }, this);
    }
  },

  serializeIntoHash(hash, type, record, options) {
    Ember.merge(hash, this.serialize(record, options));
  },

  serializeAttribute(record, json, key, attribute) {
    // These are Parse reserved properties and we won't send them.
    if ('createdAt' === key ||
        'updatedAt' === key ||
        'emailVerified' === key ||
        'sessionToken' === key
    ) {
      delete json[key];

    } else {
      this._super(record, json, key, attribute);
    }
  },

  serializeBelongsTo(record, json, relationship) {
    var key = relationship.key,
        belongsTo = record.get(key);

    if (belongsTo) {
      // @TODO: Perhaps this is working around a bug in Ember-Data? Why should
      // promises be returned here.
      if (belongsTo instanceof DS.PromiseObject) {
        if (!belongsTo.get('isFulfilled')) {
          throw new Error('belongsTo values *must* be fulfilled before attempting to serialize them');
        }

        belongsTo = belongsTo.get('content');
      }

      json[key] = {
        '__type': 'Pointer',
        'className': this.parseClassName(belongsTo.constructor.typeKey),
        'objectId': belongsTo.get('id')
      };
    }
  },

  parseClassName(key) {
    if ('User' === key) {
      return '_User';

    } else {
      return Ember.String.capitalize(Ember.String.camelize(key));
    }
  },

  serializeHasMany(record, json, relationship) {
    var key = relationship.key,
        hasMany = record.get( key ),
        options = relationship.options;

    if (hasMany && hasMany.get('length') > 0) {
      json[key] = {'objects': []};

      if (options.relation) {
        json[key].__op = 'AddRelation';
      }

      if (options.array) {
        json[key].__op = 'AddUnique';
      }

      hasMany.forEach(function(child) {
        json[key].objects.push({
          '__type': 'Pointer',
          'className': child.parseClassName(),
          'objectId': child.get('id')
        });
      });

      if (hasMany._deletedItems && hasMany._deletedItems.length) {
        if (options.relation) {
          var addOperation = json[key],
              deleteOperation = {'__op': 'RemoveRelation', 'objects': []};

          hasMany._deletedItems.forEach(function(item) {
            deleteOperation.objects.push({
              '__type': 'Pointer',
              'className': item.type,
              'objectId': item.id
            });
          });

          json[key] = {'__op': 'Batch', 'ops': [addOperation, deleteOperation]};
        }

        if (options.array) {
          json[key].deleteds = {'__op': 'Remove', 'objects': []};

          hasMany._deletedItems.forEach(function(item) {
            json[key].deleteds.objects.push({
              '__type': 'Pointer',
              'className': item.type,
              'objectId': item.id
            });
          });
        }
      }

    } else {
      json[key] = [];
    }
  }

});
<commit_msg>Minor style change<commit_after>import Ember from 'ember';
import DS from 'ember-data';

export default DS.RESTSerializer.extend({

  primaryKey: 'objectId',

  extractArray(store, primaryType, payload) {
    var namespacedPayload = {};
    namespacedPayload[Ember.String.pluralize(primaryType.typeKey)] = payload.results;

    return this._super(store, primaryType, namespacedPayload);
  },

  extractSingle(store, primaryType, payload, recordId) {
    var namespacedPayload = {};
    namespacedPayload[primaryType.typeKey] = payload; // this.normalize(primaryType, payload);

    return this._super(store, primaryType, namespacedPayload, recordId);
  },

  typeForRoot(key) {
    return Ember.String.dasherize(Ember.String.singularize(key));
  },

  /**
  * Because Parse only returns the updatedAt/createdAt values on updates
  * we have to intercept it here to assure that the adapter knows which
  * record ID we are dealing with (using the primaryKey).
  */
  extract(store, type, payload, id, requestType) {
    if (id !== null && ('updateRecord' === requestType || 'deleteRecord' === requestType)) {
      payload[this.get('primaryKey')] = id;
    }

    return this._super(store, type, payload, id, requestType);
  },

  /**
  * Extracts count from the payload so that you can get the total number
  * of records in Parse if you're using skip and limit.
  */
  extractMeta(store, type, payload) {
    if (payload && payload.count) {
      store.metaForType(type, {count: payload.count});
      delete payload.count;
    }
  },

  /**
  * Special handling for the Date objects inside the properties of
  * Parse responses.
  */
  normalizeAttributes(type, hash) {
    type.eachAttribute(function(key, meta) {
      if ('date' === meta.type && 'object' === Ember.typeOf(hash[key]) && hash[key].iso) {
        hash[key] = hash[key].iso; //new Date(hash[key].iso).toISOString();
      }
    });

    this._super(type, hash);
  },

  /*
   * Here we set the links property to a serialized version
   * of key and className. This info will be passed to adapter.findHasMany
   * where we can deserialize to create the needed Parse query.
   */
  normalizeRelationships: function(type, hash) {
    if (this.keyForRelationship) {
      type.eachRelationship(function(key, relationship) {
        if (hash[key] && 'belongsTo' === relationship.kind) {
          hash[key] = hash[key].objectId;
        }

        if (hash[key] && 'hasMany' === relationship.kind) {
          hash.links = {};
          hash.links[key] = JSON.stringify({
            key: key,
            className: hash[key].className
          });

          delete hash[key].__type;
          delete hash[key].className;
          hash[key] = [];
        }
      }, this);
    }
  },

  serializeIntoHash(hash, type, record, options) {
    Ember.merge(hash, this.serialize(record, options));
  },

  serializeAttribute(record, json, key, attribute) {
    // These are Parse reserved properties and we won't send them.
    if ('createdAt' === key ||
        'updatedAt' === key ||
        'emailVerified' === key ||
        'sessionToken' === key
    ) {
      delete json[key];

    } else {
      this._super(record, json, key, attribute);
    }
  },

  serializeBelongsTo(record, json, relationship) {
    var key = relationship.key,
        belongsTo = record.get(key);

    if (belongsTo) {
      // @TODO: Perhaps this is working around a bug in Ember-Data? Why should
      // promises be returned here.
      if (belongsTo instanceof DS.PromiseObject) {
        if (!belongsTo.get('isFulfilled')) {
          throw new Error('belongsTo values *must* be fulfilled before attempting to serialize them');
        }

        belongsTo = belongsTo.get('content');
      }

      json[key] = {
        '__type': 'Pointer',
        'className': this.parseClassName(belongsTo.constructor.typeKey),
        'objectId': belongsTo.get('id')
      };
    }
  },

  parseClassName(key) {
    if ('User' === key) {
      return '_User';

    } else {
      return Ember.String.capitalize(Ember.String.camelize(key));
    }
  },

  serializeHasMany(record, json, relationship) {
    var key = relationship.key,
        hasMany = record.get( key ),
        options = relationship.options;

    if (hasMany && hasMany.get('length') > 0) {
      json[key] = {'objects': []};

      if (options.relation) {
        json[key].__op = 'AddRelation';
      }

      if (options.array) {
        json[key].__op = 'AddUnique';
      }

      hasMany.forEach(function(child) {
        json[key].objects.push({
          '__type': 'Pointer',
          'className': child.parseClassName(),
          'objectId': child.get('id')
        });
      });

      if (hasMany._deletedItems && hasMany._deletedItems.length) {
        if (options.relation) {
          var addOperation = json[key],
              deleteOperation = {'__op': 'RemoveRelation', 'objects': []};

          hasMany._deletedItems.forEach(function(item) {
            deleteOperation.objects.push({
              '__type': 'Pointer',
              'className': item.type,
              'objectId': item.id
            });
          });

          json[key] = {'__op': 'Batch', 'ops': [addOperation, deleteOperation]};
        }

        if (options.array) {
          json[key].deleteds = {'__op': 'Remove', 'objects': []};

          hasMany._deletedItems.forEach(function(item) {
            json[key].deleteds.objects.push({
              '__type': 'Pointer',
              'className': item.type,
              'objectId': item.id
            });
          });
        }
      }

    } else {
      json[key] = [];
    }
  }

});
<|endoftext|>


<commit_before>function Player(hockey, team, positionId) {
    this.hockey = hockey;
    this.team = team;
    this.position = Player.CONSTANTS.positions[positionId];
    this.bodyRadius = 8;
    this.stickReach = 7;
    this.stickLength = 15;
    this.speed = 1;
    this.layer = team.layer;
    this.location = {
	    x: this.startingPoint('x'),
	    y: this.startingPoint('y')
    };
    this.stickAngle = Math.random() * Math.PI * 2;
    this.selected = false;
    this.rotationDirection = null;
    this.rotationSpeed = 0;
    this.render();
}

Player.prototype.startingPoint = function(axis) {
    // Randomize player starting point
    var random = Math.random() * Math.abs(this.position.boundaries[axis][1] - this.position.boundaries[axis][0]);
    var position = random + this.position.boundaries[axis][0];
    if(this.team.id > 1) {
    	position = this.hockey.rink.size[axis] - Math.min(this.position.boundaries[axis][1], this.position.boundaries[axis][0]) - random;
    }
    return position;
};

Player.prototype.render = function() {
    this.player = {};
    this.player.group = new Kinetic.Group({
        x: this.location.x + this.hockey.rink.offset.x,
        y: this.location.y + this.hockey.rink.offset.y,
        rotation: this.stickAngle
    });

    this.player.body = new Kinetic.Circle({
	    radius: this.bodyRadius,
	    stroke: '#000',
	    fill: (this.team.id == 1)? '#F47940': '#E13A3E'
	});
    this.player.group.add(this.player.body);

	this.player.numeral = new Kinetic.Text({
		fontSize:this.bodyRadius * 2,
		x:-8,
		y:-8,
		width:this.bodyRadius * 2,
		align:'center',
		text:this.position.positionId,
		fill:'black',
		fontStyle: 'bold'
	});
    this.player.group.add(this.player.numeral);

    if(this.position.hasStick) {
        var stickStart = this.bodyRadius / 2 + this.stickReach;
        this.player.stick = new Kinetic.Line({
            points: [[0, (-.5 * this.bodyRadius) - this.stickReach], [0, (-1 * this.stickLength - this.stickReach - .5 * this.bodyRadius)]],
            stroke: '#452200',
            strokeWidth: 3
        });
        this.player.group.add(this.player.stick);
    }

    this.player.boundary = new Kinetic.Line({
	// assumes all skaters can only go straight, you know, like in bubble hockey
	    points: this.team.id == 1? [
		    [this.hockey.rink.offset.x + this.position.boundaries.x[0], this.hockey.rink.offset.y + this.position.boundaries.y[0]],
		    [this.hockey.rink.offset.x + this.position.boundaries.x[1], this.hockey.rink.offset.y + this.position.boundaries.y[1]]
	    ] : [
		    [this.hockey.rink.offset.x + this.hockey.rink.size.x - Math.min(this.position.boundaries.x[1], this.position.boundaries.x[0]),
		    this.hockey.rink.offset.y + this.hockey.rink.size.y - Math.min(this.position.boundaries.y[1], this.position.boundaries.y[0])],
		    [this.hockey.rink.offset.x + this.hockey.rink.size.x - Math.max(this.position.boundaries.x[1], this.position.boundaries.x[0]),
		    this.hockey.rink.offset.y + this.hockey.rink.size.y - Math.max(this.position.boundaries.y[1], this.position.boundaries.y[0])]
	    ],
	    stroke: '#333',
	    opacity: '.3',
	    strokeWidth: 2,
	}
    );

    if(this.team.id == 1) {
        this.player.playerSelector = new PlayerSelector(this);
    }

    this.layer.add(this.player.boundary);
    this.hockey.rink.layer.add(this.player.boundary);
    this.layer.add(this.player.group);
};

Player.prototype.move = function(x,y) {
    var _this = this;
    this.stopMovement();    // One animation movement at a time people
    this.movement = new Kinetic.Animation(
	    function(frame) {
	        _this.advance(x, y);
	    }, _this.layer
    );
    this.movement.start();
};

Player.prototype.rotate = function(rad) {
    // This would be neater as an animation
	this.rotationSpeed = Math.min(Math.abs(rad), 7); // maximum rotation speed of 7 radians ~about 1 full circle
	var newRotation = this.hockey.rink.simplifyRadians(this.player.group.getRotation() + rad);
	this.rotationDirection = (rad > 0)? Player.CONSTANTS.rotationDirection.CLOCKWISE : Player.CONSTANTS.rotationDirection.COUNTER_CLOCKWISE;
	this.player.group.setRotation(newRotation);
	this.stickAngle = newRotation;
};

Player.prototype.detectCollision = function() {
	var collision = {player: this};
	var xOffset = this.location.x - this.hockey.puck.location.x, xDistance = Math.abs(xOffset);
	var yOffset = this.location.y - this.hockey.puck.location.y, yDistance = Math.abs(yOffset);
	if(xDistance < this.bodyRadius && yDistance < this.bodyRadius) {
		// Body Bump
		collision.collisionType = Player.CONSTANTS.collisionTypes.BUMP;
	} else if(xDistance < (this.bodyRadius + this.stickLength + this.stickReach) && yDistance < (this.bodyRadius + this.stickLength + this.stickReach)) {
		// Potential for Shot, depending on stick angle (ignore possibility of puck going between stick & body for now)
		var puckAngle = this.getPuckAngle();
		collision.rotationSpeed = this.rotationSpeed;
		if(Math.abs(puckAngle - this.stickAngle) < Player.CONSTANTS.collisionTolerance) { // If the stick is reasonably close
			collision.collisionType = Player.CONSTANTS.collisionTypes.SHOT;
			if(puckAngle - this.stickAngle < 0) {
				collision.collisionDirection = 'CCW';
			} else {
				collision.collisionDirection = 'CW'; // Puck is CW from Stick
			}
		}
	}
	return collision;
};

Player.prototype.getPuckAngle = function() {
	var xOffset = this.hockey.puck.location.x - this.location.x, xDistance = Math.abs(xOffset);
	var yOffset = this.hockey.puck.location.y - this.location.y, yDistance = Math.abs(yOffset);
	// atan2 is relative to x axis, our stick angle is relative to y axis.  This madness must be fixed -- returning relative to the y axis for now.
	return this.hockey.rink.simplifyRadians(Math.atan2(yOffset, xOffset) + Math.PI / 2);
};

Player.prototype.advance = function(x, y) {
    // This works fine for team 0, but the locations on team 2 are jacked.  need to figure out how to handle drawing dots better, perhaps two boundaries objects for each position, one per team?
    if(this.location.x != x) {
        // Goalie movement
        this.location.x = this.location.x + this.speed;
    }
    if(this.location.y != y) {
        // Vertical Movement of skaters
		this.location.y = this.location.y < y ? (this.location.y + Math.min(this.speed, (y - this.location.y))) : (this.location.y - Math.min(this.speed, (this.location.y - y)));
    }
    // Ensure that we're within the dimensions allowed
    if(this.location.y > this.position.boundaries.y[1]) {
        this.location.y = this.position.boundaries.y[1];
    } else if(this.location.y < this.position.boundaries.y[0]) {
        this.location.y = this.position.boundaries.y[0];
    }
    if( (this.location.x == x || this.location.x == this.position.boundaries.x[0] || this.location.x == this.position.boundaries.x[1]) && (this.location.y == y || this.location.y == this.position.boundaries.y[0] || this.location.y == this.position.boundaries.y[1]) ) {
        this.stopMovement();
        return false;
    }
    this.player.group.setX(this.location.x + this.hockey.rink.offset.x);
    this.player.group.setY(this.location.y + this.hockey.rink.offset.y);
    this.advance(x,y);
};

Player.prototype.select = function() {
    this.team.resetSelectors();
    this.player.playerSelector.icon.setFill('red');
    this.layer.draw();
    this.selected = true;
};

Player.prototype.stopMovement = function() {
    if(this.movement) this.movement.stop();
};

Player.prototype.resetCollisions = function() {
    this.collisionType = null;
};

Player.CONSTANTS = {
    positions: [ {positionId: 1, abbreviation: 'LW', name: 'Left Wing', boundaries : { x: [25, 25], y: [20, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 2, abbreviation: 'RW', name: 'Right Wing', boundaries : { x: [196, 196], y: [20, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 3, abbreviation: 'C', name: 'Center', boundaries: { x:[110, 110], y:[65, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 4, abbreviation: 'LD', name: 'Left Defenseman', boundaries: { x:[67, 67], y:[280, 500]}, hasStick: true, wrapperWidth: 80, wrapperTop: 265, wrapperLeft: -15 },
		{positionId: 5, abbreviation: 'RD', name: 'Right Defenseman', boundaries: { x:[154, 154], y:[280, 500]}, hasStick: true, wrapperWidth: 80, wrapperTop: 265, wrapperLeft: -15 },
		{positionId: 6, abbreviation: 'G', name: 'Goalie', boundaries: {x:[90,130], y:[485, 485]}, hasStick: false }
    ],
    collisionTypes: {
            "SHOT": 1,
            "BUMP": 2
    },
    collisionTolerance : .175, // Basically equiv to the size of the puck

    rotationDirection: {
    	"COUNTER_CLOCKWISE": -1,
    	"CLOCKWISE": 1
    }

};

function PlayerSelector(player) {
    this.player = player;
    this.hockey = player.hockey;
    if(this.player.position && player.position.abbreviation != 'G') {
        this.group = new Kinetic.Group({
            x: this.hockey.rink.offset.x + this.player.position.boundaries.x[0] - 25,
            y: this.hockey.rink.offset.y - 2,
            height:600
        });

        this.wrapper = new Kinetic.Rect({
            width: this.player.position.wrapperWidth,
            height: 260,
            x: this.player.position.wrapperLeft,
            y: this.player.position.wrapperTop,
            stroke: 'black',
            fill: 'blue',
            opacity: 0
        })
        this.group.add(this.wrapper);

        this.icon = new Kinetic.Circle({
            x: 25,
            radius: 15,
            stroke: 'black',
            strokeWidth:2,
            fill: '#ccc'
        });

        var _this = this;
        this.group.on('mouseover touchstart', function() {
            _this.player.select();
        });

        this.group.add(this.icon);

        this.character = new Kinetic.Text({
            fontSize: 15,
            x: 0,
            y: -8,
            width:50,
            align: 'center',
            text: this.player.position.abbreviation,
            fill: 'black',
            fontStyle: 'bold'
        });
        this.group.add(this.character);
        this.player.layer.add(this.group);
    }
};
<commit_msg>Get all of the data we need in the collision objects<commit_after>function Player(hockey, team, positionId) {
    this.hockey = hockey;
    this.team = team;
    this.position = Player.CONSTANTS.positions[positionId];
    this.bodyRadius = 8;
    this.stickReach = 7;
    this.stickLength = 15;
    this.speed = 1;  // "Skating Speed" -- Speed with which the player is capable of moving
    this.layer = team.layer;
    this.location = {
	    x: this.startingPoint('x'),
	    y: this.startingPoint('y')
    };
    this.stickAngle = Math.random() * Math.PI * 2;
    this.selected = false;
    this.rotationDirection = null;
    this.rotationSpeed = 0;
    this.movementSpeed = 0;
    this.render();
}

Player.prototype.startingPoint = function(axis) {
    // Randomize player starting point
    var random = Math.random() * Math.abs(this.position.boundaries[axis][1] - this.position.boundaries[axis][0]);
    var position = random + this.position.boundaries[axis][0];
    if(this.team.id > 1) {
    	position = this.hockey.rink.size[axis] - Math.min(this.position.boundaries[axis][1], this.position.boundaries[axis][0]) - random;
    }
    return position;
};

Player.prototype.render = function() {
    this.player = {};
    this.player.group = new Kinetic.Group({
        x: this.location.x + this.hockey.rink.offset.x,
        y: this.location.y + this.hockey.rink.offset.y,
        rotation: this.stickAngle
    });

    this.player.body = new Kinetic.Circle({
	    radius: this.bodyRadius,
	    stroke: '#000',
	    fill: (this.team.id == 1)? '#F47940': '#E13A3E'
	});
    this.player.group.add(this.player.body);

	this.player.numeral = new Kinetic.Text({
		fontSize:this.bodyRadius * 2,
		x:-8,
		y:-8,
		width:this.bodyRadius * 2,
		align:'center',
		text:this.position.positionId,
		fill:'black',
		fontStyle: 'bold'
	});
    this.player.group.add(this.player.numeral);

    if(this.position.hasStick) {
        var stickStart = this.bodyRadius / 2 + this.stickReach;
        this.player.stick = new Kinetic.Line({
            points: [[0, (-.5 * this.bodyRadius) - this.stickReach], [0, (-1 * this.stickLength - this.stickReach - .5 * this.bodyRadius)]],
            stroke: '#452200',
            strokeWidth: 3
        });
        this.player.group.add(this.player.stick);
    }

    this.player.boundary = new Kinetic.Line({
	// assumes all skaters can only go straight, you know, like in bubble hockey
	    points: this.team.id == 1? [
		    [this.hockey.rink.offset.x + this.position.boundaries.x[0], this.hockey.rink.offset.y + this.position.boundaries.y[0]],
		    [this.hockey.rink.offset.x + this.position.boundaries.x[1], this.hockey.rink.offset.y + this.position.boundaries.y[1]]
	    ] : [
		    [this.hockey.rink.offset.x + this.hockey.rink.size.x - Math.min(this.position.boundaries.x[1], this.position.boundaries.x[0]),
		    this.hockey.rink.offset.y + this.hockey.rink.size.y - Math.min(this.position.boundaries.y[1], this.position.boundaries.y[0])],
		    [this.hockey.rink.offset.x + this.hockey.rink.size.x - Math.max(this.position.boundaries.x[1], this.position.boundaries.x[0]),
		    this.hockey.rink.offset.y + this.hockey.rink.size.y - Math.max(this.position.boundaries.y[1], this.position.boundaries.y[0])]
	    ],
	    stroke: '#333',
	    opacity: '.3',
	    strokeWidth: 2,
	}
    );

    if(this.team.id == 1) {
        this.player.playerSelector = new PlayerSelector(this);
    }

    this.layer.add(this.player.boundary);
    this.hockey.rink.layer.add(this.player.boundary);
    this.layer.add(this.player.group);
};

Player.prototype.move = function(x,y) {
    var _this = this;
    this.stopMovement();    // One animation movement at a time people
    this.movement = new Kinetic.Animation(
	    function(frame) {
	        _this.advance(x, y);
	    }, _this.layer
    );
    this.movement.start();
};

Player.prototype.rotate = function(rad) {
    // This would be neater as an animation
	this.rotationSpeed = Math.min(Math.abs(rad), 7); // maximum rotation speed of 7 radians ~about 1 full circle
	var newRotation = this.hockey.rink.simplifyRadians(this.player.group.getRotation() + rad);
	this.rotationDirection = (rad > 0)? Player.CONSTANTS.rotationDirection.CLOCKWISE : Player.CONSTANTS.rotationDirection.COUNTER_CLOCKWISE;
	this.player.group.setRotation(newRotation);
	this.stickAngle = newRotation;
};

Player.prototype.detectCollision = function() {
	var collision = {player: this, rotationSpeed: this.rotationSpeed, movementSpeed: this.movementSpeed };
	var xOffset = this.location.x - this.hockey.puck.location.x, xDistance = Math.abs(xOffset);
	var yOffset = this.location.y - this.hockey.puck.location.y, yDistance = Math.abs(yOffset);
	if(xDistance < this.bodyRadius && yDistance < this.bodyRadius) {
		// Body Bump
		collision.collisionType = Player.CONSTANTS.collisionTypes.BUMP;
	} else if(xDistance < (this.bodyRadius + this.stickLength + this.stickReach) && yDistance < (this.bodyRadius + this.stickLength + this.stickReach)) {
		// Potential for Shot, depending on stick angle (ignore possibility of puck going between stick & body for now)
		var puckAngle = this.getPuckAngle();
		if(Math.abs(puckAngle - this.stickAngle) < Player.CONSTANTS.collisionTolerance) { // If the stick is reasonably close
			collision.collisionType = Player.CONSTANTS.collisionTypes.SHOT;
			if(puckAngle - this.stickAngle < 0) {
				collision.collisionDirection = 'CCW';
			} else {
				collision.collisionDirection = 'CW'; // Puck is CW from Stick
			}
		}
	}
	this.resetCollisionData();
	return collision;
};

Player.prototype.resetCollisionData = function() {
	// After detecting a player collision, reset all relevant collision data
	this.rotationSpeed = 0;
	this.movementSpeed = 0;
};

Player.prototype.getPuckAngle = function() {
	var xOffset = this.hockey.puck.location.x - this.location.x, xDistance = Math.abs(xOffset);
	var yOffset = this.hockey.puck.location.y - this.location.y, yDistance = Math.abs(yOffset);
	// atan2 is relative to x axis, our stick angle is relative to y axis.  This madness must be fixed -- returning relative to the y axis for now.
	return this.hockey.rink.simplifyRadians(Math.atan2(yOffset, xOffset) + Math.PI / 2);
};

Player.prototype.advance = function(x, y) {
    // This works fine for team 0, but the locations on team 2 are jacked.  need to figure out how to handle drawing dots better, perhaps two boundaries objects for each position, one per team?
    if(this.location.x != x) {
        // Goalie movement
        this.location.x = this.location.x + this.speed;
    }
    if(this.location.y != y) {
        // Vertical Movement of skaters
		this.location.y = this.location.y < y ? (this.location.y + Math.min(this.speed, (y - this.location.y))) : (this.location.y - Math.min(this.speed, (this.location.y - y)));
    }
    // Ensure that we're within the dimensions allowed
    if(this.location.y > this.position.boundaries.y[1]) {
        this.location.y = this.position.boundaries.y[1];
    } else if(this.location.y < this.position.boundaries.y[0]) {
        this.location.y = this.position.boundaries.y[0];
    }
    if( (this.location.x == x || this.location.x == this.position.boundaries.x[0] || this.location.x == this.position.boundaries.x[1]) && (this.location.y == y || this.location.y == this.position.boundaries.y[0] || this.location.y == this.position.boundaries.y[1]) ) {
        this.stopMovement();
        return false;
    }
    this.movementSpeed = this.speed;
    this.player.group.setX(this.location.x + this.hockey.rink.offset.x);
    this.player.group.setY(this.location.y + this.hockey.rink.offset.y);

    // Should we detect collisions for all players here?

    this.advance(x,y);
};

Player.prototype.select = function() {
    this.team.resetSelectors();
    this.player.playerSelector.icon.setFill('red');
    this.layer.draw();
    this.selected = true;
};

Player.prototype.stopMovement = function() {
    if(this.movement) this.movement.stop();
};

Player.prototype.resetCollisions = function() {
    this.collisionType = null;
};

Player.CONSTANTS = {
    positions: [ {positionId: 1, abbreviation: 'LW', name: 'Left Wing', boundaries : { x: [25, 25], y: [20, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 2, abbreviation: 'RW', name: 'Right Wing', boundaries : { x: [196, 196], y: [20, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 3, abbreviation: 'C', name: 'Center', boundaries: { x:[110, 110], y:[65, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 4, abbreviation: 'LD', name: 'Left Defenseman', boundaries: { x:[67, 67], y:[280, 500]}, hasStick: true, wrapperWidth: 80, wrapperTop: 265, wrapperLeft: -15 },
		{positionId: 5, abbreviation: 'RD', name: 'Right Defenseman', boundaries: { x:[154, 154], y:[280, 500]}, hasStick: true, wrapperWidth: 80, wrapperTop: 265, wrapperLeft: -15 },
		{positionId: 6, abbreviation: 'G', name: 'Goalie', boundaries: {x:[90,130], y:[485, 485]}, hasStick: false }
    ],
    collisionTypes: {
            "SHOT": 1,
            "BUMP": 2
    },
    collisionTolerance : .175, // Basically equiv to the size of the puck

    rotationDirection: {
    	"COUNTER_CLOCKWISE": -1,
    	"CLOCKWISE": 1
    }

};

function PlayerSelector(player) {
    this.player = player;
    this.hockey = player.hockey;
    if(this.player.position && player.position.abbreviation != 'G') {
        this.group = new Kinetic.Group({
            x: this.hockey.rink.offset.x + this.player.position.boundaries.x[0] - 25,
            y: this.hockey.rink.offset.y - 2,
            height:600
        });

        this.wrapper = new Kinetic.Rect({
            width: this.player.position.wrapperWidth,
            height: 260,
            x: this.player.position.wrapperLeft,
            y: this.player.position.wrapperTop,
            stroke: 'black',
            fill: 'blue',
            opacity: 0
        })
        this.group.add(this.wrapper);

        this.icon = new Kinetic.Circle({
            x: 25,
            radius: 15,
            stroke: 'black',
            strokeWidth:2,
            fill: '#ccc'
        });

        var _this = this;
        this.group.on('mouseover touchstart', function() {
            _this.player.select();
        });

        this.group.add(this.icon);

        this.character = new Kinetic.Text({
            fontSize: 15,
            x: 0,
            y: -8,
            width:50,
            align: 'center',
            text: this.player.position.abbreviation,
            fill: 'black',
            fontStyle: 'bold'
        });
        this.group.add(this.character);
        this.player.layer.add(this.group);
    }
};
<|endoftext|>


<commit_before>/**
	The MIT License (MIT)

	Copyright (c) 2014 MyChannel-Apps.de

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.

	@author		Adrian Preuß <Bizarrus>, Christoph Kühl <djchrisnet>
	@docs		http://www.mychannel-apps.de/documentation/core/cronjob
*/

var Cron = (new function Cron() {
	var _cronjobs	= [];
	var _watcher;
	var _offlineCheck = false;

	this.init = function init() {
		if(_watcher != undefined) {
			clearInterval(_watcher);
		}

		_watcher	= setInterval(this.run, 500);
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_enableOfflineRunCheck
	*/
	this.enableOfflineRunCheck = function enableOfflineRunCheck() {
		_offlineCheck = true;
	};

	this.checkOfflineRun = function checkOfflineRun(job) {
		if(job.getLastCheck() == 0) {
			return;
		}

		while(new Date().getTime() >= job.getLastCheck()) {
			var time = new Date(job.getLastCheck()+500);
			job.setLastCheck(time);

			if((time.getTime() - job.getLastRun() > 60000) && this.match(job.getMinutes(), time.getMinutes())&& this.match(job.getHours(), time.getHours()) && this.match(job.getDate(), time.getDate()) && this.match(job.getMonth(), time.getMonth()) && this.match(job.getDay(), time.getDay())) {
				job.run(time);
				return;
			}
		}
	};

	this.run = function run() {
		var time = new Date();

		_cronjobs.each(function CronjobsEach(job) {
			if(!job.isRunning()) {
				return;
			}

			if((time.getTime() - job.getLastRun() > 60000) && this.match(job.getMinutes(), time.getMinutes()) && this.match(job.getHours(), time.getHours()) && this.match(job.getDate(), time.getDate()) && this.match(job.getMonth(), time.getMonth()) && this.match(job.getDay(), time.getDay())) {
				job.run(time);
			}
		});
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_add
	*/
	this.add = function add(cronjob) {
		_cronjobs.push(cronjob);

		if(_offlineCheck) {
			this.checkOfflineRun(cronjob);
		}
	};

	this.match = function match(a, b) {
		var index	= 0;
		var length	= a.length;

		for(; index < length; ++index) {
			var c = a[index];

			if(c[0] === -1 || (b >= c[0] && b <= c[1])) {
				var b0 = b - c[0]; // make the modulo start from 1st miniture of from matched time, not first minute of from 0 minutes
				//WHAT THE FUCK IS b0 ?!?!?!?!?! BULLSHIT ..|..

				if(c[2] === -1 || b === 0 || b % c[2] === 0) {
					return true;
				}
			}
		}

		return  false;
	};

	this.parse = function parse(entry) {
		return entry.split(',').map(function ParseEntryEach(index) {
			var z = index.split('/');
			var x = z[0].split('-');

			if(x[0] == '*') {
				x[0] = -1;
			}

			if(x.length == 1) {
				x.push(x[0]);
			}

			x[2] = z.length === 1 ? -1 : z[1];
			x[0] = parseInt(x[0]); // 0 - from
			x[1] = parseInt(x[1]); // 1 - to
			x[2] = parseInt(x[2]); // 2 modulus

			return x;
		});
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_saveData
	*/
	this.saveData = function saveData() {
		_cronjobs.each(function CronjobsEach(cron) {
			cron.save();
		});
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_onShutdown
	*/
	this.onShutdown = function onShutdown() {
		_cronjobs.each(function CronjobsEach(cron) {
			cron.onShutdown();
		});

		if(_watcher != undefined) {
			clearInterval(_watcher);
		}
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_toString
	*/
	this.toString = function toString() {
		return '[KFramework Cron]';
	};

	this.init();
}());

function Cronjob(name, cycle, callback) {
	var _name		= '';
	var _cycle		= '* * * * *';
	var _cycle_data	= [];
	var _is_running	= false;
	var _callback	= function(time) { /* Override Me */ };
	var _last_run	= undefined;
	var _last_check = undefined;
	var _time_data	= {
		minute:	'*',
		hour:	'*',
		date:	'*',
		month:	'*',
		day:	'*'
	};

	function Cronjob(instance, name, cycle, callback) {
		_name			= name;
		_callback		= callback;

		instance.changeCycle(cycle);

		if(DB == undefined) {
			_crondb		= KnuddelsServer.getPersistence().getObject('_cron_' + _name, {run:0, check:0});
		} else {
			_crondb		= DB.load('_cron_' + _name, {run:0, check:0});
		}

		_last_run		= new Date(parseInt(_crondb.run));
		_last_check		= new Date(parseInt(_crondb.check));

		Cron.add(instance);
		instance.start();
	}

	/*
		@ToDo add to docs: change the cycle on runtime
	*/
	this.changeCycle = function changeCycle(cycle) {
		_cycle			= cycle;
		_cycle_data		= _cycle.match(/^([0-9,\-\/]+|\*{1}|\*{1}\/[0-9]+)\s+([0-9,\-\/]+|\*{1}|\*{1}\/[0-9]+)\s+([0-9,\-\/]+|\*{1}|\*{1}\/[0-9]+)\s+([0-9,\-\/]+|\*{1}|\*{1}\/[0-9]+)\s+([0-9,\-\/]+|\*{1}|\*{1}\/[0-9]+)\s*$/);
		_time_data		= {
			minute:	Cron.parse(_cycle_data[1]),
			hour:	Cron.parse(_cycle_data[2]),
			date:	Cron.parse(_cycle_data[3]),
			month:	Cron.parse(_cycle_data[4]),
			day:	Cron.parse(_cycle_data[5])
		};
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_getLastRun
	*/
	this.getLastRun = function getLastRun() {
		return _last_run.getTime();
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_getName
	*/
	this.getName = function getName() {
		return _name;
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_getLastCheck
	*/
	this.getLastCheck = function getLastCheck() {
		return _last_check.getTime();
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_SetLastCheck
	*/
	this.setLastCheck = function setLastCheck(time) {
		_last_check = time;
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_isRunning
	*/
	this.isRunning = function isRunning() {
		return _is_running;
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_start
	*/
	this.start = function start() {
		_is_running	= true;
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_stop
	*/
	this.stop = function stop() {
		_is_running	= false;
	};

	this.run = function run(time) {
		_last_check	= time;
		_last_run	= time;
		_callback(time);
	};

	this.getMinutes = function getMinutes() {
		return _time_data.minute;
	};

	this.getHours = function getHours() {
		return _time_data.hour;
	};

	this.getDate = function getDate() {
		return _time_data.date;
	};

	this.getMonth = function getMonth() {
		return _time_data.month;
	};

	this.getDay = function getDay() {
		return _time_data.day;
	};

	this.save = function save() {
		if(DB == undefined) {
			KnuddelsServer.getPersistence().setObject('_cron_' + _name, {
				run:	this.getLastRun(),
				check:	this.getLastCheck()
			});
			return;
		}

		DB.save('_cron_' + _name, {
			run:	this.getLastRun(),
			check:	this.getLastCheck()
		});
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_onShutdown
	*/
	this.onShutdown = function onShutdown() {
		this.stop();
		this.save();
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cronjob_toString
	*/
	this.toString = function toString() {
		return '[KFramework Cronjob]';
	};

	Cronjob(this, name, cycle, callback);
}
<commit_msg>fix again<commit_after>/**
	The MIT License (MIT)

	Copyright (c) 2014 MyChannel-Apps.de

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.

	@author		Adrian Preuß <Bizarrus>, Christoph Kühl <djchrisnet>
	@docs		http://www.mychannel-apps.de/documentation/core/cronjob
*/

var Cron = (new function Cron() {
	var _cronjobs	= [];
	var _watcher;
	var _offlineCheck = false;

	this.init = function init() {
		if(_watcher != undefined) {
			clearInterval(_watcher);
		}

		_watcher	= setInterval(this.run, 500);
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_enableOfflineRunCheck
	*/
	this.enableOfflineRunCheck = function enableOfflineRunCheck() {
		_offlineCheck = true;
	};

	this.checkOfflineRun = function checkOfflineRun(job) {
		if(job.getLastCheck() == 0) {
			return;
		}

		while(new Date().getTime() >= job.getLastCheck()) {
			var time = new Date(job.getLastCheck()+500);
			job.setLastCheck(time);

			if((time.getTime() - job.getLastRun() > 60000) && this.checkMatch(job.getMinutes(), time.getMinutes())&& this.checkMatch(job.getHours(), time.getHours()) && this.checkMatch(job.getDate(), time.getDate()) && this.checkMatch(job.getMonth(), time.getMonth()) && this.checkMatch(job.getDay(), time.getDay())) {
				job.run(time);
				return;
			}
		}
	};

	this.run = function run() {
		var time = new Date();

		_cronjobs.each(function CronjobsEach(job) {
			if(!job.isRunning()) {
				return;
			}

			if((time.getTime() - job.getLastRun() > 60000) && this.checkMatch(job.getMinutes(), time.getMinutes()) && this.checkMatch(job.getHours(), time.getHours()) && this.checkMatch(job.getDate(), time.getDate()) && this.checkMatch(job.getMonth(), time.getMonth()) && this.checkMatch(job.getDay(), time.getDay())) {
				job.run(time);
			}
		});
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_add
	*/
	this.add = function add(cronjob) {
		_cronjobs.push(cronjob);

		if(_offlineCheck) {
			this.checkOfflineRun(cronjob);
		}
	};

	this.checkMatch = function checkMatch(a, b) {
		var index	= 0;
		var length	= a.length;

		for(; index < length; ++index) {
			var c = a[index];

			if(c[0] === -1 || (b >= c[0] && b <= c[1])) {
				var b0 = b - c[0]; // make the modulo start from 1st miniture of from matched time, not first minute of from 0 minutes
				//WHAT THE FUCK IS b0 ?!?!?!?!?! BULLSHIT ..|..

				if(c[2] === -1 || b === 0 || b % c[2] === 0) {
					return true;
				}
			}
		}

		return  false;
	};

	this.parse = function parse(entry) {
		return entry.split(',').map(function ParseEntryEach(index) {
			var z = index.split('/');
			var x = z[0].split('-');

			if(x[0] == '*') {
				x[0] = -1;
			}

			if(x.length == 1) {
				x.push(x[0]);
			}

			x[2] = z.length === 1 ? -1 : z[1];
			x[0] = parseInt(x[0]); // 0 - from
			x[1] = parseInt(x[1]); // 1 - to
			x[2] = parseInt(x[2]); // 2 modulus

			return x;
		});
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_saveData
	*/
	this.saveData = function saveData() {
		_cronjobs.each(function CronjobsEach(cron) {
			cron.save();
		});
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_onShutdown
	*/
	this.onShutdown = function onShutdown() {
		_cronjobs.each(function CronjobsEach(cron) {
			cron.onShutdown();
		});

		if(_watcher != undefined) {
			clearInterval(_watcher);
		}
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_toString
	*/
	this.toString = function toString() {
		return '[KFramework Cron]';
	};

	this.init();
}());

function Cronjob(name, cycle, callback) {
	var _name		= '';
	var _cycle		= '* * * * *';
	var _cycle_data	= [];
	var _is_running	= false;
	var _callback	= function(time) { /* Override Me */ };
	var _last_run	= undefined;
	var _last_check = undefined;
	var _time_data	= {
		minute:	'*',
		hour:	'*',
		date:	'*',
		month:	'*',
		day:	'*'
	};

	function Cronjob(instance, name, cycle, callback) {
		_name			= name;
		_callback		= callback;

		instance.changeCycle(cycle);

		if(DB == undefined) {
			_crondb		= KnuddelsServer.getPersistence().getObject('_cron_' + _name, {run:0, check:0});
		} else {
			_crondb		= DB.load('_cron_' + _name, {run:0, check:0});
		}

		_last_run		= new Date(parseInt(_crondb.run));
		_last_check		= new Date(parseInt(_crondb.check));

		Cron.add(instance);
		instance.start();
	}

	/*
		@ToDo add to docs: change the cycle on runtime
	*/
	this.changeCycle = function changeCycle(cycle) {
		_cycle			= cycle;
		_cycle_data		= _cycle.match(/^([0-9,\-\/]+|\*{1}|\*{1}\/[0-9]+)\s+([0-9,\-\/]+|\*{1}|\*{1}\/[0-9]+)\s+([0-9,\-\/]+|\*{1}|\*{1}\/[0-9]+)\s+([0-9,\-\/]+|\*{1}|\*{1}\/[0-9]+)\s+([0-9,\-\/]+|\*{1}|\*{1}\/[0-9]+)\s*$/);
		_time_data		= {
			minute:	Cron.parse(_cycle_data[1]),
			hour:	Cron.parse(_cycle_data[2]),
			date:	Cron.parse(_cycle_data[3]),
			month:	Cron.parse(_cycle_data[4]),
			day:	Cron.parse(_cycle_data[5])
		};
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_getLastRun
	*/
	this.getLastRun = function getLastRun() {
		return _last_run.getTime();
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_getName
	*/
	this.getName = function getName() {
		return _name;
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_getLastCheck
	*/
	this.getLastCheck = function getLastCheck() {
		return _last_check.getTime();
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_SetLastCheck
	*/
	this.setLastCheck = function setLastCheck(time) {
		_last_check = time;
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_isRunning
	*/
	this.isRunning = function isRunning() {
		return _is_running;
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_start
	*/
	this.start = function start() {
		_is_running	= true;
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_stop
	*/
	this.stop = function stop() {
		_is_running	= false;
	};

	this.run = function run(time) {
		_last_check	= time;
		_last_run	= time;
		_callback(time);
	};

	this.getMinutes = function getMinutes() {
		return _time_data.minute;
	};

	this.getHours = function getHours() {
		return _time_data.hour;
	};

	this.getDate = function getDate() {
		return _time_data.date;
	};

	this.getMonth = function getMonth() {
		return _time_data.month;
	};

	this.getDay = function getDay() {
		return _time_data.day;
	};

	this.save = function save() {
		if(DB == undefined) {
			KnuddelsServer.getPersistence().setObject('_cron_' + _name, {
				run:	this.getLastRun(),
				check:	this.getLastCheck()
			});
			return;
		}

		DB.save('_cron_' + _name, {
			run:	this.getLastRun(),
			check:	this.getLastCheck()
		});
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cron_onShutdown
	*/
	this.onShutdown = function onShutdown() {
		this.stop();
		this.save();
	};

	/*
		@docs	http://www.mychannel-apps.de/documentation/Cronjob_toString
	*/
	this.toString = function toString() {
		return '[KFramework Cronjob]';
	};

	Cronjob(this, name, cycle, callback);
}
<|endoftext|>


<commit_before>/**
 * onlylemi
 *
 * @author: only乐秘
 * @time: 2016-02-24 2:54
 */

$(document).ready(function() {

    /* set https */
    if (location.hostname == "onlylemi.github.io") {
        var a = location.href;
        var b = a.substring(5);
        a = a.substring(0, 5);
        if (a == "http:") {
            location.href = "https:" + b;
        }
    }

    /* nprogress */
    $('body').show();
    NProgress.start();
    setTimeout(function() {
        NProgress.done();
    }, 300);

    setTimeout(function() {
        $(".wrapper-masthead").css({
    	'border-top': '4px solid #262b3b'
    });
    }, 600);

    /* sidebar-right */
    $(".js-scroll-top").click(function() {
        $("html,body").animate({ scrollTop: $("#wrapper-masthead").offset().top }, 1000)
    });
    $(".js-scroll-comment").click(function() {
        $("html,body").animate({ scrollTop: $("#comments").offset().top }, 1000)
    });
    $(".js-scroll-down").click(function() {
        $("html,body").animate({ scrollTop: $("#wrapper-footer").offset().top }, 1000)
    });

    /* contents */
    $('#toc').toc({
        title: '',
        minimumHeaders: 0,
        listType: 'ul',
        isEncode: false
    });

    /* post-contents */
    var show_contents = false;
    $(".contents-btn").click(function() {
        if (show_contents) {
            $(".post-contents").css({
                'visibility': 'hidden'
            });
        } else {
            $(".post-contents").css({
                'visibility': 'visible'
            });
        }
        show_contents = !show_contents;
    });

    $(".contents-close").click(function() {
        $(".post-contents").css({
            'visibility': 'hidden'
        });
        show_contents = !show_contents;
    });

    /* 滚动监听 */

    /* search */
    var time1 = 0;
    var show = false;
    var names = new Array(); //文章名字等
    var urls = new Array(); //文章地址
    $(document).keyup(function(e) {
        var time2 = new Date().getTime();
        if (e.keyCode == 17) {
            var gap = time2 - time1;
            time1 = time2;
            if (gap < 500) {
                if (show) {
                    $('.search-dialog').modal('hide')
                    show = false;
                } else {
                    $('.search-dialog').modal('show')
                    show = true;
                    $("#search-content").val("");
                    window.setTimeout("$('#search-content').focus();", 500);
                }
                time1 = 0;
            }
        } else if (e.keyCode == 27) {
            $('.search-dialog').modal('hide')
            show = false;
            time1 = 0;
        }
    });

    $("#search-content").keyup(function(e) {
        var time2 = new Date().getTime();
        if (window.event.keyCode == 17) {
            var gap = time2 - time1;
            time1 = time2;
            if (gap < 500) {
                if (show) {
                    $('.search-dialog').modal('hide');
                    show = false;
                } else {
                    $('.search-dialog').modal('show')
                    $("#search-content").val("");
                    window.setTimeout("$('#search-content').focus();", 500);
                    show = true;
                }
                time1 = 0;
            }
        }
    });

    $('#search-dialog').on('hidden.bs.modal', function(e) {
        show = false;
    })

    $(".search-btn").click(function() {
        $("#search-content").val("");
        /*$('#search-content').focus();*/
        window.setTimeout("$('#search-content').focus();", 500);
    });

    $.getJSON("/assets/search.json").done(function(data) {
        if (data.code == 0) {
            for (var index in data.data) {
                var item = data.data[index];
                names.push(item.title);
                urls.push(item.url);
            }

            $("#search-content").typeahead({
                source: names,

                afterSelect: function(item) {
                    $('.search-dialog').modal('hide')
                    show = false;
                    window.location.href = (urls[names.indexOf(item)]);
                    return item;
                }
            });
        }
    });

});
<commit_msg>update js<commit_after>/**
 * onlylemi
 *
 * @author: only乐秘
 * @time: 2016-02-24 2:54
 */

$(document).ready(function() {

    /* set https */
    if (location.hostname == "onlylemi.github.io") {
        var a = location.href;
        var b = a.substring(5);
        a = a.substring(0, 5);
        if (a == "http:") {
            location.href = "https:" + b;
        }
    }

    /* nprogress */
    $('body').show();
    NProgress.start();
    setTimeout(function() {
        NProgress.done();
    }, 300);

    setTimeout(function() {
        $(".wrapper-masthead").css({
    	'border-top': '4px solid #262b3b'
    });
    }, 600);

    /* sidebar-right */
    $(".js-scroll-top").click(function() {
        $("html,body").animate({ scrollTop: $("#wrapper-masthead").offset().top }, 1000)
    });
    $(".js-scroll-comment").click(function() {
        $("html,body").animate({ scrollTop: $("#comments").offset().top }, 1000)
    });
    $(".js-scroll-down").click(function() {
        $("html,body").animate({ scrollTop: $("#wrapper-footer").offset().top }, 1000)
    });

    /* contents */
    $('#toc').toc({
        title: '',
        minimumHeaders: 0,
        listType: 'ul',
        isEncode: false
    });

    /* post-contents */
    var show_contents = false;
    $(".contents-btn").click(function() {
        if (show_contents) {
            $(".post-contents").css({
                'visibility': 'hidden'
            });
        } else {
            $(".post-contents").css({
                'visibility': 'visible'
            });
        }
        show_contents = !show_contents;
    });

    $(".contents-close").click(function() {
        $(".post-contents").css({
            'visibility': 'hidden'
        });
        show_contents = !show_contents;
    });

    /* 滚动监听 */

    /* search */
    var time1 = 0;
    var show = false;
    var names = new Array(); //文章名字等
    var urls = new Array(); //文章地址
    $(document).keyup(function(e) {
        var time2 = new Date().getTime();
        if (e.keyCode == 17) {
            var gap = time2 - time1;
            time1 = time2;
            if (gap < 500) {
                if (show) {
                    $('.search-dialog').modal('hide')
                    show = false;
                } else {
                    $('.search-dialog').modal('show')
                    show = true;
                    $("#search-content").val("");
                    window.setTimeout("$('#search-content').focus();", 500);
                }
                time1 = 0;
            }
        } else if (e.keyCode == 27) {
            $('.search-dialog').modal('hide')
            show = false;
            time1 = 0;
        }
    });

    $("#search-content").keyup(function(e) {
        var time2 = new Date().getTime();
        if (window.event.keyCode == 17) {
            var gap = time2 - time1;
            time1 = time2;
            if (gap < 500) {
                if (show) {
                    $('.search-dialog').modal('hide');
                    show = false;
                } else {
                    $('.search-dialog').modal('show')
                    $("#search-content").val("");
                    window.setTimeout("$('#search-content').focus();", 500);
                    show = true;
                }
                time1 = 0;
            }
        }
    });

    $('#search-dialog').on('hidden.bs.modal', function(e) {
        show = false;
    })

    $(".search-btn").click(function() {
        $("#search-content").val("");
        /*$('#search-content').focus();*/
        window.setTimeout("$('#search-content').focus();", 500);
    });

    $.getJSON("assets/search.json").done(function(data) {
        if (data.code == 0) {
            for (var index in data.data) {
                var item = data.data[index];
                names.push(item.title);
                urls.push(item.url);
            }

            $("#search-content").typeahead({
                source: names,

                afterSelect: function(item) {
                    $('.search-dialog').modal('hide')
                    show = false;
                    window.location.href = (urls[names.indexOf(item)]);
                    return item;
                }
            });
        }
    });

});
<|endoftext|>


<commit_before>'use strict';

var permDeferred = null;

module.exports = function( Promise )
{
	return {
		isSupported        : isSupported,
		isPermitted        : isPermitted.bind( Promise ),
		requestPermissions : requestPermissions.bind( Promise ),
		create             : create.bind( Promise ),
		send               : send.bind( Promise )
	};
};

function makeDefault( func )
{
	module.exports[ func ] = function( )
	{
		if ( !global.Promise )
			// This is very unlikely to ever happen, since browsers
			// supporting Notifications will most likely support Promises.
			throw new Error( "The platform doesn't natively support promises" );

		var args = [ ].slice.call( arguments );
		return module.exports( global.Promise )[ func ].apply( null, args );
	}
}

// Provide default functions using the built-in Promise if available.
module.exports.isSupported = isSupported;
makeDefault( 'isSupported' );
makeDefault( 'isPermitted' );
makeDefault( 'requestPermissions' );
makeDefault( 'create' );
makeDefault( 'send' );

function defer( Promise )
{
	var deferred = { };
	deferred.promise = new Promise( function( resolve, reject ) {
		deferred.resolve = resolve;
		deferred.reject = reject;
	} );
	return deferred;
}

function isSupported( )
{
	return !!( "Notification" in window );
}

function _convertPermission( perm )
{
	if ( perm === "granted" )
		return true;
	else if ( perm === "denied" )
		return false;
	else
		return null;
}

/**
 * Checks whether notifications are permitted. Asynchronously returns
 * true, false or null if it is unknown and needs to be requested.
 *
 * @return P{Boolean || null}
 */
function isPermitted( )
{
	var Promise = this;

	if ( permDeferred )
		return permDeferred.promise;

	permDeferred = defer( Promise );

	permDeferred.resolve( _convertPermission( Notification.permission ) );

	return permDeferred.promise;
}

/**
 * Request permission for notifications. If we already have, this is
 * basically a no-op, so it is safe to just call if you want permission,
 * but not ask in vain.
 *
 * If you *only* want to know, use isPermitted().
 *
 * @return P{Boolean || null}
 */
function requestPermissions( )
{
	var Promise = this;

	function performRequest( )
	{
		permDeferred = defer( Promise );

		try
		{
			Notification.requestPermission( function( permission ) {
				permDeferred.resolve( _convertPermission( permission ) );
			} );
		}
		catch ( err )
		{
			permDeferred.reject( err );
		}

		return permDeferred.promise;
	}

	if ( !permDeferred )
		// Check if we already are allowed
		isPermitted.call( Promise );

	return permDeferred.promise.then( function( isPermitted ) {
		if ( isPermitted === true )
			return isPermitted;
		// Unknown or false - request permission
		return performRequest( );
	} );
}

var DEFAULT_TIMEOUT = 4000;

/**
 * Spawns a new notification and returns it. Will automatically close
 * after <timeout> milliseconds, unless timeout is explicitly null. Will
 * default to 4000 milliseconds.
 *
 * @return Notification
 */
function create( title, body, iconUrl, timeout /* = DEFAULT_TIMEOUT */ )
{
	var options = { };
	if ( body )
		options.body = body;
	if ( iconUrl )
		options.iconUrl = body;
	var notification = new Notification( title, options );

	if ( timeout !== null )
	{
		if ( typeof timeout === 'undefined' )
			timeout = DEFAULT_TIMEOUT;
		setTimeout( notification.close.bind( notification ), timeout );
	}

	return notification;
}

/**
 * Same as create(), but returns a promise which is fulfilled when the
 * notification is clicked, or rejected if there is an error.
 * Takes the same arguments as create(), except that if the browser doesn't
 * support the 'close' event, a default timeout is automatically enforced. The
 * returned promise would otherwise risk never to be resolved.
 *
 * @return P{Boolean} Resolves to true if the notification was clicked
 */
function send( title, body, iconUrl, timeout )
{
	var Promise = this;
	var forcedTimeout = undefined;
	if ( !( 'onclose' in Notification.prototype ) && !timeout )
		forcedTimeout = DEFAULT_TIMEOUT;

	var notification = create.call(
		this, title, body, iconUrl, timeout || forcedTimeout );

	function cleanup( )
	{
		notification.removeEventListener( 'click', onClick );
		notification.removeEventListener( 'error', onError );
		notification.removeEventListener( 'close', onClose );
	}

	var deferred = defer( Promise );

	function onClick( )
	{
		deferred.resolve( true );
		cleanup( );
	}

	function onError( event )
	{
		var err = new Error( 'Notification failed' );
		err.event = event;
		deferred.reject( err );
		cleanup( );
	}

	function onClose( )
	{
		deferred.resolve( false );
		cleanup( );
	}

	notification.addEventListener( 'click', onClick );
	notification.addEventListener( 'error', onError );
	notification.addEventListener( 'close', onClose );

	if ( forcedTimeout !== undefined )
		// We have a forced timeout. Resolve promise when it elapses.
		setTimeout( deferred.resolve.bind( null, false ), forcedTimeout );

	return deferred.promise;
}
<commit_msg>Fixed icon url<commit_after>'use strict';

var permDeferred = null;

module.exports = function( Promise )
{
	return {
		isSupported        : isSupported,
		isPermitted        : isPermitted.bind( Promise ),
		requestPermissions : requestPermissions.bind( Promise ),
		create             : create.bind( Promise ),
		send               : send.bind( Promise )
	};
};

function makeDefault( func )
{
	module.exports[ func ] = function( )
	{
		if ( !global.Promise )
			// This is very unlikely to ever happen, since browsers
			// supporting Notifications will most likely support Promises.
			throw new Error( "The platform doesn't natively support promises" );

		var args = [ ].slice.call( arguments );
		return module.exports( global.Promise )[ func ].apply( null, args );
	}
}

// Provide default functions using the built-in Promise if available.
module.exports.isSupported = isSupported;
makeDefault( 'isSupported' );
makeDefault( 'isPermitted' );
makeDefault( 'requestPermissions' );
makeDefault( 'create' );
makeDefault( 'send' );

function defer( Promise )
{
	var deferred = { };
	deferred.promise = new Promise( function( resolve, reject ) {
		deferred.resolve = resolve;
		deferred.reject = reject;
	} );
	return deferred;
}

function isSupported( )
{
	return !!( "Notification" in window );
}

function _convertPermission( perm )
{
	if ( perm === "granted" )
		return true;
	else if ( perm === "denied" )
		return false;
	else
		return null;
}

/**
 * Checks whether notifications are permitted. Asynchronously returns
 * true, false or null if it is unknown and needs to be requested.
 *
 * @return P{Boolean || null}
 */
function isPermitted( )
{
	var Promise = this;

	if ( permDeferred )
		return permDeferred.promise;

	permDeferred = defer( Promise );

	permDeferred.resolve( _convertPermission( Notification.permission ) );

	return permDeferred.promise;
}

/**
 * Request permission for notifications. If we already have, this is
 * basically a no-op, so it is safe to just call if you want permission,
 * but not ask in vain.
 *
 * If you *only* want to know, use isPermitted().
 *
 * @return P{Boolean || null}
 */
function requestPermissions( )
{
	var Promise = this;

	function performRequest( )
	{
		permDeferred = defer( Promise );

		try
		{
			Notification.requestPermission( function( permission ) {
				permDeferred.resolve( _convertPermission( permission ) );
			} );
		}
		catch ( err )
		{
			permDeferred.reject( err );
		}

		return permDeferred.promise;
	}

	if ( !permDeferred )
		// Check if we already are allowed
		isPermitted.call( Promise );

	return permDeferred.promise.then( function( isPermitted ) {
		if ( isPermitted === true )
			return isPermitted;
		// Unknown or false - request permission
		return performRequest( );
	} );
}

var DEFAULT_TIMEOUT = 4000;

/**
 * Spawns a new notification and returns it. Will automatically close
 * after <timeout> milliseconds, unless timeout is explicitly null. Will
 * default to 4000 milliseconds.
 *
 * @return Notification
 */
function create( title, body, iconUrl, timeout /* = DEFAULT_TIMEOUT */ )
{
	var options = { };
	if ( body )
		options.body = body;
	if ( iconUrl )
		options.icon = iconUrl;
	var notification = new Notification( title, options );

	if ( timeout !== null )
	{
		if ( typeof timeout === 'undefined' )
			timeout = DEFAULT_TIMEOUT;
		setTimeout( notification.close.bind( notification ), timeout );
	}

	return notification;
}

/**
 * Same as create(), but returns a promise which is fulfilled when the
 * notification is clicked, or rejected if there is an error.
 * Takes the same arguments as create(), except that if the browser doesn't
 * support the 'close' event, a default timeout is automatically enforced. The
 * returned promise would otherwise risk never to be resolved.
 *
 * @return P{Boolean} Resolves to true if the notification was clicked
 */
function send( title, body, iconUrl, timeout )
{
	var Promise = this;
	var forcedTimeout = undefined;
	if ( !( 'onclose' in Notification.prototype ) && !timeout )
		forcedTimeout = DEFAULT_TIMEOUT;

	var notification = create.call(
		this, title, body, iconUrl, timeout || forcedTimeout );

	function cleanup( )
	{
		notification.removeEventListener( 'click', onClick );
		notification.removeEventListener( 'error', onError );
		notification.removeEventListener( 'close', onClose );
	}

	var deferred = defer( Promise );

	function onClick( )
	{
		deferred.resolve( true );
		cleanup( );
	}

	function onError( event )
	{
		var err = new Error( 'Notification failed' );
		err.event = event;
		deferred.reject( err );
		cleanup( );
	}

	function onClose( )
	{
		deferred.resolve( false );
		cleanup( );
	}

	notification.addEventListener( 'click', onClick );
	notification.addEventListener( 'error', onError );
	notification.addEventListener( 'close', onClose );

	if ( forcedTimeout !== undefined )
		// We have a forced timeout. Resolve promise when it elapses.
		setTimeout( deferred.resolve.bind( null, false ), forcedTimeout );

	return deferred.promise;
}
<|endoftext|>


<commit_before>'use strict';

const Q = require('q');
const plugins = require('./plugins');
const config = require('./lib/config');

let co = {
  config: (childPath) => {
    require('./lib/config').setChildPath(childPath);
  },
  registerPlugins: () => {
    // Register the default fallback plugins
    if(config.es) {
      plugins.register(require('./plugins/elasticsearch'));
    }
    if(config.features.keystone) {
      plugins.register(require('./plugins/keystone'));
    }
  },
  initialize: (app, pluginPackages) => {
    if(!app) {
      throw new Error('Needed an Express app when initializing');
    }

    process.env.NODE_ENV = process.env.NODE_ENV || 'development';
    var config = require('./lib/config');

    // After all plugins have initialized, the main server should start
    return plugins.initialize(pluginPackages, app, config).then(() => {
      // Save the pluginPackages for later use
      app.set('co-plugins', pluginPackages);

      const ds = require('./lib/services/documents');

      require('./lib/express')(app);

      app.locals.config = config;
      app.locals.helpers = require('./lib/helpers');

      app.set('siteTitle', config.siteTitle);
      // Trust the X-Forwarded-* headers from the Nginx reverse proxy infront of
      // the app (See http://expressjs.com/api.html#app.set)
      app.set('trust proxy', 'loopback');

      const indecies = Object.keys(config.types).map((type) => {
        return config.types[type].index;
      });
      return ds.count({
        index: indecies,
        query: config.search.baseQuery
      }).then(function(response) {
        console.log('The index is created and has', response.count, 'documents.');
      }, function(err) {
        if(err.status === 404) {
          console.error('Missing document index:', indecies.join(' or '));
        } else {
          console.error('Could not connect to the Elasticsearch:',
                        'Is the elasticsearch service started?');
          process.exit(1);
        }
      })
      .then(() => {
        console.log('Starting up the server');
        // Start server
        app.listen(config.port, config.ip, function() {
          console.log('Express server listening on %s:%d, in %s mode',
                      config.ip, config.port, app.get('env'));
        });
      }, (err) => {
        console.error('Error when starting the app: ', err.stack);
        process.exit(2);
      });
    });
  },
  registerRoutes: (app) => {
    // Register routes for all plugins
    var pluginPackages = app.get('co-plugins') || [];
    // Require routes from each plugin, if they register routes
    pluginPackages.forEach((plugin) => {
      try {
        if(typeof(plugin.registerRoutes) === 'function') {
          plugin.registerRoutes(app);
        }
      } catch (err) {
        console.error('Error registering routes for a plugin: ', err);
      }
    });
    console.log('Setting up routing for the collections-online core');
    // Register the core collections-online routes
    require('./lib/routes')(app);
  },
  registerErrors: (app) => {
    require('./lib/errors')(app);
  }
};

module.exports = co;
<commit_msg>Changes to the way plugins are loaded<commit_after>'use strict';

const Q = require('q');
const plugins = require('./plugins');
const config = require('./lib/config');

let co = {
  config: (childPath) => {
    require('./lib/config').setChildPath(childPath);
  },
  registerPlugins: () => {
    // Register the default fallback plugins
    if(config.es) {
      plugins.register(require('./plugins/elasticsearch'));
    }
    if(config.features.keystone) {
      plugins.register(require('./plugins/keystone'));
    }
  },
  initialize: (app) => {
    if(!app) {
      throw new Error('Needed an Express app when initializing');
    }
    // Setting the NODE_ENV environment if it's not already sat
    // TODO: Consider removing this
    process.env.NODE_ENV = process.env.NODE_ENV || 'development';

    // After all plugins have initialized, the main server should start
    return plugins.initialize(app).then(() => {
      require('./lib/express')(app);

      const ds = require('./lib/services/documents');

      app.locals.config = config;
      app.locals.helpers = require('./lib/helpers');

      app.set('siteTitle', config.siteTitle);
      // Trust the X-Forwarded-* headers from the Nginx reverse proxy infront of
      // the app (See http://expressjs.com/api.html#app.set)
      app.set('trust proxy', 'loopback');

      const indecies = Object.keys(config.types).map((type) => {
        return config.types[type].index;
      });
      return ds.count({
        index: indecies,
        query: config.search.baseQuery
      }).then(function(response) {
        console.log('The index is created and has', response.count, 'documents.');
      }, function(err) {
        if(err.status === 404) {
          console.error('Missing document index:', indecies.join(' or '));
        } else {
          console.error('Could not connect to the Elasticsearch:',
                        'Is the elasticsearch service started?');
          process.exit(1);
        }
      })
      .then(() => {
        console.log('Starting up the server');
        // Start server
        app.listen(config.port, config.ip, function() {
          console.log('Express server listening on %s:%d, in %s mode',
                      config.ip, config.port, app.get('env'));
        });
      }, (err) => {
        console.error('Error when starting the app: ', err.stack);
        process.exit(2);
      });
    });
  },
  registerRoutes: (app) => {
    // Register routes for all plugins
    var pluginPackages = app.get('co-plugins') || [];
    // Require routes from each plugin, if they register routes
    pluginPackages.forEach((plugin) => {
      try {
        if(typeof(plugin.registerRoutes) === 'function') {
          plugin.registerRoutes(app);
        }
      } catch (err) {
        console.error('Error registering routes for a plugin: ', err);
      }
    });
    console.log('Setting up routing for the collections-online core');
    // Register the core collections-online routes
    require('./lib/routes')(app);
  },
  registerErrors: (app) => {
    require('./lib/errors')(app);
  }
};

module.exports = co;
<|endoftext|>


<commit_before>define(function(require, exports, module) {
  'use strict';

  var lodash = require('lodash');

  // Regex classes
  exports.tokensLineBreak = [ '\\r', '\\n', '\\r\\n' ];
  exports.tokensAnd = [ '&&', '\\sAND\\s' ]; // "&&" or "AND"
  exports.tokensOr = [ '\\|\\|', '\\sOR\\s' ]; // "||" or "OR"
  exports.tokensXor = [ '\\^', '\\sXOR\\s' ]; // "^" or "XOR"
  exports.tokensAndOrXor = [].concat(exports.tokensAnd, exports.tokensOr, exports.tokensXor);

  exports.tokensIgnoreLeading = [ '\\s', 'if', 'else', 'elseif', '\\}' ];
  exports.tokensIgnoreTrailing = [ '\\s', '\\{', ';' ];

  exports.findTopLevelParenthesis = function(string) {
    var isEscaped = function isEscaped(index) {
      return (index > 0 && string[index - 1] === '\\' && !isEscaped(index - 1));
    }, isFunctionCall = function(index) {
      // To determine if the parenthesis is a sub-expression or a function call, look at the preceding characters
      var precedingCharacters = string.substring(0, index + 1),
          matchConditional = new RegExp(
            '(?:^|' + exports.tokensAndOrXor.join('|') + ')\\s*\\($'
          , 'i');
      return !matchConditional.test(precedingCharacters);
    };

    var inSingleQuote = false,
      inDoubleQuote = false,
      conditionalDepth = 0,
      functionCallDepth = 0,
      parenthesisPairs = [/*{ start, end }*/],
      i, iParenthesisStart;

    for(i = 0; i < string.length; i++) {
      // If the character is escaped, don't mistake it as syntax
      if(isEscaped(i)) { continue; }

      if(inSingleQuote && string[i] === "'") { // found the closing single quote
        inSingleQuote = false;
      } else if(inDoubleQuote && string[i] === '"') { // found the closing double quote
        inDoubleQuote = false;
      } else if(string[i] === "'") { // found an opening single quote
        inSingleQuote = true;
      } else if(string[i] === '"') { // found an opening double quote
        inDoubleQuote = true;
      } else if(string[i] === ')') { // found a closing parenthesis
        if(functionCallDepth > 0) { // ignore parenthesis within a function call
          functionCallDepth--;
        } else {
          conditionalDepth--;
          if(conditionalDepth === 0) { // found a top-level parenthesis
            parenthesisPairs.push({ start: iParenthesisStart, end: i });
          }
        }
      } else if(string[i] === '(') { // found an opening parenthesis
        if(functionCallDepth > 0 || isFunctionCall(i)) { // ignore parenthesis to start or within a function call
          functionCallDepth++;
        } else {
          if(conditionalDepth === 0) { // found top-level parenthesis
            iParenthesisStart = i;
          }
          conditionalDepth++;
        }
      }
    }

    return parenthesisPairs;
  };

  exports.trimParenthesisPairs = function(string) {
    var process = function(input) {
      input = input.trim();
      if(input[0] === '(' && input[input.length - 1] === ')') {
        input = input.substring(1, input.length - 1);
      }
      return input;
    };

    var processedString = process(string);

    // Continue removing parenthesis pairs until no more remain.
    while(string !== processedString) {
      string = processedString;
      processedString = process(string);
    }

    return string;
  };

  exports.hasMixedOperators = function(operators) {
    var preceedingConditionMismatch = function(e, i, arr) {
      return (i > 0 && (e.toString() !== arr[i - 1].toString()));
    };
    return (operators.length > 1 && operators.some(preceedingConditionMismatch));
  };

  function isCustomObject(o) {
    return !(lodash.isPlainObject(o) || !lodash.isObject(o) ||
             lodash.isArray(o) || lodash.isBoolean(o) ||
             lodash.isDate(o) || lodash.isElement(o) ||
             lodash.isFunction(o) || lodash.isRegExp(o) ||
             lodash.isString(o) || lodash.isNumber(o));
  }

  exports.cloneDeep = function(value) {
    var cloneCallback = function(valueToClone) {
      if(!isCustomObject(valueToClone)) {
          // Allow lodash to handle native clones
          return;
      }

      // For custom objects, handle the clone manually so the prototype is properly set
      return Object.create(valueToClone);
    };

    return lodash.cloneDeep(value, cloneCallback);
  };

});<commit_msg>Moving the isEscaped() and isFunctionCall() functions outside of Utils.findTopLevelParenthesis() to be private functions of the Utils class for reuse in other Utils functions.<commit_after>define(function(require, exports, module) {
  'use strict';

  var lodash = require('lodash');

  // Regex classes
  exports.tokensLineBreak = [ '\\r', '\\n', '\\r\\n' ];
  exports.tokensAnd = [ '&&', '\\sAND\\s' ]; // "&&" or "AND"
  exports.tokensOr = [ '\\|\\|', '\\sOR\\s' ]; // "||" or "OR"
  exports.tokensXor = [ '\\^', '\\sXOR\\s' ]; // "^" or "XOR"
  exports.tokensAndOrXor = [].concat(exports.tokensAnd, exports.tokensOr, exports.tokensXor);

  exports.tokensIgnoreLeading = [ '\\s', 'if', 'else', 'elseif', '\\}' ];
  exports.tokensIgnoreTrailing = [ '\\s', '\\{', ';' ];

  function isEscaped(string, index) {
    return (index > 0 && string[index - 1] === '\\' && !isEscaped(string, index - 1));
  }

  function isFunctionCall(string, index) {
    // To determine if the parenthesis is a sub-expression or a function call, look at the preceding characters
    var precedingCharacters = string.substring(0, index + 1),
        matchConditional = new RegExp(
          '(?:^|' + exports.tokensAndOrXor.join('|') + ')\\s*\\($'
        , 'i');
    return !matchConditional.test(precedingCharacters);
  }

  exports.findTopLevelParenthesis = function(string) {
    var inSingleQuote = false,
      inDoubleQuote = false,
      conditionalDepth = 0,
      functionCallDepth = 0,
      parenthesisPairs = [/*{ start, end }*/],
      i, iParenthesisStart;

    for(i = 0; i < string.length; i++) {
      // If the character is escaped, don't mistake it as syntax
      if(isEscaped(string, i)) { continue; }

      if(inSingleQuote && string[i] === "'") { // found the closing single quote
        inSingleQuote = false;
      } else if(inDoubleQuote && string[i] === '"') { // found the closing double quote
        inDoubleQuote = false;
      } else if(string[i] === "'") { // found an opening single quote
        inSingleQuote = true;
      } else if(string[i] === '"') { // found an opening double quote
        inDoubleQuote = true;
      } else if(string[i] === ')') { // found a closing parenthesis
        if(functionCallDepth > 0) { // ignore parenthesis within a function call
          functionCallDepth--;
        } else {
          conditionalDepth--;
          if(conditionalDepth === 0) { // found a top-level parenthesis
            parenthesisPairs.push({ start: iParenthesisStart, end: i });
          }
        }
      } else if(string[i] === '(') { // found an opening parenthesis
        if(functionCallDepth > 0 || isFunctionCall(string, i)) { // ignore parenthesis to start or within a function call
          functionCallDepth++;
        } else {
          if(conditionalDepth === 0) { // found top-level parenthesis
            iParenthesisStart = i;
          }
          conditionalDepth++;
        }
      }
    }

    return parenthesisPairs;
  };

  exports.trimParenthesisPairs = function(string) {
    var process = function(input) {
      input = input.trim();
      if(input[0] === '(' && input[input.length - 1] === ')') {
        input = input.substring(1, input.length - 1);
      }
      return input;
    };

    var processedString = process(string);

    // Continue removing parenthesis pairs until no more remain.
    while(string !== processedString) {
      string = processedString;
      processedString = process(string);
    }

    return string;
  };

  exports.hasMixedOperators = function(operators) {
    var preceedingConditionMismatch = function(e, i, arr) {
      return (i > 0 && (e.toString() !== arr[i - 1].toString()));
    };
    return (operators.length > 1 && operators.some(preceedingConditionMismatch));
  };

  function isCustomObject(o) {
    return !(lodash.isPlainObject(o) || !lodash.isObject(o) ||
             lodash.isArray(o) || lodash.isBoolean(o) ||
             lodash.isDate(o) || lodash.isElement(o) ||
             lodash.isFunction(o) || lodash.isRegExp(o) ||
             lodash.isString(o) || lodash.isNumber(o));
  }

  exports.cloneDeep = function(value) {
    var cloneCallback = function(valueToClone) {
      if(!isCustomObject(valueToClone)) {
          // Allow lodash to handle native clones
          return;
      }

      // For custom objects, handle the clone manually so the prototype is properly set
      return Object.create(valueToClone);
    };

    return lodash.cloneDeep(value, cloneCallback);
  };

});<|endoftext|>


<commit_before>'use strict';

const { throwError } = require('../../error');
const { parsePatchOp } = require('../parse');

const { PRE_VALIDATORS } = require('./pre_validators');
const { POST_VALIDATORS } = require('./post_validators');

// Validate patch operation has valid syntax, during args.data parsing
const preValidate = function ({
  patchOp,
  commandpath,
  attrName,
  top,
  maxAttrValueSize,
  coll,
  coll: { attributes },
  mInput,
  config: { operators },
}) {
  const attr = attributes[attrName];

  const { type, opVal } = parsePatchOp(patchOp);

  // E.g. if this is not a patch operation
  if (type === undefined) { return; }

  const operator = operators[type];
  const validators = PRE_VALIDATORS;

  validatePatchOp({
    patchOp,
    top,
    operator,
    type,
    opVal,
    attr,
    coll,
    maxAttrValueSize,
    commandpath,
    attrName,
    validators,
    mInput,
  });
};

// Validate patch operation has valid syntax, after model.ATTR resolution
const postValidate = function (input) {
  const validators = POST_VALIDATORS;

  validatePatchOp({ ...input, validators });
};

// Try each validator in order, stopping at the first one that returns an error
const validatePatchOp = function (input) {
  const { commandpath, attrName, patchOp, validators } = input;

  const validatorA = validators
    .find(validator => validator(input) !== undefined);
  if (validatorA === undefined) { return; }

  const error = validatorA(input);

  checkError({ error, commandpath, attrName, patchOp });

  return error;
};

const checkError = function ({ error, commandpath, attrName, patchOp }) {
  const { message, reason } = typeof error === 'string'
    ? { message: error, reason: 'INPUT_VALIDATION' }
    : error;

  if (message !== undefined) { return; }

  const commandpathA = [...commandpath, attrName].join('.');
  const messageA = `At '${commandpathA}', wrong operation '${JSON.stringify(patchOp)}': ${message}`;
  throwError(messageA, { reason });
};

module.exports = {
  preValidate,
  postValidate,
};
<commit_msg>Fix patch validation<commit_after>'use strict';

const { throwError } = require('../../error');
const { parsePatchOp } = require('../parse');

const { PRE_VALIDATORS } = require('./pre_validators');
const { POST_VALIDATORS } = require('./post_validators');

// Validate patch operation has valid syntax, during args.data parsing
const preValidate = function ({
  patchOp,
  commandpath,
  attrName,
  top,
  maxAttrValueSize,
  coll,
  coll: { attributes },
  mInput,
  config: { operators },
}) {
  const attr = attributes[attrName];

  const { type, opVal } = parsePatchOp(patchOp);

  // E.g. if this is not a patch operation
  if (type === undefined) { return; }

  const operator = operators[type];
  const validators = PRE_VALIDATORS;

  validatePatchOp({
    patchOp,
    top,
    operator,
    type,
    opVal,
    attr,
    coll,
    maxAttrValueSize,
    commandpath,
    attrName,
    validators,
    mInput,
  });
};

// Validate patch operation has valid syntax, after model.ATTR resolution
const postValidate = function (input) {
  const validators = POST_VALIDATORS;

  validatePatchOp({ ...input, validators });
};

// Try each validator in order, stopping at the first one that returns an error
const validatePatchOp = function (input) {
  const { commandpath, attrName, patchOp, validators } = input;

  const validatorA = validators
    .find(validator => validator(input) !== undefined);
  if (validatorA === undefined) { return; }

  const error = validatorA(input);

  checkError({ error, commandpath, attrName, patchOp });
};

const checkError = function ({ error, commandpath, attrName, patchOp }) {
  const { message, reason } = typeof error === 'string'
    ? { message: error, reason: 'INPUT_VALIDATION' }
    : error;

  const commandpathA = [...commandpath, attrName].join('.');
  const messageA = `At '${commandpathA}', wrong operation '${JSON.stringify(patchOp)}': ${message}`;
  throwError(messageA, { reason });
};

module.exports = {
  preValidate,
  postValidate,
};
<|endoftext|>


<commit_before>var gulp = require('gulp');
var plugins = require('gulp-load-plugins')();
var runSequence = require('run-sequence');

gulp.task('test', function(done) {
	return runSequence('test-unit', 'test-complexity', 'test-coverage', done);
});

gulp.task('test-complexity', function() {
	return gulp.src(['lib/**/*.js'])
		.pipe(plugins.complexity({
			halstead: [15, 15, 20]
		}));
});

gulp.task('test-unit', function() {
	return gulp.src(['test/**/*.js', '!test/fixture/*.js'])
		.pipe(plugins.mocha());
});

gulp.task('test-cover', function() {
	return gulp.src(['lib/**/*.js'])
		.pipe(plugins.istanbul())
		.pipe(plugins.istanbul.hookRequire());
});

gulp.task('test-coverage', ['test-cover'], function() {
	return gulp.src(['test/**/*.js', '!test/fixture/*.js'])
		.pipe(plugins.mocha())
		.pipe(plugins.istanbul.writeReports());
});<commit_msg>Fix display of coverage report<commit_after>var gulp = require('gulp');
var plugins = require('gulp-load-plugins')();
var runSequence = require('run-sequence');

gulp.task('test', function(done) {
	return runSequence('test-unit', 'test-coverage', 'test-complexity', done);
});

gulp.task('test-complexity', function() {
	return gulp.src(['lib/**/*.js'])
		.pipe(plugins.complexity({
			halstead: [15, 15, 20]
		}));
});

gulp.task('test-unit', function() {
	return gulp.src(['test/**/*.js', '!test/fixture/*.js'])
		.pipe(plugins.mocha());
});

gulp.task('test-cover', function() {
	return gulp.src(['lib/**/*.js'])
		.pipe(plugins.istanbul())
		.pipe(plugins.istanbul.hookRequire());
});

gulp.task('test-coverage', ['test-cover'], function() {
	return gulp.src(['test/**/*.js', '!test/fixture/*.js'])
		.pipe(plugins.mocha())
		.pipe(plugins.istanbul.writeReports());
});<|endoftext|>


<commit_before>import _ from 'lodash'

const CARRY_OVER_PROPERTIES = ['label', 'dependsOn', 'description', 'disabled', 'model', 'placeholder']
const ARRAY_CELL_PROPERTIES = ['autoAdd', 'compact', 'showLabel', 'sortable']

/**
 * Turns rootContainers from V1 view into cells in v2 view
 *
 * @export
 * @param {Object[]} rootContainers Containers to convert
 * @returns {object[]} Array of cells converted from rootContainers
 */
export function generateCells (rootContainers) {
  return _.chain(rootContainers).flattenDeep().map(convertCell).filter().value()
}

/**
 * Converts an complex container cell for an object into a v2 cell
 *
 * @param {object} cell Cell that should display an object
 * @returns {object} Cell converted to v2
 */
function convertObjectCell (cell) {
  return _.chain(cell.rows)
  .map(rowsToCells)
  .assign(_.pick(cell, CARRY_OVER_PROPERTIES))
  .value()
}

/**
 * Converts a container cell that displays an array into a v2 cell
 *
 * @param {object} cell Cell that should display an array
 * @returns {object} Cell converted to v2
 */
function convertArrayCell (cell) {
  const {item} = cell
  const arrayOptions = _.chain(item)
  .pick(ARRAY_CELL_PROPERTIES)
  .assign({
    itemCell: convertCell(item)
  })
  .value()
  return {
    arrayOptions,
    model: cell.model
  }
}

/**
 * Creates custom renderer information for a cell
 *
 * @param {string} renderer Name of the desired renderer
 * @param {object} properties Info for to the custom renderer
 * @returns {object} Custom renderer block
 */
function customRenderer (renderer, properties) {
  return _.assign({
    name: renderer
  }, properties)
}

/**
 * Converts v1 renderer information to v2 for a cell
 *
 * @param {object} cell Cell with a custom renderer
 * @returns {object} Custom renderer block for the given cell
 */
function convertRenderer (cell) {
  const {renderer} = cell
  if (renderer === undefined) {
    return
  }
  switch (renderer) {
    case 'boolean':
    case 'string':
    case 'number':
      return {name: renderer}
    case 'button-group':
    case 'select':
    case 'multi-select':
    case 'textarea':
    default:
      return customRenderer(renderer, cell.properties)
  }
}

/**
 * Creates class name block for v2 cell
 *
 * @param {object} cell Cell to get class names from
 * @returns {object} Class name block for the cell
 */
function grabClassNames (cell) {
  const classNames = _.pickBy({
    cell: cell.className,
    value: cell.inputClassName,
    label: cell.labelClassName
  })
  if (_.size(classNames) > 0) {
    return classNames
  }
}

/**
 * Converts a cell for simple data type (e.g. string, or number)
 *
 * @param {object} cell Cell in v1 to convert to v2
 * @returns {object} Cell converted to v2
 */
function convertBasicCell (cell) {
  return _.pickBy(_.assign({
    renderer: convertRenderer(cell)
  }, _.pick(cell, CARRY_OVER_PROPERTIES)))
}

/**
 * Determines which cell conversion function to use to convert v1 cell to v2 and converts it
 *
 * @export
 * @param {object} cell A v1 cell to convert to v2
 * @returns {object} The cell converted to v2
 */
export function convertCell (cell) {
  let cellConverter
  if (cell.item) {
    cellConverter = convertArrayCell
  } else if (cell.rows) {
    cellConverter = convertObjectCell
  } else {
    cellConverter = convertBasicCell
  }

  return _.pickBy(_.assign({
    extends: cell.container,
    classNames: grabClassNames(cell)
  }, cellConverter(cell)))
}

/**
 * Converts rows of v1 cells to v2 cells. Simplifies the row structure when possible.
 *
 * @param {object[][]} rows A set of rows to convert
 * @returns {object} A v2 cell
 */
function rowsToCells (rows) {
  if (!rows) {
    return null
  }

  const children = rows
    .map((row) => {
      return _.map(row, convertCell)
    })
    .reduce((a, b) => a.concat(b), [])

  return {
    children
  }
}

/**
 * Converts the container declarations in a v1 view to cell definitions in a v2 view
 *
 * @export
 * @param {object[]} containers Set of containers to convert
 * @returns {object} cell definitions for the v2 view.
 */
export function generateCellDefinitions (containers) {
  return _.chain(containers)
  .map(function (container) {
    const {collapsible, rows, id, className, label} = container
    const cell = rowsToCells(rows)

    if (!cell) {
      return null
    }

    if (className !== undefined) {
      cell.classNames = {
        cell: className
      }
    }

    if (collapsible !== undefined) {
      cell.collapsible = collapsible
    }

    if (label !== undefined) {
      cell.label = label
    }

    return [id, cell]
  })
  .without(null)
  .fromPairs()
  .value()
}

/**
 * Converts a bunsen version 1 view into a bunsen/ui schema version 2 view
 *
 * @export
 * @param {any} v1View A v1 view to convert to v2
 * @returns {object} The v2 view generated from the given v1 view
 */
export default function viewV1ToV2 (v1View) {
  const {type} = v1View

  if (v1View.rootContainers.length === 1) {
    delete v1View.rootContainers[0].label
  }

  const cells = generateCells(v1View.rootContainers)

  const cellDefinitions = generateCellDefinitions(v1View.containers)

  return {
    version: '2.0',
    type,
    cells,
    cellDefinitions
  }
}
<commit_msg>Minor cleanup<commit_after>import _ from 'lodash'

const CARRY_OVER_PROPERTIES = ['label', 'dependsOn', 'description', 'disabled', 'model', 'placeholder']
const ARRAY_CELL_PROPERTIES = ['autoAdd', 'compact', 'showLabel', 'sortable']

/**
 * Turns rootContainers from V1 view into cells in v2 view
 *
 * @export
 * @param {Object[]} rootContainers Containers to convert
 * @returns {object[]} Array of cells converted from rootContainers
 */
export function generateCells (rootContainers) {
  return _.chain(rootContainers).flattenDeep().map(convertCell).filter().value()
}

/**
 * Converts an complex container cell for an object into a v2 cell
 *
 * @param {object} cell Cell that should display an object
 * @returns {object} Cell converted to v2
 */
function convertObjectCell (cell) {
  return _.chain(cell.rows)
  .map(rowsToCells)
  .assign(_.pick(cell, CARRY_OVER_PROPERTIES))
  .value()
}

/**
 * Converts a container cell that displays an array into a v2 cell
 *
 * @param {object} cell Cell that should display an array
 * @returns {object} Cell converted to v2
 */
function convertArrayCell (cell) {
  const {item} = cell
  const arrayOptions = _.chain(item)
  .pick(ARRAY_CELL_PROPERTIES)
  .assign({
    itemCell: convertCell(item)
  })
  .value()
  return {
    arrayOptions,
    model: cell.model
  }
}

/**
 * Creates custom renderer information for a cell
 *
 * @param {string} renderer Name of the desired renderer
 * @param {object} properties Info for to the custom renderer
 * @returns {object} Custom renderer block
 */
function customRenderer (renderer, properties) {
  return _.assign({
    name: renderer
  }, properties)
}

/**
 * Converts v1 renderer information to v2 for a cell
 *
 * @param {object} cell Cell with a custom renderer
 * @returns {object} Custom renderer block for the given cell
 */
function convertRenderer (cell) {
  const {renderer} = cell
  if (renderer === undefined) {
    return
  }
  switch (renderer) {
    case 'boolean':
    case 'string':
    case 'number':
      return {name: renderer}
    case 'button-group':
    case 'select':
    case 'multi-select':
    case 'textarea':
    default:
      return customRenderer(renderer, cell.properties)
  }
}

/**
 * Creates class name block for v2 cell
 *
 * @param {object} cell Cell to get class names from
 * @returns {object} Class name block for the cell
 */
function grabClassNames (cell) {
  const classNames = _.pickBy({
    cell: cell.className,
    value: cell.inputClassName,
    label: cell.labelClassName
  })
  if (_.size(classNames) > 0) {
    return classNames
  }
}

/**
 * Converts a cell for simple data type (e.g. string, or number)
 *
 * @param {object} cell Cell in v1 to convert to v2
 * @returns {object} Cell converted to v2
 */
function convertBasicCell (cell) {
  return _.pickBy(_.assign({
    renderer: convertRenderer(cell)
  }, _.pick(cell, CARRY_OVER_PROPERTIES)))
}

/**
 * Determines which cell conversion function to use to convert v1 cell to v2 and converts it
 *
 * @export
 * @param {object} cell A v1 cell to convert to v2
 * @returns {object} The cell converted to v2
 */
export function convertCell (cell) {
  let cellConverter
  if (cell.item) {
    cellConverter = convertArrayCell
  } else if (cell.rows) {
    cellConverter = convertObjectCell
  } else {
    cellConverter = convertBasicCell
  }

  return _.pickBy(_.assign({
    extends: cell.container,
    classNames: grabClassNames(cell)
  }, cellConverter(cell)))
}

/**
 * Converts rows of v1 cells to v2 cells. Simplifies the row structure when possible.
 *
 * @param {object[][]} rows A set of rows to convert
 * @returns {object} A v2 cell
 */
function rowsToCells (rows) {
  if (!rows) {
    return {}
  }

  const children = rows
    .map((row) => {
      return _.map(row, convertCell)
    })
    .reduce((a, b) => a.concat(b), [])

  return {
    children
  }
}

/**
 * Converts the container declarations in a v1 view to cell definitions in a v2 view
 *
 * @export
 * @param {object[]} containers Set of containers to convert
 * @returns {object} cell definitions for the v2 view.
 */
export function generateCellDefinitions (containers) {
  return _.chain(containers)
  .map(function (container) {
    const {collapsible, rows, id, className, label} = container
    const cell = rowsToCells(rows)

    if (className !== undefined) {
      cell.classNames = {
        cell: className
      }
    }

    if (collapsible !== undefined) {
      cell.collapsible = collapsible
    }

    if (label !== undefined) {
      cell.label = label
    }

    return [id, cell]
  })
  .fromPairs()
  .value()
}

/**
 * Converts a bunsen version 1 view into a bunsen/ui schema version 2 view
 *
 * @export
 * @param {any} v1View A v1 view to convert to v2
 * @returns {object} The v2 view generated from the given v1 view
 */
export default function viewV1ToV2 (v1View) {
  const {type} = v1View

  if (v1View.rootContainers.length === 1) {
    delete v1View.rootContainers[0].label
  }

  const cells = generateCells(v1View.rootContainers)

  const cellDefinitions = generateCellDefinitions(v1View.containers)

  return {
    version: '2.0',
    type,
    cells,
    cellDefinitions
  }
}
<|endoftext|>


<commit_before>export default {
    transformKeys: (obj) => {
        Object.keys(obj).forEach(function(key) {
            obj["_" + key] = obj[key];
        });
        return obj;
    },
}<commit_msg>beam vue trannsformer use undefined instead of null values<commit_after>export default {
    transformKeys: (obj) => {
        Object.keys(obj).forEach(function(key) {
            obj["_" + key] = obj[key] === null ? undefined : obj[key];
        });
        return obj;
    },
}
<|endoftext|>