<commit_before>import '/resources/polymer/@polymer/polymer/polymer-legacy.js';

import '/resources/components/postal-lib.js';

import { random } from '/resources/reflection/tg-numeric-utils.js';

//Holds all registered event sources in application.
const eventSources = {};

/**
 *  Registers new event source or returns the existing one
 */
const registerEventSource = function (uri) {
    let source = eventSources[uri];
    if (!source) {
        source = {initialised: true, uri: uri, shouldReconnectWhenError: true, errorReconnectionDelay: 15000};
        eventSources[uri] = source;
        console.log('Determine if EventSrouce needs polyfilling: ', window.EventSource)
        if (window.EventSource == undefined) {
            console.warn('EventSrouce polyfilling is in progress.');
            const esPoly = document.createElement('script');
            esPoly.src = '/resources/polyfills/eventsource.min.js';
            esPoly.onload = () => registerEventSourceHandlers(source);
            document.head.appendChild(esPoly);
        } else {
            registerEventSourceHandlers(source);
        }
    }
    return source;
};

const registerEventSourceHandlers = function (sourceObj) {
    console.log("Registering EventSrouce handlers for URI ", sourceObj.uri);

    const source = new EventSource(sourceObj.uri);
    sourceObj.source = source;

    source.addEventListener('message', function (e) {

        postal.publish({
            channel: "sse-event",
            topic: sourceObj.uri + "/message",
            data: {
                msg: JSON.parse(e.data)
            }
        });

    }, false);

    source.addEventListener('completed', function (e) {
        console.log('the observable at the server-side completed');
    }, false);

    source.addEventListener('connection', function (e) {
        console.log('connection message received from server');
    }, false);

    source.addEventListener('open', function (e) {
        console.log('opened connection');
    }, false);

    source.addEventListener('error', function (e) {
        console.log('an error occurred: ', e);

        // publish event for error handling
        postal.publish({
            channel: "sse-event",
            topic: sourceObj.uri + "/error",
            data: {
                event: e,
                source: sourceObj
            }
        });

        // only after custom error handling should we attempt to reconnect
        // this is because the decision to reconnect can be made in the custom error handler
        if (sourceObj.shouldReconnectWhenError === true && e.eventPhase === EventSource.CLOSED) {
            // connection was closed by the server;
            // ensure client-side EventSource to be closed and _initialised flag to be false;
            // ensure also that previous reconnection timeout is cancelled and made null;
            // previous reconnection timeout should not be possible here because SSE EventSource (and server side resource) will be closed prior to this;
            // however it is not harmful to check -- see closeEventSource() method
            closeEventSource(sourceObj);
            // Let's kick a timer for reconnection...
            sourceObj._timerIdForReconnection = setTimeout(() => {
                try {
                    registerEventSourceHandlers(sourceObj);
                } finally {
                    sourceObj._timerIdForReconnection = null;
                }
            }, sourceObj.errorReconnectionDelay);
        }

    }, false);
};

/* Closes an existing event source connection. This operation also ensures that any scheduled reconnections are cancelled and cleared after EventSource closing. */
const closeEventSource = function (sourceObj) {
    if (sourceObj.source) {
        sourceObj.initialised = false;
        const src = sourceObj.source;
        sourceObj.source = null;
        src.close();
    }
    if (typeof sourceObj._timerIdForReconnection === 'number') { // if there is in-progress timeout handling reconnection
        clearTimeout(sourceObj._timerIdForReconnection); // then cancel this timeout immediately
        sourceObj._timerIdForReconnection = null;
    }
};

export const TgSseBehavior = {

    properties: {
        /* A URI that should be connected to for listening to push events. */
        uri: {
            type: String,
            observer: '_uriChanged'
        },

        /* A function that takes a single String arugment representing the push event data. */
        dataHandler: {
            type: Function
        },

        /* An optional function to react to SSE errors. Accepts error event as an argument. */
        errorHandler: {
            type: Function
        },

        /* A timer ID to delay reactions to SSE events in case a delay was requested. */
        _timerIdForDataHandling: {
            type: Number,
            value: null
        },

        /* Indicates the need to schedule dataHandler execution based on 'minDelay' and 'delay' properties of event data. */
        useTimerBasedScheduling: {
            type: Boolean,
            value: true
        }

    },

    /** EventSource registration happens as the result of assiging the value to property uri. */
    _uriChanged: function (newUri, oldUri) {

        //Make sure that previous event source assoiciated with oldUri is closed otherwise throw an error.
        const source = eventSources[oldUri];
        if (source) {
            throw new Error('EventSource for URI [' + oldUri + '] should not be initialised to [' + newUri + '] without closing an existing event source connection.');
        }

        // if uri value is missing then skip EvenSource registration
        if (newUri) {
            this._source = registerEventSource(newUri);
        }
    },

    _registerEventSourceHandlers: function () {
        const self = this;
        console.log("Registering EventSrouce handlers for URI ", self.uri);

        self._source = new EventSource(self.uri);
        const source = self._source;

        source.addEventListener('message', function (e) {
            if (self.useTimerBasedScheduling) {
                // Ensures that if there is a pending data handling request then new requests are ignored
                if (self._timerIdForDataHandling) {
                    return;
                }
                const msg = JSON.parse(e.data);
                const minDelay = msg.minDelay ? msg.minDelay : 0;
                const rndDelay = msg.delay ? random(msg.delay) + 1 : 1;
                console.log("min delay: ", minDelay, " rnd delay: ", rndDelay);
                self._execDataHandlerWithDelay(msg, minDelay + rndDelay);
            } else {
                self.dataHandler(JSON.parse(e.data));
            }
        }, false);

        source.addEventListener('completed', function (e) {
            console.log('the observable at the server-side completed');
        }, false);

        source.addEventListener('connection', function (e) {
            console.log('connection message received from server');
        }, false);

        source.addEventListener('open', function (e) {
            console.log('opened connection');
        }, false);

        source.addEventListener('error', function (e) {
            console.log('an error occurred: ', e);

            // invoke error handler if provided
            if (self.errorHandler) {
                self.errorHandler(e);
            }

            // only after custom error handling should we attempt to reconnect
            // this is because the decision to reconnect can be made in the custom error handler
            if (self.shouldReconnectWhenError === true && e.eventPhase === EventSource.CLOSED) {
                // connection was closed by the server;
                // ensure client-side EventSource to be closed and _initialised flag to be false;
                // ensure also that previous reconnection timeout is cancelled and made null;
                // previous reconnection timeout should not be possible here because SSE EventSource (and server side resource) will be closed prior to this;
                // however it is not harmful to check -- see closeEventSource() method
                self.closeEventSource();
                // Let's kick a timer for reconnection...
                self._timerIdForReconnection = setTimeout(() => {
                    try {
                        self._registerEventSourceHandlers();
                    } finally {
                        self._timerIdForReconnection = null;
                    }
                }, self.errorReconnectionDelay);
            }

        }, false);
    },

    /** Executes dataHandler with delay. */
    _execDataHandlerWithDelay: function (msg, delay) {
        const self = this;
        self._timerIdForDataHandling = setTimeout(() => {
            try {
                self.dataHandler(msg);
            } finally {
                self._timerIdForDataHandling = null;
            }
        }, delay);
    }

}; // end of behaviour declaration<commit_msg>#1835 Enhanced tg-sse-behavior with postal subscribers to EventSource events to make single event source instance for two identical uris<commit_after>import '/resources/polymer/@polymer/polymer/polymer-legacy.js';

import '/resources/components/postal-lib.js';

import { random } from '/resources/reflection/tg-numeric-utils.js';

//Holds all registered event sources in application.
const eventSources = {};

/**
 *  Registers new event source or returns the existing one
 */
const registerEventSource = function (uri) {
    let source = eventSources[uri];
    if (!source) {
        source = {initialised: false, uri: uri, shouldReconnectWhenError: true, errorReconnectionDelay: 15000};
        eventSources[uri] = source;
        console.log('Determine if EventSrouce needs polyfilling: ', window.EventSource)
        if (window.EventSource == undefined) {
            console.warn('EventSrouce polyfilling is in progress.');
            const esPoly = document.createElement('script');
            esPoly.src = '/resources/polyfills/eventsource.min.js';
            esPoly.onload = () => registerEventSourceHandlers(source);
            document.head.appendChild(esPoly);
        } else {
            registerEventSourceHandlers(source);
        }
    }
    return source;
};

const registerEventSourceHandlers = function (sourceObj) {
    console.log("Registering EventSrouce handlers for URI ", sourceObj.uri);

    const source = new EventSource(sourceObj.uri);
    sourceObj.source = source;
    sourceObj.initialised = true;

    source.addEventListener('message', function (e) {

        postal.publish({
            channel: "sse-event",
            topic: sourceObj.uri + "/message",
            data: {
                msg: JSON.parse(e.data)
            }
        });

    }, false);

    source.addEventListener('completed', function (e) {
        console.log('the observable at the server-side completed');
    }, false);

    source.addEventListener('connection', function (e) {
        console.log('connection message received from server');
    }, false);

    source.addEventListener('open', function (e) {
        console.log('opened connection');
    }, false);

    source.addEventListener('error', function (e) {
        console.log('an error occurred: ', e);

        // publish event for error handling
        postal.publish({
            channel: "sse-event",
            topic: sourceObj.uri + "/error",
            data: {
                event: e,
                source: sourceObj
            }
        });

        // only after custom error handling should we attempt to reconnect
        // this is because the decision to reconnect can be made in the custom error handler
        if (sourceObj.shouldReconnectWhenError === true && e.eventPhase === EventSource.CLOSED) {
            // connection was closed by the server;
            // ensure client-side EventSource to be closed and initialised flag to be false;
            // ensure also that previous reconnection timeout is cancelled and made null;
            // previous reconnection timeout should not be possible here because SSE EventSource (and server side resource) will be closed prior to this;
            // however it is not harmful to check -- see closeEventSource() method
            closeEventSource(sourceObj);
            // Let's kick a timer for reconnection...
            sourceObj._timerIdForReconnection = setTimeout(() => {
                try {
                    registerEventSourceHandlers(sourceObj);
                } finally {
                    sourceObj._timerIdForReconnection = null;
                }
            }, sourceObj.errorReconnectionDelay);
        }

    }, false);
};

/* Closes an existing event source connection. This operation also ensures that any scheduled reconnections are cancelled and cleared after EventSource closing. */
const closeEventSource = function (sourceObj) {
    if (sourceObj.source) {
        sourceObj.initialised = false;
        const src = sourceObj.source;
        sourceObj.source = null;
        src.close();
    }
    if (typeof sourceObj._timerIdForReconnection === 'number') { // if there is in-progress timeout handling reconnection
        clearTimeout(sourceObj._timerIdForReconnection); // then cancel this timeout immediately
        sourceObj._timerIdForReconnection = null;
    }
};

export const TgSseBehavior = {

    properties: {
        /* A URI that should be connected to for listening to push events. */
        uri: {
            type: String,
            observer: '_uriChanged'
        },

        /* A function that takes a single String arugment representing the push event data. */
        dataHandler: {
            type: Function
        },

        /* An optional function to react to SSE errors. Accepts error event as an argument. */
        errorHandler: {
            type: Function
        },

        /**postal subcription on sse message event */
        _messageSubscription: Object,

        /**postal subscription on sse error event */
        _errorSubscription: Object,

        /* A timer ID to delay reactions to SSE events in case a delay was requested. */
        _timerIdForDataHandling: {
            type: Number,
            value: null
        },

        /* Indicates the need to schedule dataHandler execution based on 'minDelay' and 'delay' properties of event data. */
        useTimerBasedScheduling: {
            type: Boolean,
            value: true
        }

    },

    attached: function () {
        if (this.uri && this.source && !this._messageSubscription && !this._errorSubscription) {
            this._subscribeToSseEvent();
        }
    },

    detached: function () {
        this._unsubscribeFromSseEvents();
    },

    /** EventSource registration happens as the result of assiging the value to property uri. */
    _uriChanged: function (newUri, oldUri) {
        //Unsubscribe from old sse evnts.
        if (oldUri) {
            this._unsubscribeFromSseEvents();
            this._source = null;
        }

        // if uri value is missing then skip EvenSource registration
        if (newUri) {
            this._source = registerEventSource(newUri);
            this._subscribeToSseEvent();
        }

    },

    _subscribeToSseEvent: function () {
        if (this._messageSubscription) {
            throw new Error("Unsubscribe from sse message event first in order to subscribe to the new one");
        }

        if (this._errorSubscription) {
            throw new Error("Unsubscribe from sse error event first in order to subscribe to the new one");
        }

        this._messageSubscription = postal.subscribe({
            channel: "sse-event",
            topic: this.uri + "/message",
            callback: (data, envelope) => {
                const msg = data.msg;
                if (this.useTimerBasedScheduling) {
                    // Ensures that if there is a pending data handling request then new requests are ignored
                    if (this._timerIdForDataHandling) {
                        return;
                    }
                    const minDelay = msg.minDelay ? msg.minDelay : 0;
                    const rndDelay = msg.delay ? random(msg.delay) + 1 : 1;
                    console.log("min delay: ", minDelay, " rnd delay: ", rndDelay);
                    this._execDataHandlerWithDelay(msg, minDelay + rndDelay);
                } else {
                    this.dataHandler(msg);
                }
            }
        });

        this._errorSubscription = postal.subscribe({
            channel: "sse-event",
            topic: this.uri + "/error",
            callback: (data, envelope) => {
                // invoke error handler if provided
                if (this.errorHandler) {
                    this.errorHandler(data.event, data.source);
                }
            }
        });
    },

    _unsubscribeFromSseEvents: function () {
        this._messageSubscription && this._messageSubscription.unsubscribe();
        this._errorSubscription && this._errorSubscription.unsubscribe();
        delete this._messageSubscription;
        delete this._errorSubscription;
    },

    /** Executes dataHandler with delay. */
    _execDataHandlerWithDelay: function (msg, delay) {
        const self = this;
        self._timerIdForDataHandling = setTimeout(() => {
            try {
                self.dataHandler(msg);
            } finally {
                self._timerIdForDataHandling = null;
            }
        }, delay);
    }

}; // end of behaviour declaration<|endoftext|>


<commit_before>// TODO make sessions expire
var env = process.env;
var EventEmitter = require('events').EventEmitter;
var cookie = require('cookie');
var clone = require(env.Z_PATH_UTILS + 'clone');
var cache = require(env.Z_PATH_CACHE + 'cache');

var sessionCache = cache.pojo('sessions');
var randomStringBase = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var publicSid = uid();
var sessionCookieKey = 'sid';
var roleKey = env.Z_SESSION_ROLE_KEY;
var userKey = env.Z_SESSION_USER_KEY;
var localeKey = env.Z_SESSION_LOCALE_KEY;

module.exports = middleware;

// session class
var Session = {};

// create a new session
Session.create = function (role, locale, user, callback) {
    var self = this;

    self.sid = uid();
    self[roleKey] = role;
    self[userKey] = user;
    self[localeKey] = locale || 'en_US';

    // save session
    self.save(callback);
};

Session.regenerate = function () {
    // renew session with a new sid
};

Session.destroy = function (callback) {
    var self = this;

    // remove from cache
    sessionCache.rm(self.sid);

    // remove session data
    delete self.sid;
    delete self[roleKey];
    delete self[userKey];
    delete self[localeKey];

    callback();
};

Session.reload = function () {
    // reload session data
};

// save session in cache
Session.save = function (callback) {
    var self = this;

    // save session
    sessionCache.set(self.sid, self);

    callback();
};

Session.touch = function () {
    // update expire
};

Session.maxAge = function () {
    // expire info
};

// get or create session
function middleware (connectionHandler) {

    return function (connection, response) {

        // make it compatible with ws connections
        var con = connection.upgradeReq ? connection.upgradeReq : connection;

        connection.session = null;

        // create an empty session
        var session;

        // parse cookies, get session id
        if (con.headers.cookie) {

            var parsedCookie = cookie.parse(con.headers.cookie);

            if (parsedCookie && parsedCookie[sessionCookieKey]) {

                var sessionId = parsedCookie[sessionCookieKey];

                // get session
                // TODO update expire
                session = sessionCache.get(sessionId/*, now: new Date().getTime() / 1000*/);
            }
        }

        // append session to connection
        connection.session = session || clone(Session);

        // continue with request
        connectionHandler(null, connection, response);
    };
}

// random string generator
function uid (len, string) {
    len = len || 23;
    string = string || '';

    for (var i = 0; i < len; ++i) {
        string += randomStringBase[0 | Math.random() * 62];
    }
    return string;
}
<commit_msg>save session only when a sid is set<commit_after>// TODO make sessions expire
var env = process.env;
var EventEmitter = require('events').EventEmitter;
var cookie = require('cookie');
var clone = require(env.Z_PATH_UTILS + 'clone');
var cache = require(env.Z_PATH_CACHE + 'cache');

var sessionCache = cache.pojo('sessions');
var randomStringBase = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var publicSid = uid();
var sessionCookieKey = 'sid';
var roleKey = env.Z_SESSION_ROLE_KEY;
var userKey = env.Z_SESSION_USER_KEY;
var localeKey = env.Z_SESSION_LOCALE_KEY;

module.exports = middleware;

// session class
var Session = {};

// create a new session
Session.create = function (role, locale, user, callback) {
    var self = this;

    self.sid = uid();
    self[roleKey] = role;
    self[userKey] = user;
    self[localeKey] = locale || 'en_US';

    // save session
    self.save(callback);
};

Session.regenerate = function () {
    // renew session with a new sid
};

Session.destroy = function (callback) {
    var self = this;

    // remove from cache
    sessionCache.rm(self.sid);

    // remove session data
    delete self.sid;
    delete self[roleKey];
    delete self[userKey];
    delete self[localeKey];

    callback();
};

Session.reload = function () {
    // reload session data
};

// save session in cache
Session.save = function (callback) {
    var self = this;

    // save session
    if (self.sid) {
        sessionCache.set(self.sid, self);
    }

    callback();
};

Session.touch = function () {
    // update expire
};

Session.maxAge = function () {
    // expire info
};

// get or create session
function middleware (connectionHandler) {

    return function (connection, response) {

        // make it compatible with ws connections
        var con = connection.upgradeReq ? connection.upgradeReq : connection;

        connection.session = null;

        // create an empty session
        var session;

        // parse cookies, get session id
        if (con.headers.cookie) {

            var parsedCookie = cookie.parse(con.headers.cookie);

            if (parsedCookie && parsedCookie[sessionCookieKey]) {

                var sessionId = parsedCookie[sessionCookieKey];

                // get session
                // TODO update expire
                session = sessionCache.get(sessionId/*, now: new Date().getTime() / 1000*/);
            }
        }

        // append session to connection
        connection.session = session || clone(Session);

        // continue with request
        connectionHandler(null, connection, response);
    };
}

// random string generator
function uid (len, string) {
    len = len || 23;
    string = string || '';

    for (var i = 0; i < len; ++i) {
        string += randomStringBase[0 | Math.random() * 62];
    }
    return string;
}
<|endoftext|>


<commit_before>/**
 * Mixin for cluster
 * @function clusterMix
 * @param {function} BaseClass - Class to mix
 * @returns {function} Mixed class
 */
'use strict'

const cluster = require('cluster')
const uuid = require('uuid')
const clayResourceName = require('clay-resource-name')

/** @lends clusterMix */
function clusterMix (BaseClass) {
  /** @class ClusterMixed */
  class ClusterMixed extends BaseClass {
    get $$clusterMixed () {
      return true
    }

    constructor () {
      super(...arguments)
      this._clusterWorkerListners = {}
      this._clusterDoneCallbacks = {}
    }

    setupCluster () {
      if (cluster.isMaster) {
        const {workers} = cluster
        for (const id of Object.keys(workers || {})) {
          this.bindClusterWorker(workers[id])
        }
        cluster.setMaxListeners(cluster.getMaxListeners() + 2)
        cluster.addListener('online', (worker) => this.bindClusterWorker(worker))
        cluster.addListener('exit', (worker) => this.unbindClusterWorker(worker))
      } else {
        const {worker} = cluster
        this.bindClusterWorker(worker)
      }
    }

    bindClusterWorker (worker) {
      const resourceName = String(clayResourceName(this))
      const listeners = {
        'message': async ({
                            $$done,
                            $$id,
                            $$clay,
                            $$from,
                            $$resource,
                            event,
                            data
                          }) => {
          if (!$$clay) {
            return
          }
          if ($$done) {
            if (cluster.isMaster) {
              this.relayToWorkers($$from, {$$clay, $$done})
            }
            const callback = this._clusterDoneCallbacks[$$done]
            callback && callback({$$done, $$from})
            delete this._clusterDoneCallbacks[$$done]
            return
          }

          if (resourceName !== $$resource) {
            return
          }

          if (/^cly:entity/.test(event)) {
            data = await this.restoreClusterEventData(data)
          }
          for (const listener of (this.listeners(event) || [])) {
            await listener(data)
          }
          if (cluster.isMaster) {
            this.relayToWorkers($$from, {$$clay, $$resource, $$from, event, data})
          }
          worker.send({$$clay, $$from, $$done: true, $$id})
        }
      }
      this._clusterWorkerListners[worker.id] = listeners
      const events = Object.keys(listeners)
      worker.setMaxListeners(worker.getMaxListeners() + events.length)
      for (const event of events) {
        worker.addListener(event, listeners[event])
      }
    }

    relayToWorkers ($$from, paylaod) {
      const {workers} = cluster

      for (const id of Object.keys(workers)) {
        const worker = workers[id]
        const skip = String(id) === String($$from)
        if (skip) {
          continue
        }
        worker.send(paylaod)
      }
    }

    unbindClusterWorker (worker) {
      const listeners = this._clusterWorkerListners[worker.id] || {}
      for (const event of Object.keys(listeners)) {
        worker.removeListener(event, listeners[event])
      }
    }

    async emitClusterEvent (event, data) {
      const $$id = uuid.v4()
      const resourceName = String(clayResourceName(this))
      return await new Promise((resolve, reject) => {
        if (cluster.isMaster) {
          const {workers} = cluster
          const workerIds = Object.keys(workers)
          if (workerIds.length === 0) {
            resolve()
            return
          }
          for (const id of workerIds) {
            const worker = workers[id]
            worker.send({$$id, $$clay: true, $$resource: resourceName, event, data})
          }
        } else {
          const {worker} = cluster
          worker.send({$$id, $$clay: true, $$resource: resourceName, $$from: worker.id, event, data})
        }
        this._clusterDoneCallbacks[$$id] = ({$$done, $$from}) => {
          resolve()
        }
      })
    }

    isClusterMaster () {
      return cluster.isMaster
    }

    async restoreClusterEventData (data) {
      const resourceName = String(clayResourceName(this))
      if (data === null) {
        return null
      }
      if (Array.isArray(data)) {
        return Promise.all(data.map((data) => this.restoreClusterEventData(data)))
      }
      switch (typeof data) {
        case 'undefined':
        case 'string':
        case 'number':
        case 'function':
        case 'boolean':
          return data
        default: {
          if (data.$$entity) {
            const {$$as, id} = data
            const Resource = $$as === resourceName ? this : this.getRef($$as)
            if (Resource) {
              const one = await Resource.one(id, {ignoreCached: true})
              if (!one) {
                console.warn(`[ClayDB][Cluster]Unknown resource "${id}" on ${$$as}`)
              }
              return one
            } else {
              const knownNames = Object.keys(this._refResources)
              console.warn(`Unknown ref: ${$$as}. (Known: ${JSON.stringify(knownNames)} )`)
              return data
            }
          } else {
            for (const key of Object.keys(data)) {
              if (/^[$_]/.test(key)) {
                continue
              }
              data[key] = await this.restoreClusterEventData(data[key])
            }
          }
        }
      }
      return data
    }
  }

  return ClusterMixed
}

module.exports = clusterMix<commit_msg>remove event listener<commit_after>/**
 * Mixin for cluster
 * @function clusterMix
 * @param {function} BaseClass - Class to mix
 * @returns {function} Mixed class
 */
'use strict'

const cluster = require('cluster')
const uuid = require('uuid')
const clayResourceName = require('clay-resource-name')

/** @lends clusterMix */
function clusterMix (BaseClass) {
  /** @class ClusterMixed */
  class ClusterMixed extends BaseClass {
    get $$clusterMixed () {
      return true
    }

    constructor () {
      super(...arguments)
      this._clusterWorkerListners = {}
      this._clusterDoneCallbacks = {}
    }

    setupCluster () {
      if (cluster.isMaster) {
        const {workers} = cluster
        for (const id of Object.keys(workers || {})) {
          this.bindClusterWorker(workers[id])
        }
        cluster.setMaxListeners(cluster.getMaxListeners() + 2)
        cluster.addListener('online', (worker) => this.bindClusterWorker(worker))
        cluster.addListener('exit', (worker) => this.unbindClusterWorker(worker))
      } else {
        const {worker} = cluster
        this.bindClusterWorker(worker)
      }
    }

    bindClusterWorker (worker) {
      const resourceName = String(clayResourceName(this))
      const listeners = {
        'message': async ({
                            $$done,
                            $$id,
                            $$clay,
                            $$from,
                            $$resource,
                            event,
                            data
                          }) => {
          if (!$$clay) {
            return
          }
          if ($$done) {
            if (cluster.isMaster) {
              this.relayToWorkers($$from, {$$clay, $$done})
            }
            const callback = this._clusterDoneCallbacks[$$done]
            callback && callback({$$done, $$from})
            delete this._clusterDoneCallbacks[$$done]
            return
          }

          if (resourceName !== $$resource) {
            return
          }

          if (/^cly:entity/.test(event)) {
            data = await this.restoreClusterEventData(data)
          }
          for (const listener of (this.listeners(event) || [])) {
            await listener(data)
          }
          if (cluster.isMaster) {
            this.relayToWorkers($$from, {$$clay, $$resource, $$from, event, data})
          }
          worker.send({$$clay, $$from, $$done: $$id})
        }
      }
      this._clusterWorkerListners[worker.id] = listeners
      const events = Object.keys(listeners)
      worker.setMaxListeners(worker.getMaxListeners() + events.length)
      for (const event of events) {
        worker.addListener(event, listeners[event])
      }
    }

    relayToWorkers ($$from, paylaod) {
      const {workers} = cluster

      for (const id of Object.keys(workers)) {
        const worker = workers[id]
        const skip = String(id) === String($$from)
        if (skip) {
          continue
        }
        worker.send(paylaod)
      }
    }

    unbindClusterWorker (worker) {
      const listeners = this._clusterWorkerListners[worker.id] || {}
      for (const event of Object.keys(listeners)) {
        worker.removeListener(event, listeners[event])
      }
    }

    async emitClusterEvent (event, data) {
      const $$id = uuid.v4()
      const resourceName = String(clayResourceName(this))
      return await new Promise((resolve, reject) => {
        if (cluster.isMaster) {
          const {workers} = cluster
          const workerIds = Object.keys(workers)
          if (workerIds.length === 0) {
            resolve()
            return
          }
          for (const id of workerIds) {
            const worker = workers[id]
            worker.send({$$id, $$clay: true, $$resource: resourceName, event, data})
          }
        } else {
          const {worker} = cluster
          worker.send({$$id, $$clay: true, $$resource: resourceName, $$from: worker.id, event, data})
        }
        this._clusterDoneCallbacks[$$id] = ({$$done, $$from}) => {
          resolve()
        }
      })
    }

    isClusterMaster () {
      return cluster.isMaster
    }

    async restoreClusterEventData (data) {
      const resourceName = String(clayResourceName(this))
      if (data === null) {
        return null
      }
      if (Array.isArray(data)) {
        return Promise.all(data.map((data) => this.restoreClusterEventData(data)))
      }
      switch (typeof data) {
        case 'undefined':
        case 'string':
        case 'number':
        case 'function':
        case 'boolean':
          return data
        default: {
          if (data.$$entity) {
            const {$$as, id} = data
            const Resource = $$as === resourceName ? this : this.getRef($$as)
            if (Resource) {
              const one = await Resource.one(id, {ignoreCached: true})
              if (!one) {
                console.warn(`[ClayDB][Cluster]Unknown resource "${id}" on ${$$as}`)
              }
              return one
            } else {
              const knownNames = Object.keys(this._refResources)
              console.warn(`Unknown ref: ${$$as}. (Known: ${JSON.stringify(knownNames)} )`)
              return data
            }
          } else {
            for (const key of Object.keys(data)) {
              if (/^[$_]/.test(key)) {
                continue
              }
              data[key] = await this.restoreClusterEventData(data[key])
            }
          }
        }
      }
      return data
    }
  }

  return ClusterMixed
}

module.exports = clusterMix<|endoftext|>


<commit_before>const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const async = require('async');
const bitcore = require('bitcore-lib');

const Coin = mongoose.model('Coin');
const WalletAddress = mongoose.model('WalletAddress');

const TransactionSchema = new Schema({
  txid: String,
  network: String,
  chain: String,
  blockHeight: Number,
  blockHash: String,
  blockTime: Date,
  blockTimeNormalized: Date,
  coinbase: Boolean,
  fee: Number,
  size: Number,
  locktime: Number,
  wallets: { type: [Schema.Types.ObjectId] },
});

TransactionSchema.index({txid: 1});
TransactionSchema.index({blockHeight: 1});
TransactionSchema.index({blockHash: 1});
TransactionSchema.index({blockTimeNormalized: 1});
TransactionSchema.index({wallets: 1}, {sparse: true});

TransactionSchema.statics.batchImport = async function (params) {
  return new Promise(async (resolve) => {
    let coinOps = await Transaction.mintCoins(params);
    coinOps.concat(Transaction.spendCoins(params));
    await Coin.collection.bulkWrite(coinOps, { ordered: true });

    let txOps = await Transaction.addTransactions(params);
    await Transaction.collection.bulkWrite(txOps);
    resolve();
  });
};

TransactionSchema.statics.addTransactions = function(params){
  let { blockHash, blockTime, blockTimeNormalized, height, network, txs } = params;
  return new Promise((resolve, reject) => {
    async.map(txs, function(tx, cb){
      Coin.aggregate([
        {
          $match: {
            $or: [
              { spentTxid: tx.hash },
              { mintTxid: tx.hash }
            ]
          }
        },
        { $unwind: '$wallets' },
        { $group: { _id: null, wallets: { $addToSet: '$wallets' } } },
        { $project: { _id: false } }
      ]).exec(function(err, wallets){
        if (err){
          return cb(err);
        }
        if (wallets.length) {
          wallets = wallets[0].wallets;
        }
        let op = {
          updateOne: {
            filter: {
              txid: tx.hash
            },
            update: {
              $set: {
                network: network,
                blockHeight: height,
                blockHash: blockHash,
                blockTime: blockTime,
                blockTimeNormalized: blockTimeNormalized,
                coinbase: tx.isCoinbase(),
                size: tx.toBuffer().length,
                locktime: tx.nLockTime,
                wallets: wallets
              }
            },
            upsert: true
          }
        };
        cb(null, op);
      });
    }, (err, txOps) => {
      if (err){
        reject(err);
      }
      resolve(txOps);
    });
  });
};

TransactionSchema.statics.mintCoins = async function (params) {
  let { height, network, txs } = params;
  let mintOps = [];
  return new Promise((resolve, reject) => {
    async.each(txs, (tx, cb) => {
      async.eachOf(tx.outputs, function(output, index, cb) {
        let address;
        try {
          address = output.script.toAddress(network).toString();
          if (address === 'false' && output.script.classify() === 'Pay to public key') {
            let hash = bitcore.crypto.Hash.sha256ripemd160(output.script.chunks[0].buf);
            address = bitcore.Address(hash, network).toString();
          }
        } catch (e) {
          address = 'noAddress';
        }

        WalletAddress.find({address}, function(err, wallets){
          if (err){
            return cb(err);
          }
          wallets = wallets.map(wallet => wallet.wallet);
          let op = {
            updateOne: {
              filter: {
                mintTxid: tx.hash,
                mintIndex: index,
                spentHeight: { $lt: 0 }
              },
              update: {
                $set: {
                  network: network,
                  mintHeight: height,
                  coinbase: tx.isCoinbase(),
                  value: output.satoshis,
                  address: address,
                  script: output.script && output.script.toBuffer(),
                  spentHeight: -2,
                  wallets: wallets
                }
              },
              upsert: true
            }
          };
          mintOps.push(op);
          cb();
        });
      }, cb);
    }, (err) => {
      if (err){
        reject(err);
      }
      resolve(mintOps);
    });
  });

};

TransactionSchema.statics.spendCoins = function (params) {
  let { height, txs } = params;
  let spendOps = [];
  for (let tx of txs) {
    if (tx.isCoinbase()) {
      continue;
    }
    for (let input of tx.inputs) {
      input = input.toObject();
      spendOps.push({
        updateOne: {
          filter: {
            mintTxid: input.prevTxId,
            mintIndex: input.outputIndex,
            spentHeight: { $lt: 0 }
          },
          update: {
            $set: {
              spentTxid: tx.hash,
              spentHeight: height
            }
          }
        }
      });
    }
  }
  return spendOps;
};

TransactionSchema.statics.getTransactions = function (params) {
  let query = params.query;
  return this.collection.aggregate([
    { $match: query },
    {
      $lookup:
        {
          from: 'coins',
          localField: 'txid',
          foreignField: 'spentTxid',
          as: 'inputs'
        }
    },
    {
      $lookup:
        {
          from: 'coins',
          localField: 'txid',
          foreignField: 'mintTxid',
          as: 'outputs'
        }
    }
  ]);
};

TransactionSchema.statics._apiTransform = function(tx, options) {
  let transform = {
    txid: tx.txid,
    network: tx.network,
    blockHeight: tx.blockHeight,
    blockHash: tx.blockHash,
    blockTime: tx.blockTime,
    blockTimeNormalized: tx.blockTimeNormalized,
    coinbase: tx.coinbase,
    fee: tx.fee,
  };
  if(options && options.object) {
    return transform;
  }
  return JSON.stringify(transform);
};

var Transaction = module.exports = mongoose.model('Transaction', TransactionSchema);<commit_msg>limits on async<commit_after>const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const async = require('async');
const bitcore = require('bitcore-lib');

const Coin = mongoose.model('Coin');
const WalletAddress = mongoose.model('WalletAddress');

const TransactionSchema = new Schema({
  txid: String,
  network: String,
  chain: String,
  blockHeight: Number,
  blockHash: String,
  blockTime: Date,
  blockTimeNormalized: Date,
  coinbase: Boolean,
  fee: Number,
  size: Number,
  locktime: Number,
  wallets: { type: [Schema.Types.ObjectId] },
});

TransactionSchema.index({txid: 1});
TransactionSchema.index({blockHeight: 1});
TransactionSchema.index({blockHash: 1});
TransactionSchema.index({blockTimeNormalized: 1});
TransactionSchema.index({wallets: 1}, {sparse: true});

TransactionSchema.statics.batchImport = async function (params) {
  return new Promise(async (resolve) => {
    let coinOps = await Transaction.mintCoins(params);
    coinOps.concat(Transaction.spendCoins(params));
    await Coin.collection.bulkWrite(coinOps, { ordered: true });

    let txOps = await Transaction.addTransactions(params);
    await Transaction.collection.bulkWrite(txOps);
    resolve();
  });
};

TransactionSchema.statics.addTransactions = function(params){
  let { blockHash, blockTime, blockTimeNormalized, height, network, txs } = params;
  return new Promise((resolve, reject) => {
    async.mapLimit(txs, 10, function(tx, cb){
      Coin.aggregate([
        {
          $match: {
            $or: [
              { spentTxid: tx.hash },
              { mintTxid: tx.hash }
            ]
          }
        },
        { $unwind: '$wallets' },
        { $group: { _id: null, wallets: { $addToSet: '$wallets' } } },
        { $project: { _id: false } }
      ]).exec(function(err, wallets){
        if (err){
          return cb(err);
        }
        if (wallets.length) {
          wallets = wallets[0].wallets;
        }
        let op = {
          updateOne: {
            filter: {
              txid: tx.hash
            },
            update: {
              $set: {
                network: network,
                blockHeight: height,
                blockHash: blockHash,
                blockTime: blockTime,
                blockTimeNormalized: blockTimeNormalized,
                coinbase: tx.isCoinbase(),
                size: tx.toBuffer().length,
                locktime: tx.nLockTime,
                wallets: wallets
              }
            },
            upsert: true
          }
        };
        cb(null, op);
      });
    }, (err, txOps) => {
      if (err){
        reject(err);
      }
      resolve(txOps);
    });
  });
};

TransactionSchema.statics.mintCoins = async function (params) {
  let { height, network, txs } = params;
  let mintOps = [];
  return new Promise((resolve, reject) => {
    async.eachLimit(txs, 10, (tx, cb) => {
      async.eachOfLimit(tx.outputs, 10, function(output, index, cb) {
        let address;
        try {
          address = output.script.toAddress(network).toString();
          if (address === 'false' && output.script.classify() === 'Pay to public key') {
            let hash = bitcore.crypto.Hash.sha256ripemd160(output.script.chunks[0].buf);
            address = bitcore.Address(hash, network).toString();
          }
        } catch (e) {
          address = 'noAddress';
        }

        WalletAddress.find({address}, function(err, wallets){
          if (err){
            return cb(err);
          }
          wallets = wallets.map(wallet => wallet.wallet);
          let op = {
            updateOne: {
              filter: {
                mintTxid: tx.hash,
                mintIndex: index,
                spentHeight: { $lt: 0 }
              },
              update: {
                $set: {
                  network: network,
                  mintHeight: height,
                  coinbase: tx.isCoinbase(),
                  value: output.satoshis,
                  address: address,
                  script: output.script && output.script.toBuffer(),
                  spentHeight: -2,
                  wallets: wallets
                }
              },
              upsert: true
            }
          };
          mintOps.push(op);
          cb();
        });
      }, cb);
    }, (err) => {
      if (err){
        reject(err);
      }
      resolve(mintOps);
    });
  });

};

TransactionSchema.statics.spendCoins = function (params) {
  let { height, txs } = params;
  let spendOps = [];
  for (let tx of txs) {
    if (tx.isCoinbase()) {
      continue;
    }
    for (let input of tx.inputs) {
      input = input.toObject();
      spendOps.push({
        updateOne: {
          filter: {
            mintTxid: input.prevTxId,
            mintIndex: input.outputIndex,
            spentHeight: { $lt: 0 }
          },
          update: {
            $set: {
              spentTxid: tx.hash,
              spentHeight: height
            }
          }
        }
      });
    }
  }
  return spendOps;
};

TransactionSchema.statics.getTransactions = function (params) {
  let query = params.query;
  return this.collection.aggregate([
    { $match: query },
    {
      $lookup:
        {
          from: 'coins',
          localField: 'txid',
          foreignField: 'spentTxid',
          as: 'inputs'
        }
    },
    {
      $lookup:
        {
          from: 'coins',
          localField: 'txid',
          foreignField: 'mintTxid',
          as: 'outputs'
        }
    }
  ]);
};

TransactionSchema.statics._apiTransform = function(tx, options) {
  let transform = {
    txid: tx.txid,
    network: tx.network,
    blockHeight: tx.blockHeight,
    blockHash: tx.blockHash,
    blockTime: tx.blockTime,
    blockTimeNormalized: tx.blockTimeNormalized,
    coinbase: tx.coinbase,
    fee: tx.fee,
  };
  if(options && options.object) {
    return transform;
  }
  return JSON.stringify(transform);
};

var Transaction = module.exports = mongoose.model('Transaction', TransactionSchema);<|endoftext|>


<commit_before>var methods = {};

var validateOne = function(validatorFunction, fieldName) {
  // Get field value.
  var value = this.get(fieldName);

  try {
    // Run validator function which throws exception which we have to catch and
    // add error message to appropriate field.
    validatorFunction.call(this, fieldName, value);

    // If a field's validation didn't throw an exception then clear an error
    // message for the given field.
    this._errors.delete(fieldName);

    return true;
  } catch (e) {
    if (e instanceof ValidationError) {
      this._errors.set(fieldName, e.message);
    } else {
      throw e;
    }

    return false;
  }
};

methods.validate = function(fieldName) {
  // Run validation for current and parent classes. The "isValid" variable will
  // be set to true when all validators for all classes passed validation.
  var isValid = Astro.utils.everyClass(this.constructor, function(Class) {
    // Get list of all validators for given class.
    var validators = Class.getValidators();

    if (fieldName) {
      // Get validation function for given field name.
      var validatorFunction = validators[fieldName];

      // Validate field only if there is validation function.
      if (validatorFunction) {
        return validateOne.call(this, validatorFunction, fieldName);
      } else {
        return true;
      }
    } else {
      // Take validators and run each one.
      return _.every(validators, validateOne, this);
    }
  }, this);

  return isValid;
};

methods.getValidationError = function(fieldName) {
  return this._errors.get(fieldName);
};

methods.getValidationErrors = function() {
  return this._errors.all();
};

methods.hasValidationError = function(fieldName) {
  return this._errors.has(fieldName);
};

methods.hasValidationErrors = function() {
  return this._errors.size > 0;
};

methods.throwValidationException = function() {
  throw new Meteor.Error('validation-error', this.getValidationErrors());
};

methods.catchValidationException = function(exception) {
  if (!(exception instanceof Meteor.Error) ||
    exception.error !== 'validation-error' ||
    !_.isObject(exception.reason)
  ) {
    return;
  }

  this._errors.set(exception.reason);
};

var events = {};

events.tojsonvalue = function(e) {
  var self = this;

  Tracker.nonreactive(function() {
    e.data.errors = self._errors.all();
  });
};

events.fromjsonvalue = function(e) {
  this._errors.set(e.data.errors);
};

onInitModule = function() {
  _.extend(Astro.BaseClass.prototype, methods);

  Astro.on('tojsonvalue', events.tojsonvalue);
  Astro.on('fromjsonvalue', events.fromjsonvalue);
};
<commit_msg>Change events names case<commit_after>var methods = {};

var validateOne = function(validatorFunction, fieldName) {
  // Get field value.
  var value = this.get(fieldName);

  try {
    // Run validator function which throws exception which we have to catch and
    // add error message to appropriate field.
    validatorFunction.call(this, fieldName, value);

    // If a field's validation didn't throw an exception then clear an error
    // message for the given field.
    this._errors.delete(fieldName);

    return true;
  } catch (e) {
    if (e instanceof ValidationError) {
      this._errors.set(fieldName, e.message);
    } else {
      throw e;
    }

    return false;
  }
};

methods.validate = function(fieldName) {
  // Run validation for current and parent classes. The "isValid" variable will
  // be set to true when all validators for all classes passed validation.
  var isValid = Astro.utils.everyClass(this.constructor, function(Class) {
    // Get list of all validators for given class.
    var validators = Class.getValidators();

    if (fieldName) {
      // Get validation function for given field name.
      var validatorFunction = validators[fieldName];

      // Validate field only if there is validation function.
      if (validatorFunction) {
        return validateOne.call(this, validatorFunction, fieldName);
      } else {
        return true;
      }
    } else {
      // Take validators and run each one.
      return _.every(validators, validateOne, this);
    }
  }, this);

  return isValid;
};

methods.getValidationError = function(fieldName) {
  return this._errors.get(fieldName);
};

methods.getValidationErrors = function() {
  return this._errors.all();
};

methods.hasValidationError = function(fieldName) {
  return this._errors.has(fieldName);
};

methods.hasValidationErrors = function() {
  return this._errors.size > 0;
};

methods.throwValidationException = function() {
  throw new Meteor.Error('validation-error', this.getValidationErrors());
};

methods.catchValidationException = function(exception) {
  if (!(exception instanceof Meteor.Error) ||
    exception.error !== 'validation-error' ||
    !_.isObject(exception.reason)
  ) {
    return;
  }

  this._errors.set(exception.reason);
};

var events = {};

events.toJsonValue = function(e) {
  var self = this;

  Tracker.nonreactive(function() {
    e.data.errors = self._errors.all();
  });
};

events.fromJsonValue = function(e) {
  this._errors.set(e.data.errors);
};

onInitModule = function() {
  _.extend(Astro.BaseClass.prototype, methods);

  Astro.on('toJsonValue', events.toJsonValue);
  Astro.on('fromJsonValue', events.fromJsonValue);
};
<|endoftext|>


<commit_before>import { expect } from 'chai';
import { context, beforeEach, describe, it } from 'mocha';
import { setupComponentTest } from 'ember-mocha';
import hbs from 'htmlbars-inline-precompile';
import { make, manualSetup } from 'ember-data-factory-guy';

let broadcast, intl;

describe('Integration | Component | decision card', function() {
  setupComponentTest('decision-card', {
    integration: true,
    setup() {
      intl = this.container.lookup('service:intl');
      intl.setLocale('en');
    }
  });
  beforeEach(function(){
    manualSetup(this.container);
    broadcast = make('broadcast', {
      title: 'This is the title'
    });
  });

  it('renders', function() {
    this.set('broadcast', broadcast);
    this.render(hbs`{{decision-card broadcast=broadcast}}`);
    expect(this.$()).to.have.length(1);
  });

  it('displays the title', function() {
    this.set('broadcast', broadcast);
    this.render(hbs`{{decision-card broadcast=broadcast}}`);
    expect(this.$('.title').text().trim()).to.eq('This is the title');
  });

  describe('click on support button', function() {
    it('turns the heart red', function() {
      this.set('broadcast', broadcast);
      this.render(hbs`{{decision-card broadcast=broadcast}}`);
      expect(this.$('.decision-card-support-button i.icon.heart').hasClass('red')).to.be.false;
      this.$('.decision-card-support-button').click();
      expect(this.$('.decision-card-support-button i.icon.heart').hasClass('red')).to.be.true;
    });

    it('saves the response on the broadcast', function() {
      this.set('broadcast', broadcast);
      this.render(hbs`{{decision-card broadcast=broadcast}}`);
      expect(broadcast.get('response')).to.be.undefined;
      this.$('.decision-card-support-button').click();
      expect(broadcast.get('response')).to.eq('positive');
    });

    it('calls respondAction and returns the broadcast, if respondAction given', function(done) {
      let broadcast = make('broadcast', {
        title: 'this title is to be expected'
      });
      this.set('broadcast', broadcast);
      this.set('callback', function(broadcast) {
        expect(broadcast.get('title')).to.eq('this title is to be expected');
        done();
      });
      this.render(hbs`{{decision-card broadcast=broadcast respondAction=callback}}`);
      this.$('.decision-card-support-button').click();
    });

    context('broadcast is already supported', function() {
      beforeEach(function(){
        make('impression', {
          broadcast: broadcast,
          response: 'positive'
        });
      });

      it('turns the heart grey', function() {
        this.set('broadcast', broadcast);
        this.render(hbs`{{decision-card broadcast=broadcast}}`);
        expect(this.$('.decision-card-support-button i.icon.heart').hasClass('red')).to.be.true;
        this.$('.decision-card-support-button').click();
        expect(this.$('.decision-card-support-button i.icon.heart').hasClass('red')).to.be.false;
      });

      it('toggles the response on the broadcast', function() {
        this.set('broadcast', broadcast);
        this.render(hbs`{{decision-card broadcast=broadcast}}`);
        expect(broadcast.get('response')).to.eq('positive');
        this.$('.decision-card-support-button').click();
        expect(broadcast.get('response')).to.eq('neutral');
      });
    });
  });
});
<commit_msg>integration test decision card when unauthenticated<commit_after>import Ember from 'ember';
import { expect } from 'chai';
import { context, beforeEach, describe, it } from 'mocha';
import { setupComponentTest } from 'ember-mocha';
import hbs from 'htmlbars-inline-precompile';
import { make, manualSetup } from 'ember-data-factory-guy';

let broadcast, intl;
const sessionStub = Ember.Service.extend({
  isAuthenticated: true,
});

describe('Integration | Component | decision card', function() {
  setupComponentTest('decision-card', {
    integration: true,
    setup() {
      intl = this.container.lookup('service:intl');
      intl.setLocale('en');
    }
  });

  beforeEach(function(){
    manualSetup(this.container);
    broadcast = make('broadcast', {
      title: 'This is the title'
    });
  });

  context('authenticated', function() {
    describe('click on support button', function() {
      it('calls the login action', function(done) {
        this.set('broadcast', broadcast);
        this.set('loginAction', function() {
          done();
        });
        this.render(hbs`{{decision-card broadcast=broadcast login=loginAction}}`);
        this.$('.decision-card-support-button').click();
      });

      it('keeps the response to the broadcast unchanged', function() {
        this.set('broadcast', broadcast);
        this.render(hbs`{{decision-card broadcast=broadcast}}`);
        this.$('.decision-card-support-button').click();
        expect(broadcast.get('response')).to.be.undefined;
      });
    });
  });

  context('authenticated', function() {
    beforeEach(function(){
      this.register('service:session', sessionStub);
      this.inject.service('session', { as: 'sessionService' });
    });

    it('renders', function() {
      this.set('broadcast', broadcast);
      this.render(hbs`{{decision-card broadcast=broadcast}}`);
      expect(this.$()).to.have.length(1);
    });

    it('displays the title', function() {
      this.set('broadcast', broadcast);
      this.render(hbs`{{decision-card broadcast=broadcast}}`);
      expect(this.$('.title').text().trim()).to.eq('This is the title');
    });

    describe('click on support button', function() {
      it('turns the heart red', function() {
        this.set('broadcast', broadcast);
        this.render(hbs`{{decision-card broadcast=broadcast}}`);
        expect(this.$('.decision-card-support-button i.icon.heart').hasClass('red')).to.be.false;
        this.$('.decision-card-support-button').click();
        expect(this.$('.decision-card-support-button i.icon.heart').hasClass('red')).to.be.true;
      });

      it('saves the response on the broadcast', function() {
        this.set('broadcast', broadcast);
        this.render(hbs`{{decision-card broadcast=broadcast}}`);
        expect(broadcast.get('response')).to.be.undefined;
        this.$('.decision-card-support-button').click();
        expect(broadcast.get('response')).to.eq('positive');
      });

      it('calls respondAction and returns the broadcast, if respondAction given', function(done) {
        let broadcast = make('broadcast', {
          title: 'this title is to be expected'
        });
        this.set('broadcast', broadcast);
        this.set('callback', function(broadcast) {
          expect(broadcast.get('title')).to.eq('this title is to be expected');
          done();
        });
        this.render(hbs`{{decision-card broadcast=broadcast respondAction=callback}}`);
        this.$('.decision-card-support-button').click();
      });

      context('broadcast is already supported', function() {
        beforeEach(function(){
          make('impression', {
            broadcast: broadcast,
            response: 'positive'
          });
        });

        it('turns the heart grey', function() {
          this.set('broadcast', broadcast);
          this.render(hbs`{{decision-card broadcast=broadcast}}`);
          expect(this.$('.decision-card-support-button i.icon.heart').hasClass('red')).to.be.true;
          this.$('.decision-card-support-button').click();
          expect(this.$('.decision-card-support-button i.icon.heart').hasClass('red')).to.be.false;
        });

        it('toggles the response on the broadcast', function() {
          this.set('broadcast', broadcast);
          this.render(hbs`{{decision-card broadcast=broadcast}}`);
          expect(broadcast.get('response')).to.eq('positive');
          this.$('.decision-card-support-button').click();
          expect(broadcast.get('response')).to.eq('neutral');
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>var aacolor = d3.scale.ordinal()
	.range(['#CCFF00','#FFFF00','#FF0000','#FF0066','#00FF66','#FF9900','#0066FF',
		'#66FF00','#6600FF','#33FF00','#00FF00','#CC00FF','#FFCC00','#FF00CC',
		'#0000FF','#FF3300','#FF6600','#99FF00','#00CCFF','#00FFCC','#000000'])
	.domain(['A','C','D','E','F','G','H','I','K','L','M',
		'N','P','Q','R','S','T','V','W','Y','-']);
var barmargin = {top: 5, right: 10, bottom: 0, left: 10},
	barwidth = 200,
	barheight = 20,
	barpadding = .1;
var barchartmargin = {top: 15, right: 100, bottom: 10, left: 50},
	barchartwidth = 250,
	barchartheight = 70;

var plac_scale = d3.scale.linear()
	.range([0, barwidth]);
var vac_scale = d3.scale.linear()
	.range([0, barwidth]);

var selected_sites = [];

/** Generate visualization */
function generateVis(){

	plac_scale.domain([0, numplac]);
	vac_scale.domain([0, numvac]);

	generateSiteSelector();
	drawPyramid([]);
}

function generateSiteSelector() {
	var margin =  {top: 10, right: 10, bottom: 100, left: 20},
		margin2 = {top: 150, right: 10, bottom: 20, left: 20},
		width = 500 - margin.left - margin.right,
		height =  200 - margin.top - margin.bottom,
		height2 = 200 - margin2.top - margin2.bottom;

	var xScale = d3.scale.linear()
			.domain([0, vaccine.sequence.length])
			.range([0, width]),
		x2Scale = d3.scale.linear()
			.domain(xScale.domain())
			.range([0, width]),
		yScale = d3.scale.linear()
			.domain([0, 1])
			.range([height, 0]),
		y2Scale = d3.scale.linear()
			.domain(yScale.domain())
			.range([height2, 0]);

	var xAxis = d3.svg.axis()
			.scale(xScale)
			.orient("bottom"),
		x2Axis = d3.svg.axis()
			.scale(x2Scale)
			.orient("bottom");

	var x2brush = d3.svg.brush()
			.x(x2Scale)
			.on("brush", brushed);

	var barwidth = xScale.range()[1] / d3.max(xScale.domain());
			// = totalwidth/numbars

	var siteselSVG = d3.select("#overview").append("svg")
	    .attr("width", width + margin.left + margin.right)
	    .attr("height", height + margin.top + margin.bottom)
		.attr("id", "siteselSVG");

	siteselSVG.append("defs").append("clipPath")
		.attr("id", "clip")
		.append("rect")
			.attr("width", width)
			.attr("height", height);
	// focus group
	var focus = siteselSVG.append("g")
		.attr("class", "focus")
		.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
	// context group
	var context = siteselSVG.append("g")
		.attr("class", "context")
		.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");

	// append focus drawings (we could use y-dimension for an encoding)
	var focusbars = focus.selectAll(".sitebar")
	    .data(vaccine.sequence)
	  .enter().append("rect")
	  	.attr("class", "focus sitebar")
	    .attr("x", function (d,i) { return xScale(i) - barwidth/2; })
		.attr("y", yScale(1))
		.attr("width", barwidth)
		.attr("height", height - yScale(1))
		.attr("fill", function (d) {
			return aacolor(d);
		})
		.attr("opacity", 0.5)
    .on("click",doOnClick);
	// append focus axis
	focus.append("g")
		.attr("class", "x axis")
		.attr("transform", "translate(0," + (height + 5) + ")")
		.call(xAxis);

	// append context drawings
	var contextbars = context.selectAll(".sitebar")
	    .data(vaccine.sequence)
	  .enter().append("rect")
	  	.attr("class", "context sitebar")
	    .attr("x", function (d,i) { return x2Scale(i) - barwidth/2; })
		.attr("y", y2Scale(1))
		.attr("width", barwidth)
		.attr("height", height2 - y2Scale(1))
		.attr("fill", "steelblue")
		.attr("opacity", 0.5);
	// append context axis
	context.append("g")
		.attr("class", "x axis")
		.attr("transform", "translate(0," + (height2 + 2) + ")")
		.call(x2Axis);
	// append context brush
	context.append("g")
		.attr("class", "x brush")
		.call(x2brush)
		.selectAll("rect")
		.attr("y", -6)
		.attr("height", height2 + 7);

	var sitelist_svg = d3.select("#overview").append("svg")
		.attr("width", width + margin.left + margin.right)
		.attr("height", 0);

	// draw charts for initial (default) selection
	selected_sites = [];
	update_AAsites(selected_sites);
	updatePyramid(selected_sites);
	//update_sitelisttext(selected_sites);
  generateTable();

	function brushed() {
		var extent0 = x2brush.extent(),
			extent1;
		// if dragging, preserve width
		if (d3.event.mode === "move") {
			var d0 = Math.round(extent0[0]),
				d1 = Math.round(extent0[1]);
			extent1 = [d0, d1];
		}
		// otherwise, if resizing, round both sides
		else {
			extent1 = extent0.map(function(d) {return Math.round(d); });

			// in case empty when rounded, use floor & ceil instead
			if (extent1[0] >= extent1[1]) {
				extent1[0] = Math.floor(extent0[0]);
				extent1[1] = Math.ceil(extent0[1]);
			}
		}

		// redefine xScale's domain, barwidth
		xScale.domain(extent1);
		barwidth = (xScale.range()[1] - xScale.range()[0]) / (extent1[1] - extent1[0]);

		// redraw bars
		focusbars = focus.selectAll(".sitebar") // selection
			.data(vaccine.sequence.slice(x2brush.extent()[0], x2brush.extent()[1] + 1)/*, function(d,i) { return (i+x2brush.extent()[0]); }*/);

		focusbars.transition() // updaters
			.attr("transform", function (d,i) { return "translate(" + (xScale(i) - barwidth/2) +  ",0)"; })
			.attr("width", barwidth);
		focusbars.exit().transition() //exiters
			.attr("transform", function (d,i) { return "translate(" + (xScale(i) - barwidth/2) +  ",0)"; })
			.remove();
		focusbars.enter().append("rect") //enterers
	  		.attr("class", "focus sitebar")
	    	.attr("transform", function (d,i) { return "translate(" + (xScale(i) - barwidth/2) +  ",0)"; })
			.attr("width", barwidth)
			.attr("height", height - yScale(1))
			.attr("fill", function (d) {
				return aacolor(d);
			})
			.attr("opacity", 0.5)
    		.on("click",doOnClick);

		// redraw axis
		focus.select(".x.axis").call(xAxis);
		// then update the brush's extent
		d3.select(this).call(x2brush.extent(extent1));
	}

	function doOnClick(d, i) {
		if (!d3.select(this).classed("selected")) { // if not selected
			// add to and sort array
			selected_sites.push(i);
			selected_sites.sort();
			// change formatting and set selected to true
			d3.select(this)
				.attr("opacity", 1)
				.attr("y", yScale(1.25))
				.classed("selected",true);
		} else { // if already selected
			// remove from array
			var index = selected_sites.indexOf(i);
			selected_sites.splice(index, 1);
			// reset formatting, set selected to false
			d3.select(this)
				.attr('opacity', 0.5)
				.attr("y", yScale(1))
				.classed("selected",false);
		}
		update_AAsites(selected_sites);
		updatePyramid(selected_sites);
    	updateTable(selected_sites);
	}

	/* Demo for logging keystrokes. May be useful
		later in site selection functionality

	d3.select("body")
    .on("keydown", function() {
        d3.select("#seqchart").append("text")
            .attr("x", (width/2) + "px")
            .attr("y", (height) + "px")
            .style("font-size","50px")
            .text("keyCode: " + d3.event.keyCode)
          .transition().duration(2000)
            .style("font-size","5px")
            .style("fill-opacity",".1")
          .remove();
    });*/
}<commit_msg>Works for selections at the left end of the axis<commit_after>var aacolor = d3.scale.ordinal()
	.range(['#CCFF00','#FFFF00','#FF0000','#FF0066','#00FF66','#FF9900','#0066FF',
		'#66FF00','#6600FF','#33FF00','#00FF00','#CC00FF','#FFCC00','#FF00CC',
		'#0000FF','#FF3300','#FF6600','#99FF00','#00CCFF','#00FFCC','#000000'])
	.domain(['A','C','D','E','F','G','H','I','K','L','M',
		'N','P','Q','R','S','T','V','W','Y','-']);
var barmargin = {top: 5, right: 10, bottom: 0, left: 10},
	barwidth = 200,
	barheight = 20,
	barpadding = .1;
var barchartmargin = {top: 15, right: 100, bottom: 10, left: 50},
	barchartwidth = 250,
	barchartheight = 70;

var plac_scale = d3.scale.linear()
	.range([0, barwidth]);
var vac_scale = d3.scale.linear()
	.range([0, barwidth]);

var selected_sites = [];

/** Generate visualization */
function generateVis(){

	plac_scale.domain([0, numplac]);
	vac_scale.domain([0, numvac]);

	generateSiteSelector();
	drawPyramid([]);
}

function generateSiteSelector() {
	var margin =  {top: 10, right: 10, bottom: 100, left: 20},
		margin2 = {top: 150, right: 10, bottom: 20, left: 20},
		width = 500 - margin.left - margin.right,
		height =  200 - margin.top - margin.bottom,
		height2 = 200 - margin2.top - margin2.bottom;

	var xScale = d3.scale.linear()
			.domain([0, vaccine.sequence.length])
			.range([0, width]),
		x2Scale = d3.scale.linear()
			.domain(xScale.domain())
			.range([0, width]),
		yScale = d3.scale.linear()
			.domain([0, 1])
			.range([height, 0]),
		y2Scale = d3.scale.linear()
			.domain(yScale.domain())
			.range([height2, 0]);

	var xAxis = d3.svg.axis()
			.scale(xScale)
			.orient("bottom"),
		x2Axis = d3.svg.axis()
			.scale(x2Scale)
			.orient("bottom");

	var x2brush = d3.svg.brush()
			.x(x2Scale)
			.on("brush", brushed);

	var sitebarwidth = xScale.range()[1] / d3.max(xScale.domain());
			// = totalwidth/numbars

	var siteselSVG = d3.select("#overview").append("svg")
	    .attr("width", width + margin.left + margin.right)
	    .attr("height", height + margin.top + margin.bottom)
		.attr("id", "siteselSVG");

	siteselSVG.append("defs").append("clipPath")
		.attr("id", "clip")
		.append("rect")
			.attr("width", width)
			.attr("height", height);
	// focus group
	var focus = siteselSVG.append("g")
		.attr("class", "focus")
		.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
	// context group
	var context = siteselSVG.append("g")
		.attr("class", "context")
		.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");

	// append focus drawings (we could use y-dimension for an encoding)
	var focusbars = focus.selectAll(".sitebar")
	    .data(vaccine.sequence)
	  .enter().append("rect")
	  	.attr("class", "focus sitebar")
	    .attr("transform", function (d,i) { return "translate(" + (xScale(i) - sitebarwidth/2) +  ",0)"; })
		.attr("width", sitebarwidth)
		.attr("height", height - yScale(1))
		.attr("fill", function (d) {
			return aacolor(d);
		})
		.attr("opacity", 0.5)
    .on("click",doOnClick);
	// append focus axis
	focus.append("g")
		.attr("class", "x axis")
		.attr("transform", "translate(0," + (height + 5) + ")")
		.call(xAxis);

	// append context drawings
	var contextbars = context.selectAll(".sitebar")
	    .data(vaccine.sequence)
	  .enter().append("rect")
	  	.attr("class", "context sitebar")
	    .attr("x", function (d,i) { return x2Scale(i) - sitebarwidth/2; })
		.attr("y", y2Scale(1))
		.attr("width", sitebarwidth)
		.attr("height", height2 - y2Scale(1))
		.attr("fill", "steelblue")
		.attr("opacity", 0.5);
	// append context axis
	context.append("g")
		.attr("class", "x axis")
		.attr("transform", "translate(0," + (height2 + 2) + ")")
		.call(x2Axis);
	// append context brush
	context.append("g")
		.attr("class", "x brush")
		.call(x2brush)
		.selectAll("rect")
		.attr("y", -6)
		.attr("height", height2 + 7);

	var sitelist_svg = d3.select("#overview").append("svg")
		.attr("width", width + margin.left + margin.right)
		.attr("height", 0);

	// draw charts for initial (default) selection
	selected_sites = [];
	update_AAsites(selected_sites);
	updatePyramid(selected_sites);
	//update_sitelisttext(selected_sites);
  generateTable();

	function brushed() {
		var extent0 = x2brush.extent(),
			extent1;
		// if dragging, preserve width
		if (d3.event.mode === "move") {
			var d0 = Math.round(extent0[0]),
				d1 = Math.round(extent0[1]);
			extent1 = [d0, d1];
		}
		// otherwise, if resizing, round both sides
		else {
			extent1 = extent0.map(function(d) {return Math.round(d); });

			// in case empty when rounded, use floor & ceil instead
			if (extent1[0] >= extent1[1]) {
				extent1[0] = Math.floor(extent0[0]);
				extent1[1] = Math.ceil(extent0[1]);
			}
		}

		// redefine xScale's domain, barwidth, brush's extent
		xScale.domain(extent1);
		sitebarwidth = (xScale.range()[1] - xScale.range()[0]) / (extent1[1] - extent1[0]);
		d3.select(this).call(x2brush.extent(extent1));

		// redraw bars
		var newfocusbars = focus.selectAll(".sitebar") // selection
			.data(vaccine.sequence.slice(extent1[0], extent1[1] + 1)/*, function(d,i) { return (i+x2brush.extent()[0]); }*/);

		newfocusbars.transition() // updaters
			.attr("transform", function (d,i) { return "translate(" + (xScale(i) - sitebarwidth/2) +  ",0)"; })
			.attr("width", sitebarwidth);
		newfocusbars.exit().transition() //exiters
			.attr("transform", function (d,i) { return "translate(" + (xScale(i) - sitebarwidth/2) +  ",0)"; })
			.remove();
		newfocusbars.enter().append("rect") //enterers
	  		.attr("class", "focus sitebar")
	    	.attr("transform", function (d,i) { return "translate(" + (xScale(i) - sitebarwidth/2) +  ",0)"; })
			.attr("width", sitebarwidth)
			.attr("height", height - yScale(1))
			.attr("fill", function (d) {
				return aacolor(d);
			})
			.attr("opacity", 0.5)
    		.on("click",doOnClick);

		// redraw axis
		focus.select(".x.axis").call(xAxis);
	}

	function doOnClick(d, i) {
		if (!d3.select(this).classed("selected")) { // if not selected
			// add to and sort array
			selected_sites.push(i);
			selected_sites.sort();
			// change formatting and set selected to true
			d3.select(this)
				.attr("opacity", 1)
				.attr("y", yScale(1.25))
				.classed("selected",true);
		} else { // if already selected
			// remove from array
			var index = selected_sites.indexOf(i);
			selected_sites.splice(index, 1);
			// reset formatting, set selected to false
			d3.select(this)
				.attr('opacity', 0.5)
				.attr("y", yScale(1))
				.classed("selected",false);
		}
		update_AAsites(selected_sites);
		updatePyramid(selected_sites);
    	updateTable(selected_sites);
	}

	/* Demo for logging keystrokes. May be useful
		later in site selection functionality

	d3.select("body")
    .on("keydown", function() {
        d3.select("#seqchart").append("text")
            .attr("x", (width/2) + "px")
            .attr("y", (height) + "px")
            .style("font-size","50px")
            .text("keyCode: " + d3.event.keyCode)
          .transition().duration(2000)
            .style("font-size","5px")
            .style("fill-opacity",".1")
          .remove();
    });*/
}<|endoftext|>


<commit_before>app.factory('DownloadService', ['$http', 'PollingService', function($http, PollingService) {

    /* get the base url */

    var baseurl = angular.element('meta[name="baseurl"]').attr('content');

    /* configure urls */

    var base_download_url = baseurl + 'query/api/jobs/';

    var service = {
        pending_downloads: 0
    };

    service.init = function(opt) {
        service.job = opt.job;

        service.archive_columns = [];

        angular.forEach(service.job.columns, function(column) {
            angular.forEach(['meta.note', 'meta.preview', 'meta.file'], function(key) {
                if (column.ucd && column.ucd.indexOf(key) > -1) {
                    service.archive_columns.push(column.name);
                }
            });
        });
    }

    service.start_download = function(format_key) {
        service.job.download_failed = false;

        var url = base_download_url + service.job.id + '/download/';
        $http.post(url, {
            format_key: format_key
        }).then(function(result) {
            var download_id = result.data.id;

            service.pending_downloads++;
            PollingService.register(download_id, service.poll_download, {
                job: service.job,
                download_id: download_id
            });
        }, function() {
            // display error message
            service.job.download_failed = true;
        });
    };

    service.poll_download = function(options) {
        var url = base_download_url + options.job.id + '/download/' + options.download_id + '/';
        $http.get(url + '?download=').then(function(result) {
            if (result.data == 'COMPLETED') {
                service.pending_downloads--;
                PollingService.unregister(options.download_id);

                // download the file, headers will prevent the browser reloading the page
                window.location.href = url;
            } else {
                service.pending_downloads--;
                PollingService.unregister(options.download_id);

                // display error message
                options.job.download_failed = true;
            }
        }, function() {
            service.pending_downloads--;
            PollingService.unregister(options.download_id);

            // display error message
            options.job.download_failed = true;
        });
    };

    service.start_archive = function(column_name) {
        service.job.download_failed = false;

        var url = base_download_url + service.job.id + '/archive/';
        $http.post(url, {
            column_name: column_name
        }).then(function(result) {
            var archive_id = result.data.id;

            service.pending_downloads++;
            PollingService.register(archive_id, service.poll_archive, {
                job: service.job,
                archive_id: archive_id
            });
        }, function() {
            // display error message
            service.job.download_failed = true;
        });
    };

    service.poll_archive = function(options) {
        var url = base_download_url + options.job.id + '/archive/' + options.archive_id + '/';
        $http.get(url + '?download=').then(function(result) {
            if (result.data == 'COMPLETED') {
                service.pending_downloads--;
                PollingService.unregister(options.archive_id);

                // download the file, headers will prevent the browser reloading the page
                window.location.href = url;
            }
        }, function() {
            service.pending_downloads--;
            PollingService.unregister(options.archive_id);

            // display error message
            options.job.download_failed = true;
        });
    };

    return service;
}]);
<commit_msg>Fix download.js<commit_after>app.factory('DownloadService', ['$http', 'PollingService', function($http, PollingService) {

    /* get the base url */

    var baseurl = angular.element('meta[name="baseurl"]').attr('content');

    /* configure urls */

    var base_download_url = baseurl + 'query/api/jobs/';

    var service = {
        pending_downloads: 0
    };

    service.init = function(opt) {
        service.job = opt.job;

        service.archive_columns = [];

        angular.forEach(service.job.columns, function(column) {
            angular.forEach(['meta.note', 'meta.preview', 'meta.file'], function(key) {
                if (column.ucd && column.ucd.indexOf(key) > -1) {
                    service.archive_columns.push(column.name);
                }
            });
        });
    }

    service.start_download = function(format_key) {
        service.job.download_failed = false;

        var url = base_download_url + service.job.id + '/download/';
        $http.post(url, {
            format_key: format_key
        }).then(function(result) {
            var download_id = result.data.id;

            service.pending_downloads++;
            PollingService.register(download_id, service.poll_download, {
                job: service.job,
                download_id: download_id
            });
        }, function() {
            // display error message
            service.job.download_failed = true;
        });
    };

    service.poll_download = function(options) {
        var url = base_download_url + options.job.id + '/download/' + options.download_id + '/';
        $http.get(url + '?download=').then(function(result) {
            if (result.data == 'COMPLETED') {
                service.pending_downloads--;
                PollingService.unregister(options.download_id);

                // download the file, headers will prevent the browser reloading the page
                window.location.href = url;
            } else if (result.data == 'ERROR') {
                service.pending_downloads--;
                PollingService.unregister(options.download_id);

                // display error message
                options.job.download_failed = true;
            }
        }, function() {
            service.pending_downloads--;
            PollingService.unregister(options.download_id);

            // display error message
            options.job.download_failed = true;
        });
    };

    service.start_archive = function(column_name) {
        service.job.download_failed = false;

        var url = base_download_url + service.job.id + '/archive/';
        $http.post(url, {
            column_name: column_name
        }).then(function(result) {
            var archive_id = result.data.id;

            service.pending_downloads++;
            PollingService.register(archive_id, service.poll_archive, {
                job: service.job,
                archive_id: archive_id
            });
        }, function() {
            // display error message
            service.job.download_failed = true;
        });
    };

    service.poll_archive = function(options) {
        var url = base_download_url + options.job.id + '/archive/' + options.archive_id + '/';
        $http.get(url + '?download=').then(function(result) {
            if (result.data == 'COMPLETED') {
                service.pending_downloads--;
                PollingService.unregister(options.archive_id);

                // download the file, headers will prevent the browser reloading the page
                window.location.href = url;
            } else if (result.data == 'ERROR') {
                service.pending_downloads--;
                PollingService.unregister(options.download_id);

                // display error message
                options.job.download_failed = true;
            }
        }, function() {
            service.pending_downloads--;
            PollingService.unregister(options.archive_id);

            // display error message
            options.job.download_failed = true;
        });
    };

    return service;
}]);
<|endoftext|>


<commit_before>define([
  'jquery',
  'backbone',
  'template'
], function(
  $,
  Backbone,
  Template
){

  var EventEditNewsItemView = Backbone.View.extend({

    template: Template['app/template/event-edit-newsitem.hbs'],

    tagName: 'li',

    initialize: function (model) {
      this.model = model;
    },

    addNewsItemToStoryline: function(){
      var identifier = this.model.newsItem.identifier;
      var isInArray = false;
      this.model.projectStoryline.newsItems.forEach(function(newsItem){
        if (newsItem.identifier === identifier) {
          isInArray = true;
        }
      });
      if (!isInArray) {
        this.model.projectStoryline.newsItems.push(this.model.newsItem);
      }
    },

    removeNewsItemFromStoryline: function(){
      var identifier = this.model.newsItem.identifier;
      var isInArray = false, matchedIndex;
      this.model.projectStoryline.newsItems.forEach(function(newsItem, index){
        if (newsItem.identifier === identifier) {
          isInArray = true;
          matchedIndex = index;
        }
      });
      if (isInArray) {
        this.model.projectStoryline.newsItems = this.model.projectStoryline.newsItems.slice(matchedIndex, matchedIndex);
      }
    },

    render: function () {
      var _this = this;
      var html = this.template(this.model.newsItem);
      $(this.el).append(html);
      $(this.el).find('input').on('change', function(){
        var value = _this.$el.find('input').is(':checked');
        if (value) {
          _this.addNewsItemToStoryline();
        } else {
          _this.removeNewsItemFromStoryline();
        }
      });
      return this;
    }

  });

  var EventEditNewsItemsView = Backbone.View.extend({

    template: Template['app/template/event-edit-newsitems.hbs'],

    el: "#editor-content",

    initialize: function (model) {
      this.model = model;
      if (!this.model.projectEvent.newsItems) {
        this.model.projectEvent.newsItems = [];
      }
    },

    getEventNewsItem: function(identifier) {
      var matchedNewsItem;
      this.model.projectEvent.newsItems.forEach(function(event){
        if (identifier === newsItem.identifier) {
          matchedNewsItem = newsItem;
        }
      });
      return matchedNewsItem;
    },

    render: function () {
      var _this = this;
      var html = this.template(this.model);
      $(this.el).append(html);

      html = $('
');
      var newsItemView;
      this.model.expandedEvent.newsItems.forEach(function(newsItem){
        if (_this.getEventNewsItem(newsItem.identifier)){
          newsItem.isKey = true;
        } else {
          newsItem.isKey = false;
        };
        newsItemView = new EventEditNewsItemView({
          eventStoryline: _this.model.projectEvent,
          newsItem: newsItem
        });
        html.append(newsItemView.render().el);

      });
      this.$el.find('#event-edit-newsitems h2').after(html);

      return this;
    }

  });

  return EventEditNewsItemsView;
});<commit_msg>Adding news items to events.<commit_after>define([
  'jquery',
  'backbone',
  'template'
], function(
  $,
  Backbone,
  Template
){

  var EventEditNewsItemView = Backbone.View.extend({

    template: Template['app/template/event-edit-newsitem.hbs'],

    tagName: 'li',

    initialize: function (model) {
      this.model = model;
    },

    addNewsItemToEvent: function(){
      var identifier = this.model.newsItem.identifier;
      var isInArray = false;
      this.model.eventStoryline.newsItems.forEach(function(newsItem){
        if (newsItem.identifier === identifier) {
          isInArray = true;
        }
      });
      if (!isInArray) {
        this.model.eventStoryline.newsItems.push(this.model.newsItem);
      }
    },

    removeNewsItemFromEvent: function(){
      var identifier = this.model.newsItem.identifier;
      var isInArray = false, matchedIndex;
      this.model.projectStoryline.newsItems.forEach(function(newsItem, index){
        if (newsItem.identifier === identifier) {
          isInArray = true;
          matchedIndex = index;
        }
      });
      if (isInArray) {
        this.model.eventStoryline.newsItems = this.model.projectStoryline.newsItems.slice(matchedIndex, matchedIndex);
      }
    },

    render: function () {
      var _this = this;
      var html = this.template(this.model.newsItem);
      $(this.el).append(html);
      $(this.el).find('input').on('change', function(){
        var value = _this.$el.find('input').is(':checked');
        if (value) {
          _this.addNewsItemToEvent();
        } else {
          _this.removeNewsItemFromEvent();
        }
      });
      return this;
    }

  });

  var EventEditNewsItemsView = Backbone.View.extend({

    template: Template['app/template/event-edit-newsitems.hbs'],

    el: "#editor-content",

    initialize: function (model) {
      this.model = model;
      if (!this.model.projectEvent.newsItems) {
        this.model.projectEvent.newsItems = [];
      }
    },

    getEventNewsItem: function(identifier) {
      var matchedNewsItem;
      this.model.projectEvent.newsItems.forEach(function(newsItem){
        if (identifier === newsItem.identifier) {
          matchedNewsItem = newsItem;
        }
      });
      return matchedNewsItem;
    },

    render: function () {
      var _this = this;
      var html = this.template(this.model);
      $(this.el).append(html);

      html = $('
');
      var newsItemView;
      this.model.expandedEvent.newsItems.forEach(function(newsItem){
        if (_this.getEventNewsItem(newsItem.identifier)){
          newsItem.isKey = true;
        } else {
          newsItem.isKey = false;
        };
        newsItemView = new EventEditNewsItemView({
          eventStoryline: _this.model.projectEvent,
          newsItem: newsItem
        });
        html.append(newsItemView.render().el);

      });
      this.$el.find('#event-edit-newsitems h2').after(html);

      return this;
    }

  });

  return EventEditNewsItemsView;
});<|endoftext|>


<commit_before>define([
    "backbone",
     "handlebars",
     "audio_video/AudioVideo"
], function(
    Backbone,
    Handlebars,
    AudioVideo
) {
  var AudioVideoEditView = Backbone.View.extend(
  /** @lends AudioVideoEditView.prototype */
  {
    /**
     * @class This is the view of the Audio Video Model.
     *
     * @extends Backbone.View
     * @constructs
     */
    initialize : function() {
    },

    events : {
      "dragenter" : function(e){
        this.dragEnterAudio(e);
      },
      "dragover" : function(e){
        this.dragOverAudio(e);
      },
      "dragleave" : function(e){
        this.dragLeave(e);
      },
      "drop": function(e){
        this.dropAudio(e, this);
        this.set("filename",e.dataTransfer.files[0].name);
      }
    },

    model : AudioVideo,

    classname : "audio_video",

    template : Handlebars.templates.audio_video_edit_embedded,

    render : function() {
      var dropzone = document.createElement("input");
      dropzone.classList.add("drop-zone");
      dropzone.classList.add("pull-right");
      dropzone.addEventListener('drop', this.dropAudio, false);
      dropzone.addEventListener('dragover', this.dragOverAudio, false);
      dropzone.setAttribute("placeholder","Drop audio or video files here!");
      $(this.el).html(dropzone);
      return this;
    },

    dragEnterAudio: function(e) {
      e.stopPropagation();
      e.preventDefault();
      this.classList.add('halfopacity');
    },

    dragOverAudio : function(e) {
//      e.stopPropagation();
      e.preventDefault();
      e.dataTransfer.dropEffect = 'move'; // Explicitly show this is a copy.
      return false;
    },

    dragLeave: function(e) {
      this.classList.remove('halfopacity');
    },

    dropAudio: function(e, self) {
      Utils.debug("Recieved a drop event ");
      if (e.stopPropagation) {
        e.stopPropagation(); // stops the browser from redirecting.
      }
      if (e.preventDefault) {
        e.preventDefault();
      }
      this.classList.remove('halfopacity');
      window.appView.term.addDroppedFiles(e.dataTransfer.files);
      window.appView.term.output('
File(s) added!
');
      this.value = e.dataTransfer.files[0].name;
//      self.model.set("filename",e.dataTransfer.files[0].name);
      alert("Audio file was copied! (cant play yet)");
      return false;
    }
  });

  return AudioVideoEditView;
});<commit_msg>trying to store teh audio filename into the audiofile object but not geting access ot the this object<commit_after>define([
    "backbone",
     "handlebars",
     "audio_video/AudioVideo"
], function(
    Backbone,
    Handlebars,
    AudioVideo
) {
  var AudioVideoEditView = Backbone.View.extend(
  /** @lends AudioVideoEditView.prototype */
  {
    /**
     * @class This is the view of the Audio Video Model.
     *
     * @extends Backbone.View
     * @constructs
     */
    initialize : function() {
//      $(this.el).bind("dragover", _.bind(this.dragOverAudio, this));
//      $(this.el).bind("dragenter", _.bind(this.dragEnterAudio, this));
//      $(this.el).bind("dragleave", _.bind(this.dragLeave, this));
//      $(this.el).bind("drop", _.bind(this.dropAudio, this));
//      this._draghoverClassAdded = false;
    },

    events : {
      "dragenter .drop-zone" : function(e){
        this.dragEnterAudio(e);
      },
      "dragover .drop-zone" : function(e){
        this.dragOverAudio(e);
      },
      "dragleave .drop-zone" : function(e){
        this.dragLeave(e);
      },
      "drop .drop-zone": function(e){
        this.model.set("filename", e.dataTransfer.files[0].name);
        this.dropAudio(e);
      }
    },

    model : AudioVideo,

    classname : "audio_video",

    template : Handlebars.templates.audio_video_edit_embedded,

    render : function() {
      var dropzone = document.createElement("input");
      dropzone.classList.add("drop-zone");
      dropzone.classList.add("pull-right");
      var self = this;
      dropzone.addEventListener('drop', this.dropAudio, false);
      dropzone.addEventListener('dragover', this.dragOverAudio, false);
      dropzone.setAttribute("placeholder","Drop audio or video files here!");
      $(this.el).html(dropzone);
      return this;
    },

    dragEnterAudio: function(e) {
      e.stopPropagation();
      e.preventDefault();
      this.classList.add('halfopacity');
    },

    dragOverAudio : function(e) {
//      e.stopPropagation();
      e.preventDefault();
      e.dataTransfer.dropEffect = 'move'; // Explicitly show this is a copy.
      return false;
    },

    dragLeave: function(e) {
      this.classList.remove('halfopacity');
    },

    dropAudio: function(e) {
      Utils.debug("Recieved a drop event ");
      if (e.stopPropagation) {
        e.stopPropagation(); // stops the browser from redirecting.
      }
      if (e.preventDefault) {
        e.preventDefault();
      }
      this.classList.remove('halfopacity');
      //Use the terminal to put the file into the file system
      window.appView.term.addDroppedFiles(e.dataTransfer.files);
      window.appView.term.output('
File(s) added!
');
      this.value = e.dataTransfer.files[0].name;
//      self.model.set("filename",e.dataTransfer.files[0].name);
      alert("Audio file was copied! (click on play button to listen)");
      return false;
    }
  });

  return AudioVideoEditView;
});<|endoftext|>