<commit_before>import Ember from 'ember';
import loadAll from 'ember-osf/utils/load-relationship';
import config from 'ember-get-config';
import Analytics from '../mixins/analytics';
import permissions from 'ember-osf/const/permissions';
import KeenTracker from 'ember-osf/mixins/keen-tracker';

/**
 * Takes an object with query parameter name as the key and value, or [value, maxLength] as the values.
 *
 * @method queryStringify
 * @param queryParams {!object}
 * @param queryParams.key {!array|!string}
 * @param queryParams.key[0] {!string}
 * @param queryParams.key[1] {int}
 * @return {string}
 */
function queryStringify(queryParams) {
    const query = [];

    // TODO set up ember to transpile Object.entries
    for (const param in queryParams) {
        let value = queryParams[param];
        let maxLength = null;

        if (Array.isArray(value)) {
            maxLength = value[1];
            value = value[0];
        }

        if (!value)
            continue;

        value = encodeURIComponent(value);

        if (maxLength)
            value = value.slice(0, maxLength);

        query.push(`${param}=${value}`);
    }

    return query.join('&');
}

/**
 * @module ember-preprints
 * @submodule controllers
 */

/**
 * @class Content Controller
 */
export default Ember.Controller.extend(Analytics, KeenTracker, {
    theme: Ember.inject.service(),
    fullScreenMFR: false,
    currentUser: Ember.inject.service(),
    expandedAuthors: true,
    showLicenseText: false,
    expandedAbstract: false,
    isAdmin: Ember.computed('node', function() {
        // True if the current user has admin permissions for the node that contains the preprint
        return (this.get('node.currentUserPermissions') || []).includes(permissions.ADMIN);
    }),
    twitterHref: Ember.computed('node', function() {
        const queryParams = {
            url: window.location.href,
            text: this.get('node.title'),
            via: 'OSFramework'
        };

        return `https://twitter.com/intent/tweet?${queryStringify(queryParams)}`;
    }),
    /* TODO: Update this with new Facebook Share Dialog, but an App ID is required
     * https://developers.facebook.com/docs/sharing/reference/share-dialog
     */
    facebookHref: Ember.computed('model', function() {
        const queryParams = {
            app_id: config.FB_APP_ID,
            display: 'popup',
            href: window.location.href,
            redirect_uri: window.location.href
        };

        return `https://www.facebook.com/dialog/share?${queryStringify(queryParams)}`;
    }),
    // https://developer.linkedin.com/docs/share-on-linkedin
    linkedinHref: Ember.computed('node', function() {
        const queryParams = {
            url: [window.location.href, 1024],          // required
            mini: ['true', 4],                          // required
            title: [this.get('node.title'), 200],      // optional
            summary: [this.get('node.description'), 256], // optional
            source: ['Open Science Framework', 200]     // optional
        };

        return `https://www.linkedin.com/shareArticle?${queryStringify(queryParams)}`;
    }),
    emailHref: Ember.computed('node', function() {
        const queryParams = {
            subject: this.get('node.title'),
            body: window.location.href
        };

        return `mailto:?${queryStringify(queryParams)}`;
    }),
    // The currently selected file (defaults to primary)
    activeFile: null,

    disciplineReduced: Ember.computed('model.subjects', function() {
        // Preprint disciplines are displayed in collapsed form on content page
        return this.get('model.subjects').reduce((acc, val) => acc.concat(val), []).uniqBy('id');
    }),

    hasTag: Ember.computed('node.tags', function() {
        return (this.get('node.tags') || []).length;
    }),

    getAuthors: Ember.observer('node', function() {
        // Cannot be called until node has loaded!
        const node = this.get('node');
        if (!node) return [];

        const contributors = Ember.A();
        loadAll(node, 'contributors', contributors).then(() =>
            this.set('authors', contributors)
        );
    }),

    doiUrl: Ember.computed('model.doi', function() {
        return `https://dx.doi.org/${this.get('model.doi')}`;
    }),

    fullLicenseText: Ember.computed('model.license', function() {
        let text = this.get('model.license.text');
        if (text) {
            text = text.replace(/({{year}})/g, this.get('model.licenseRecord').year || '');
            text = text.replace(/({{copyrightHolders}})/g, this.get('model.licenseRecord').copyright_holders ? this.get('model.licenseRecord').copyright_holders.join(',') : false || '');
        }
        return text;
    }),

    useShortenedDescription: Ember.computed('node.description', function() {
        return this.get('node.description') ? this.get('node.description').length > 350 : false;
    }),

    description: Ember.computed('node.description', 'expandedAbstract', function() {
        // Get a shortened version of the abstract, but doesnt cut in the middle of word by going
        // to the last space.
        if (this.get('expandedAbstract')) {
            return this.get('node.description');
        }
        let text = this.get('node.description').slice(0, 350).split(' ');
        text.pop();
        return text.join(' ') + ' ...';
    }),

    actions: {
        toggleLicenseText() {
            const licenseState = this.toggleProperty('showLicenseText') ? 'Expand' : 'Contract';
            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'button',
                    action: 'click',
                    label: `Preprints - Content - License ${licenseState}`
                });
        },
        expandMFR() {
            // State of fullScreenMFR before the transition (what the user perceives as the action)
            const beforeState = this.toggleProperty('fullScreenMFR') ? 'Expand' : 'Contract';

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'button',
                    action: 'click',
                    label: `Preprints - Content - MFR ${beforeState}`
                });
        },
        // Unused
        expandAuthors() {
            this.toggleProperty('expandedAuthors');
        },
        expandAbstract() {
            this.toggleProperty('expandedAbstract');
        },
        // Metrics are handled in the component
        chooseFile(fileItem) {
            this.set('activeFile', fileItem);
        },
        shareLink(href, network, action) {
            const metrics = Ember.get(this, 'metrics');

            if (network.includes('email')) {
                metrics.trackEvent({
                    category: 'link',
                    action,
                    label: `Preprints - Content - Email ${window.location.href}`
                });
            } else {
                window.open(href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=600,height=400');
                // TODO submit PR to ember-metrics for a trackSocial function for Google Analytics. For now, we'll use trackEvent.
                metrics.trackEvent({
                    category: network,
                    action,
                    label: `Preprints - Content - ${window.location.href}`
                });
            }

            return false;
        },
        // Sends Event to GA.  Only sends event to Keen if non-contributor.
        dualTrackNonContributors(category, label, url) {
            this.send('click', category, label, url); // Sends event to Google Analytics
            const authors = this.get('authors');
            let userIsContrib = false;
            this.get('currentUser').load()
                .then(user => {
                    if (user) {
                        const userId = user.id;
                        authors.forEach((author) => {
                            if (author.get('userId') === userId) {
                                userIsContrib = true;
                            }
                        });
                    }
                    if (!userIsContrib) {
                        this.send('keenClick', category, `Non-Contributor ${label}`, url);  // Sends event to Keen if logged in user is not a preprint author
                    }
                })
                .catch(() => {
                    this.send('keenClick', category, `${label} as Non-Contributor`, url); // Sends event to Keen for non-authenticated user
                });
        }
    },
});
<commit_msg>Log preprint_downloads under their own collection.<commit_after>import Ember from 'ember';
import loadAll from 'ember-osf/utils/load-relationship';
import config from 'ember-get-config';
import Analytics from '../mixins/analytics';
import permissions from 'ember-osf/const/permissions';
import KeenTracker from 'ember-osf/mixins/keen-tracker';

/**
 * Takes an object with query parameter name as the key and value, or [value, maxLength] as the values.
 *
 * @method queryStringify
 * @param queryParams {!object}
 * @param queryParams.key {!array|!string}
 * @param queryParams.key[0] {!string}
 * @param queryParams.key[1] {int}
 * @return {string}
 */
function queryStringify(queryParams) {
    const query = [];

    // TODO set up ember to transpile Object.entries
    for (const param in queryParams) {
        let value = queryParams[param];
        let maxLength = null;

        if (Array.isArray(value)) {
            maxLength = value[1];
            value = value[0];
        }

        if (!value)
            continue;

        value = encodeURIComponent(value);

        if (maxLength)
            value = value.slice(0, maxLength);

        query.push(`${param}=${value}`);
    }

    return query.join('&');
}

/**
 * @module ember-preprints
 * @submodule controllers
 */

/**
 * @class Content Controller
 */
export default Ember.Controller.extend(Analytics, KeenTracker, {
    theme: Ember.inject.service(),
    fullScreenMFR: false,
    currentUser: Ember.inject.service(),
    expandedAuthors: true,
    showLicenseText: false,
    expandedAbstract: false,
    isAdmin: Ember.computed('node', function() {
        // True if the current user has admin permissions for the node that contains the preprint
        return (this.get('node.currentUserPermissions') || []).includes(permissions.ADMIN);
    }),
    twitterHref: Ember.computed('node', function() {
        const queryParams = {
            url: window.location.href,
            text: this.get('node.title'),
            via: 'OSFramework'
        };

        return `https://twitter.com/intent/tweet?${queryStringify(queryParams)}`;
    }),
    /* TODO: Update this with new Facebook Share Dialog, but an App ID is required
     * https://developers.facebook.com/docs/sharing/reference/share-dialog
     */
    facebookHref: Ember.computed('model', function() {
        const queryParams = {
            app_id: config.FB_APP_ID,
            display: 'popup',
            href: window.location.href,
            redirect_uri: window.location.href
        };

        return `https://www.facebook.com/dialog/share?${queryStringify(queryParams)}`;
    }),
    // https://developer.linkedin.com/docs/share-on-linkedin
    linkedinHref: Ember.computed('node', function() {
        const queryParams = {
            url: [window.location.href, 1024],          // required
            mini: ['true', 4],                          // required
            title: [this.get('node.title'), 200],      // optional
            summary: [this.get('node.description'), 256], // optional
            source: ['Open Science Framework', 200]     // optional
        };

        return `https://www.linkedin.com/shareArticle?${queryStringify(queryParams)}`;
    }),
    emailHref: Ember.computed('node', function() {
        const queryParams = {
            subject: this.get('node.title'),
            body: window.location.href
        };

        return `mailto:?${queryStringify(queryParams)}`;
    }),
    // The currently selected file (defaults to primary)
    activeFile: null,

    disciplineReduced: Ember.computed('model.subjects', function() {
        // Preprint disciplines are displayed in collapsed form on content page
        return this.get('model.subjects').reduce((acc, val) => acc.concat(val), []).uniqBy('id');
    }),

    hasTag: Ember.computed('node.tags', function() {
        return (this.get('node.tags') || []).length;
    }),

    getAuthors: Ember.observer('node', function() {
        // Cannot be called until node has loaded!
        const node = this.get('node');
        if (!node) return [];

        const contributors = Ember.A();
        loadAll(node, 'contributors', contributors).then(() =>
            this.set('authors', contributors)
        );
    }),

    doiUrl: Ember.computed('model.doi', function() {
        return `https://dx.doi.org/${this.get('model.doi')}`;
    }),

    fullLicenseText: Ember.computed('model.license', function() {
        let text = this.get('model.license.text');
        if (text) {
            text = text.replace(/({{year}})/g, this.get('model.licenseRecord').year || '');
            text = text.replace(/({{copyrightHolders}})/g, this.get('model.licenseRecord').copyright_holders ? this.get('model.licenseRecord').copyright_holders.join(',') : false || '');
        }
        return text;
    }),

    useShortenedDescription: Ember.computed('node.description', function() {
        return this.get('node.description') ? this.get('node.description').length > 350 : false;
    }),

    description: Ember.computed('node.description', 'expandedAbstract', function() {
        // Get a shortened version of the abstract, but doesnt cut in the middle of word by going
        // to the last space.
        if (this.get('expandedAbstract')) {
            return this.get('node.description');
        }
        let text = this.get('node.description').slice(0, 350).split(' ');
        text.pop();
        return text.join(' ') + ' ...';
    }),

    actions: {
        toggleLicenseText() {
            const licenseState = this.toggleProperty('showLicenseText') ? 'Expand' : 'Contract';
            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'button',
                    action: 'click',
                    label: `Preprints - Content - License ${licenseState}`
                });
        },
        expandMFR() {
            // State of fullScreenMFR before the transition (what the user perceives as the action)
            const beforeState = this.toggleProperty('fullScreenMFR') ? 'Expand' : 'Contract';

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'button',
                    action: 'click',
                    label: `Preprints - Content - MFR ${beforeState}`
                });
        },
        // Unused
        expandAuthors() {
            this.toggleProperty('expandedAuthors');
        },
        expandAbstract() {
            this.toggleProperty('expandedAbstract');
        },
        // Metrics are handled in the component
        chooseFile(fileItem) {
            this.set('activeFile', fileItem);
        },
        shareLink(href, network, action) {
            const metrics = Ember.get(this, 'metrics');

            if (network.includes('email')) {
                metrics.trackEvent({
                    category: 'link',
                    action,
                    label: `Preprints - Content - Email ${window.location.href}`
                });
            } else {
                window.open(href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=600,height=400');
                // TODO submit PR to ember-metrics for a trackSocial function for Google Analytics. For now, we'll use trackEvent.
                metrics.trackEvent({
                    category: network,
                    action,
                    label: `Preprints - Content - ${window.location.href}`
                });
            }

            return false;
        },
        // Sends Event to GA.  Only sends event to Keen if non-contributor.
        dualTrackNonContributors(category, label, url) {
            this.send('click', category, label, url); // Sends event to Google Analytics
            const authors = this.get('authors');
            let userIsContrib = false;

            const eventData = {
                download_info: {
                    preprint: {
                        type: 'preprint',
                        id: this.get('model.id')
                    },
                    file: {
                        id: this.get('activeFile.id'),
                        primaryFile: this.get('model.primaryFile.id') === this.get('activeFile.id')
                    }
                },
                interaction: {
                    category: category,
                    action: 'click',
                    label: `${label} as Non-Contributor`,
                    url: url
                }
            };

            this.get('currentUser').load()
                .then(user => {
                    if (user) {
                        const userId = user.id;
                        authors.forEach((author) => {
                            if (author.get('userId') === userId) {
                                userIsContrib = true;
                            }
                        });
                    }
                    if (!userIsContrib) {
                        this.keenTrackEvent('preprint_downloads', eventData, this.get('node'));  // Sends event to Keen if logged in user is not a preprint author
                    }
                })
                .catch(() => {
                    this.keenTrackEvent('preprint_downloads', eventData, this.get('node')); // Sends event to Keen for non-authenticated user
                });
        }
    },
});

<|endoftext|>


<commit_before>import { msft } from '../util/datasets';
import showComponent from '../util/showComponent';

window.onload = () => {
  showComponent('LineChart', 'div', {
    data: msft,
    x: 'date',
    y: ['price'],
    width: 735,
    height: 535,
    padding: {
      top: 20,
      bottom: 45,
      left: 45,
      right: 20
    },
    renderer: 'svg'
  });
};
<commit_msg>test(LineChart): Add hoverSize option to LineChart test/example<commit_after>import { msft } from '../util/datasets';
import showComponent from '../util/showComponent';

window.onload = () => {
  showComponent('LineChart', 'div', {
    data: msft,
    x: 'date',
    y: ['price'],
    width: 735,
    height: 535,
    hoverSize: 50,
    padding: {
      top: 20,
      bottom: 45,
      left: 45,
      right: 20
    },
    renderer: 'svg'
  });
};
<|endoftext|>


<commit_before>"use strict";

let s = require('underscore.string');
let url = require('url');
let cheerio = require('cheerio');
let async = require('async');
let priceHelper = require('helpers/price');
let fetchHelper = require('helpers/fetch');

const theatreKey = 'shevchenko';
const baseUrl = 'http://www.theatre-shevchenko.com.ua';
const sourceUrl = baseUrl + '/repertuar/month.php?id=';

const defaultScene = 'big';
const sceneMap = {
    'Мала сцена': 'small',
    'Експериментальна сцена': 'exp',
    'Велика сцена': 'big',
};

let fetcher = function(callback) {

    const today = new Date();
    const month = today.getMonth();
    const year = today.getFullYear();

    async.map([0, 1, 2], function(monthDelta, callback) {
        let yearToParse = year;
        let monthToParse = month + monthDelta;
        if (monthToParse > 11) {
            monthToParse = 11 - monthToParse;
            yearToParse += 1;
        }
        fetchHelper.getContent(sourceUrl + (monthToParse + 1), 'WINDOWS-1251', function(err, content) {
            if (err) return callback(err);
            callback(null, getSchedule(content, monthToParse, yearToParse));
        });
    }, function(err, results) {
        if (err) return callback(err);
        callback(null, Array.prototype.concat.apply([], results));
    });

    function getSchedule(content, month, year) {
        const parsedShows = parseShows(content, month, year);
        const translatedShows = parsedShows.map(translateRawShow).filter(show => show !== null);
        return translatedShows.reduce(fetchHelper.splitShowByDates, []);
    }

    function parseShows(content, month, year) {
        const TEXT_NODE_TYPE = 3;
        let $ = cheerio.load(content);
        return $('.primer-list li').map(function (index, li) {
            let $li = $(li);
            return {
                theatre: theatreKey,
                title: $li.find('h3').text(),
                playUrl: $li.find('h3 a').attr('href'),
                date: $li.find('.date').text(),
                month: month,
                year: year,
                scene: $li.find('em')
                    .contents()
                    .filter(function() { return this.nodeType === TEXT_NODE_TYPE; })
                    .text().trim(),
                price: $li.find('.prices').text()
            };
        }).get();
    }

    function translateRawShow(rawShow) {
        const dateParts = /(\d+).+(\d\d)[.:](\d\d)/u.exec(rawShow.date);
        return {
            theatre: rawShow.theatre,
            theatreRawData: {
                title: 'Театр имени Шевченко',
                url: baseUrl,
                hasFetcher: true
            },
            title: rawShow.title.replace(/\s+/, ' '),
            playUrl: url.resolve(sourceUrl, rawShow.playUrl),
            date: dateParts ?
                new Date(rawShow.year, rawShow.month, dateParts[1], dateParts[2], dateParts[3], 0) :
                null,
            scene: sceneMap[rawShow.scene] || defaultScene,
            price: /\d/.test(rawShow.price) ?
                priceHelper.normalize(s.strRight(rawShow.price, 'квиток коштує ')) :
                null,
        };
    }
};

module.exports.fetch = fetcher;<commit_msg>Fetch premiere flag for shevchenko theatre<commit_after>"use strict";

let s = require('underscore.string');
let url = require('url');
let cheerio = require('cheerio');
let async = require('async');
let priceHelper = require('helpers/price');
let fetchHelper = require('helpers/fetch');

const theatreKey = 'shevchenko';
const baseUrl = 'http://www.theatre-shevchenko.com.ua';
const sourceUrl = baseUrl + '/repertuar/month.php?id=';

const defaultScene = 'big';
const sceneMap = {
    'Мала сцена': 'small',
    'Експериментальна сцена': 'exp',
    'Велика сцена': 'big',
};

let fetcher = function(callback) {

    const today = new Date();
    const month = today.getMonth();
    const year = today.getFullYear();

    async.map([0, 1, 2], function(monthDelta, callback) {
        let yearToParse = year;
        let monthToParse = month + monthDelta;
        if (monthToParse > 11) {
            monthToParse = 11 - monthToParse;
            yearToParse += 1;
        }
        fetchHelper.getContent(sourceUrl + (monthToParse + 1), 'WINDOWS-1251', function(err, content) {
            if (err) return callback(err);
            callback(null, getSchedule(content, monthToParse, yearToParse));
        });
    }, function(err, results) {
        if (err) return callback(err);
        callback(null, Array.prototype.concat.apply([], results));
    });

    function getSchedule(content, month, year) {
        const parsedShows = parseShows(content, month, year);
        const translatedShows = parsedShows.map(translateRawShow).filter(show => show !== null);
        return translatedShows.reduce(fetchHelper.splitShowByDates, []);
    }

    function parseShows(content, month, year) {
        const TEXT_NODE_TYPE = 3;
        let $ = cheerio.load(content);
        return $('.primer-list li').map(function (index, li) {
            let $li = $(li);
            return {
                theatre: theatreKey,
                title: $li.find('h3').text(),
                playUrl: $li.find('h3 a').attr('href'),
                date: $li.find('.date').text(),
                premiere: !!$li.find('.primer').length,
                month: month,
                year: year,
                scene: $li.find('em')
                    .contents()
                    .filter(function() { return this.nodeType === TEXT_NODE_TYPE; })
                    .text().trim(),
                price: $li.find('.prices').text()
            };
        }).get();
    }

    function translateRawShow(rawShow) {
        const dateParts = /(\d+).+(\d\d)[.:](\d\d)/u.exec(rawShow.date);
        return {
            theatre: rawShow.theatre,
            theatreRawData: {
                title: 'Театр имени Шевченко',
                url: baseUrl,
                hasFetcher: true
            },
            title: rawShow.title.replace(/\s+/, ' '),
            playUrl: url.resolve(sourceUrl, rawShow.playUrl),
            date: dateParts ?
                new Date(rawShow.year, rawShow.month, dateParts[1], dateParts[2], dateParts[3], 0) :
                null,
            scene: sceneMap[rawShow.scene] || defaultScene,
            price: /\d/.test(rawShow.price) ?
                priceHelper.normalize(s.strRight(rawShow.price, 'квиток коштує ')) :
                null,
            premiere: rawShow.premiere
        };
    }
};

module.exports.fetch = fetcher;<|endoftext|>


<commit_before>/*  Facebox for Prototype, version 2.0
 *  By Robert Gaal - http://wakoopa.com
 *
 *  Heavily based on Facebox by Chris Wanstrath - http://famspam.com/facebox
 *  First ported to Prototype by Phil Burrows - http://blog.philburrows.com
 *  Additional modifications by James Urquhart - http://jamesu.net
 *  Contains code from facybox, Copyright 2009 Mauricio Wolff [ chris@ozmm.org ]
 *
 *  Licensed under the MIT:
 *  http://www.opensource.org/licenses/mit-license.php
 *
 *  Need help?  Join the Google Groups mailing list:
 *  http://groups.google.com/group/facebox/
 *
 *  Dependencies:   prototype & script.aculo.us + images & CSS files from original facebox
 *  Usage:          Append 'rel="facebox"' to an element to call it inside a so-called facebox
 *
 *--------------------------------------------------------------------------*/

var Facebox = Class.create({
  initialize: function() {
    this.preloadImages = [];

    var elements = $('facebox').select('.n, .s, .w, .e, .nw, .ne, .sw, .se, .loading, .close');
    elements.each(function(element) {
      this.preloadImages.push(new Image());
      this.preloadImages.last().src = element.getStyle('background-image').replace(/url\((.+)\)/, '$1');
    }.bind(this));
    if (Prototype.Browser.IE)
      this.fixPNG($('facebox').select('.n, .s, .w, .e, .nw, .ne, .sw, .se'));

    this.container = $('facebox');
    this.contentHolder = $$('#facebox .content').first();
    this.footer = $$('#facebox .footer').first();
    this.is_image = false;

    this.documentEventHandler = this.onDocumentEvent.bindAsEventListener(this);

    Event.observe(document, 'keypress', this.documentEventHandler);
    Event.observe(document, 'click', this.documentEventHandler);

    Event.observe($$('#facebox .close').first(), 'click', function(e) {
      Event.stop(e);
      this.hide();
    }.bindAsEventListener(this));

	Event.observe($$('#facebox .footer').first(), 'click', function(e) {
	  if (!this.is_image)
	    return false;
      Event.stop(e);
      document.location = this.href;
      this.hide();
    }.bindAsEventListener(this));
  },

  fixPNG: function(elements) {
    return elements.each(function (el) {
	  var element = $(el);
      var image = element.getStyle('background-image');

      if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) {
        image = RegExp.$1;
        element.setStyle({
          'background-image': 'none',
          'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=" + (element.getStyle('background-repeat') == 'no-repeat' ? 'crop' : 'scale') + ", src='" + image + "')"
        });
        var position = element.getStyle('position');
        if (position != 'absolute' && position != 'relative')
          element.setStyle({'position': 'relative'});
      }
    });
  },

  fitContent: function(size) {
	var size = size ? size : this.contentHolder.getDimensions();
	this.footer.setStyle({'width': size.width + 'px', 'height': size.height + 'px'});
  },

  showOverlay: function() {
	if(!$('facebox_overlay')){
      $(document.body).insert({bottom:'

'});
    }

    var overlay = $('facebox_overlay');
    overlay.hide().addClassName("facebox_overlayBG").setStyle({
      'opacity': 0.3});
    Effect.Appear(overlay, { duration: .3, to:0.3 });
  },

  hideOverlay: function() {
	var overlay = $('facebox_overlay');
    Effect.Fade(overlay, { duration: .3, from:0.3,
	  afterFinish: function(){
	    overlay.removeClassName("facebox_overlayBG").
	            addClassName("facebox_hide").
	            remove();
      }
    });
  },

  onAnchorClick: function(anchor) {
    this.setLoading();
    this.show();
    this.href = anchor.href;

    // support for rel="facebox[.cssclass]" syntax, to add a css class
    var className = anchor.rel.match(/facebox\[\.(\w+)\]/) && RegExp.$1;

    if (anchor.href.match(/#/)) {
      var url    = window.location.href.split('#')[0];
      var target = $(anchor.href.replace(url + '#', ''));

      // create a new element so as to not delete the original on hide()
      var content = new Element(target.tagName);
      content.innerHTML = target.innerHTML;

      this.setContent(content, className);
      this.centralize();
      this.is_image = false;
    }
    else if (anchor.href.match(/\.(png|jpg|jpeg|gif)$/i)) {
      var image = new Image();
      var ref = this;
      ref.is_image = true;
      var timeoutFunc = function(){ref.centralize();};
      image.onload = function() {
        ref.setContent('

', className);
        ref.centralize();
        ref.fitContent({width:image.width, height:image.height});
        setTimeout(timeoutFunc, 0);
      }.bind(this);
	  image.src = anchor.href;
    }
    else {
      this.is_image = false;
      new Ajax.Request(anchor.href, {
        method: 'get',
        onComplete: function(transport) {
          this.setContent(transport.responseText, className);
          this.centralize();
          this.fitContent();
        }.bind(this)
      });
    }
  },

  onDocumentEvent: function(e) {
    // hide if ESC is pressed or if there's a click outside of the facebox
    if (e.keyCode == 27 || !Event.element(e).descendantOf(this.container)) {
      this.hide();
    }
  },

  centralize: function() {
	var pageDim = document.viewport.getDimensions();
	var pageScroll = document.viewport.getScrollOffsets();
	var size = this.container.getDimensions();

	var wl = (pageDim.width/2) - (size.width / 2);
	var fh = size.height;

	if (pageDim.height > fh) {
		this.container.setStyle({
	      'left': wl + 'px',
	      'top': (pageScroll.top + ((pageDim.height - fh)/2)) + 'px'
	    });
    } else {
		this.container.setStyle({
	      'left': wl + 'px',
	      'top': (pageScroll.top + (pageDim.height/10)) + 'px'
	    });
    }
  },

  setLoading: function() {
    if (this.currentClassName) {
      this.contentHolder.removeClassName(this.currentClassName);
      this.currentClassName = null;
    }

    this.contentHolder.update('
Loading...
');
    this.centralize();

    return this;
  },

  setContent: function(content, className) {
    if (className) {
      this.contentHolder.addClassName(className);
      this.currentClassName = className;
    }

    this.contentHolder.update(content);
    this.centralize();

    this.footer.childElements().invoke('show');

    return this;
  },

  visible: function() {
    return this.container.visible();
  },

  show: function() {
    if (!this.visible()) {
	  this.showOverlay();
      new Effect.Appear(this.container, { duration: .3 });
    }

    return this;
  },

  hide: function() {
    if (this.visible()) {
	  this.hideOverlay();
      new Effect.Fade(this.container, { duration: .3 });
    }

    return this;
  },

  reveal: function(content, className) {
    this.setLoading();
    this.show();
    this.setContent(content, className);

    return this;
  }
});

Event.addBehavior({
	"a[rel=facebox]:click": function(e) {
        Event.stop(e);
		window.facebox.onAnchorClick($(this));
	}
});

document.observe('dom:loaded', function() {
  window.facebox = new Facebox();
});

<commit_msg>Tidy up tabs<commit_after>/*  Facebox for Prototype, version 2.0
 *  By Robert Gaal - http://wakoopa.com
 *
 *  Heavily based on Facebox by Chris Wanstrath - http://famspam.com/facebox
 *  First ported to Prototype by Phil Burrows - http://blog.philburrows.com
 *  Additional modifications by James Urquhart - http://jamesu.net
 *  Contains code from facybox, Copyright 2009 Mauricio Wolff [ chris@ozmm.org ]
 *
 *  Licensed under the MIT:
 *  http://www.opensource.org/licenses/mit-license.php
 *
 *  Need help?  Join the Google Groups mailing list:
 *  http://groups.google.com/group/facebox/
 *
 *  Dependencies:   prototype & script.aculo.us + images & CSS files from original facebox
 *  Usage:          Append 'rel="facebox"' to an element to call it inside a so-called facebox
 *
 *--------------------------------------------------------------------------*/

var Facebox = Class.create({
  initialize: function() {
    this.preloadImages = [];

    var elements = $('facebox').select('.n, .s, .w, .e, .nw, .ne, .sw, .se, .loading, .close');
    elements.each(function(element) {
      this.preloadImages.push(new Image());
      this.preloadImages.last().src = element.getStyle('background-image').replace(/url\((.+)\)/, '$1');
    }.bind(this));
    if (Prototype.Browser.IE)
      this.fixPNG($('facebox').select('.n, .s, .w, .e, .nw, .ne, .sw, .se'));

    this.container = $('facebox');
    this.contentHolder = $$('#facebox .content').first();
    this.footer = $$('#facebox .footer').first();
    this.is_image = false;

    this.documentEventHandler = this.onDocumentEvent.bindAsEventListener(this);

    Event.observe(document, 'keypress', this.documentEventHandler);
    Event.observe(document, 'click', this.documentEventHandler);

    Event.observe($$('#facebox .close').first(), 'click', function(e) {
      Event.stop(e);
      this.hide();
    }.bindAsEventListener(this));

    Event.observe($$('#facebox .footer').first(), 'click', function(e) {
      if (!this.is_image)
        return false;
      Event.stop(e);
      document.location = this.href;
      this.hide();
    }.bindAsEventListener(this));
  },

  fixPNG: function(elements) {
    return elements.each(function (el) {
      var element = $(el);
      var image = element.getStyle('background-image');

      if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) {
        image = RegExp.$1;
        element.setStyle({
          'background-image': 'none',
          'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=" + (element.getStyle('background-repeat') == 'no-repeat' ? 'crop' : 'scale') + ", src='" + image + "')"
        });
        var position = element.getStyle('position');
        if (position != 'absolute' && position != 'relative')
          element.setStyle({'position': 'relative'});
      }
    });
  },

  fitContent: function(size) {
    var size = size ? size : this.contentHolder.getDimensions();
    this.footer.setStyle({'width': size.width + 'px', 'height': size.height + 'px'});
  },

  showOverlay: function() {
    if(!$('facebox_overlay')){
      $(document.body).insert({bottom:'

'});
    }

    var overlay = $('facebox_overlay');
    overlay.hide().addClassName("facebox_overlayBG").setStyle({
      'opacity': 0.3});
    Effect.Appear(overlay, { duration: .3, to:0.3 });
  },

  hideOverlay: function() {
    var overlay = $('facebox_overlay');
    Effect.Fade(overlay, { duration: .3, from:0.3,
      afterFinish: function(){
        overlay.removeClassName("facebox_overlayBG").
                addClassName("facebox_hide").
                remove();
      }
    });
  },

  onAnchorClick: function(anchor) {
    this.setLoading();
    this.show();
    this.href = anchor.href;

    // support for rel="facebox[.cssclass]" syntax, to add a css class
    var className = anchor.rel.match(/facebox\[\.(\w+)\]/) && RegExp.$1;

    if (anchor.href.match(/#/)) {
      var url    = window.location.href.split('#')[0];
      var target = $(anchor.href.replace(url + '#', ''));

      // create a new element so as to not delete the original on hide()
      var content = new Element(target.tagName);
      content.innerHTML = target.innerHTML;

      this.setContent(content, className);
      this.centralize();
      this.is_image = false;
    }
    else if (anchor.href.match(/\.(png|jpg|jpeg|gif)$/i)) {
      var image = new Image();
      var ref = this;
      ref.is_image = true;
      var timeoutFunc = function(){ref.centralize();};
      image.onload = function() {
        ref.setContent('

', className);
        ref.centralize();
        ref.fitContent({width:image.width, height:image.height});
        setTimeout(timeoutFunc, 0);
      }.bind(this);
      image.src = anchor.href;
    }
    else {
      this.is_image = false;
      new Ajax.Request(anchor.href, {
        method: 'get',
        onComplete: function(transport) {
          this.setContent(transport.responseText, className);
          this.centralize();
          this.fitContent();
        }.bind(this)
      });
    }
  },

  onDocumentEvent: function(e) {
    // hide if ESC is pressed or if there's a click outside of the facebox
    if (e.keyCode == 27 || !Event.element(e).descendantOf(this.container)) {
      this.hide();
    }
  },

  centralize: function() {
    var pageDim = document.viewport.getDimensions();
    var pageScroll = document.viewport.getScrollOffsets();
    var size = this.container.getDimensions();

    var wl = (pageDim.width/2) - (size.width / 2);
    var fh = size.height;

    if (pageDim.height > fh) {
      this.container.setStyle({
          'left': wl + 'px',
          'top': (pageScroll.top + ((pageDim.height - fh)/2)) + 'px'
        });
    } else {
        this.container.setStyle({
          'left': wl + 'px',
          'top': (pageScroll.top + (pageDim.height/10)) + 'px'
        });
    }
  },

  setLoading: function() {
    if (this.currentClassName) {
      this.contentHolder.removeClassName(this.currentClassName);
      this.currentClassName = null;
    }

    this.contentHolder.update('
Loading...
');
    this.centralize();

    return this;
  },

  setContent: function(content, className) {
    if (className) {
      this.contentHolder.addClassName(className);
      this.currentClassName = className;
    }

    this.contentHolder.update(content);
    this.centralize();

    this.footer.childElements().invoke('show');

    return this;
  },

  visible: function() {
    return this.container.visible();
  },

  show: function() {
    if (!this.visible()) {
      this.showOverlay();
      new Effect.Appear(this.container, { duration: .3 });
    }

    return this;
  },

  hide: function() {
    if (this.visible()) {
      this.hideOverlay();
      new Effect.Fade(this.container, { duration: .3 });
    }

    return this;
  },

  reveal: function(content, className) {
    this.setLoading();
    this.show();
    this.setContent(content, className);

    return this;
  }
});

Event.addBehavior({
  "a[rel=facebox]:click": function(e) {
    Event.stop(e);
    window.facebox.onAnchorClick($(this));
  }
});

document.observe('dom:loaded', function() {
  window.facebox = new Facebox();
});

<|endoftext|>


<commit_before>_.extend(_, {
  sum: function (arr) {
    return _.reduce(arr, function(sum, v) {return sum + v;}, 0);
  },
  isPresent : function(obj) {
    return !(_.isNull(obj) || _.isUndefined(obj));
  }
});

window.AppView = Backbone.View.extend({
  API_URL : globals.api_url,

  initialize : function() {
    _.bindAll(this, 'api_result', 'handleInputElementsUpdate');

    // Jaap MVC legacy
    this.inputElementsController = window.input_elements;
    this.inputElementsController.bind("change", this.handleInputElementsUpdate);

    this.municipalityController = new MunicipalityController();

    this.settings = new Setting(); // At this point settings is empty.
    // let's get the ruby-fetched api_session_key
    this.settings.set({'api_session_key' : globals.api_session_key});
    this.scenario = new Scenario();
    this.peak_load = new PeakLoad();
  },

  bootstrap : function() {
    if (this.scenario.api_session_key() == null) {
      this.scenario.new_session();
      // after copmleting new_session() App.bootstrap is called again and will thus continue the else part
    } else {
      this.load_user_values();
      this.call_api();
    }
  },

  // Load User values for Sliders
  load_user_values : function() {
    this.inputElementsController.load_user_values();
  },

  call_api : function(input_params) {
    var url = this.scenario.query_url(input_params);
    var params = {'result' : window.gqueries.keys() };

    LockableFunction.setLock('call_api');
    showLoading();
    $.jsonp({
      url: url + '?callback=?',
      data: params,
      success: this.handle_api_result,
      error: function() {
        console.log("Something went wrong");
        hideLoading();
      }
    });
  },

  // The following method could need some refactoring
  // e.g. el.set({ api_result : value_arr })
  // window.charts.first().trigger('change');
  // window.dashboard.trigger('change');
  handle_api_result : function(data) {
    LockableFunction.removeLock('call_api');
    var result   = data.result;   // Results of this request for every "result[]" parameter

    $.each(result, function(gquery_key, value_arr) {
      $.each(window.gqueries.with_key(gquery_key), function(i, gquery) {
        gquery.handle_api_result(value_arr);
      });
    });
    window.charts.each(function(chart) { chart.trigger('change'); });
    window.input_elements.init_legacy_controller();
    window.policy_goals.invoke('update_view');
    window.policy_goals.update_totals();
    App.peak_load.trigger('change');

    $("body").trigger("dashboardUpdate");
    hideLoading();
  },

  /**
   * Set the update in a cancelable action
   */
  handleInputElementsUpdate:function() {
    var func = $.proxy(this.doUpdateRequest, this);
    var lockable_function = function() { LockableFunction.deferExecutionIfLocked('update', func); };
    Util.cancelableAction('update',  lockable_function, {'sleepTime':500});
  },

  /**
   * Get the value of all changed sliders. Get the chart. Sends those values to the server.
   */
  doUpdateRequest:function() {
    var dirtyInputElements = window.input_elements.dirty();

    if (dirtyInputElements.length == 0) { return; }

    window.App.call_api(window.input_elements.api_update_params());
    window.input_elements.reset_dirty();
  }
});

function showLoading() { $("#loading").show(); }
function hideLoading() { $("#loading").hide(); }

window.App = App = new AppView();

<commit_msg>jsonp error. closes #106<commit_after>_.extend(_, {
  sum: function (arr) {
    return _.reduce(arr, function(sum, v) {return sum + v;}, 0);
  },
  isPresent : function(obj) {
    return !(_.isNull(obj) || _.isUndefined(obj));
  }
});

window.AppView = Backbone.View.extend({
  API_URL : globals.api_url,

  initialize : function() {
    _.bindAll(this, 'api_result', 'handleInputElementsUpdate');

    // Jaap MVC legacy
    this.inputElementsController = window.input_elements;
    this.inputElementsController.bind("change", this.handleInputElementsUpdate);

    this.municipalityController = new MunicipalityController();

    this.settings = new Setting(); // At this point settings is empty.
    // let's get the ruby-fetched api_session_key
    this.settings.set({'api_session_key' : globals.api_session_key});
    this.scenario = new Scenario();
    this.peak_load = new PeakLoad();
  },

  bootstrap : function() {
    if (this.scenario.api_session_key() == null) {
      this.scenario.new_session();
      // after copmleting new_session() App.bootstrap is called again and will thus continue the else part
    } else {
      this.load_user_values();
      this.call_api();
    }
  },

  // Load User values for Sliders
  load_user_values : function() {
    this.inputElementsController.load_user_values();
  },

  call_api : function(input_params) {
    var url = this.scenario.query_url(input_params);
    var params = {'result' : window.gqueries.keys() };

    LockableFunction.setLock('call_api');
    showLoading();
    $.jsonp({
      url: url,
      data: params,
      success: this.handle_api_result,
      error: function() {
        console.log("Something went wrong");
        hideLoading();
      }
    });
  },

  // The following method could need some refactoring
  // e.g. el.set({ api_result : value_arr })
  // window.charts.first().trigger('change');
  // window.dashboard.trigger('change');
  handle_api_result : function(data) {
    LockableFunction.removeLock('call_api');
    var result   = data.result;   // Results of this request for every "result[]" parameter

    $.each(result, function(gquery_key, value_arr) {
      $.each(window.gqueries.with_key(gquery_key), function(i, gquery) {
        gquery.handle_api_result(value_arr);
      });
    });
    window.charts.each(function(chart) { chart.trigger('change'); });
    window.input_elements.init_legacy_controller();
    window.policy_goals.invoke('update_view');
    window.policy_goals.update_totals();
    App.peak_load.trigger('change');

    $("body").trigger("dashboardUpdate");
    hideLoading();
  },

  /**
   * Set the update in a cancelable action
   */
  handleInputElementsUpdate:function() {
    var func = $.proxy(this.doUpdateRequest, this);
    var lockable_function = function() { LockableFunction.deferExecutionIfLocked('update', func); };
    Util.cancelableAction('update',  lockable_function, {'sleepTime':500});
  },

  /**
   * Get the value of all changed sliders. Get the chart. Sends those values to the server.
   */
  doUpdateRequest:function() {
    var dirtyInputElements = window.input_elements.dirty();

    if (dirtyInputElements.length == 0) { return; }

    window.App.call_api(window.input_elements.api_update_params());
    window.input_elements.reset_dirty();
  }
});

function showLoading() { $("#loading").show(); }
function hideLoading() { $("#loading").hide(); }

window.App = App = new AppView();

<|endoftext|>


<commit_before>/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

/**
 * Rooms DB
 *
 * This code will be in charge of keeping and maintain a cache of the
 * rooms created and the ones we get an invitation, storing as well
 * all events related with the RoomObject.
 * Events will be:
 *   - Room Created: Time and subject
 *   - Room Renamed: Time and new subject
 *   - Anyone Joined: Time and Who
 *   - I joined: Time
 *   - Communication established: Time and with who
 *
 * The object will have the following schema:
 *
 * RoomSchema: {
 *   roomToken: <string> (primary key),
 *   roomUrl: <string>,
 *   roomName: <string> (index),
 *   roomOwner: <string>,
 *   maxSize: <Number>,
 *   creationTime: <Date> (index),
 *   expiresAt: <Date>,
 *   ctime: <Date>,
 *   localCtime: <Date> (index), // Automatically updated
 *   user: <string> (index), // Automatically updated
 *   events: [
 *     {
 *       // EventObject
 *       id: <Number>,
 *       action: <String>,
 *       date: <Date> (index),
 *       params: {}
 *     }
 *   ]
 * }
 *
 * Based on this info, we will expose the methods under the Object
 * RoomsDB, you can check above.
 */

(function(exports) {
  'use strict';

  const _roomsStore = 'rooms';
  const _eventsStore = 'roomEvents';
  var _dbHelper = new DatabaseHelper({
    name: 'roomsLog',
    version: 2,
    maxNumerOfRecords: 200,
    numOfRecordsToDelete: 50
  }, {
    'rooms': {
      primary: 'roomToken',
      indexes: [{
        name: 'roomName',
        fields: 'roomName',
        params: {
          multientry: true
        }
      }, {
        name: 'creationTime',
        fields: [ 'user', 'creationTime' ],
        params: {
          multientry: true
        }
      }, {
        name: 'localCtime',
        fields: 'localCtime',
        params: {
          multientry: true
        }
      }, {
        name: 'user',
        fields: 'user',
        params: {
          multientry: true
        }
      }],
      fields: [
        'roomToken',
        'roomUrl',
        'roomName',
        'roomOwner',
        'maxSize',
        'creationTime',
        'expiresAt',
        'ctime',
        'localCtime',
        'user'
      ]},

    'roomEvents': {
      primary: 'id',
      indexes: [{
        name: 'roomToken',
        fields: 'roomToken',
        params: {
          multientry: true
        }
      }, {
        name: 'date',
        fields: 'date',
        params: {
          multientry: true
        }
      }],
      fields: [
        'id',
        'roomToken',
        'action',
        'date',
        'params'
      ]}
  });

  function FilteredCursor(cursor, filter) {
    this.cursor = cursor;
    this.filter = filter;
    this.returnedItems = 0;
    this._onsuccess = this._onerror = null;
  }

  FilteredCursor.prototype = {
    set onsuccess(success) {
      this._onsuccess = success;
      this._perform();
    },

    set onerror(error) {
      this._onerror = error;
      this._perform();
    },

    _perform: function() {
      if (!this._onsuccess || !this._onerror) {
        return;
      }

      var cursor = this.cursor;
      var filter = this.filter;

      cursor.onsuccess = (event) => {
        var item = event.target.result;
        if (!item) {
          return this._onsuccess(event);
        }

        if (item.value[filter.name] === filter.value) {
          this.returnedItems++;
          this._onsuccess(event);
        } else {
          if (this.returnedItems > 0) {
            // That's all folks !
            return this._onsuccess({
              target: {
                result: null
              }});
          }
          item.continue();
        }
      };

      cursor.onerror = (event) => {
        this._onerror(event);
      };
    }
  };

  var RoomsDB = {
    /**
     * Update the localCtime of a room
     *
     * param room
     *       Object with the room
     */
    setLocalCtime: function(room) {
      room.localCtime = Date.now();
    },

    /**
     * Store a room in our DB.
     *
     * param room
     *       Object created from the API.
     * return Promise.  The resolved promise will contain the stored record.
     *                  The rejected promise, an error string.
     */
    create: function(room) {
      return new Promise(function(resolve, reject) {
        RoomsDB.setLocalCtime(room);
        room.user = Controller.identity;
        _dbHelper.addRecord(function(error, storedRecord) {
          if (error) {
            reject(error);
          } else {
            resolve(storedRecord);
          }
        }, _roomsStore, room);
      });
    },

    /**
     * Get a room in our DB given a token.
     *
     * param token
     *       String which identify a room.
     * return Promise.  The resolved promise will contain as result the room
     *                  The rejected promise, an error string.
     */
    get: function(token) {
      return new Promise(function(resolve, reject) {
        _dbHelper.getRecord(function(error, result) {
          if (error) {
            reject(error);
          } else {
            resolve(result);
          }
        }, _roomsStore, {
          key: token
        });
      });
    },

    /**
     * Get all rooms stored in our DB given a filter and
     * sorting.
     *
     * param field
     *       String which identify field in RoomSchema.
     * param sorting
     *       String. This could be 'ASC' or 'DESC' based on the
     * sorting we want. By default is 'DESC'
     * return Promise.  The resolved promise will contain as result a cursor
     *                  with all the rooms.
     *                  The rejected promise, an error string.
     */
    getAll: function(field, sorting) {
      var aFilter = {};
      if (field) {
        aFilter.sortedBy = field;
      }
      aFilter.prev = (sorting === 'DESC' ? 'prev' : null);
      return new Promise(function(resolve, reject) {
        _dbHelper.getList(function(error, cursor) {
          if (error) {
            return reject(error);
          } else {
            if (!field || field === '' || field === 'creationTime') {
              // These are special cases. We want to filter & sort
              // since this isn't directly supported by IndexedDB, we'll
              // create a filteredCursor in which we'll filter by logged user
              var filteredCursor = new FilteredCursor(cursor, {
                name: 'user',
                value: Controller.identity
              });
              resolve(filteredCursor);
            } else {
              resolve(cursor);
            }
          }
        }, _roomsStore, aFilter);
      });
    },

    /**
     * Delete a room in our DB given a token.
     *
     * param token
     *       String which identify a room.
     * return Promise.  The resolved promise will contain no params.
     *                  The rejected promise, an error string.
     */
    delete: function(tokensArray) {
      if (!Array.isArray(tokensArray)) {
        tokensArray = [ tokensArray ];
      }
      var tokensLen = tokensArray.length;
      if (tokensLen === 0) {
        return Promise.resolve();
      }
      return new Promise(function(resolve, reject) {
        tokensArray.forEach(function(token) {
          RoomsDB.deleteAllEvents(token).then(function() {
            _dbHelper.deleteRecord(function(error) {
              if (error) {
                return reject(error);
              }

              if (! --tokensLen) {
                resolve();
              }
            }, _roomsStore, token);
          }, reject);
        });
     });
    },

    /**
     * Change room name in our DB.
     *
     * param token
     *       String which identify a room.
     * param name
     *       String. New name of the room.
     * param expiresAt
     *       String. New expiration date coming from API.
     * return Promise.  The resolved promise will contain no params.
     *                  The rejected promise, an error string.
     */
    changeName: function(token, name, expiresAt) {
      return new Promise(function(resolve, reject) {
        _dbHelper.updateRecord(function(error) {
          if (error) {
            reject(error);
          } else {
            resolve();
          }
        }, _roomsStore, { key: token }, {
          roomName: name,
          expiresAt: expiresAt,
          localCtime: Date.now()
        });
      });
    },

    /**
     * Update all rooms created by us in our DB.
     *
     * param rooms
     *       Array which contain all rooms we created as owner.
     * return Promise.  The resolved promise will contain no params.
     *                  The rejected promise, an error string.
     */
    update: function(rooms) {
      if (!Array.isArray(rooms)) {
        rooms = [ rooms ];
      }
      var roomsLength = rooms.length;
      var processed = 0;
      return new Promise(function(resolve, reject) {
        for (var i = 0; i < roomsLength; i++) {
          var room = rooms[i];
          if (typeof room !== 'object') {
            return reject('BAD ROOM OBJECT TYPE');
          }
          if (!room.roomToken) {
            return reject('ROOM WITHOUT roomToken');
          }

          RoomsDB.setLocalCtime(room);

          _dbHelper.updateRecord(function(error) {
            if (error) {
              return reject(error);
            }

            if (++processed === roomsLength) {
              resolve();
            }
          }, _roomsStore, { key: room.roomToken }, room);
        }
      });
    },

    /**
     * Store an event in a specific Room.
     *
     * param token
     *       String which identify a room.
     * param eventObject
     *       Object with all params to store.
     * return Promise.  The resolved promise will contain the EventObject stored.
     *                  The rejected promise, an error string.
     */
    addEvent: function(token, eventObject) {
      if (typeof eventObject !== 'object' ||
          typeof eventObject.action !== 'string') {
        return Promise.reject('BAD EVENT OBJECT');
      }
      if (eventObject.params && typeof eventObject.params !== 'object') {
        return Promise.reject('BAD EVENT PARAMS');
      }

      return new Promise(function(resolve, reject) {
        RoomsDB.get(token).then(function(room) {
          if (!room) {
            return reject('ROOM ' + token + ' NOT FOUND');
          }

          eventObject.roomToken = token;
          eventObject.id = window.performance.now();
          eventObject.date = new Date();

          _dbHelper.addRecord(function(error, storedRecord) {
            if (error) {
              reject(error);
            }
            resolve(storedRecord);
          }, _eventsStore, eventObject);
        }, reject);
      });
    },

    /**
     * Delete a group of events in our DB given a token.
     *
     * param token
     *       String which identify a room.
     * param eventIds
     *       Array with all event IDs to remove
     * return Promise.  The resolved promise will contain no params.
     *                  The rejected promise, an error string.
     */
    deleteEvent: function(token, eventsIds) {
      if (!Array.isArray(eventsIds)) {
        eventsIds = [ eventsIds ];
      }
      var eventsLen = eventsIds.length;
      if (eventsLen === 0) {
        return Promise.resolve();
      }
      return new Promise(function(resolve, reject) {
        RoomsDB.get(token).then(function(room) {
          if (!room) {
            return reject('ROOM ' + token + ' NOT FOUND');
          }

          eventsIds.forEach(function(eventId) {
            _dbHelper.deleteRecord(function(error) {
              if (error) {
                return reject(error);
              }

              if (! --eventsLen) {
                resolve();
              }
            }, _eventsStore, eventId);
          });
        }, reject);
      });
    },

    /**
     * Delete a events in our DB for given a token.
     *
     * param token
     *       String which identify a room.
     * return Promise.  The resolved promise will contain no params.
     *                  The rejected promise, an error string.
     */
    deleteAllEvents: function(token) {
      var itemsToRemove = [];
      return new Promise(function(resolve, reject) {
        RoomsDB.getEvents(token).then(function(cursor) {

          cursor.onsuccess = function onsuccess(event) {
            var item = event.target.result;
            if (!item) {
              RoomsDB.deleteEvent(token, itemsToRemove).then(resolve, reject);
              return;
            }

            itemsToRemove.push(item.value.id);
            item.continue();
          };

          cursor.onerror = function onerror(event) {
            reject(event.target.error.name);
          };
        }, reject);
      });
    },

    /**
     * Get all events given a room token
     *
     * param token
     *       String which identify a room.
     * return Promise.  The resolved promise will contain as result a cursor
     *                  with all the events given a room token.
     *                  The rejected promise, an error string.
     */
    getEvents: function(token) {
      return new Promise(function(resolve, reject) {
        _dbHelper.getList(function(error, cursor) {
          if (error) {
            reject(error);
          } else {
            resolve(cursor);
          }
        }, _eventsStore, { index: { name: 'roomToken', value: token }});
      });
    }
  };

  exports.RoomsDB = RoomsDB;

})(window);
<commit_msg>Bug 1107069 - Add contactinfo to RoomsDB schema, r=crdlc<commit_after>/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

/**
 * Rooms DB
 *
 * This code will be in charge of keeping and maintain a cache of the
 * rooms created and the ones we get an invitation, storing as well
 * all events related with the RoomObject.
 * Events will be:
 *   - Room Created: Time and subject
 *   - Room Renamed: Time and new subject
 *   - Anyone Joined: Time and Who
 *   - I joined: Time
 *   - Communication established: Time and with who
 *
 * The object will have the following schema:
 *
 * RoomSchema: {
 *   roomToken: <string> (primary key),
 *   roomUrl: <string>,
 *   roomName: <string> (index),
 *   roomOwner: <string>,
 *   maxSize: <Number>,
 *   creationTime: <Date> (index),
 *   expiresAt: <Date>,
 *   ctime: <Date>,
 *   identities: [<string>],
 *   contactId: [<string>],
 *   contactPrimaryInfo: <string>,
 *   contactPhoto: blob,
 *   localCtime: <Date> (index), // Automatically updated
 *   user: <string> (index), // Automatically updated
 *   events: [
 *     {
 *       // EventObject
 *       id: <Number>,
 *       action: <String>,
 *       date: <Date> (index),
 *       params: {}
 *     }
 *   ]
 * }
 *
 * Based on this info, we will expose the methods under the Object
 * RoomsDB, you can check above.
 */

(function(exports) {
  'use strict';

  const _roomsStore = 'rooms';
  const _eventsStore = 'roomEvents';
  var _dbHelper = new DatabaseHelper({
    name: 'roomsLog',
    version: 2,
    maxNumerOfRecords: 200,
    numOfRecordsToDelete: 50
  }, {
    'rooms': {
      primary: 'roomToken',
      indexes: [{
        name: 'roomName',
        fields: 'roomName',
        params: {
          multientry: true
        }
      }, {
        name: 'creationTime',
        fields: [ 'user', 'creationTime' ],
        params: {
          multientry: true
        }
      }, {
        name: 'localCtime',
        fields: 'localCtime',
        params: {
          multientry: true
        }
      }, {
        name: 'user',
        fields: 'user',
        params: {
          multientry: true
        }
      }],
      fields: [
        'roomToken',
        'roomUrl',
        'roomName',
        'roomOwner',
        'maxSize',
        'creationTime',
        'expiresAt',
        'ctime',
        'identities',
        'contactId',
        'contactPrimaryInfo',
        'contactPhoto',
        'localCtime',
        'user'
      ]},

    'roomEvents': {
      primary: 'id',
      indexes: [{
        name: 'roomToken',
        fields: 'roomToken',
        params: {
          multientry: true
        }
      }, {
        name: 'date',
        fields: 'date',
        params: {
          multientry: true
        }
      }],
      fields: [
        'id',
        'roomToken',
        'action',
        'date',
        'params'
      ]}
  });

  function FilteredCursor(cursor, filter) {
    this.cursor = cursor;
    this.filter = filter;
    this.returnedItems = 0;
    this._onsuccess = this._onerror = null;
  }

  FilteredCursor.prototype = {
    set onsuccess(success) {
      this._onsuccess = success;
      this._perform();
    },

    set onerror(error) {
      this._onerror = error;
      this._perform();
    },

    _perform: function() {
      if (!this._onsuccess || !this._onerror) {
        return;
      }

      var cursor = this.cursor;
      var filter = this.filter;

      cursor.onsuccess = (event) => {
        var item = event.target.result;
        if (!item) {
          return this._onsuccess(event);
        }

        if (item.value[filter.name] === filter.value) {
          this.returnedItems++;
          this._onsuccess(event);
        } else {
          if (this.returnedItems > 0) {
            // That's all folks !
            return this._onsuccess({
              target: {
                result: null
              }});
          }
          item.continue();
        }
      };

      cursor.onerror = (event) => {
        this._onerror(event);
      };
    }
  };

  var RoomsDB = {
    /**
     * Update the localCtime of a room
     *
     * param room
     *       Object with the room
     */
    setLocalCtime: function(room) {
      room.localCtime = Date.now();
    },

    /**
     * Update Room object with contactInfo
     * param room
     *       Object with the room
     * param contactInfo
     *       Object with the contact information
     */
    setContactInfo: function(room, contactInfo) {
      if (contactInfo) {
        room.contactId = contactInfo.contactIds || null;
        var contact = contactInfo.contacts ? contactInfo.contacts[0] : null;
        if (contact) {
          room.contactPrimaryInfo = ContactsHelper.getPrimaryInfo(contact);
          room.contactPhoto = contact.photo ? contact.photo[0] : null;
        }

        var identities = [];
        ['tel', 'email'].forEach(function(field) {
          if (contact[field]) {
            for (var i = 0, l = contact[field].length; i < l; i++) {
              identities.push(contact[field][i].value);
            }
          }
        });
        room.identities = identities;
      }
    },

    /**
     * Store a room in our DB.
     *
     * param room
     *       Object created from the API.
     * param contactInfo
     *       Contact information object
     * return Promise.  The resolved promise will contain the stored record.
     *                  The rejected promise, an error string.
     */
    create: function(room, contactInfo) {
      return new Promise(function(resolve, reject) {
        RoomsDB.setLocalCtime(room);
        room.user = Controller.identity;
        RoomsDB.setContactInfo(room, contactInfo);
        _dbHelper.addRecord(function(error, storedRecord) {
          if (error) {
            reject(error);
          } else {
            resolve(storedRecord);
          }
        }, _roomsStore, room);
      });
    },

    /**
     * Get a room in our DB given a token.
     *
     * param token
     *       String which identify a room.
     * return Promise.  The resolved promise will contain as result the room
     *                  The rejected promise, an error string.
     */
    get: function(token) {
      return new Promise(function(resolve, reject) {
        _dbHelper.getRecord(function(error, result) {
          if (error) {
            reject(error);
          } else {
            resolve(result);
          }
        }, _roomsStore, {
          key: token
        });
      });
    },

    /**
     * Get all rooms stored in our DB given a filter and
     * sorting.
     *
     * param field
     *       String which identify field in RoomSchema.
     * param sorting
     *       String. This could be 'ASC' or 'DESC' based on the
     * sorting we want. By default is 'DESC'
     * return Promise.  The resolved promise will contain as result a cursor
     *                  with all the rooms.
     *                  The rejected promise, an error string.
     */
    getAll: function(field, sorting) {
      var aFilter = {};
      if (field) {
        aFilter.sortedBy = field;
      }
      aFilter.prev = (sorting === 'DESC' ? 'prev' : null);
      return new Promise(function(resolve, reject) {
        _dbHelper.getList(function(error, cursor) {
          if (error) {
            return reject(error);
          } else {
            if (!field || field === '' || field === 'creationTime') {
              // These are special cases. We want to filter & sort
              // since this isn't directly supported by IndexedDB, we'll
              // create a filteredCursor in which we'll filter by logged user
              var filteredCursor = new FilteredCursor(cursor, {
                name: 'user',
                value: Controller.identity
              });
              resolve(filteredCursor);
            } else {
              resolve(cursor);
            }
          }
        }, _roomsStore, aFilter);
      });
    },

    /**
     * Delete a room in our DB given a token.
     *
     * param token
     *       String which identify a room.
     * return Promise.  The resolved promise will contain no params.
     *                  The rejected promise, an error string.
     */
    delete: function(tokensArray) {
      if (!Array.isArray(tokensArray)) {
        tokensArray = [ tokensArray ];
      }
      var tokensLen = tokensArray.length;
      if (tokensLen === 0) {
        return Promise.resolve();
      }
      return new Promise(function(resolve, reject) {
        tokensArray.forEach(function(token) {
          RoomsDB.deleteAllEvents(token).then(function() {
            _dbHelper.deleteRecord(function(error) {
              if (error) {
                return reject(error);
              }

              if (! --tokensLen) {
                resolve();
              }
            }, _roomsStore, token);
          }, reject);
        });
     });
    },

    /**
     * Change room name in our DB.
     *
     * param token
     *       String which identify a room.
     * param name
     *       String. New name of the room.
     * param expiresAt
     *       String. New expiration date coming from API.
     * return Promise.  The resolved promise will contain no params.
     *                  The rejected promise, an error string.
     */
    changeName: function(token, name, expiresAt) {
      return new Promise(function(resolve, reject) {
        _dbHelper.updateRecord(function(error) {
          if (error) {
            reject(error);
          } else {
            resolve();
          }
        }, _roomsStore, { key: token }, {
          roomName: name,
          expiresAt: expiresAt,
          localCtime: Date.now()
        });
      });
    },

    /**
     * Update all rooms created by us in our DB.
     *
     * param rooms
     *       Array which contain all rooms we created as owner.
     * return Promise.  The resolved promise will contain no params.
     *                  The rejected promise, an error string.
     */
    update: function(rooms) {
      if (!Array.isArray(rooms)) {
        rooms = [ rooms ];
      }
      var roomsLength = rooms.length;
      var processed = 0;
      return new Promise(function(resolve, reject) {
        for (var i = 0; i < roomsLength; i++) {
          var room = rooms[i];
          if (typeof room !== 'object') {
            return reject('BAD ROOM OBJECT TYPE');
          }
          if (!room.roomToken) {
            return reject('ROOM WITHOUT roomToken');
          }

          RoomsDB.setLocalCtime(room);

          _dbHelper.updateRecord(function(error) {
            if (error) {
              return reject(error);
            }

            if (++processed === roomsLength) {
              resolve();
            }
          }, _roomsStore, { key: room.roomToken }, room);
        }
      });
    },

    /**
     * Store an event in a specific Room.
     *
     * param token
     *       String which identify a room.
     * param eventObject
     *       Object with all params to store.
     * return Promise.  The resolved promise will contain the EventObject stored.
     *                  The rejected promise, an error string.
     */
    addEvent: function(token, eventObject) {
      if (typeof eventObject !== 'object' ||
          typeof eventObject.action !== 'string') {
        return Promise.reject('BAD EVENT OBJECT');
      }
      if (eventObject.params && typeof eventObject.params !== 'object') {
        return Promise.reject('BAD EVENT PARAMS');
      }

      return new Promise(function(resolve, reject) {
        RoomsDB.get(token).then(function(room) {
          if (!room) {
            return reject('ROOM ' + token + ' NOT FOUND');
          }

          eventObject.roomToken = token;
          eventObject.id = window.performance.now();
          eventObject.date = new Date();

          _dbHelper.addRecord(function(error, storedRecord) {
            if (error) {
              reject(error);
            }
            resolve(storedRecord);
          }, _eventsStore, eventObject);
        }, reject);
      });
    },

    /**
     * Delete a group of events in our DB given a token.
     *
     * param token
     *       String which identify a room.
     * param eventIds
     *       Array with all event IDs to remove
     * return Promise.  The resolved promise will contain no params.
     *                  The rejected promise, an error string.
     */
    deleteEvent: function(token, eventsIds) {
      if (!Array.isArray(eventsIds)) {
        eventsIds = [ eventsIds ];
      }
      var eventsLen = eventsIds.length;
      if (eventsLen === 0) {
        return Promise.resolve();
      }
      return new Promise(function(resolve, reject) {
        RoomsDB.get(token).then(function(room) {
          if (!room) {
            return reject('ROOM ' + token + ' NOT FOUND');
          }

          eventsIds.forEach(function(eventId) {
            _dbHelper.deleteRecord(function(error) {
              if (error) {
                return reject(error);
              }

              if (! --eventsLen) {
                resolve();
              }
            }, _eventsStore, eventId);
          });
        }, reject);
      });
    },

    /**
     * Delete a events in our DB for given a token.
     *
     * param token
     *       String which identify a room.
     * return Promise.  The resolved promise will contain no params.
     *                  The rejected promise, an error string.
     */
    deleteAllEvents: function(token) {
      var itemsToRemove = [];
      return new Promise(function(resolve, reject) {
        RoomsDB.getEvents(token).then(function(cursor) {

          cursor.onsuccess = function onsuccess(event) {
            var item = event.target.result;
            if (!item) {
              RoomsDB.deleteEvent(token, itemsToRemove).then(resolve, reject);
              return;
            }

            itemsToRemove.push(item.value.id);
            item.continue();
          };

          cursor.onerror = function onerror(event) {
            reject(event.target.error.name);
          };
        }, reject);
      });
    },

    /**
     * Get all events given a room token
     *
     * param token
     *       String which identify a room.
     * return Promise.  The resolved promise will contain as result a cursor
     *                  with all the events given a room token.
     *                  The rejected promise, an error string.
     */
    getEvents: function(token) {
      return new Promise(function(resolve, reject) {
        _dbHelper.getList(function(error, cursor) {
          if (error) {
            reject(error);
          } else {
            resolve(cursor);
          }
        }, _eventsStore, { index: { name: 'roomToken', value: token }});
      });
    }
  };

  exports.RoomsDB = RoomsDB;

})(window);
<|endoftext|>


<commit_before>function results(req, res) {
  const location = res.locals.location;
  const altResultsMessage = 'See only open places nearby';
  const altResultsUrl = `/symptoms/stomach-ache/results?location=${location}&open=true`;

  /* eslint-disable no-param-reassign */
  res.locals.altResults = {};
  res.locals.altResults.url = altResultsUrl;
  res.locals.altResults.message = altResultsMessage;
  /* eslint-enable no-param-reassign */

  res.render('results');
}

function findHelp(req, res) {
  res.render('find-help');
}

module.exports = {
  results,
  findHelp,
};
<commit_msg>:do_not_litter: Remove tab related stuff<commit_after>function results(req, res) {
  res.render('results');
}

function findHelp(req, res) {
  res.render('find-help');
}

module.exports = {
  results,
  findHelp,
};
<|endoftext|>


<commit_before>core.factory("AbstractModel", function ($q, $rootScope, WsApi, ValidationStore, ModelCache, ModelUpdateService) {

    return function AbstractModel() {

        var abstractModel;

        var mapping;

        var entityName;

        var validations;

        var defer = $q.defer();

        var listenCallbacks = [];

        var shadow = {};

        var validationResults = {};

        var listening = false;

        var combinationOperation = 'extend';

        var beforeMethodBuffer = [];

        this.before = function (beforeMethod) {
            beforeMethodBuffer.push(beforeMethod);
        }

        this.fetch = function () {
            if (mapping.instantiate !== undefined) {

                var fetch = true;
                if (mapping.caching) {
                    var cachedModel = ModelCache.get(entityName);
                    if (cachedModel) {
                        setData(cachedModel);
                        fetch = false;
                    }
                }

                if (fetch) {
                    WsApi.fetch(mapping.instantiate).then(function (res) {
                        processResponse(res);
                    }, function (error) {
                        defer.reject(error);
                    });
                }

            }
        }

        this.init = function (data, apiMapping) {

            abstractModel = this;

            entityName = abstractModel.constructor.getName();

            validations = ValidationStore.getValidations(entityName);

            mapping = apiMapping;

            if (data) {
                setData(data);
            } else {
                if (!mapping.lazy) {
                    this.fetch();
                }
            }

            this.ready().then(function () {
                ModelUpdateService.register(abstractModel);
            });

        };

        this.enableMergeCombinationOperation = function () {
            combinationOperation = 'merge';
        };

        this.enableExtendCombinationOperation = function () {
            combinationOperation = 'extend';
        };

        this.getEntityName = function () {
            return entityName;
        };

        this.getValidations = function () {
            return validations;
        };

        this.getMapping = function () {
            return mapping;
        };

        this.ready = function () {
            return defer.promise;
        };

        this.save = function () {
            var promise = $q(function (resolve) {
                if (abstractModel.dirty()) {
                    angular.extend(mapping.update, {
                        data: abstractModel
                    });
                    WsApi.fetch(mapping.update).then(function (res) {
                        resolve(res);
                    });
                } else {
                    var payload = {};
                    payload[abstractModel.constructor.name] = abstractModel;
                    resolve({
                        body: angular.toJson({
                            payload: payload,
                            meta: {
                                type: "SUCCESS"
                            }
                        })
                    });
                }
            });
            promise.then(function (res) {
                if (angular.fromJson(res.body).meta.type != "INVALID") {
                    angular.extend(abstractModel, angular.fromJson(res.body).payload);
                    shadow = angular.copy(abstractModel);
                }
            });
            return promise;
        };

        this.delete = function () {
            angular.extend(mapping.remove, {
                data: abstractModel
            });
            var promise = WsApi.fetch(mapping.remove);
            promise.then(function (res) {
                if (angular.fromJson(res.body).meta.type == "INVALID") {
                    angular.extend(abstractModel, angular.fromJson(res.body).payload);
                }
            });
            return promise;
        };

        this.listen = function (cb) {
            listenCallbacks.push(cb);
        };

        this.refresh = function () {
            angular.extend(abstractModel, shadow);
        };

        this.dirty = function () {
            return angular.toJson(abstractModel) !== angular.toJson(shadow);
        };

        this.setValidationResults = function (results) {
            angular.extend(validationResults, results);
        };

        this.getValidationResults = function () {
            return validationResults;
        };

        this.clearValidationResults = function () {
            if (validationResults.messages !== undefined) {
                delete validationResults.messages;
            }
        };

        this.update = function (data) {
            angular[combinationOperation](abstractModel, data);
            shadow = angular.copy(abstractModel);
        };

        $rootScope.$on("$locationChangeSuccess", function () {
            listenCallbacks.length = 0;
        });

        var setData = function (data) {
            angular[combinationOperation](abstractModel, data);
            shadow = angular.copy(abstractModel);
            if (!listening) {
                listen();
            }
            if (mapping.caching) {
                var cachedModel = ModelCache.get(entityName);
                if (cachedModel === undefined) {
                    ModelCache.set(entityName, abstractModel);
                } else {
                    // could possibly update cache here
                }
            }
            angular.forEach(beforeMethodBuffer, function (beforeMethod) {
                beforeMethod();
            });
            defer.resolve(abstractModel);
        };

        var listen = function () {
            if (abstractModel && mapping.listen) {
                if (abstractModel.id) {
                    angular.extend(mapping.listen, {
                        method: "/" + abstractModel.id
                    });
                }
                var notifyPromise = WsApi.listen(mapping.listen);
                notifyPromise.then(null, null, function (res) {
                    processResponse(res);
                    angular.forEach(listenCallbacks, function (cb) {
                        cb(res);
                    });
                });
                listening = true;
                return notifyPromise;
            }
        };

        var processResponse = function (res) {
            var resObj = angular.fromJson(res.body);
            if (resObj.meta.type != 'ERROR') {
                angular.forEach(resObj.payload, function (datum) {
                    angular[combinationOperation](abstractModel, datum);
                });
                setData(abstractModel);
            } else {
                abstractModel.refresh();
            }
        };

        // additional core level model methods and variables

        return this;

    };

});
<commit_msg>Require apiMapping validation true to use validations on model.<commit_after>core.factory("AbstractModel", function ($q, $rootScope, WsApi, ValidationStore, ModelCache, ModelUpdateService) {

    return function AbstractModel() {

        var abstractModel;

        var mapping;

        var entityName;

        var validations;

        var defer = $q.defer();

        var listenCallbacks = [];

        var shadow = {};

        var validationResults = {};

        var listening = false;

        var combinationOperation = 'extend';

        var beforeMethodBuffer = [];

        this.before = function (beforeMethod) {
            beforeMethodBuffer.push(beforeMethod);
        }

        this.fetch = function () {
            if (mapping.instantiate !== undefined) {

                var fetch = true;
                if (mapping.caching) {
                    var cachedModel = ModelCache.get(entityName);
                    if (cachedModel) {
                        setData(cachedModel);
                        fetch = false;
                    }
                }

                if (fetch) {
                    WsApi.fetch(mapping.instantiate).then(function (res) {
                        processResponse(res);
                    }, function (error) {
                        defer.reject(error);
                    });
                }

            }
        }

        this.init = function (data, apiMapping) {

            abstractModel = this;

            mapping = apiMapping;

            entityName = abstractModel.constructor.getName();

            if (mapping.validations) {
                validations = ValidationStore.getValidations(entityName);
            }

            if (data) {
                setData(data);
            } else {
                if (!mapping.lazy) {
                    this.fetch();
                }
            }

            this.ready().then(function () {
                ModelUpdateService.register(abstractModel);
            });

        };

        this.enableMergeCombinationOperation = function () {
            combinationOperation = 'merge';
        };

        this.enableExtendCombinationOperation = function () {
            combinationOperation = 'extend';
        };

        this.getEntityName = function () {
            return entityName;
        };

        this.getValidations = function () {
            return validations;
        };

        this.getMapping = function () {
            return mapping;
        };

        this.ready = function () {
            return defer.promise;
        };

        this.save = function () {
            var promise = $q(function (resolve) {
                if (abstractModel.dirty()) {
                    angular.extend(mapping.update, {
                        data: abstractModel
                    });
                    WsApi.fetch(mapping.update).then(function (res) {
                        resolve(res);
                    });
                } else {
                    var payload = {};
                    payload[abstractModel.constructor.name] = abstractModel;
                    resolve({
                        body: angular.toJson({
                            payload: payload,
                            meta: {
                                type: "SUCCESS"
                            }
                        })
                    });
                }
            });
            promise.then(function (res) {
                if (angular.fromJson(res.body).meta.type != "INVALID") {
                    angular.extend(abstractModel, angular.fromJson(res.body).payload);
                    shadow = angular.copy(abstractModel);
                }
            });
            return promise;
        };

        this.delete = function () {
            angular.extend(mapping.remove, {
                data: abstractModel
            });
            var promise = WsApi.fetch(mapping.remove);
            promise.then(function (res) {
                if (angular.fromJson(res.body).meta.type == "INVALID") {
                    angular.extend(abstractModel, angular.fromJson(res.body).payload);
                }
            });
            return promise;
        };

        this.listen = function (cb) {
            listenCallbacks.push(cb);
        };

        this.refresh = function () {
            angular.extend(abstractModel, shadow);
        };

        this.dirty = function () {
            return angular.toJson(abstractModel) !== angular.toJson(shadow);
        };

        this.setValidationResults = function (results) {
            angular.extend(validationResults, results);
        };

        this.getValidationResults = function () {
            return validationResults;
        };

        this.clearValidationResults = function () {
            if (validationResults.messages !== undefined) {
                delete validationResults.messages;
            }
        };

        this.update = function (data) {
            angular[combinationOperation](abstractModel, data);
            shadow = angular.copy(abstractModel);
        };

        $rootScope.$on("$locationChangeSuccess", function () {
            listenCallbacks.length = 0;
        });

        var setData = function (data) {
            angular[combinationOperation](abstractModel, data);
            shadow = angular.copy(abstractModel);
            if (!listening) {
                listen();
            }
            if (mapping.caching) {
                var cachedModel = ModelCache.get(entityName);
                if (cachedModel === undefined) {
                    ModelCache.set(entityName, abstractModel);
                } else {
                    // could possibly update cache here
                }
            }
            angular.forEach(beforeMethodBuffer, function (beforeMethod) {
                beforeMethod();
            });
            defer.resolve(abstractModel);
        };

        var listen = function () {
            if (abstractModel && mapping.listen) {
                if (abstractModel.id) {
                    angular.extend(mapping.listen, {
                        method: "/" + abstractModel.id
                    });
                }
                var notifyPromise = WsApi.listen(mapping.listen);
                notifyPromise.then(null, null, function (res) {
                    processResponse(res);
                    angular.forEach(listenCallbacks, function (cb) {
                        cb(res);
                    });
                });
                listening = true;
                return notifyPromise;
            }
        };

        var processResponse = function (res) {
            var resObj = angular.fromJson(res.body);
            if (resObj.meta.type != 'ERROR') {
                angular.forEach(resObj.payload, function (datum) {
                    angular[combinationOperation](abstractModel, datum);
                });
                setData(abstractModel);
            } else {
                abstractModel.refresh();
            }
        };

        // additional core level model methods and variables

        return this;

    };

});
<|endoftext|>


<commit_before>import Ember from 'ember';

import Message from './message';

const {
    get,
    inject,
    computed,
    observer,
    run,
    RSVP,
    Logger
} = Ember;

export default Ember.Object.extend({
    store: inject.service(),
    skype: inject.service(),

    conversation: null,
    extraConversations: null,

    messages: null,
    loadedHistory: false,

    deferred: null,
    _setupComplete: false,

    messageSortingAsc: ['sortComparison:asc'],
    sortedMessages: computed.sort('messages', 'messageSortingAsc'),

    name: computed.reads('conversationTarget.name'),

    loaded: computed('deferred.promise', function () {
        return this.get('deferred.promise');
    }),

    isReady: computed('_setupComplete', function () {
        return this.get('_setupComplete');
    }),

    conversationTarget: computed('conversation', function () {
        const person = get(this.get('conversation').participants(), 'firstObject.person');
        if (!person) {
            return {};
        }
        return this.get('store').getUserForPerson(person);
    }),

    avatarUrl: computed('conversation', function () {
        const conversation = this.get('conversation');
        const avatarUrl = conversation.avatarUrlLarge();
        if (typeof avatarUrl === 'string') {
            return RSVP.resolve(avatarUrl);
        }

        return new RSVP.Promise(resolve => {
            conversation.avatarUrl.get().then(resolve);
        });
    }),

    conversationChange: observer('conversation', function () {
        run.once(this, this._setup);
    }),

    init() {
        this._super(...arguments);

        this.set('messages', []);
        this.set('extraConversations', []);
        this.set('deferred', RSVP.defer());

        const id = this.get('id');
        if (!id) {
            throw new Error('Conversation id is required.');
        }

        const conversation = this.get('conversation');
        if (!conversation) {
            Logger.warn('Conversation model created without skype conversation.');
            return;
        }

        this._setup();
    },

    sendMessage(message) {
        this.get('conversation').chatService.sendMessage(message)
            .then(function () {
                Logger.log('Message sent.');
            });
    },

    loadMessageHistory() {
        if (!this.get('loadedHistory')) {
            this.get('deferred.promise').then(() => {
                this.get('conversation').historyService.getMoreActivityItems().then(() => {
                    Logger.log('History loaded', {
                        name: this.get('name'),
                        conversationId: this.get('conversation.id')
                    });
                    this.set('loadedHistory', true);
                });
            });
        }
    },

    _setup() {
        if (this.get('_setupComplete')) {
            return;
        }

        this.set('_setupComplete', true);

        const conversation = this.get('conversation');

        conversation.participants.added(person => {
            Logger.log('conversation.participants.added', person);
            this.notifyPropertyChange('conversationTarget');
            this.get('deferred').resolve();
        });

        conversation.historyService.activityItems.added(message => {
            Logger.log('HISTORY', message);

            let sender = this.get('store').getUserForPerson(message.sender);

            if (message.type && message.type() !== 'TextMessage') {
                Logger.log('Unsupported message type:', {
                    message,
                    messageType: message.type()
                });
                return;
            }

            let messageModel = Message.create({
                sender,
                skypeMessage: message
            });

            Logger.log('conversation.historyService.activityItems.added', { message: messageModel });

            this.get('messages').pushObject(messageModel);
        });

        conversation.state.changed((newValue, reason, oldValue) => {
            Logger.log('conversation.state.changed', newValue, reason, oldValue);
        });
    }
});
<commit_msg>Attach message listener for merged conversations<commit_after>import Ember from 'ember';

import Message from './message';

const {
    get,
    inject,
    computed,
    observer,
    run,
    RSVP,
    Logger
} = Ember;

export default Ember.Object.extend({
    store: inject.service(),
    skype: inject.service(),

    conversation: null,
    extraConversations: null,

    messages: null,
    loadedHistory: false,

    deferred: null,
    _setupComplete: false,

    messageSortingAsc: ['sortComparison:asc'],
    sortedMessages: computed.sort('messages', 'messageSortingAsc'),

    name: computed.reads('conversationTarget.name'),

    loaded: computed('deferred.promise', function () {
        return this.get('deferred.promise');
    }),

    isReady: computed('_setupComplete', function () {
        return this.get('_setupComplete');
    }),

    conversationTarget: computed('conversation', function () {
        const person = get(this.get('conversation').participants(), 'firstObject.person');
        if (!person) {
            return {};
        }
        return this.get('store').getUserForPerson(person);
    }),

    avatarUrl: computed('conversation', function () {
        const conversation = this.get('conversation');
        const avatarUrl = conversation.avatarUrlLarge();
        if (typeof avatarUrl === 'string') {
            return RSVP.resolve(avatarUrl);
        }

        return new RSVP.Promise(resolve => {
            conversation.avatarUrl.get().then(resolve);
        });
    }),

    conversationChange: observer('conversation', function () {
        run.once(this, this._setup);
    }),

    incomingExtraConversation: observer('extraConversations.[]', function () {
        run.once(this, this._setupMessageHandling, this.get('extraConversations.lastObject'));
    }),

    init() {
        this._super(...arguments);

        this.set('messages', []);
        this.set('extraConversations', []);
        this.set('deferred', RSVP.defer());

        const id = this.get('id');
        if (!id) {
            throw new Error('Conversation id is required.');
        }

        const conversation = this.get('conversation');
        if (!conversation) {
            Logger.warn('Conversation model created without skype conversation.');
            return;
        }

        this._setup();
    },

    sendMessage(message) {
        this.get('conversation').chatService.sendMessage(message)
            .then(function () {
                Logger.log('Message sent.');
            });
    },

    loadMessageHistory() {
        if (!this.get('loadedHistory')) {
            this.get('deferred.promise').then(() => {
                this.get('conversation').historyService.getMoreActivityItems().then(() => {
                    Logger.log('History loaded', {
                        name: this.get('name'),
                        conversationId: this.get('conversation.id')
                    });
                    this.set('loadedHistory', true);
                });
            });
        }
    },

    _setup() {
        if (this.get('_setupComplete')) {
            return;
        }

        this.set('_setupComplete', true);

        const conversation = this.get('conversation');

        conversation.participants.added(person => {
            Logger.log('conversation.participants.added', person);
            this.notifyPropertyChange('conversationTarget');
            this.get('deferred').resolve();
        });

        this._setupMessageHandling(conversation);
    },

    _setupMessageHandling(conversation) {
        conversation.historyService.activityItems.added(message => {
            Logger.log('HISTORY', message);

            let sender = this.get('store').getUserForPerson(message.sender);

            if (message.type && message.type() !== 'TextMessage') {
                Logger.log('Unsupported message type:', {
                    message,
                    messageType: message.type()
                });
                return;
            }

            let messageModel = Message.create({
                sender,
                skypeMessage: message
            });

            Logger.log('conversation.historyService.activityItems.added', { message: messageModel });

            this.get('messages').pushObject(messageModel);
        });

        conversation.state.changed((newValue, reason, oldValue) => {
            Logger.log('conversation.state.changed', newValue, reason, oldValue);
        });
    }
});
<|endoftext|>