<commit_before>'use strict';

var hooks = require('hooks'),
    assert = require('assert');

var path = require('path');
var url = require('url');
var querystring = require('querystring');
var request = require('request');
var randomstring = require('randomstring');
var util = require('util');

var products = require(path.resolve('./depends/catalogue')).api.resources.products;
var suppliers = require(path.resolve('./depends/catalogue')).api.resources.suppliers;
var pickups = require(path.resolve('./depends/stock')).api.resources.pickups;

var dummyUser = {
    _id: randomstring.generate({ length: 24, charset: 'hex' }),
    username: 'checkoutdummy',
    displayName: 'Checkout Dummy',
    email: 'checkout@dummy.com'
};

var pickup, product, supplier, product_to_cancel;

hooks.before('POST /checkout/{method} -> 200', function(test, done) {
    test.request.params.method = 'local-psp';

    pickups.post({
        description: 'Pickup description',
        location: '1234567890abcdef123456a1',
        start: '2016-10-28T12:00:00.000Z',
        end: '2016-10-28T16:00:00.000Z',
        state: 'open'
    })
    .then((res) => {
        assert.equal(res.status,200,'failed to create pickup: ' + res.body.message);
        pickup = res.body;
        return suppliers.post({
            name: 'foofactory'
        });
    })
    .then((res) => {
        assert.equal(res.status,200,'failed to create supplier: ' + res.body.message);
        supplier = res.body;
    })
    .then(() => {
        //create a product to order
        return products.post({
            name: 'foomania',
            supplierPrice: 13.41,
            supplier: supplier._id
        });
    })
    .then((res) => {
        assert.equal(res.status,200,'failed to create product: ' + res.body.message);
        product = res.body;
    })
    .then(() => {
        //create a product to order
        return products.post({
            name: 'cancelmania',
            supplierPrice: 16.01,
            supplier: supplier._id
        });
    })
    .then((res) => {
        assert.equal(res.status,200,'failed to create product to cancel: ' + res.body.message);
        product_to_cancel = res.body;
    })
    .then(() => {
        test.request.body.pickupId = pickup._id;
        test.request.body.items = [{
            _product: product._id,
            price: product.price,
            name: product.descName,
            supplierId: product.supplier,
            total: 9,
            quantity: 2
        }];
        test.request.body.user = dummyUser;
        done();
    })
    .catch((err) => { console.error(err); done(); });
});

//{ redirect: 'http://localhost:3030/checkout/local-psp/580e719f6b3d1e0abb07e640/redirected?token=TOKEN&PayerID=localpayer' }
function parseRedirectUrl(redirectUrl) {
    var parsed = {};
    var redirect = url.parse(redirectUrl);
    var paths = redirect.pathname.split('/');
    parsed.method = paths[2];
    parsed.checkoutOrderId = paths[3];
    var query = querystring.parse(redirect.query);
    parsed.token = query.token;
    parsed.PayerID = query.PayerID;

    return parsed;
}

var checkout = {};

//{ redirect: 'http://localhost:3030/checkout/local-psp/580e719f6b3d1e0abb07e640/redirected?token=TOKEN&PayerID=localpayer' }
hooks.after('POST /checkout/{method} -> 200', function(test, done) {
    checkout = parseRedirectUrl(test.response.body.redirect);
    done();
});

hooks.before('PUT /checkout/{method}/{checkoutOrderId}/{token}/redirected -> 200', function(test,done) {
    test.request.params.method = 'local-psp';
    test.request.params.checkoutOrderId = checkout.checkoutOrderId;
    test.request.params.token = checkout.token;
    test.request.body = { token: checkout.token };
    done();
});

hooks.after('PUT /checkout/{method}/{checkoutOrderId}/{token}/redirected -> 200', function(test, done) {
    assert.equal(test.response.body.total,26.82);
    assert.equal(test.response.body.items[0].total,26.82);
    assert.equal(test.response.body.state,'gotdetails');
    done();
});

hooks.before('GET /checkout/{method}/{checkoutOrderId}/{token}/confirm -> 200', function(test, done) {
    test.request.params.method = 'local-psp';
    test.request.params.checkoutOrderId = checkout.checkoutOrderId;
    test.request.params.token = checkout.token;
    done();
});

hooks.after('GET /checkout/{method}/{checkoutOrderId}/{token}/confirm -> 200', function(test, done) {
    assert.equal(test.response.body.state,'confirmed');
    done();
});

hooks.before('PUT /checkout/{method}/{checkoutOrderId}/{token}/cancelled -> 400', function(test,done) {
    test.request.params.method = 'local-psp';
    test.request.params.checkoutOrderId = checkout.checkoutOrderId;
    test.request.params.token = checkout.token;
    done();
});

var checkoutOrderId_to_cancel;
hooks.before('PUT /checkout/{method}/{checkoutOrderId}/{token}/cancelled -> 200', function(test,done) {
    // create a new order to cancel
    request.post(
        test.request.server + '/checkout/local-psp',
        {
            form: {
                state: 'new',
                items: [{
                    _product: product.id,
                    price: product.price,
                    total: product.price,
                    name: product.name,
                    quantity: 1
                }],
                total : product.price,
                user: dummyUser,
                orderType: 'customer',
                method: 'local-psp',
                pickupId: pickup._id
            },
            json: true
        },
        function(err,res,bod) {
            assert.ifError(err);
            assert.equal(res.statusCode, 200);

            var redirect = parseRedirectUrl(res.body.redirect);
            test.request.params.checkoutOrderId = redirect.checkoutOrderId;
            checkoutOrderId_to_cancel = redirect.checkoutOrderId;
            test.request.params.token = redirect.token;
            test.request.params.method = redirect.method;

            done();
        }
    );
});

hooks.before('GET /orders/{orderId} -> 200', function(test,done) {
    test.request.params.orderId = checkout.checkoutOrderId;
    done();
});

hooks.before('DELETE /orders/{orderId} -> 200', function(test,done) {
    test.request.params.orderId = checkout.checkoutOrderId;
    done();
});

hooks.before('GET /orders/{orderId}/recalculate -> 200', function(test,done) {
    test.request.params.orderId = checkoutOrderId_to_cancel;
    done();
});

hooks.before('PUT /orders/{orderId}/recalculate -> 200', function(test,done) {
    test.request.params.orderId = checkoutOrderId_to_cancel;
    done();
});

hooks.before('GET /orders/history/{orderUserId} -> 200', function(test,done) {
    test.request.params.orderUserId = dummyUser._id;
    done();
});

hooks.after('GET /orders/history/{orderUserId} -> 200', function(test,done) {
    assert(test.response.body.length > 0);
    done();
});

var checkout_to_finalise;
hooks.after('POST /checkout/{method} -> 200', function(test, done) {
    test.request.body.items.push({
        _product: product_to_cancel.id,
        price: product_to_cancel.price,
        total: product_to_cancel.price,
        name: product_to_cancel.name,
        quantity: 1
    });

    request.post(
        test.request.server + '/checkout/local-psp',
        {form: test.request.body, json: true},
        function(err,res,bod) {
            checkout_to_finalise = parseRedirectUrl(bod.redirect);
            done();
        }
    );
});

hooks.after('PUT /checkout/{method}/{checkoutOrderId}/{token}/redirected -> 200', function(test,done) {
    test.request.params.method = 'local-psp';
    test.request.params.checkoutOrderId = checkout.checkoutOrderId;
    test.request.params.token = checkout.token;
    test.request.body = { token: checkout.token };

    request.put(
        test.request.server + util.format(
            '/checkout/%s/%s/%s/redirected',
            checkout_to_finalise.method,
            checkout_to_finalise.checkoutOrderId,
            checkout_to_finalise.token
        ),
        {form: { token: checkout_to_finalise.token }, json: true},
        function(err,res,bod) {
            assert.equal(test.response.body.state,'gotdetails');
            done();
        }
    );
});

hooks.after('GET /checkout/{method}/{checkoutOrderId}/{token}/confirm -> 200', function(test, done) {
    request.get(
        test.request.server + util.format(
            '/checkout/%s/%s/%s/confirm',
            checkout_to_finalise.method,
            checkout_to_finalise.checkoutOrderId,
            checkout_to_finalise.token
        ),
        function(err,res,bod) {
            assert.equal(test.response.body.state,'confirmed');
            done();
        }
    );
});

hooks.before('PUT /orders/{orderId}/finalise -> 200', function(test,done) {
    test.request.params.orderId = checkout_to_finalise.checkoutOrderId;
    test.request.body = [{
        productId: product._id,
        quantity: 2,
        action: 'finalise'
    }, {
        productId: product_to_cancel._id,
        quantity: 1,
        action: 'cancel'
    }];
    done();
});

hooks.after('PUT /orders/{orderId}/finalise -> 200', function(test,done) {
    assert.equal(test.response.body.state, 'finalised');
    assert.equal(test.response.body.due, -16.01);
    done();
});

hooks.before('PUT /orders/{orderId}/finalise -> 400', function(test,done) {
    test.request.params.orderId = checkoutOrderId_to_cancel;
    done();
});

hooks.before('GET /checkout/{method}/{checkoutOrderId}/close -> 200', function(test,done) {
    test.request.params.checkoutOrderId = checkout_to_finalise.checkoutOrderId;
    test.request.params.method = checkout_to_finalise.method;
    done();
});

hooks.after('GET /checkout/{method}/{checkoutOrderId}/close -> 200', function(test,done) {
    assert.equal(test.response.body.state, 'closed');
    assert.equal(test.response.body.due, -16.01);
    done();
});
<commit_msg>silly test fail<commit_after>'use strict';

var hooks = require('hooks'),
    assert = require('assert');

var path = require('path');
var url = require('url');
var querystring = require('querystring');
var request = require('request');
var randomstring = require('randomstring');
var util = require('util');

var products = require(path.resolve('./depends/catalogue')).api.resources.products;
var suppliers = require(path.resolve('./depends/catalogue')).api.resources.suppliers;
var pickups = require(path.resolve('./depends/stock')).api.resources.pickups;

var dummyUser = {
    _id: randomstring.generate({ length: 24, charset: 'hex' }),
    username: 'checkoutdummy',
    displayName: 'Checkout Dummy',
    email: 'checkout@dummy.com'
};

var pickup, product, supplier, product_to_cancel;

hooks.before('POST /checkout/{method} -> 200', function(test, done) {
    test.request.params.method = 'local-psp';

    pickups.post({
        description: 'Pickup description',
        location: '1234567890abcdef123456a1',
        start: '2016-10-28T12:00:00.000Z',
        end: '2016-10-28T16:00:00.000Z',
        state: 'open'
    })
    .then((res) => {
        assert.equal(res.status,200,'failed to create pickup: ' + res.body.message);
        pickup = res.body;
        return suppliers.post({
            name: 'foofactory'
        });
    })
    .then((res) => {
        assert.equal(res.status,200,'failed to create supplier: ' + res.body.message);
        supplier = res.body;
    })
    .then(() => {
        //create a product to order
        return products.post({
            name: 'foomania',
            supplierPrice: 13.41,
            supplier: supplier._id
        });
    })
    .then((res) => {
        assert.equal(res.status,200,'failed to create product: ' + res.body.message);
        product = res.body;
    })
    .then(() => {
        //create a product to order
        return products.post({
            name: 'cancelmania',
            supplierPrice: 16.01,
            supplier: supplier._id
        });
    })
    .then((res) => {
        assert.equal(res.status,200,'failed to create product to cancel: ' + res.body.message);
        product_to_cancel = res.body;
    })
    .then(() => {
        test.request.body.pickupId = pickup._id;
        test.request.body.items = [{
            _product: product._id,
            price: product.price,
            name: product.descName,
            supplierId: product.supplier,
            total: 9,
            quantity: 2
        }];
        test.request.body.user = dummyUser;
        done();
    })
    .catch((err) => { console.error(err); done(); });
});

//{ redirect: 'http://localhost:3030/checkout/local-psp/580e719f6b3d1e0abb07e640/redirected?token=TOKEN&PayerID=localpayer' }
function parseRedirectUrl(redirectUrl) {
    var parsed = {};
    var redirect = url.parse(redirectUrl);
    var paths = redirect.pathname.split('/');
    parsed.method = paths[2];
    parsed.checkoutOrderId = paths[3];
    var query = querystring.parse(redirect.query);
    parsed.token = query.token;
    parsed.PayerID = query.PayerID;

    return parsed;
}

var checkout = {};

//{ redirect: 'http://localhost:3030/checkout/local-psp/580e719f6b3d1e0abb07e640/redirected?token=TOKEN&PayerID=localpayer' }
hooks.after('POST /checkout/{method} -> 200', function(test, done) {
    checkout = parseRedirectUrl(test.response.body.redirect);
    done();
});

hooks.before('PUT /checkout/{method}/{checkoutOrderId}/{token}/redirected -> 200', function(test,done) {
    test.request.params.method = 'local-psp';
    test.request.params.checkoutOrderId = checkout.checkoutOrderId;
    test.request.params.token = checkout.token;
    test.request.body = { token: checkout.token };
    done();
});

hooks.after('PUT /checkout/{method}/{checkoutOrderId}/{token}/redirected -> 200', function(test, done) {
    assert.equal(test.response.body.total,26.82);
    assert.equal(test.response.body.items[0].total,26.82);
    assert.equal(test.response.body.state,'gotdetails');
    done();
});

hooks.before('GET /checkout/{method}/{checkoutOrderId}/{token}/confirm -> 200', function(test, done) {
    test.request.params.method = 'local-psp';
    test.request.params.checkoutOrderId = checkout.checkoutOrderId;
    test.request.params.token = checkout.token;
    done();
});

hooks.after('GET /checkout/{method}/{checkoutOrderId}/{token}/confirm -> 200', function(test, done) {
    assert.equal(test.response.body.state,'confirmed');
    done();
});

hooks.before('PUT /checkout/{method}/{checkoutOrderId}/{token}/cancelled -> 400', function(test,done) {
    test.request.params.method = 'local-psp';
    test.request.params.checkoutOrderId = checkout.checkoutOrderId;
    test.request.params.token = checkout.token;
    done();
});

var checkoutOrderId_to_cancel;
hooks.before('PUT /checkout/{method}/{checkoutOrderId}/{token}/cancelled -> 200', function(test,done) {
    // create a new order to cancel
    request.post(
        test.request.server + '/checkout/local-psp',
        {
            form: {
                state: 'new',
                items: [{
                    _product: product.id,
                    price: product.price,
                    total: product.price,
                    name: product.name,
                    quantity: 1
                }],
                total : product.price,
                user: dummyUser,
                orderType: 'customer',
                method: 'local-psp',
                pickupId: pickup._id
            },
            json: true
        },
        function(err,res,bod) {
            assert.ifError(err);
            assert.equal(res.statusCode, 200);

            var redirect = parseRedirectUrl(res.body.redirect);
            test.request.params.checkoutOrderId = redirect.checkoutOrderId;
            checkoutOrderId_to_cancel = redirect.checkoutOrderId;
            test.request.params.token = redirect.token;
            test.request.params.method = redirect.method;

            done();
        }
    );
});

hooks.before('GET /orders/{orderId} -> 200', function(test,done) {
    test.request.params.orderId = checkout.checkoutOrderId;
    done();
});

hooks.before('DELETE /orders/{orderId} -> 200', function(test,done) {
    test.request.params.orderId = checkout.checkoutOrderId;
    done();
});

hooks.before('GET /orders/{orderId}/recalculate -> 200', function(test,done) {
    test.request.params.orderId = checkoutOrderId_to_cancel;
    done();
});

hooks.before('PUT /orders/{orderId}/recalculate -> 200', function(test,done) {
    test.request.params.orderId = checkoutOrderId_to_cancel;
    done();
});

hooks.before('GET /orders/history/{orderUserId} -> 200', function(test,done) {
    test.request.params.orderUserId = dummyUser._id;
    done();
});

hooks.after('GET /orders/history/{orderUserId} -> 200', function(test,done) {
    assert(test.response.body.length > 0);
    done();
});

var checkout_to_finalise;
hooks.after('POST /checkout/{method} -> 200', function(test, done) {
    test.request.body.items.push({
        _product: product_to_cancel.id,
        price: product_to_cancel.price,
        total: product_to_cancel.price,
        name: product_to_cancel.name,
        quantity: 1
    });

    request.post(
        test.request.server + '/checkout/local-psp',
        {form: test.request.body, json: true},
        function(err,res,bod) {
            checkout_to_finalise = parseRedirectUrl(bod.redirect);
            done();
        }
    );
});

hooks.after('PUT /checkout/{method}/{checkoutOrderId}/{token}/redirected -> 200', function(test,done) {
    test.request.params.method = 'local-psp';
    test.request.params.checkoutOrderId = checkout.checkoutOrderId;
    test.request.params.token = checkout.token;
    test.request.body = { token: checkout.token };

    request.put(
        test.request.server + util.format(
            '/checkout/%s/%s/%s/redirected',
            checkout_to_finalise.method,
            checkout_to_finalise.checkoutOrderId,
            checkout_to_finalise.token
        ),
        {form: { token: checkout_to_finalise.token }, json: true},
        function(err,res,bod) {
            assert.equal(test.response.body.state,'gotdetails');
            done();
        }
    );
});

hooks.after('GET /checkout/{method}/{checkoutOrderId}/{token}/confirm -> 200', function(test, done) {
    request.get(
        test.request.server + util.format(
            '/checkout/%s/%s/%s/confirm',
            checkout_to_finalise.method,
            checkout_to_finalise.checkoutOrderId,
            checkout_to_finalise.token
        ),
        function(err,res,bod) {
            assert.equal(test.response.body.state,'confirmed');
            done();
        }
    );
});

hooks.before('PUT /orders/{orderId}/finalise -> 200', function(test,done) {
    test.request.params.orderId = checkout_to_finalise.checkoutOrderId;
    test.request.body = [{
        productId: product._id,
        quantity: 2,
        action: 'finalise'
    }, {
        productId: product_to_cancel._id,
        quantity: 1,
        action: 'cancel'
    }];
    done();
});

hooks.after('PUT /orders/{orderId}/finalise -> 200', function(test,done) {
    assert.equal(test.response.body.state, 'finalised');
    assert.equal(test.response.body.due, -16.01);
    done();
});

hooks.before('PUT /orders/{orderId}/finalise -> 400', function(test,done) {
    test.request.params.orderId = checkoutOrderId_to_cancel;
    done();
});

hooks.before('GET /checkout/{method}/{checkoutOrderId}/close -> 200', function(test,done) {
    test.request.params.checkoutOrderId = checkout_to_finalise.checkoutOrderId;
    test.request.params.method = checkout_to_finalise.method;
    done();
});

hooks.after('GET /checkout/{method}/{checkoutOrderId}/close -> 200', function(test,done) {
    assert.equal(test.response.body.state, 'closed');
    assert.equal(test.response.body.due, 0);
    assert.equal(test.response.body.items[1].state, 'refunded');
    done();
});
<|endoftext|>


<commit_before>import Realm from 'realm';
import { complement } from 'set-manipulator';

import { createRecord, getTotal } from '../utilities';

export class TransactionItem extends Realm.Object {

  destructor(database) {
    if (this.transaction.isFinalised) {
      throw new Error('Cannot delete an item from a finalised transaction');
    }
    database.delete('TransactionBatch', this.batches);
  }

  get itemId() {
    return this.item ? this.item.id : '';
  }

  get itemName() {
    return this.item ? this.item.name : '';
  }

  get itemCode() {
    return this.item ? this.item.code : '';
  }

  get totalQuantity() {
    return getTotal(this.batches, 'totalQuantity');
  }

  get totalQuantitySent() {
    return getTotal(this.batches, 'totalQuantitySent');
  }
  get totalPrice() {
    return getTotal(this.batches, 'totalPrice');
  }

  get numberOfBatches() {
    return this.batches.length;
  }

  // For customer invoices, return how much can be issued in total, accounting
  // for the fact that any issued in a confirmed customer invoice has already
  // been taken off the total
  get availableQuantity() {
    if (this.transaction.isOutgoing &&
       (this.transaction.isConfirmed ||
        this.transaction.isFinalised)) {
      return this.item.totalQuantity + this.totalQuantity;
    }
    return this.item.totalQuantity;
  }

  /**
   * Returns the batch attached to this transaction item with the given item batch id
   * @param  {string} itemId The item id to look for
   * @return {object}        The TransactionBatch with the matching item id
   */
  getBatch(itemBatchId) {
    return this.batches.find(transactionBatch => transactionBatch.itemBatchId === itemBatchId);
  }

  addBatch(transactionBatch) {
    this.batches.push(transactionBatch);
  }

  /**
   * Sets the quantity for the current item by applying the difference to the
   * shortest expiry batches possible.
   * N.B. For customer invoices, will create and delete transaction batches as appropriate.
   * N.B. Supplier invoices do not take effect on the rest of the stock until they
   * are finalised, whereas customer invoices immediately influence stock levels.
   * @param {Database} Realm DB wrapper
   * @param {double} requestedQuantity try to set this quanity against all batches
   * @param {double} referenceQuantity max available quantity or -1 to use requestedQuantity
   */
  setTotalQuantity(database, requestedQuantity, referenceQuantity = -1) {
    if (this.transaction.isFinalised) {
      throw new Error('Cannot set quantity of an item in a finalised transaction');
    }
    let quantity = requestedQuantity;
    if (referenceQuantity > 0) quantity = Math.min(requestedQuantity, referenceQuantity);
    if (quantity < 0) throw new Error('Cannot set a negative quantity on a transaction item');

    const difference = quantity - this.totalQuantity; // Positive if new quantity is greater

    // Apply the difference to make the new quantity
    this.allocateDifferenceToBatches(database, difference);

    // See if any batches can be pruned, i.e. have 0 quantity for this invoice
    this.pruneEmptyBatches(database);
  }

  /**
   * Applies the given difference in quantity to the appropriate batches. If the difference
   * is an increase, it will apply to the shortest expiry batches possible. If a reduction,
   * it will apply to the longest batches possible. In this way it is FEFO for customer invoices,
   * and pessimistic with changes to supplier invoices (assumes you got more of the shortest
   * batch or less of the longest batch.) Side effect, which we don't care about, is that
   * it is FEFO for supplier credits made from stocktake inventory adjustments, which is
   * optimistic, but it's all a guess about what is really going on in their stock anyway.
   * Null expiries are treated as absolute shortest, which means they will be issued first.
   *
   * @param {Realm}      database      App wide local database
   * @param {double}     difference    The difference in quantity to set across all batches.
   *                                   Will be positive if greater new quantity, negative
   *                                   if lesser.
   * @return {none}
   */
  allocateDifferenceToBatches(database, difference) {
    // Sort batches shortest -> longest batch if increasing, longest -> shortest if reducing
    const batches = this.batches.sorted('expiryDate', difference < 0);

    // First apply as much of the quantity as possible to existing batches
    let remainder = difference;
    for (let index = 0; remainder !== 0 && index < batches.length; index++) {
      remainder = this.allocateDifferenceToBatch(database, remainder, batches[index]);
    }

    // If there is a positive remainder, i.e. more to allocate, add more batches
    if (remainder > 0) {
      // Use only batches that have some stock on hand (only ones we can issue
      // from, and also most likely batches to have found more of in stocktake)
      // Sorted shortest to longest expiry date, so that customer invoices issue
      // following a FEFO policy.
      const batchesWithStock = this.item.batchesWithStock.sorted('expiryDate');

      // Unless there are no batches with stock, in which case start with the batch
      // that was most likely to be recently in stock, i.e. the one with the longest
      // expiry date.
      const batchesToUse = batchesWithStock.length > 0 ?
                           batchesWithStock :
                           this.item.batches.sorted('expiryDate', true);

      // Use complement to only get batches not already in the transaction.
      // TODO may actually slow things if most batches not already in transaction item
      // or all batches already in
      const itemBatchesToAdd = complement(batchesToUse,
                                          this.batches.map((transactionBatch) =>
                                            ({ id: transactionBatch.itemBatchId })),
                                          (batch) => batch.id);
      // Go through item batches, adding transaction batches and allocating remainder
      // until no remainder left
      for (let index = 0; index < itemBatchesToAdd.length && remainder !== 0; index ++) {
        // Create the new transaction batch and attach it to this transaction item
        const newBatch = createRecord(database, 'TransactionBatch', this, itemBatchesToAdd[index]);

        // Apply as much of the remainder to it as possible
        remainder = this.allocateDifferenceToBatch(database, remainder, newBatch);
      }
    }

    if (remainder > 0) { // Something went wrong
      throw new Error(`Failed to allocate ${remainder} of ${difference} to ${this.item.name}`);
    }
  }

  /**
   * Allocates as much as possible of the quantity passed in to the given batch
   * @param  {Realm}  database    App wide local database
   * @param  {number} difference  The difference in quantity to try to allocate
   * @param  {object} batch       The TransactionBatch to apply the quantity to
   * @return {integer}            The remainder that couldn't be applied
   */
  allocateDifferenceToBatch(database, difference, batch) {
    const batchAddQuantity = batch.getAmountToAllocate(difference);
    batch.setTotalQuantity(database, batch.totalQuantity + batchAddQuantity);
    database.save('TransactionBatch', batch);
    return difference - batchAddQuantity;
  }

  /**
   * Delete any batches from this transaction that aren't contributing to the total
   * E.g. if the quantity being issued in a customer invoice is reduced, we don't
   * want to keep a batch that is no longer being issued in the transaction.
   * @param  {Realm} database   App wide database
   * @return {none}
   */
  pruneEmptyBatches(database) {
    const batchesToDelete = [];
    this.batches.forEach(batch => {
      if (batch.totalQuantity === 0 && batch.totalQuantitySent === 0) batchesToDelete.push(batch);
    });
    database.delete('TransactionBatch', batchesToDelete);
  }
}

TransactionItem.schema = {
  name: 'TransactionItem',
  primaryKey: 'id',
  properties: {
    id: 'string',
    item: 'Item',
    transaction: 'Transaction',
    batches: { type: 'list', objectType: 'TransactionBatch' },
  },
};
<commit_msg>setTotalQuantity minus one<commit_after>import Realm from 'realm';
import { complement } from 'set-manipulator';

import { createRecord, getTotal } from '../utilities';

export class TransactionItem extends Realm.Object {

  destructor(database) {
    if (this.transaction.isFinalised) {
      throw new Error('Cannot delete an item from a finalised transaction');
    }
    database.delete('TransactionBatch', this.batches);
  }

  get itemId() {
    return this.item ? this.item.id : '';
  }

  get itemName() {
    return this.item ? this.item.name : '';
  }

  get itemCode() {
    return this.item ? this.item.code : '';
  }

  get totalQuantity() {
    return getTotal(this.batches, 'totalQuantity');
  }

  get totalQuantitySent() {
    return getTotal(this.batches, 'totalQuantitySent');
  }
  get totalPrice() {
    return getTotal(this.batches, 'totalPrice');
  }

  get numberOfBatches() {
    return this.batches.length;
  }

  // For customer invoices, return how much can be issued in total, accounting
  // for the fact that any issued in a confirmed customer invoice has already
  // been taken off the total
  get availableQuantity() {
    if (this.transaction.isOutgoing &&
       (this.transaction.isConfirmed ||
        this.transaction.isFinalised)) {
      return this.item.totalQuantity + this.totalQuantity;
    }
    return this.item.totalQuantity;
  }

  /**
   * Returns the batch attached to this transaction item with the given item batch id
   * @param  {string} itemId The item id to look for
   * @return {object}        The TransactionBatch with the matching item id
   */
  getBatch(itemBatchId) {
    return this.batches.find(transactionBatch => transactionBatch.itemBatchId === itemBatchId);
  }

  addBatch(transactionBatch) {
    this.batches.push(transactionBatch);
  }

  /**
   * Sets the quantity for the current item by applying the difference to the
   * shortest expiry batches possible.
   * N.B. For customer invoices, will create and delete transaction batches as appropriate.
   * N.B. Supplier invoices do not take effect on the rest of the stock until they
   * are finalised, whereas customer invoices immediately influence stock levels.
   * @param {Database} Realm DB wrapper
   * @param {double} requestedQuantity try to set this quanity against all batches
   * @param {double} referenceQuantity max available quantity or -1 to use requestedQuantity
   */
  setTotalQuantity(database, requestedQuantity, referenceQuantity) {
    if (this.transaction.isFinalised) {
      throw new Error('Cannot set quantity of an item in a finalised transaction');
    }
    let quantity = requestedQuantity;
    if (referenceQuantity !== undefined) quantity = Math.min(requestedQuantity, referenceQuantity);
    if (quantity < 0) throw new Error('Cannot set a negative quantity on a transaction item');

    const difference = quantity - this.totalQuantity; // Positive if new quantity is greater

    // Apply the difference to make the new quantity
    this.allocateDifferenceToBatches(database, difference);

    // See if any batches can be pruned, i.e. have 0 quantity for this invoice
    this.pruneEmptyBatches(database);
  }

  /**
   * Applies the given difference in quantity to the appropriate batches. If the difference
   * is an increase, it will apply to the shortest expiry batches possible. If a reduction,
   * it will apply to the longest batches possible. In this way it is FEFO for customer invoices,
   * and pessimistic with changes to supplier invoices (assumes you got more of the shortest
   * batch or less of the longest batch.) Side effect, which we don't care about, is that
   * it is FEFO for supplier credits made from stocktake inventory adjustments, which is
   * optimistic, but it's all a guess about what is really going on in their stock anyway.
   * Null expiries are treated as absolute shortest, which means they will be issued first.
   *
   * @param {Realm}      database      App wide local database
   * @param {double}     difference    The difference in quantity to set across all batches.
   *                                   Will be positive if greater new quantity, negative
   *                                   if lesser.
   * @return {none}
   */
  allocateDifferenceToBatches(database, difference) {
    // Sort batches shortest -> longest batch if increasing, longest -> shortest if reducing
    const batches = this.batches.sorted('expiryDate', difference < 0);

    // First apply as much of the quantity as possible to existing batches
    let remainder = difference;
    for (let index = 0; remainder !== 0 && index < batches.length; index++) {
      remainder = this.allocateDifferenceToBatch(database, remainder, batches[index]);
    }

    // If there is a positive remainder, i.e. more to allocate, add more batches
    if (remainder > 0) {
      // Use only batches that have some stock on hand (only ones we can issue
      // from, and also most likely batches to have found more of in stocktake)
      // Sorted shortest to longest expiry date, so that customer invoices issue
      // following a FEFO policy.
      const batchesWithStock = this.item.batchesWithStock.sorted('expiryDate');

      // Unless there are no batches with stock, in which case start with the batch
      // that was most likely to be recently in stock, i.e. the one with the longest
      // expiry date.
      const batchesToUse = batchesWithStock.length > 0 ?
                           batchesWithStock :
                           this.item.batches.sorted('expiryDate', true);

      // Use complement to only get batches not already in the transaction.
      // TODO may actually slow things if most batches not already in transaction item
      // or all batches already in
      const itemBatchesToAdd = complement(batchesToUse,
                                          this.batches.map((transactionBatch) =>
                                            ({ id: transactionBatch.itemBatchId })),
                                          (batch) => batch.id);
      // Go through item batches, adding transaction batches and allocating remainder
      // until no remainder left
      for (let index = 0; index < itemBatchesToAdd.length && remainder !== 0; index ++) {
        // Create the new transaction batch and attach it to this transaction item
        const newBatch = createRecord(database, 'TransactionBatch', this, itemBatchesToAdd[index]);

        // Apply as much of the remainder to it as possible
        remainder = this.allocateDifferenceToBatch(database, remainder, newBatch);
      }
    }

    if (remainder > 0) { // Something went wrong
      throw new Error(`Failed to allocate ${remainder} of ${difference} to ${this.item.name}`);
    }
  }

  /**
   * Allocates as much as possible of the quantity passed in to the given batch
   * @param  {Realm}  database    App wide local database
   * @param  {number} difference  The difference in quantity to try to allocate
   * @param  {object} batch       The TransactionBatch to apply the quantity to
   * @return {integer}            The remainder that couldn't be applied
   */
  allocateDifferenceToBatch(database, difference, batch) {
    const batchAddQuantity = batch.getAmountToAllocate(difference);
    batch.setTotalQuantity(database, batch.totalQuantity + batchAddQuantity);
    database.save('TransactionBatch', batch);
    return difference - batchAddQuantity;
  }

  /**
   * Delete any batches from this transaction that aren't contributing to the total
   * E.g. if the quantity being issued in a customer invoice is reduced, we don't
   * want to keep a batch that is no longer being issued in the transaction.
   * @param  {Realm} database   App wide database
   * @return {none}
   */
  pruneEmptyBatches(database) {
    const batchesToDelete = [];
    this.batches.forEach(batch => {
      if (batch.totalQuantity === 0 && batch.totalQuantitySent === 0) batchesToDelete.push(batch);
    });
    database.delete('TransactionBatch', batchesToDelete);
  }
}

TransactionItem.schema = {
  name: 'TransactionItem',
  primaryKey: 'id',
  properties: {
    id: 'string',
    item: 'Item',
    transaction: 'Transaction',
    batches: { type: 'list', objectType: 'TransactionBatch' },
  },
};
<|endoftext|>


<commit_before>var anyDB = require('../')
var test = require('tap').test
require('sqlite3').verbose()

var databaseUrls = exports.databaseUrls = {
	mysql: "mysql://root@localhost/any_db_test",
	postgres: "postgres://postgres@localhost/any_db_test",
	sqlite3: "sqlite3://:memory:",
}

if (process.env.TEST_DRIVERS) {
	Object.keys(databaseUrls).forEach(function (driver) {
		if (process.env.TEST_DRIVERS.indexOf(driver) == -1) {
			delete databaseUrls[driver]
		}
	})
}

/**
 * Run ``callback(conn, tap_test)`` where ``conn`` is a connection to the test
 * database, and ``tap_test`` is a node-tap test object
 */
exports.allDrivers = testRunner(function (description, opts, callback) {
	_testEachDriver(description, opts, function (connString, t) {
		anyDB.createConnection(connString, function (err, conn) {
			if (err) throw err
			if (opts.autoEnd !== false) t.on('end', conn.end.bind(conn))
			callback(conn, t)
		})
	})
})

/**
 * Run ``callback(tx, tap_test)`` where ``tx`` is an open transaction
 * on the test database, and ``tap_test`` is a node-tap test object
 */
exports.allTransactions = testRunner(function (description, opts, callback) {
	_testEachDriver(description, opts, function (connString, t) {
		anyDB.createConnection(connString, function (err, conn) {
			if (err) throw err
			var tx = conn.begin()
			tx.url = connString
			t.on('end', function () { tx.rollback(conn.end.bind(conn)) })
			tx.on('error', function (err) { t.emit('error', err) })
			callback(tx, t)
		})
	})
})

/**
 * Run ``callback(pool, tap_test)`` where ``pool`` is a connection pool
 * that will connect to the test database and ``tap_test`` is a node-tap test
 * object.
 */
exports.allPools = testRunner(function (description, opts, callback) {
	_testEachDriver(description, opts, function (connString, t) {
		var pool = anyDB.createPool(connString, {
			max: 2,
			min: 0,
			idleTimoutMillis: 1000
		})
		t.on('end', pool.close.bind(pool))
		callback(pool, t)
	})
})

function _testEachDriver (description, opts, callback) {
	var testOpts = {
		timeout: opts.timeout || 3000,
		drivers: opts.drivers || Object.keys(databaseUrls)
	}
	test(description, testOpts, function (t) {
		t.plan(testOpts.drivers.length)
		testOpts.drivers.forEach(function (driver) {
			t.test(driver, function (t) {
				callback(databaseUrls[driver], t)
			})
		})
	})
}

function testRunner (run) {
	return function (description, opts, callback) {
		if (!callback) {
			callback = opts
			opts = {}
		}
		run(description, opts, callback)
	}
}

exports.debugIf = function (orig, cond) {
	return function () {
		if (cond.apply(this, arguments)) {
			console.log(Array.prototype.slice.apply(arguments))
			debugger
		}
		return orig.apply(this, arguments)
	}
}
<commit_msg>Use file on disk for sqlite3 tests<commit_after>var fs = require('fs')
var anyDB = require('../')
var test = require('tap').test
require('sqlite3').verbose()

var sqliteFilename =  "/tmp/any-db-test.db"

var databaseUrls = exports.databaseUrls = {
	mysql: "mysql://root@localhost/any_db_test",
	postgres: "postgres://postgres@localhost/any_db_test",
	sqlite3: "sqlite3://" + sqliteFilename,
}

if (process.env.TEST_DRIVERS) {
	Object.keys(databaseUrls).forEach(function (driver) {
		if (process.env.TEST_DRIVERS.indexOf(driver) == -1) {
			delete databaseUrls[driver]
		}
	})
}

/**
 * Run ``callback(conn, tap_test)`` where ``conn`` is a connection to the test
 * database, and ``tap_test`` is a node-tap test object
 */
exports.allDrivers = testRunner(function (description, opts, callback) {
	_testEachDriver(description, opts, function (connString, t) {
		anyDB.createConnection(connString, function (err, conn) {
			if (err) throw err
			if (opts.autoEnd !== false) t.on('end', conn.end.bind(conn))
			callback(conn, t)
		})
	})
})

/**
 * Run ``callback(tx, tap_test)`` where ``tx`` is an open transaction
 * on the test database, and ``tap_test`` is a node-tap test object
 */
exports.allTransactions = testRunner(function (description, opts, callback) {
	_testEachDriver(description, opts, function (connString, t) {
		anyDB.createConnection(connString, function (err, conn) {
			if (err) throw err
			var tx = conn.begin()
			tx.url = connString
			t.on('end', function () { tx.rollback(conn.end.bind(conn)) })
			tx.on('error', function (err) { t.emit('error', err) })
			callback(tx, t)
		})
	})
})

/**
 * Run ``callback(pool, tap_test)`` where ``pool`` is a connection pool
 * that will connect to the test database and ``tap_test`` is a node-tap test
 * object.
 */
exports.allPools = testRunner(function (description, opts, callback) {
	_testEachDriver(description, opts, function (connString, t) {
		var pool = anyDB.createPool(connString, {
			max: 2,
			min: 0,
			idleTimoutMillis: 1000
		})
		t.on('end', pool.close.bind(pool))
		callback(pool, t)
	})
})

function _testEachDriver (description, opts, callback) {
	var testOpts = {
		timeout: opts.timeout || 3000,
		drivers: opts.drivers || Object.keys(databaseUrls)
	}
	test(description, testOpts, function (t) {
		t.plan(testOpts.drivers.length)
		testOpts.drivers.forEach(function (driver) {
			if (driver == 'sqlite3') {
				try { fs.unlinkSync(sqliteFilename) } catch (e) {}
			}
			t.test(driver, function (t) {
				callback(databaseUrls[driver], t)
			})
		})
	})
}

function testRunner (run) {
	return function (description, opts, callback) {
		if (!callback) {
			callback = opts
			opts = {}
		}
		run(description, opts, callback)
	}
}

exports.debugIf = function (orig, cond) {
	return function () {
		if (cond.apply(this, arguments)) {
			console.log(Array.prototype.slice.apply(arguments))
			debugger
		}
		return orig.apply(this, arguments)
	}
}
<|endoftext|>


<commit_before>define(
  'ephox.porkbun.demo.Sheriff',

  [
    'ephox.wrap.JQuery',
    'ephox.porkbun.Events',
    'ephox.scullion.Struct'
  ],

  function ($, Events, Struct) {
    var create = function () {
      var container = $('
');
      container.css({
        float: 'left',
        width: "200px",
        textAlign: 'center'
      });

      var img = $('');
      img.height('200px');

      var caption = $('
Sheriff
');
      caption.css({ textAlign: 'center', fontWeight: 'bold' });

      var chaseButton = $('<button disabled="true">Give chase</button>');

      var actions = $('
');
      actions.css({ float: 'right' });

      actions.append(chaseButton);
      caption.append(actions);
      container.append(img, caption);

      var getElement = function () {
        return container;
      };

      var events = Events.create({
        chase: Struct.immutable('target')
      });

      var watch = function (establishment) {
        establishment.events.shooting.bind(shooting);
      };

      var shooting = function (event) {
        chaseButton.attr('disabled', false);
        chaseButton.bind('click', function () {
          chaseButton.detach();
          events.trigger.chasing(event.shooter());
        });
      };

      return {
        getElement: getElement,
        events: events.registry,
        watch: watch
      };
    };

    return {
      create: create
    };
  }
);
<commit_msg>Cleanups<commit_after>define(
  'ephox.porkbun.demo.Sheriff',

  [
    'ephox.wrap.JQuery',
    'ephox.porkbun.Events',
    'ephox.scullion.Struct'
  ],

  function ($, Events, Struct) {
    var create = function () {
      var container = $('
');
      container.css({
        float: 'left',
        width: "200px",
        textAlign: 'center'
      });

      var img = $('');
      img.height('200px');

      var caption = $('
Sheriff
');
      caption.css({ textAlign: 'center', fontWeight: 'bold' });

      var chaseButton = $('<button disabled="true">Give chase</button>');

      var actions = $('
');
      actions.css({ float: 'right' });

      actions.append(chaseButton);
      caption.append(actions);
      container.append(img, caption);

      var getElement = function () {
        return container;
      };

      var events = Events.create({
        chase: Struct.immutable('target')
      });

      var watch = function (establishment) {
        establishment.events.shooting.bind(shooting);
      };

      var shooting = function (event) {
        chaseButton.attr('disabled', false);
        chaseButton.bind('click', function () {
          chaseButton.detach();
          events.trigger.chase(event.shooter());
        });
      };

      return {
        getElement: getElement,
        events: events.registry,
        watch: watch
      };
    };

    return {
      create: create
    };
  }
);
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types'
import styled from 'styled-components'
import Prism from 'prismjs'

const Pre = styled.pre`
  &, &[class*=language-] {
    background: rgb(40, 44, 52);
    border: 1px solid #18191E;
    border-radius: 0;
    border-width: ${p => p.theme.spacing / 4}px 1px ${p => p.theme.spacing / 4}px;
    padding: 0.5em;
  }
`

const Code = styled.code`
  &, &[class*=language-] {
    font-family: 'Source Code Pro', monospace;
  }
`

class Snippet extends PureComponent {
  static propTypes = {
    children: PropTypes.string.isRequired,
    language: PropTypes.string.isRequired
  }

  static defaultProps = {
    language: 'javascript'
  }

  render () {
    const {
      children,
      language,
      ...props
    } = this.props

    const html = Prism.highlight(children, Prism.languages.javascript)

    return <Pre {...props}>
      <Code
        className={`language-${language}`}
        dangerouslySetInnerHTML={{__html: html}}
      />
    </Pre>
  }
}

export default Snippet
<commit_msg>Fix linting.<commit_after>import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import Prism from 'prismjs'

const Pre = styled.pre`
  &, &[class*=language-] {
    background: rgb(40, 44, 52);
    border: 1px solid #18191E;
    border-radius: 0;
    border-width: ${p => p.theme.spacing / 4}px 1px ${p => p.theme.spacing / 4}px;
    padding: 0.5em;
  }
`

const Code = styled.code`
  &, &[class*=language-] {
    font-family: 'Source Code Pro', monospace;
  }
`

class Snippet extends PureComponent {
  static propTypes = {
    children: PropTypes.string.isRequired,
    language: PropTypes.string.isRequired
  }

  static defaultProps = {
    language: 'javascript'
  }

  render () {
    const {
      children,
      language,
      ...props
    } = this.props

    const html = Prism.highlight(children, Prism.languages.javascript)

    return <Pre {...props}>
      <Code
        className={`language-${language}`}
        dangerouslySetInnerHTML={{__html: html}}
      />
    </Pre>
  }
}

export default Snippet
<|endoftext|>


<commit_before>/**
 * Copyright 2015 aixigo AG
 * Released under the MIT license.
 * http://laxarjs.org/license
 */
define( [
   'laxar',
   'backbone'
], function( ax, backbone ) {
   'use strict';

   var widgetModules = {};
   function bootstrap( modules ) {
      modules.forEach( function( module ) {
         widgetModules[ module.name ] = module;
      } );
   }

   ///////////////////////////////////////////////////////////////////////////////////////////////////////////

   function create( environment ) {

      var exports = {
         createController: createController,
         domPrepare: domPrepare,
         domAttachTo: domAttachTo,
         domDetach: domDetach,
         widgetId: widgetId,
         destroy: destroy
      };

      var context = environment.context;
      var moduleName = environment.specification.name.replace( /^./, function( _ ) { return _.toLowerCase(); } );

      var backboneApp_ = null;
      var hasDom_ = false;
      var backboneViewDefinition = null;
      var assetUrlPromise_ = null;

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function createController() {
         var module = widgetModules[ moduleName ];
         var injector = createInjector();
         var injections = ( module.injections || [] ).map( function( injection ) {
            return injector.get( injection );
         } );

         backboneViewDefinition = module.create.apply( module, injections );
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function domPrepare() {
         if( !assetUrlPromise_ ) {
            assetUrlPromise_ = environment.assetResolver.resolve();

            if( environment.specification.integration.type === 'activity' ) {
               environment.anchorElement = null;
               render();
               return assetUrlPromise_;
            }

            return assetUrlPromise_
               .then( function( urls ) {
                  urls.cssFileUrls.forEach( function( url ) {
                     environment.assetResolver.loadCss( url );
                  } );

                  if( urls.templateUrl ) {
                     hasDom_ = true;
                     return environment.assetResolver.provide( urls.templateUrl )
                        .then( function( templateHtml ) {
                           environment.anchorElement.innerHTML = templateHtml;
                        } );
                  }
               } )
               .finally( render );
         }

         return assetUrlPromise_;

         /////////////////////////////////////////////////////////////////////////////////////////////////////

         function render() {
            backboneApp_ = backbone.View.extend( ax.object.options( backboneViewDefinition, {
               el: environment.anchorElement
            } ) );
         }
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function domAttachTo( areaElement ) {
         areaElement.appendChild( environment.anchorElement );
         // backbone likes constructors and inheritance. We respect this here as much as necessary ;-)
         /* jshint newcap: false, nonew: false */
         new backboneApp_();
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function domDetach() {
         environment.anchorElement.parentNode.removeChild( environment.anchorElement );
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function widgetId() {
         return context.widget.id;
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function destroy() {
         environment.release();
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function createInjector() {

         var map = {
            axEventBus: context.eventBus,
            axFeatures: context.features || {},
            axId: context.id,
            axWidget: context.widget
         };

         /////////////////////////////////////////////////////////////////////////////////////////////////////

         return {
            get: function( name ) {
               if( !( name in map ) ) {
                  throw new Error( 'Unknown dependency "' + name + '".' );
               }

               return map[ name ];
            }
         };

      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      return exports;

   }

   ///////////////////////////////////////////////////////////////////////////////////////////////////////////

   return {
      technology: 'backbone',
      create: create,
      bootstrap: bootstrap
   };

} );
<commit_msg>Fixed copyright header.<commit_after>/**
 * Copyright 2015 Alexander Wilden
 * Released under the MIT license.
 * http://laxarjs.org/license
 */
define( [
   'laxar',
   'backbone'
], function( ax, backbone ) {
   'use strict';

   var widgetModules = {};
   function bootstrap( modules ) {
      modules.forEach( function( module ) {
         widgetModules[ module.name ] = module;
      } );
   }

   ///////////////////////////////////////////////////////////////////////////////////////////////////////////

   function create( environment ) {

      var exports = {
         createController: createController,
         domPrepare: domPrepare,
         domAttachTo: domAttachTo,
         domDetach: domDetach,
         widgetId: widgetId,
         destroy: destroy
      };

      var context = environment.context;
      var moduleName = environment.specification.name.replace( /^./, function( _ ) { return _.toLowerCase(); } );

      var backboneApp_ = null;
      var hasDom_ = false;
      var backboneViewDefinition = null;
      var assetUrlPromise_ = null;

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function createController() {
         var module = widgetModules[ moduleName ];
         var injector = createInjector();
         var injections = ( module.injections || [] ).map( function( injection ) {
            return injector.get( injection );
         } );

         backboneViewDefinition = module.create.apply( module, injections );
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function domPrepare() {
         if( !assetUrlPromise_ ) {
            assetUrlPromise_ = environment.assetResolver.resolve();

            if( environment.specification.integration.type === 'activity' ) {
               environment.anchorElement = null;
               render();
               return assetUrlPromise_;
            }

            return assetUrlPromise_
               .then( function( urls ) {
                  urls.cssFileUrls.forEach( function( url ) {
                     environment.assetResolver.loadCss( url );
                  } );

                  if( urls.templateUrl ) {
                     hasDom_ = true;
                     return environment.assetResolver.provide( urls.templateUrl )
                        .then( function( templateHtml ) {
                           environment.anchorElement.innerHTML = templateHtml;
                        } );
                  }
               } )
               .finally( render );
         }

         return assetUrlPromise_;

         /////////////////////////////////////////////////////////////////////////////////////////////////////

         function render() {
            backboneApp_ = backbone.View.extend( ax.object.options( backboneViewDefinition, {
               el: environment.anchorElement
            } ) );
         }
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function domAttachTo( areaElement ) {
         areaElement.appendChild( environment.anchorElement );
         // backbone likes constructors and inheritance. We respect this here as much as necessary ;-)
         /* jshint newcap: false, nonew: false */
         new backboneApp_();
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function domDetach() {
         environment.anchorElement.parentNode.removeChild( environment.anchorElement );
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function widgetId() {
         return context.widget.id;
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function destroy() {
         environment.release();
      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      function createInjector() {

         var map = {
            axEventBus: context.eventBus,
            axFeatures: context.features || {},
            axId: context.id,
            axWidget: context.widget
         };

         /////////////////////////////////////////////////////////////////////////////////////////////////////

         return {
            get: function( name ) {
               if( !( name in map ) ) {
                  throw new Error( 'Unknown dependency "' + name + '".' );
               }

               return map[ name ];
            }
         };

      }

      ////////////////////////////////////////////////////////////////////////////////////////////////////////

      return exports;

   }

   ///////////////////////////////////////////////////////////////////////////////////////////////////////////

   return {
      technology: 'backbone',
      create: create,
      bootstrap: bootstrap
   };

} );
<|endoftext|>


<commit_before>'use strict'

function busMall() {

var imagesArray = [];

function Image(productName, filePath) {
  this.name = productName;
  this.filePath = filePath;
  this.numClicks = 0;
  imagesArray.push(this);
}
var bagImage = new Image('bag','bag.jpg');
var bananaImage = new Image('banana', 'banana.jpg');
var bootsImage = new Image('boots', 'boots.jpg');
var chairImage = new Image('chair', 'chair.jpg');
var cthulhuImage = new Image('cthulhu', 'cthulhu.jpg');
var dragonImage = new Image('dragon', 'dragon.jpg');
var penImage = new Image('pen', 'pen.jpg');
var scissorsImage = new Image('scissors', 'scissors.jpg');
var sharkImage = new Image('shark', 'shark.jpg');
var sweepImage = new Image('sweep', 'sweep.png');
var unicornImage = new Image('unicorn', 'unicorn.jpg');
var usbImage = new Image('usb', 'usb.gif');
var water_canImage = new Image('water_can', 'water-can.jpg');// BEWARE: underscore vs. dash
var wine_glassImage = new Image('wine_glass', 'wine-glass.jpg');

var image1 = document.getElementById('image1');
var image2 = document.getElementById('image2');
var image3 = document.getElementById('image3');
var randomNumber1 = 0;
var randomNumber2 = 0;
var randomNumber3 = 0;

function getThreeImages() {
  getRandomImage1();
  getRandomImage2();
  getRandomImage3();
}
function duplicatePreventer() {
  while (image1.src === image2.src || image1.src === image3.src || image2.src === image3.src){
    getThreeImages();
  };
}
function getRandomImage1() {
  randomNumber1 = Math.floor(Math.random() * imagesArray.length);
  document.getElementById('image1').src= "images-to-be-used/" +  imagesArray[randomNumber1].filePath;
}
function getRandomImage2() {
  randomNumber2 = Math.floor(Math.random() * imagesArray.length);
  document.getElementById('image2').src= "images-to-be-used/" +  imagesArray[randomNumber2].filePath;
}
function getRandomImage3() {
  randomNumber3 = Math.floor(Math.random() * imagesArray.length);
  document.getElementById('image3').src= "images-to-be-used/" +  imagesArray[randomNumber3].filePath;
}
  image1.addEventListener('click', handleClickOnFirst);
  image2.addEventListener('click', handleClickOnSecond);
  image3.addEventListener('click', handleClickOnThird);

var image1Clicks = 0;
var image2Clicks = 0;
var image3Clicks = 0;

function handleClickOnFirst() {
  imagesArray[randomNumber1].numClicks += 1;
  getThreeImages();
  duplicatePreventer();//replaces the while loop
}
function handleClickOnSecond() {
  imagesArray[randomNumber2].numClicks += 1;
  getThreeImages();
  duplicatePreventer();
}
function handleClickOnThird() {
  imagesArray[randomNumber3].numClicks += 1;
  getThreeImages();
  duplicatePreventer();
}
getThreeImages();
duplicatePreventer(); //replaces the while loop
}
busMall();
<commit_msg>refactored randApp.js<commit_after>'use strict'

var imagesArray = [];

function Image(productName, filePath) {
  this.name = productName;
  this.filePath = filePath;
  this.numClicks = 0;
  imagesArray.push(this);
}
var bagImage = new Image('bag','bag.jpg');
var bananaImage = new Image('banana', 'banana.jpg');
var bootsImage = new Image('boots', 'boots.jpg');
var chairImage = new Image('chair', 'chair.jpg');
var cthulhuImage = new Image('cthulhu', 'cthulhu.jpg');
var dragonImage = new Image('dragon', 'dragon.jpg');
var penImage = new Image('pen', 'pen.jpg');
var scissorsImage = new Image('scissors', 'scissors.jpg');
var sharkImage = new Image('shark', 'shark.jpg');
var sweepImage = new Image('sweep', 'sweep.png');
var unicornImage = new Image('unicorn', 'unicorn.jpg');
var usbImage = new Image('usb', 'usb.gif');
var water_canImage = new Image('water_can', 'water-can.jpg');// BEWARE: underscore vs. dash
var wine_glassImage = new Image('wine_glass', 'wine-glass.jpg');

var image1 = document.getElementById('image1');
var image2 = document.getElementById('image2');
var image3 = document.getElementById('image3');
var randomNumber1 = 0;
var randomNumber2 = 0;
var randomNumber3 = 0;

function getThreeImages() {
  getRandomImage1();
  getRandomImage2();
  getRandomImage3();
  duplicatePreventer();
}
function duplicatePreventer() {
  while (image1.src === image2.src || image1.src === image3.src || image2.src === image3.src){
    getThreeImages();
  };
}
function getRandomImage1() {
  randomNumber1 = Math.floor(Math.random() * imagesArray.length);
  document.getElementById('image1').src= "images-to-be-used/" +  imagesArray[randomNumber1].filePath;
}
function getRandomImage2() {
  randomNumber2 = Math.floor(Math.random() * imagesArray.length);
  document.getElementById('image2').src= "images-to-be-used/" +  imagesArray[randomNumber2].filePath;
}
function getRandomImage3() {
  randomNumber3 = Math.floor(Math.random() * imagesArray.length);
  document.getElementById('image3').src= "images-to-be-used/" +  imagesArray[randomNumber3].filePath;
}
  image1.addEventListener('click', handleClickOnFirst);
  image2.addEventListener('click', handleClickOnSecond);
  image3.addEventListener('click', handleClickOnThird);

var image1Clicks = 0;
var image2Clicks = 0;
var image3Clicks = 0;

function handleClickOnFirst() {
  imagesArray[randomNumber1].numClicks += 1;
  getThreeImages();
}
function handleClickOnSecond() {
  imagesArray[randomNumber2].numClicks += 1;
  getThreeImages();
}
function handleClickOnThird() {
  imagesArray[randomNumber3].numClicks += 1;
  getThreeImages();
}
getThreeImages();
<|endoftext|>


<commit_before>function indicesOf(string, value) {
    let startIndex = 0,
        indices = [],
        index

    while ((index = string.indexOf(value, startIndex)) > -1) {
        indices.push(index)
        startIndex = index + value.length
    }
    return indices
}

export default indicesOf
<commit_msg>return empty array for falsy inputs<commit_after>function indicesOf(string = '', value = '') {
    let startIndex = 0,
        indices = [],
        index

    if (!string || !value) {
        return []
    }
    while ((index = string.indexOf(value, startIndex)) > -1) {
        indices.push(index)
        startIndex = index + value.length
    }
    return indices
}

export default indicesOf
<|endoftext|>


<commit_before>/**
 * Created by gkedge on 4/18/2016.
 */

var kanban;
var expect = require('chai').expect;

module.exports = {
    'Open': function (client) {
        kanban = client.page.kanbanPage().navigate();
        kanban.expect.element('@page').to.be.visible.before(100);
        kanban.assert.title('Kanban app');
    },

    'Add 1st Lane': function () {
        kanban.addLane().waitForElementVisible(kanban.laneSele(0), 100);
        kanban.expect.element(kanban.deleteLaneBtnSele(0)).to.be.present;
        kanban.expect.element(kanban.addNoteBtnSele(0)).to.be.visible;
        kanban.expect.element(kanban.laneNameSele(0))
            .to.be.visible
            .and.text.to.equal('New lane');

        kanban.expect.element(kanban.notesSele(0)).to.be.visible;

        kanban.laneNotes(0, function (notes) {
            expect(notes.value.length).to.equal(0);
        });
    },

    'Delete 1st Lane': function () {
        kanban.deleteLane(0);
        kanban.waitForElementNotPresent(kanban.laneSele(0), 100);
    },

    'Add 1st Note to 2nd Lane': function () {
        kanban.addLane().expect.element(kanban.laneSele(1))
            .to.be.present.before(100);
        kanban.addNote(1).expect.element(kanban.noteSele(1, 0))
            .to.be.present.before(100);

        kanban.laneNotes(1, function (notes) {
            expect(notes.value.length).to.equal(1);
        });

        kanban.expect.element(kanban.noteValueSele(1, 0))
            .to.be.visible
            .and.text.to.equal('New task');
    },

    'Delete 1st Note to 2nd Lane': function () {
        kanban.deleteLNote(1, 0)
            .expect.element(kanban.noteSele(1, 0)).to.not.be.present.after(100);

        kanban.laneNotes(1, function (notes) {
            expect(notes.value.length).to.equal(0);
        });

        kanban.deleteLane(1)
            .expect.element(kanban.laneSele(1)).to.not.be.present.after(100);
    },

    'Add 3 Notes to 3nd Lane': function () {
        kanban.addLane().expect.element(kanban.laneSele(2))
            .to.be.present.before(100);
        kanban.addNote(2).expect.element(kanban.noteSele(2, 0))
            .to.be.present.before(100);

        kanban.addNote(2).expect.element(kanban.noteSele(2, 1))
            .to.be.present.before(100);

        kanban.addNote(2).expect.element(kanban.noteSele(2, 2))
            .to.be.present.before(100);

        kanban.laneNotes(2, function (notes) {
            expect(notes.value.length).to.equal(3);
        });

    },

    'Delete 3 Notes to 3nd Lane': function () {
        kanban.deleteLNote(2, 0)
            .expect.element(kanban.noteSele(1, 2)).to.not.be.present.after(100);

        kanban.deleteLNote(2, 0)
            .expect.element(kanban.noteSele(1, 1)).to.not.be.present.after(100);

        kanban.deleteLNote(2, 0)
            .expect.element(kanban.noteSele(1, 0)).to.not.be.present.after(100);

        kanban.laneNotes(2, function (notes) {
            expect(notes.value.length).to.equal(0);
        });

        kanban.deleteLane(2)
            .expect.element(kanban.laneSele(2)).to.not.be.present.after(100);
    },

    'Create a 4th & 5th lane w/ the 4th lane containing 3 notes': function () {
        kanban.addLane().expect.element(kanban.laneSele(3))
            .to.be.present.before(100);
        kanban.addLane().expect.element(kanban.laneSele(4))
            .to.be.present.before(100);

        kanban.addNote(3).expect.element(kanban.noteSele(3, 0))
            .to.be.present.before(100);

        kanban.addNote(3).expect.element(kanban.noteSele(3, 1))
            .to.be.present.before(100);

        kanban.addNote(3).expect.element(kanban.noteSele(3, 2))
            .to.be.present.before(100);

        kanban.laneNotes(3, function (notes) {
            expect(notes.value.length).to.equal(3);
        });

        kanban.laneNotes(4, function (notes) {
            expect(notes.value.length).to.equal(0);
        });

    },

    'Delete 4th & 5th Lanes': function () {
        kanban.deleteLane(3)
            .expect.element(kanban.laneSele(3)).to.not.be.present.after(100);
        kanban.deleteLane(4)
            .expect.element(kanban.laneSele(4)).to.not.be.present.after(100);
    },

    "Close Session": function (client) {
        client.end();
    }
};
<commit_msg>Creating Selenium tests.<commit_after>/**
 * Created by gkedge on 4/18/2016.
 */

var kanban;
var expect = require('chai').expect;

module.exports = {
    'Open': function (client) {
        kanban = client.page.kanbanPage().navigate();
        kanban.expect.element('@page').to.be.visible.before(100);
        kanban.assert.title('Kanban app');
    },

    'Add 1st Lane': function () {
        kanban.addLane().waitForElementVisible(kanban.laneSele(0), 100);
        kanban.expect.element(kanban.deleteLaneBtnSele(0)).to.be.present.before(100);
        kanban.expect.element(kanban.addNoteBtnSele(0)).to.be.visible.before(100);
        kanban.expect.element(kanban.laneNameSele(0))
            .to.be.visible
            .and.text.to.equal('New lane');

        kanban.expect.element(kanban.notesSele(0)).to.be.visible.before(100);

        kanban.laneNotes(0, function (notes) {
            expect(notes.value.length).to.equal(0);
        });
    },

    'Delete 1st Lane': function () {
        kanban.deleteLane(0);
        kanban.waitForElementNotPresent(kanban.laneSele(0), 100);
    },

    'Add 1st Note to 2nd Lane': function () {
        kanban.addLane().expect.element(kanban.laneSele(1))
            .to.be.present.before(100);
        kanban.addNote(1).expect.element(kanban.noteSele(1, 0))
            .to.be.present.before(100);

        kanban.laneNotes(1, function (notes) {
            expect(notes.value.length).to.equal(1);
        });

        kanban.expect.element(kanban.noteValueSele(1, 0))
            .to.be.visible
            .and.text.to.equal('New task');
    },

    'Delete 1st Note to 2nd Lane': function () {
        kanban.deleteLNote(1, 0)
            .expect.element(kanban.noteSele(1, 0)).to.not.be.present.after(100);

        kanban.laneNotes(1, function (notes) {
            expect(notes.value.length).to.equal(0);
        });

        kanban.deleteLane(1)
            .expect.element(kanban.laneSele(1)).to.not.be.present.after(100);
    },

    'Add 3 Notes to 3nd Lane': function () {
        kanban.addLane().expect.element(kanban.laneSele(2))
            .to.be.present.before(100);
        kanban.addNote(2).expect.element(kanban.noteSele(2, 0))
            .to.be.present.before(100);

        kanban.addNote(2).expect.element(kanban.noteSele(2, 1))
            .to.be.present.before(100);

        kanban.addNote(2).expect.element(kanban.noteSele(2, 2))
            .to.be.present.before(100);

        kanban.laneNotes(2, function (notes) {
            expect(notes.value.length).to.equal(3);
        });
    },

    'Delete 3 Notes to 3nd Lane': function () {
        kanban.deleteLNote(2, 0)
            .expect.element(kanban.noteSele(1, 2)).to.not.be.present.after(100);

        kanban.deleteLNote(2, 0)
            .expect.element(kanban.noteSele(1, 1)).to.not.be.present.after(100);

        kanban.deleteLNote(2, 0)
            .expect.element(kanban.noteSele(1, 0)).to.not.be.present.after(100);

        kanban.laneNotes(2, function (notes) {
            expect(notes.value.length).to.equal(0);
        });

        kanban.deleteLane(2)
            .expect.element(kanban.laneSele(2)).to.not.be.present.after(100);
    },

    'Create a 4th & 5th lane w/ the 4th lane containing 3 notes': function () {
        kanban.addLane().expect.element(kanban.laneSele(3))
            .to.be.present.before(100);
        kanban.addLane().expect.element(kanban.laneSele(4))
            .to.be.present.before(100);

        kanban.addNote(3).expect.element(kanban.noteSele(3, 0))
            .to.be.present.before(100);

        kanban.addNote(3).expect.element(kanban.noteSele(3, 1))
            .to.be.present.before(100);

        kanban.addNote(3).expect.element(kanban.noteSele(3, 2))
            .to.be.present.before(100);

        kanban.laneNotes(3, function (notes) {
            expect(notes.value.length).to.equal(3);
        });

        kanban.laneNotes(4, function (notes) {
            expect(notes.value.length).to.equal(0);
        });
    },

    'Delete 4th & 5th Lanes': function () {
        kanban.deleteLane(3)
            .expect.element(kanban.laneSele(3)).to.not.be.present.after(100);
        kanban.deleteLane(4)
            .expect.element(kanban.laneSele(4)).to.not.be.present.after(100);
    },

    "Close Session": function (client) {
        client.end();
    }
};
<|endoftext|>


<commit_before>/*!

   MPEG-DASH engine plugin for Flowplayer HTML5 version 5.x

   Copyright 2015 Flowplayer Oy

   Released under the MIT License:
   http://www.opensource.org/licenses/mit-license.php

   Includes dash.all.js:
   Copyright (c) 2015, Dash Industry Forum. **All rights reserved.
   https://github.com/Dash-Industry-Forum/dash.js/blob/development/LICENSE.md

   requires:
   - Flowplayer HTML5 version 5.x
   - dash.js https://github.com/Dash-Industry-Forum/dash.js

*/

(function ($) {
    if (!flowplayer.support.video ||
            !window.MediaSource ||
            !window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.640029, mp4a.40.5"')) {
        return;
    }

    flowplayer.engine.mpegdash = function (player, root) {
        var mediaPlayer,
            videoTag,
            context = new Dash.di.DashContext();

        return {
            pick: function (sources) {
                var i;

                for (i = 0; i < sources.length; i++) {
                    if (sources[i].type === "application/dash+xml") {
                        return sources[i];
                    }
                }
            },
            load: function (video) {
                root.find('video').remove();
                videoTag = $("<video/>")[0];
                $(videoTag).on('play', function () {
                    root.trigger('resume', [player]);
                });
                $(videoTag).on('pause', function () {
                    root.trigger('pause', [player]);
                });
                $(videoTag).on('timeupdate', function () {
                    root.trigger('progress', [player, videoTag.currentTime]);
                });
                $(videoTag).on('loadeddata', function () {
                    video.duration = video.seekable = videoTag.duration;
                    root.trigger('ready', [player, video]);
                });
                $(videoTag).on('seeked', function () {
                    root.trigger('seek', [player, videoTag.currentTime]);
                });
                $(videoTag).on('progress', function (e) {
                    try {
                        var buffered = videoTag.buffered,
                                buffer = buffered.end(0), // first loaded buffer
                                ct = videoTag.currentTime,
                                buffend = 0,
                                i;

                        // buffered.end(null) will not always return the current buffer
                        // so we cycle through the time ranges to obtain it
                        if (ct) {
                            for (i = 1; i < buffered.length; i++) {
                                buffend = buffered.end(i);

                                if (buffend >= ct && buffered.start(i) <= ct) {
                                    buffer = buffend;
                                }
                            }
                        }
                        video.buffer = buffer;
                    } catch (ignored) {}
                    root.trigger('buffer', [player, e]);
                });
                $(videoTag).on('ended', function () {
                    root.trigger('finish', [player]);
                });
                $(videoTag).on('volumechange', function () {
                    root.trigger('volume', [player, videoTag.volume]);
                });

                videoTag.className = 'fp-engine dash-engine';
                root.prepend(videoTag);

                mediaPlayer = new MediaPlayer(context);
                mediaPlayer.startup();
                mediaPlayer.attachView(videoTag);

                // caching can cause failures in playlists
                // for the moment disable entirely
                mediaPlayer.enableLastBitrateCaching(false);
                // handled by fp API
                mediaPlayer.setAutoPlay(false);
                // for seeking in paused state
                mediaPlayer.setScheduleWhilePaused(true);

                mediaPlayer.attachSource(video.src);

                if (player.conf.autoplay) {
                    // let the fp API take care of autoplay
                    // otherwise dash.js triggers play when seeking to
                    // unbuffered positions
                    videoTag.play();
                }
            },
            resume: function () {
                videoTag.play();
            },
            pause: function () {
                videoTag.pause();
            },
            seek: function (time) {
                videoTag.currentTime = time;
            },
            volume: function (level) {
                if (videoTag) {
                    videoTag.volume = level;
                }
            },
            speed: function (val) {
                videoTag.playbackRate = val;
                root.trigger('speed', [player, val]);
            },
            unload: function () {
                if (mediaPlayer) {
                    mediaPlayer.reset();
                }
                $(videoTag).remove();
                root.trigger("unload", [player]);
            }

        };

    };

    $(function () {
        // hack: globally force dash engine, but allow other global config
        flowplayer.conf.engine = "mpegdash";
    });

}(jQuery));
<commit_msg>v5: incorporate latest unload and autoplay changes<commit_after>/*!

   MPEG-DASH engine plugin for Flowplayer HTML5 version 5.x

   Copyright 2015 Flowplayer Oy

   Released under the MIT License:
   http://www.opensource.org/licenses/mit-license.php

   Includes dash.all.js:
   Copyright (c) 2015, Dash Industry Forum. **All rights reserved.
   https://github.com/Dash-Industry-Forum/dash.js/blob/development/LICENSE.md

   requires:
   - Flowplayer HTML5 version 5.x
   - dash.js https://github.com/Dash-Industry-Forum/dash.js

*/

(function ($) {
    if (!flowplayer.support.video ||
            !window.MediaSource ||
            !window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.640029, mp4a.40.5"')) {
        return;
    }

    flowplayer.engine.mpegdash = function (player, root) {
        var mediaPlayer,
            videoTag,
            context = new Dash.di.DashContext();

        return {
            pick: function (sources) {
                var i;

                for (i = 0; i < sources.length; i++) {
                    if (sources[i].type === "application/dash+xml") {
                        return sources[i];
                    }
                }
            },
            load: function (video) {
                root.find('video').remove();
                videoTag = $("<video/>")[0];
                $(videoTag).on('play', function () {
                    root.trigger('resume', [player]);
                });
                $(videoTag).on('pause', function () {
                    root.trigger('pause', [player]);
                });
                $(videoTag).on('timeupdate', function () {
                    root.trigger('progress', [player, videoTag.currentTime]);
                });
                $(videoTag).on('loadeddata', function () {
                    video.duration = video.seekable = videoTag.duration;
                    root.trigger('ready', [player, video]);

                    if (player.conf.autoplay) {
                        // let the fp API take care of autoplay
                        // otherwise dash.js triggers play when seeking to
                        // unbuffered positions
                        videoTag.play();
                    }
                });
                $(videoTag).on('seeked', function () {
                    root.trigger('seek', [player, videoTag.currentTime]);
                });
                $(videoTag).on('progress', function (e) {
                    try {
                        var buffered = videoTag.buffered,
                                buffer = buffered.end(0), // first loaded buffer
                                ct = videoTag.currentTime,
                                buffend = 0,
                                i;

                        // buffered.end(null) will not always return the current buffer
                        // so we cycle through the time ranges to obtain it
                        if (ct) {
                            for (i = 1; i < buffered.length; i++) {
                                buffend = buffered.end(i);

                                if (buffend >= ct && buffered.start(i) <= ct) {
                                    buffer = buffend;
                                }
                            }
                        }
                        video.buffer = buffer;
                    } catch (ignored) {}
                    root.trigger('buffer', [player, e]);
                });
                $(videoTag).on('ended', function () {
                    root.trigger('finish', [player]);
                });
                $(videoTag).on('volumechange', function () {
                    root.trigger('volume', [player, videoTag.volume]);
                });

                videoTag.className = 'fp-engine dash-engine';
                root.prepend(videoTag);

                mediaPlayer = new MediaPlayer(context);
                mediaPlayer.startup();
                mediaPlayer.attachView(videoTag);

                // caching can cause failures in playlists
                // for the moment disable entirely
                mediaPlayer.enableLastBitrateCaching(false);
                // handled by fp API
                mediaPlayer.setAutoPlay(false);
                // for seeking in paused state
                mediaPlayer.setScheduleWhilePaused(true);

                mediaPlayer.attachSource(video.src);
            },
            resume: function () {
                videoTag.play();
            },
            pause: function () {
                videoTag.pause();
            },
            seek: function (time) {
                videoTag.currentTime = time;
            },
            volume: function (level) {
                if (videoTag) {
                    videoTag.volume = level;
                }
            },
            speed: function (val) {
                videoTag.playbackRate = val;
                root.trigger('speed', [player, val]);
            },
            unload: function () {
                root.trigger("unload", [player]);
                mediaPlayer.reset();
            }

        };

    };

    $(function () {
        // hack: globally force dash engine, but allow other global config
        flowplayer.conf.engine = "mpegdash";
    });

}(jQuery));
<|endoftext|>


<commit_before>/*
* Flocking Node.js-Specific Code
* http://github.com/colinbdclark/flocking
*
* Copyright 2013, Colin Clark
* Dual licensed under the MIT and GPL Version 2 licenses.
*/

/*global */
/*jslint white: true, vars: true, undef: true, newcap: true, regexp: true, browser: true,
    forin: true, continue: true, nomen: true, bitwise: true, maxerr: 100, indent: 4 */

var fluid = fluid || require("infusion"),
    flock = fluid.registerNamespace("flock");

(function () {
    "use strict";

    var Speaker = require("speaker");
    var Readable = require("stream").Readable;

    // Override the default browser-based worker clocks with their same-thread equivalents.
    fluid.demands("flock.scheduler.webWorkerIntervalClock", ["flock.platform.nodejs", "flock.scheduler.async"], {
        funcName: "flock.scheduler.intervalClock"
    });

    fluid.demands("flock.scheduler.webWorkerScheduleClock", ["flock.platform.nodejs", "flock.scheduler.async"], {
        funcName: "flock.scheduler.scheduleClock"
    });

    fluid.registerNamespace("flock.enviro");

    fluid.defaults("flock.enviro.nodejs", {
        gradeNames: ["fluid.modelComponent", "autoInit"],
        mergePolicy: {
            genFn: "nomerge",
            nodes: "nomerge",
            buses: "nomerge"
        }
    });

    flock.enviro.nodejs.finalInit = function (that) {
        that.audioSettings = that.options.audioSettings;
        that.gen = that.options.genFn;
        that.buses = that.options.buses;
        that.nodes = that.options.nodes;

        that.startGeneratingSamples = function () {
            that.outputStream._read = that.writeSamples;
            that.outputStream.pipe(that.speaker);
        };

        that.writeSamples = function (numBytes) {
            var settings = that.audioSettings,
                playState = that.model;

            if (that.nodes.length < 1) {
                // If there are no nodes providing samples, write out silence.
                that.outputStream.push(that.silence);
            } else {
                // TODO: Inline interleavedWriter
                flock.interleavedWriter(that.outputView, that.gen, that.buses, that.audioSettings);

                // TODO: Get rid of buffer copying!
                for (var i = 0; i < that.outputView.length; i++) {
                    that.outputBuffer.writeFloatLE(that.outputView[i], i * 4);
                }
                that.outputStream.push(that.outputBuffer);
            }

            // TODO: This code is likely similar or identical in all environment strategies.
            playState.written += settings.bufferSize * settings.chans;
            if (playState.written >= playState.total) {
                that.stop();
            }
        };

        that.stopGeneratingSamples = function () {
            that.outputStream.unpipe(that.speaker);
            that.outputStream._read = undefined;
        };

        that.init = function () {
            var settings = that.audioSettings,
                numSamps = settings.bufferSize * settings.chans,
                numBytes = numSamps * 4; // Flocking uses Float32s, hence * 4

            that.speaker = new Speaker();
            that.outputStream = flock.enviro.nodejs.setupOutputStream(settings);
            that.outputView = new Float32Array(numSamps);
            that.outputBuffer = new Buffer(numBytes);
            that.silence = flock.generate.silence(new Buffer(numBytes));
        };

        that.init();
    };

    flock.enviro.nodejs.setupOutputStream = function (settings) {
        var outputStream = new Readable();
        outputStream.bitDepth = 32;
        outputStream.float = true
        outputStream.signed = true;
        outputStream.channels = settings.chans;
        outputStream.sampleRate = settings.rates.audio;
        outputStream.samplesPerFrame = settings.bufferSize;

        return outputStream;
    };

    fluid.demands("flock.enviro.audioStrategy", "flock.platform.nodejs", {
        funcName: "flock.enviro.nodejs"
    });

}());
<commit_msg>Removes buffer copying from output callback.<commit_after>/*
* Flocking Node.js-Specific Code
* http://github.com/colinbdclark/flocking
*
* Copyright 2013, Colin Clark
* Dual licensed under the MIT and GPL Version 2 licenses.
*/

/*global */
/*jslint white: true, vars: true, undef: true, newcap: true, regexp: true, browser: true,
    forin: true, continue: true, nomen: true, bitwise: true, maxerr: 100, indent: 4 */

var fluid = fluid || require("infusion"),
    flock = fluid.registerNamespace("flock");

(function () {
    "use strict";

    var Speaker = require("speaker");
    var Readable = require("stream").Readable;

    // Override the default browser-based worker clocks with their same-thread equivalents.
    fluid.demands("flock.scheduler.webWorkerIntervalClock", ["flock.platform.nodejs", "flock.scheduler.async"], {
        funcName: "flock.scheduler.intervalClock"
    });

    fluid.demands("flock.scheduler.webWorkerScheduleClock", ["flock.platform.nodejs", "flock.scheduler.async"], {
        funcName: "flock.scheduler.scheduleClock"
    });

    fluid.registerNamespace("flock.enviro");

    fluid.defaults("flock.enviro.nodejs", {
        gradeNames: ["fluid.modelComponent", "autoInit"],
        mergePolicy: {
            genFn: "nomerge",
            nodes: "nomerge",
            buses: "nomerge"
        }
    });

    flock.enviro.nodejs.finalInit = function (that) {
        that.audioSettings = that.options.audioSettings;
        that.gen = that.options.genFn;
        that.buses = that.options.buses;
        that.nodes = that.options.nodes;

        that.startGeneratingSamples = function () {
            that.outputStream._read = that.writeSamples;
            that.outputStream.pipe(that.speaker);
        };

        // TODO: The current implementation ignores the numBytes argument in favour of a fixed buffer size.
        // This may cause over or underruns in cases where node-speaker doesn't pull samples at a fixed rate.
        that.writeSamples = function (numBytes) {
            var settings = that.audioSettings,
                playState = that.model,
                kr = settings.rates.control,
                chans = settings.chans,
                out = that.outputBuffer;

            if (that.nodes.length < 1) {
                // If there are no nodes providing samples, write out silence.
                that.outputStream.push(that.silence);
            } else {
                // TODO: Some duplication with flock.enviro.moz.interleavedWriter().
                for (var i = 0; i < that.model.krPeriods; i++) {
                    that.gen();
                    var offset = i * kr * chans;

                    // Interleave each output channel.
                    for (var chan = 0; chan < chans; chan++) {
                        var bus = that.buses[chan];
                        for (var sampIdx = 0; sampIdx < kr; sampIdx++) {
                            var frameIdx = sampIdx * chans + offset;
                            out.writeFloatLE(bus[sampIdx], (frameIdx + chan) * 4);
                        }
                    }
                }

                that.outputStream.push(out);
            }

            playState.written += settings.bufferSize * settings.chans;
            if (playState.written >= playState.total) {
                that.stop();
            }
        };

        that.stopGeneratingSamples = function () {
            that.outputStream.unpipe(that.speaker);
            that.outputStream._read = undefined;
        };

        that.init = function () {
            var settings = that.audioSettings,
                numSamps = settings.bufferSize * settings.chans,
                numBytes = numSamps * 4; // Flocking uses Float32s, hence * 4

            that.speaker = new Speaker();
            that.outputStream = flock.enviro.nodejs.setupOutputStream(settings);
            that.outputBuffer = new Buffer(numBytes);
            that.silence = flock.generate.silence(new Buffer(numBytes));

            that.model.krPeriods = settings.bufferSize / settings.rates.control;
        };

        that.init();
    };

    flock.enviro.nodejs.setupOutputStream = function (settings) {
        var outputStream = new Readable();
        outputStream.bitDepth = 32;
        outputStream.float = true
        outputStream.signed = true;
        outputStream.channels = settings.chans;
        outputStream.sampleRate = settings.rates.audio;
        outputStream.samplesPerFrame = settings.bufferSize;

        return outputStream;
    };

    fluid.demands("flock.enviro.audioStrategy", "flock.platform.nodejs", {
        funcName: "flock.enviro.nodejs"
    });

}());
<|endoftext|>