<commit_before>var fs = require('fs'),
    Path = require('path'),
    logger = require('npmlog');

var defaultSettings = {
    token: '',
    channels: ['random'],
    loggerLevel: 'info',
    autoWatch: false,
    silencerEmojis: ['no_entry_sign'],
    mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/digest',
    memcachedHost: process.env.MEMCACHED_HOST || '127.0.0.1',
    memcachedPort: process.env.MEMCACHED_PORT || '11211',
    redisUrl: process.env.REDIS_URL || 'redis://127.0.0.1:6379',
    outputLimit: 20,
    timezone: 'America/Sao_Paulo',
    showLinksWithoutReaction: false,
    notificationChannel: 'digest_notifications',
    queueName: 'digest_process_queue'
};

class Settings {
    static rootPath() {
        return Path.join(__dirname, '..', '..');
    }

    static getSettingsPath() {
        return process.env.WD_SETTINGS || Path.join(Settings.rootPath(), './settings.json');
    }

    static loadSettings() {
        return JSON.parse(fs.readFileSync(Settings.getSettingsPath()));
    }

    static sharedInstance() {
        if(!Settings.instance) {
            Settings.instance = new Settings();
        }
        return Settings.instance;
    }

    constructor() {
        this.settingsLoaded = false;
        this.messageMatcherRegex = /\b(http|https)?(:\/\/)?(\S*)\.(\w{2,4})(\/([^\s]+))?\b/ig;
        var readSettings = defaultSettings;
        try {
            readSettings = Settings.loadSettings();
            this.settingsLoaded = true;
        } catch(ex) {
            if(ex.code === 'ENOENT') {
                logger.error('settings', 'Settings file could not be found. Assuming default settings...');
            } else if(ex instanceof SyntaxError) {
                logger.error('settings', 'Error parsing Settings file: ', ex);
            } else {
                logger.error('settings', 'Unexpected error: ', ex);
            }
        }
        var settingsKeys = Object.keys(readSettings),
            defaultKeys = Object.keys(defaultSettings),
            channels = defaultSettings.channels;
        defaultKeys.forEach(key => {
            if(key !== 'channels') {
                if(settingsKeys.indexOf(key) === -1) {
                    readSettings[key] = defaultSettings[key];
                }
            } else {
                if(settingsKeys.indexOf('channels') > -1) {
                    channels = readSettings.channels;
                }
            }
        });
        delete readSettings['channels'];
        this._channels = channels;
        Object.assign(this, readSettings);
        Object.defineProperty(this, 'channels', {
            get: () => {
                return this._channels;
            },
            set: value => {
                var settings = readSettings;
                try {
                    settings = Settings.readSettings();
                } catch(ex) {
                    /* Too bad. */
                }
                settings.channels = [ ...new Set(this._channels.concat(settings.channels).concat(value)) ].filter(v => v);
                try {
                    fs.writeFileSync(Settings.getSettingsPath(), JSON.stringify(settings, null, 4));
                } catch(ex) {
                    logger.error('settings', 'Cannot rewrite settings: ', ex);
                }
                this._channels = settings.channels;
            }
        });
        logger.level = this.loggerLevel;
    }
}

module.exports = Settings;
<commit_msg>Add new settings properties<commit_after>var fs = require('fs'),
    Path = require('path'),
    logger = require('npmlog');

var defaultSettings = {
    token: '',
    channels: ['random'],
    loggerLevel: 'info',
    autoWatch: false,
    silencerEmojis: ['no_entry_sign'],
    mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/digest',
    memcachedHost: process.env.MEMCACHED_HOST || '127.0.0.1',
    memcachedPort: process.env.MEMCACHED_PORT || '11211',
    redisUrl: process.env.REDIS_URL || 'redis://127.0.0.1:6379',
    outputLimit: 20,
    timezone: 'America/Sao_Paulo',
    showLinksWithoutReaction: false,
    notificationChannel: 'digest_notifications',
    processQueueName: 'digest_process_queue',
    prefetchQueueName: 'digest_prefetch_queue',
    errorQueueNane: 'digest_error_queue',
    metaCachePrefix: 'd-m-',
    itemCachePrefix: 'd-i-'
};

class Settings {
    static rootPath() {
        return Path.join(__dirname, '..', '..');
    }

    static getSettingsPath() {
        return process.env.WD_SETTINGS || Path.join(Settings.rootPath(), './settings.json');
    }

    static loadSettings() {
        return JSON.parse(fs.readFileSync(Settings.getSettingsPath()));
    }

    static sharedInstance() {
        if(!Settings.instance) {
            Settings.instance = new Settings();
        }
        return Settings.instance;
    }

    constructor() {
        this.settingsLoaded = false;
        this.messageMatcherRegex = /\b(http|https)?(:\/\/)?(\S*)\.(\w{2,4})(\/([^\s]+))?\b/ig;
        var readSettings = defaultSettings;
        try {
            readSettings = Settings.loadSettings();
            this.settingsLoaded = true;
        } catch(ex) {
            if(ex.code === 'ENOENT') {
                logger.error('settings', 'Settings file could not be found. Assuming default settings...');
            } else if(ex instanceof SyntaxError) {
                logger.error('settings', 'Error parsing Settings file: ', ex);
            } else {
                logger.error('settings', 'Unexpected error: ', ex);
            }
        }
        var settingsKeys = Object.keys(readSettings),
            defaultKeys = Object.keys(defaultSettings),
            channels = defaultSettings.channels;
        defaultKeys.forEach(key => {
            if(key !== 'channels') {
                if(settingsKeys.indexOf(key) === -1) {
                    readSettings[key] = defaultSettings[key];
                }
            } else {
                if(settingsKeys.indexOf('channels') > -1) {
                    channels = readSettings.channels;
                }
            }
        });
        delete readSettings['channels'];
        this._channels = channels;
        Object.assign(this, readSettings);
        Object.defineProperty(this, 'channels', {
            get: () => {
                return this._channels;
            },
            set: value => {
                var settings = readSettings;
                try {
                    settings = Settings.readSettings();
                } catch(ex) {
                    /* Too bad. */
                }
                settings.channels = [ ...new Set(this._channels.concat(settings.channels).concat(value)) ].filter(v => v);
                try {
                    fs.writeFileSync(Settings.getSettingsPath(), JSON.stringify(settings, null, 4));
                } catch(ex) {
                    logger.error('settings', 'Cannot rewrite settings: ', ex);
                }
                this._channels = settings.channels;
            }
        });
        logger.level = this.loggerLevel;
    }
}

module.exports = Settings;
<|endoftext|>


<commit_before>var previousRadio = Backbone.Radio;

var Radio = Backbone.Radio = {};

Radio.VERSION = '<%= version %>';

// This allows you to run multiple instances of Radio on the same
// webapp. After loading the new version, call `noConflict()` to
// get a reference to it. At the same time the old version will be
// returned to Backbone.Radio.
Radio.noConflict = function () {
  Backbone.Radio = previousRadio;
  return this;
};

// Whether or not we're in DEBUG mode or not. DEBUG mode helps you
// get around the issues of lack of warnings when events are mis-typed.
Radio.DEBUG = false;

// This is the method that's called when an unregistered event was called.
// By default, it logs warning to the console. By overriding this you could
// make it throw an Error, for instance. This would make firing a nonexistent event
// have the same consequence as firing a nonexistent method on an Object.
Radio.debugLog = function(warning, eventName, channelName) {
  if (!Radio.DEBUG) { return; }
  var channelText = channelName ? ' on the ' + channelName + ' channel' : '';
  if (console && console.warn) {
    console.warn(warning + channelText + ': "' + eventName + '"');
  }
};

var eventSplitter = /\s+/;

// An internal method used to handle Radio's method overloading for Requests and
// Commands. It's borrowed from Backbone.Events. It differs from Backbone's overload
// API (which is used in Backbone.Events) in that it doesn't support space-separated
// event names.
function eventsApi(obj, action, name, rest) {
  if (!name) {
    return false;
  }

  var results = [];

  // Handle event maps.
  if (typeof name === 'object') {
    for (var key in name) {
      var result = obj[action].apply(obj, [key, name[key]].concat(rest));
      eventSplitter.test(key) ? (results = results.concat(result)) : results.push(result);
    }
    return results;
  }

  // Handle space separated event names.
  if (eventSplitter.test(name)) {
    var names = name.split(eventSplitter);
    for (var i = 0, l = names.length; i < l; i++) {
      results.push(obj[action].apply(obj, [names[i]].concat(rest)));
    }
    return results;
  }

  return false;
}

// An optimized way to execute callbacks.
function callHandler(callback, context, args) {
  var a1 = args[0], a2 = args[1], a3 = args[2];
  switch(args.length) {
    case 0: return callback.call(context);
    case 1: return callback.call(context, a1);
    case 2: return callback.call(context, a1, a2);
    case 3: return callback.call(context, a1, a2, a3);
    default: return callback.apply(context, args);
  }
}

// A helper used by `off` methods to the handler from the store
function removeHandler(store, name, callback, context) {
  var event = store[name];
  if (
     (!callback || (callback === event.callback || callback === event.callback._callback)) &&
     (!context || (context === event.context))
  ) {
    delete store[name];
    return true;
  }
}

function removeHandlers(store, name, callback, context) {
  store || (store = {});
  var names = name ? [name] : _.keys(store);
  var matched = false;

  for (var i = 0, length = names.length; i < length; i++) {
    name = names[i];

    // If there's no event by this name, log it and continue
    // with the loop
    if (!store[name]) {
      continue;
    }

    if (removeHandler(store, name, callback, context)) {
      matched = true;
    }
  }

  return matched;
}

/*
 * tune-in
 * -------
 * Get console logs of a channel's activity
 *
 */

var _logs = {};

// This is to produce an identical function in both tuneIn and tuneOut,
// so that Backbone.Events unregisters it.
function _partial(channelName) {
  return _logs[channelName] || (_logs[channelName] = _.partial(Radio.log, channelName));
}

_.extend(Radio, {

  // Log information about the channel and event
  log: function(channelName, eventName) {
    var args = _.rest(arguments, 2);
    console.log('[' + channelName + '] "' + eventName + '"', args);
  },

  // Logs all events on this channel to the console. It sets an
  // internal value on the channel telling it we're listening,
  // then sets a listener on the Backbone.Events
  tuneIn: function(channelName) {
    var channel = Radio.channel(channelName);
    channel._tunedIn = true;
    channel.on('all', _partial(channelName));
    return this;
  },

  // Stop logging all of the activities on this channel to the console
  tuneOut: function(channelName) {
    var channel = Radio.channel(channelName);
    channel._tunedIn = false;
    channel.off('all', _partial(channelName));
    delete _logs[channelName];
    return this;
  }
});

/*
 * Backbone.Radio.Commands
 * -----------------------
 * A messaging system for sending orders.
 *
 */

Radio.Commands = {

  // Issue a command
  command: function(name) {
    var args = _.rest(arguments);
    if (eventsApi(this, 'command', name, args)) {
      return this;
    }
    var channelName = this.channelName;
    var commands = this._commands;

    // Check if we should log the command, and if so, do it
    if (channelName && this._tunedIn) {
      Radio.log.apply(this, [channelName, name].concat(args));
    }

    // If the command isn't handled, log it in DEBUG mode and exit
    if (commands && (commands[name] || commands['default'])) {
      var handler = commands[name] || commands['default'];
      args = commands[name] ? args : arguments;
      callHandler(handler.callback, handler.context, args);
    } else {
      Radio.debugLog('An unhandled command was fired', name, channelName);
    }

    return this;
  },

  // Register a handler for a command.
  comply: function(name, callback, context) {
    if (eventsApi(this, 'comply', name, [callback, context])) {
      return this;
    }
    this._commands || (this._commands = {});

    if (this._commands[name]) {
      Radio.debugLog('A command was overwritten', name, this.channelName);
    }

    this._commands[name] = {
      callback: callback,
      context: context || this
    };

    return this;
  },

  // Register a handler for a command that happens just once.
  complyOnce: function(name, callback, context) {
    if (eventsApi(this, 'complyOnce', name, [callback, context])) {
      return this;
    }
    var self = this;

    var once = _.once(function() {
      self.stopComplying(name);
      return callback.apply(this, arguments);
    });

    return this.comply(name, once, context);
  },

  // Remove handler(s)
  stopComplying: function(name, callback, context) {
    if (eventsApi(this, 'stopComplying', name)) {
      return this;
    }

    // Remove everything if there are no arguments passed
    if (!name && !callback && !context) {
      delete this._commands;
    } else if (!removeHandlers(this._commands, name, callback, context)) {
      Radio.debugLog('Attempted to remove the unregistered command', name, this.channelName);
    }

    return this;
  }
};

/*
 * Backbone.Radio.Requests
 * -----------------------
 * A messaging system for requesting data.
 *
 */

function makeCallback(callback) {
  return _.isFunction(callback) ? callback : function () { return callback; };
}

Radio.Requests = {

  // Make a request
  request: function(name) {
    var args = _.rest(arguments);
    var results = eventsApi(this, 'request', name, args);
    if (results) {
      return results;
    }
    var channelName = this.channelName;
    var requests = this._requests;

    // Check if we should log the request, and if so, do it
    if (channelName && this._tunedIn) {
      Radio.log.apply(this, [channelName, name].concat(args));
    }

    // If the request isn't handled, log it in DEBUG mode and exit
    if (requests && (requests[name] || requests['default'])) {
      var handler = requests[name] || requests['default'];
      args = requests[name] ? args : arguments;
      return callHandler(handler.callback, handler.context, args);
    } else {
      Radio.debugLog('An unhandled request was fired', name, channelName);
    }
  },

  // Set up a handler for a request
  reply: function(name, callback, context) {
    if (eventsApi(this, 'reply', name, [callback, context])) {
      return this;
    }

    this._requests || (this._requests = {});

    if (this._requests[name]) {
      Radio.debugLog('A request was overwritten', name, this.channelName);
    }

    this._requests[name] = {
      callback: makeCallback(callback),
      context: context || this
    };

    return this;
  },

  // Set up a handler that can only be requested once
  replyOnce: function(name, callback, context) {
    if (eventsApi(this, 'replyOnce', name, [callback, context])) {
      return this;
    }

    var self = this;

    var once = _.once(function() {
      self.stopReplying(name);
      return makeCallback(callback).apply(this, arguments);
    });

    return this.reply(name, once, context);
  },

  // Remove handler(s)
  stopReplying: function(name, callback, context) {
    if (eventsApi(this, 'stopReplying', name)) {
      return this;
    }

    // Remove everything if there are no arguments passed
    if (!name && !callback && !context) {
      delete this._requests;
    } else if (!removeHandlers(this._requests, name, callback, context)) {
      Radio.debugLog('Attempted to remove the unregistered request', name, this.channelName);
    }

    return this;
  }
};

/*
 * Backbone.Radio.channel
 * ----------------------
 * Get a reference to a channel by name.
 *
 */

Radio._channels = {};

Radio.channel = function(channelName) {
  if (!channelName) {
    throw new Error('You must provide a name for the channel.');
  }

  if (Radio._channels[channelName]) {
    return Radio._channels[channelName];
  } else {
    return (Radio._channels[channelName] = new Radio.Channel(channelName));
  }
};

/*
 * Backbone.Radio.Channel
 * ----------------------
 * A Channel is an object that extends from Backbone.Events,
 * Radio.Commands, and Radio.Requests.
 *
 */

Radio.Channel = function(channelName) {
  this.channelName = channelName;
};

_.extend(Radio.Channel.prototype, Backbone.Events, Radio.Commands, Radio.Requests, {

  // Remove all handlers from the messaging systems of this channel
  reset: function() {
    this.off();
    this.stopListening();
    this.stopComplying();
    this.stopReplying();
    return this;
  }
});

/*
 * Top-level API
 * -------------
 * Supplies the 'top-level API' for working with Channels directly
 * from Backbone.Radio.
 *
 */

var channel, args, systems = [Backbone.Events, Radio.Commands, Radio.Requests];

_.each(systems, function(system) {
  _.each(system, function(method, methodName) {
    Radio[methodName] = function(channelName) {
      args = _.rest(arguments);
      channel = this.channel(channelName);
      return channel[methodName].apply(channel, args);
    };
  });
});

Radio.reset = function(channelName) {
  var channels = !channelName ? this._channels : [this._channels[channelName]];
  _.invoke(channels, 'reset');
};
<commit_msg>Expose eventsApi and callHandler<commit_after>var previousRadio = Backbone.Radio;

var Radio = Backbone.Radio = {};

Radio.VERSION = '<%= version %>';

// This allows you to run multiple instances of Radio on the same
// webapp. After loading the new version, call `noConflict()` to
// get a reference to it. At the same time the old version will be
// returned to Backbone.Radio.
Radio.noConflict = function () {
  Backbone.Radio = previousRadio;
  return this;
};

// Whether or not we're in DEBUG mode or not. DEBUG mode helps you
// get around the issues of lack of warnings when events are mis-typed.
Radio.DEBUG = false;

// This is the method that's called when an unregistered event was called.
// By default, it logs warning to the console. By overriding this you could
// make it throw an Error, for instance. This would make firing a nonexistent event
// have the same consequence as firing a nonexistent method on an Object.
Radio.debugLog = function(warning, eventName, channelName) {
  if (!Radio.DEBUG) { return; }
  var channelText = channelName ? ' on the ' + channelName + ' channel' : '';
  if (console && console.warn) {
    console.warn(warning + channelText + ': "' + eventName + '"');
  }
};

var eventSplitter = /\s+/;

// An internal method used to handle Radio's method overloading for Requests and
// Commands. It's borrowed from Backbone.Events. It differs from Backbone's overload
// API (which is used in Backbone.Events) in that it doesn't support space-separated
// event names.
Radio._eventsApi = function(obj, action, name, rest) {
  if (!name) {
    return false;
  }

  var results = [];

  // Handle event maps.
  if (typeof name === 'object') {
    for (var key in name) {
      var result = obj[action].apply(obj, [key, name[key]].concat(rest));
      eventSplitter.test(key) ? (results = results.concat(result)) : results.push(result);
    }
    return results;
  }

  // Handle space separated event names.
  if (eventSplitter.test(name)) {
    var names = name.split(eventSplitter);
    for (var i = 0, l = names.length; i < l; i++) {
      results.push(obj[action].apply(obj, [names[i]].concat(rest)));
    }
    return results;
  }

  return false;
};

// An optimized way to execute callbacks.
Radio._callHandler = function(callback, context, args) {
  var a1 = args[0], a2 = args[1], a3 = args[2];
  switch(args.length) {
    case 0: return callback.call(context);
    case 1: return callback.call(context, a1);
    case 2: return callback.call(context, a1, a2);
    case 3: return callback.call(context, a1, a2, a3);
    default: return callback.apply(context, args);
  }
};

// A helper used by `off` methods to the handler from the store
function removeHandler(store, name, callback, context) {
  var event = store[name];
  if (
     (!callback || (callback === event.callback || callback === event.callback._callback)) &&
     (!context || (context === event.context))
  ) {
    delete store[name];
    return true;
  }
}

function removeHandlers(store, name, callback, context) {
  store || (store = {});
  var names = name ? [name] : _.keys(store);
  var matched = false;

  for (var i = 0, length = names.length; i < length; i++) {
    name = names[i];

    // If there's no event by this name, log it and continue
    // with the loop
    if (!store[name]) {
      continue;
    }

    if (removeHandler(store, name, callback, context)) {
      matched = true;
    }
  }

  return matched;
}

/*
 * tune-in
 * -------
 * Get console logs of a channel's activity
 *
 */

var _logs = {};

// This is to produce an identical function in both tuneIn and tuneOut,
// so that Backbone.Events unregisters it.
function _partial(channelName) {
  return _logs[channelName] || (_logs[channelName] = _.partial(Radio.log, channelName));
}

_.extend(Radio, {

  // Log information about the channel and event
  log: function(channelName, eventName) {
    var args = _.rest(arguments, 2);
    console.log('[' + channelName + '] "' + eventName + '"', args);
  },

  // Logs all events on this channel to the console. It sets an
  // internal value on the channel telling it we're listening,
  // then sets a listener on the Backbone.Events
  tuneIn: function(channelName) {
    var channel = Radio.channel(channelName);
    channel._tunedIn = true;
    channel.on('all', _partial(channelName));
    return this;
  },

  // Stop logging all of the activities on this channel to the console
  tuneOut: function(channelName) {
    var channel = Radio.channel(channelName);
    channel._tunedIn = false;
    channel.off('all', _partial(channelName));
    delete _logs[channelName];
    return this;
  }
});

/*
 * Backbone.Radio.Commands
 * -----------------------
 * A messaging system for sending orders.
 *
 */

Radio.Commands = {

  // Issue a command
  command: function(name) {
    var args = _.rest(arguments);
    if (Radio._eventsApi(this, 'command', name, args)) {
      return this;
    }
    var channelName = this.channelName;
    var commands = this._commands;

    // Check if we should log the command, and if so, do it
    if (channelName && this._tunedIn) {
      Radio.log.apply(this, [channelName, name].concat(args));
    }

    // If the command isn't handled, log it in DEBUG mode and exit
    if (commands && (commands[name] || commands['default'])) {
      var handler = commands[name] || commands['default'];
      args = commands[name] ? args : arguments;
      Radio._callHandler(handler.callback, handler.context, args);
    } else {
      Radio.debugLog('An unhandled command was fired', name, channelName);
    }

    return this;
  },

  // Register a handler for a command.
  comply: function(name, callback, context) {
    if (Radio._eventsApi(this, 'comply', name, [callback, context])) {
      return this;
    }
    this._commands || (this._commands = {});

    if (this._commands[name]) {
      Radio.debugLog('A command was overwritten', name, this.channelName);
    }

    this._commands[name] = {
      callback: callback,
      context: context || this
    };

    return this;
  },

  // Register a handler for a command that happens just once.
  complyOnce: function(name, callback, context) {
    if (Radio._eventsApi(this, 'complyOnce', name, [callback, context])) {
      return this;
    }
    var self = this;

    var once = _.once(function() {
      self.stopComplying(name);
      return callback.apply(this, arguments);
    });

    return this.comply(name, once, context);
  },

  // Remove handler(s)
  stopComplying: function(name, callback, context) {
    if (Radio._eventsApi(this, 'stopComplying', name)) {
      return this;
    }

    // Remove everything if there are no arguments passed
    if (!name && !callback && !context) {
      delete this._commands;
    } else if (!removeHandlers(this._commands, name, callback, context)) {
      Radio.debugLog('Attempted to remove the unregistered command', name, this.channelName);
    }

    return this;
  }
};

/*
 * Backbone.Radio.Requests
 * -----------------------
 * A messaging system for requesting data.
 *
 */

function makeCallback(callback) {
  return _.isFunction(callback) ? callback : function () { return callback; };
}

Radio.Requests = {

  // Make a request
  request: function(name) {
    var args = _.rest(arguments);
    var results = Radio._eventsApi(this, 'request', name, args);
    if (results) {
      return results;
    }
    var channelName = this.channelName;
    var requests = this._requests;

    // Check if we should log the request, and if so, do it
    if (channelName && this._tunedIn) {
      Radio.log.apply(this, [channelName, name].concat(args));
    }

    // If the request isn't handled, log it in DEBUG mode and exit
    if (requests && (requests[name] || requests['default'])) {
      var handler = requests[name] || requests['default'];
      args = requests[name] ? args : arguments;
      return Radio._callHandler(handler.callback, handler.context, args);
    } else {
      Radio.debugLog('An unhandled request was fired', name, channelName);
    }
  },

  // Set up a handler for a request
  reply: function(name, callback, context) {
    if (Radio._eventsApi(this, 'reply', name, [callback, context])) {
      return this;
    }

    this._requests || (this._requests = {});

    if (this._requests[name]) {
      Radio.debugLog('A request was overwritten', name, this.channelName);
    }

    this._requests[name] = {
      callback: makeCallback(callback),
      context: context || this
    };

    return this;
  },

  // Set up a handler that can only be requested once
  replyOnce: function(name, callback, context) {
    if (Radio._eventsApi(this, 'replyOnce', name, [callback, context])) {
      return this;
    }

    var self = this;

    var once = _.once(function() {
      self.stopReplying(name);
      return makeCallback(callback).apply(this, arguments);
    });

    return this.reply(name, once, context);
  },

  // Remove handler(s)
  stopReplying: function(name, callback, context) {
    if (Radio._eventsApi(this, 'stopReplying', name)) {
      return this;
    }

    // Remove everything if there are no arguments passed
    if (!name && !callback && !context) {
      delete this._requests;
    } else if (!removeHandlers(this._requests, name, callback, context)) {
      Radio.debugLog('Attempted to remove the unregistered request', name, this.channelName);
    }

    return this;
  }
};

/*
 * Backbone.Radio.channel
 * ----------------------
 * Get a reference to a channel by name.
 *
 */

Radio._channels = {};

Radio.channel = function(channelName) {
  if (!channelName) {
    throw new Error('You must provide a name for the channel.');
  }

  if (Radio._channels[channelName]) {
    return Radio._channels[channelName];
  } else {
    return (Radio._channels[channelName] = new Radio.Channel(channelName));
  }
};

/*
 * Backbone.Radio.Channel
 * ----------------------
 * A Channel is an object that extends from Backbone.Events,
 * Radio.Commands, and Radio.Requests.
 *
 */

Radio.Channel = function(channelName) {
  this.channelName = channelName;
};

_.extend(Radio.Channel.prototype, Backbone.Events, Radio.Commands, Radio.Requests, {

  // Remove all handlers from the messaging systems of this channel
  reset: function() {
    this.off();
    this.stopListening();
    this.stopComplying();
    this.stopReplying();
    return this;
  }
});

/*
 * Top-level API
 * -------------
 * Supplies the 'top-level API' for working with Channels directly
 * from Backbone.Radio.
 *
 */

var channel, args, systems = [Backbone.Events, Radio.Commands, Radio.Requests];

_.each(systems, function(system) {
  _.each(system, function(method, methodName) {
    Radio[methodName] = function(channelName) {
      args = _.rest(arguments);
      channel = this.channel(channelName);
      return channel[methodName].apply(channel, args);
    };
  });
});

Radio.reset = function(channelName) {
  var channels = !channelName ? this._channels : [this._channels[channelName]];
  _.invoke(channels, 'reset');
};
<|endoftext|>


<commit_before>/*
 IDBWrapper - A cross-browser wrapper for IndexedDB
 Copyright (c) 2011 - 2015 Jens Arps
 http://jensarps.de/

 Licensed under the MIT (X11) license
*/
(function(h,j,i){"function"===typeof define?define(j):"undefined"!==typeof module&&module.exports?module.exports=j():i[h]=j()})("IDBStore",function(){var h=function(b){throw b;},j={storeName:"Store",storePrefix:"IDBWrapper-",dbVersion:1,keyPath:"id",autoIncrement:!0,onStoreReady:function(){},onError:h,indexes:[]},i=function(b,c){"undefined"==typeof c&&"function"==typeof b&&(c=b);"[object Object]"!=Object.prototype.toString.call(b)&&(b={});for(var a in j)this[a]="undefined"!=typeof b[a]?b[a]:j[a];
this.dbName=this.storePrefix+this.storeName;this.dbVersion=parseInt(this.dbVersion,10)||1;c&&(this.onStoreReady=c);a="object"==typeof window?window:self;this.idb=a.indexedDB||a.webkitIndexedDB||a.mozIndexedDB;this.keyRange=a.IDBKeyRange||a.webkitIDBKeyRange||a.mozIDBKeyRange;this.features={hasAutoIncrement:!a.mozIndexedDB};this.consts={READ_ONLY:"readonly",READ_WRITE:"readwrite",VERSION_CHANGE:"versionchange",NEXT:"next",NEXT_NO_DUPLICATE:"nextunique",PREV:"prev",PREV_NO_DUPLICATE:"prevunique"};this.openDB()};
i.prototype={constructor:i,version:"1.4.1",db:null,dbName:null,dbVersion:null,store:null,storeName:null,keyPath:null,autoIncrement:null,indexes:null,features:null,onStoreReady:null,onError:null,_insertIdCount:0,openDB:function(){var b=this.idb.open(this.dbName,this.dbVersion),c=!1;b.onerror=function(a){var b=!1;"error"in a.target?b="VersionError"==a.target.error.name:"errorCode"in a.target&&(b=12==a.target.errorCode);if(b)this.onError(Error("The version number provided is lower than the existing one."));
else this.onError(a)}.bind(this);b.onsuccess=function(a){if(!c)if(this.db)this.onStoreReady();else if(this.db=a.target.result,"string"==typeof this.db.version)this.onError(Error("The IndexedDB implementation in this browser is outdated. Please upgrade your browser."));else if(this.db.objectStoreNames.contains(this.storeName)){this.store=this.db.transaction([this.storeName],this.consts.READ_ONLY).objectStore(this.storeName);var b=Array.prototype.slice.call(this.getIndexList());this.indexes.forEach(function(a){var e=
a.name;e?(this.normalizeIndexData(a),this.hasIndex(e)?(this.indexComplies(this.store.index(e),a)||(c=!0,this.onError(Error('Cannot modify index "'+e+'" for current version. Please bump version number to '+(this.dbVersion+1)+"."))),b.splice(b.indexOf(e),1)):(c=!0,this.onError(Error('Cannot create new index "'+e+'" for current version. Please bump version number to '+(this.dbVersion+1)+".")))):(c=!0,this.onError(Error("Cannot create index: No index name given.")))},this);b.length&&(c=!0,this.onError(Error('Cannot delete index(es) "'+
b.toString()+'" for current version. Please bump version number to '+(this.dbVersion+1)+".")));c||this.onStoreReady()}else this.onError(Error("Something is wrong with the IndexedDB implementation in this browser. Please upgrade your browser."))}.bind(this);b.onupgradeneeded=function(a){this.db=a.target.result;if(this.db.objectStoreNames.contains(this.storeName))this.store=a.target.transaction.objectStore(this.storeName);else{a={autoIncrement:this.autoIncrement};if(null!==this.keyPath)a.keyPath=this.keyPath;
this.store=this.db.createObjectStore(this.storeName,a)}var b=Array.prototype.slice.call(this.getIndexList());this.indexes.forEach(function(a){var e=a.name;e||(c=!0,this.onError(Error("Cannot create index: No index name given.")));this.normalizeIndexData(a);this.hasIndex(e)?(this.indexComplies(this.store.index(e),a)||(this.store.deleteIndex(e),this.store.createIndex(e,a.keyPath,{unique:a.unique,multiEntry:a.multiEntry})),b.splice(b.indexOf(e),1)):this.store.createIndex(e,a.keyPath,{unique:a.unique,
multiEntry:a.multiEntry})},this);b.length&&b.forEach(function(a){this.store.deleteIndex(a)},this)}.bind(this)},deleteDatabase:function(){this.idb.deleteDatabase&&this.idb.deleteDatabase(this.dbName)},put:function(b,c,a,d){null!==this.keyPath&&(d=a,a=c,c=b);d||(d=h);a||(a=k);var f=!1,e=null,g=this.db.transaction([this.storeName],this.consts.READ_WRITE);g.oncomplete=function(){(f?a:d)(e)};g.onabort=d;g.onerror=d;null!==this.keyPath?(this._addIdPropertyIfNeeded(c),b=g.objectStore(this.storeName).put(c)):
b=g.objectStore(this.storeName).put(c,b);b.onsuccess=function(a){f=!0;e=a.target.result};b.onerror=d;return g},get:function(b,c,a){a||(a=h);c||(c=k);var d=!1,f=null,e=this.db.transaction([this.storeName],this.consts.READ_ONLY);e.oncomplete=function(){(d?c:a)(f)};e.onabort=a;e.onerror=a;b=e.objectStore(this.storeName).get(b);b.onsuccess=function(a){d=!0;f=a.target.result};b.onerror=a;return e},remove:function(b,c,a){a||(a=h);c||(c=k);var d=!1,f=null,e=this.db.transaction([this.storeName],this.consts.READ_WRITE);
e.oncomplete=function(){(d?c:a)(f)};e.onabort=a;e.onerror=a;b=e.objectStore(this.storeName)["delete"](b);b.onsuccess=function(a){d=!0;f=a.target.result};b.onerror=a;return e},batch:function(b,c,a){a||(a=h);c||(c=k);"[object Array]"!=Object.prototype.toString.call(b)&&a(Error("dataArray argument must be of type Array."));var d=this.db.transaction([this.storeName],this.consts.READ_WRITE);d.oncomplete=function(){(g?c:a)(g)};d.onabort=a;d.onerror=a;var f=b.length,e=!1,g=!1,l=function(){f--;0===f&&!e&&
(g=e=!0)};b.forEach(function(b){var c=b.type,f=b.key,g=b.value,b=function(b){d.abort();e||(e=!0,a(b,c,f))};if("remove"==c)g=d.objectStore(this.storeName)["delete"](f),g.onsuccess=l,g.onerror=b;else if("put"==c)null!==this.keyPath?(this._addIdPropertyIfNeeded(g),g=d.objectStore(this.storeName).put(g)):g=d.objectStore(this.storeName).put(g,f),g.onsuccess=l,g.onerror=b},this);return d},putBatch:function(b,c,a){return this.batch(b.map(function(a){return{type:"put",value:a}}),c,a)},removeBatch:function(b,
c,a){return this.batch(b.map(function(a){return{type:"remove",key:a}}),c,a)},getBatch:function(b,c,a,d){a||(a=h);c||(c=k);d||(d="sparse");"[object Array]"!=Object.prototype.toString.call(b)&&a(Error("keyArray argument must be of type Array."));var f=this.db.transaction([this.storeName],this.consts.READ_ONLY);f.oncomplete=function(){(l?c:a)(i)};f.onabort=a;f.onerror=a;var e=[],g=b.length,l=!1,i=null,j=function(a){a.target.result||"dense"==d?e.push(a.target.result):"sparse"==d&&e.length++;g--;0===g&&
(l=!0,i=e)};b.forEach(function(b){b=f.objectStore(this.storeName).get(b);b.onsuccess=j;b.onerror=function(b){i=b;a(b);f.abort()}},this);return f},getAll:function(b,c){c||(c=h);b||(b=k);var a=this.db.transaction([this.storeName],this.consts.READ_ONLY),d=a.objectStore(this.storeName);d.getAll?this._getAllNative(a,d,b,c):this._getAllCursor(a,d,b,c);return a},_getAllNative:function(b,c,a,d){var f=!1,e=null;b.oncomplete=function(){(f?a:d)(e)};b.onabort=d;b.onerror=d;b=c.getAll();b.onsuccess=function(a){f=
!0;e=a.target.result};b.onerror=d},_getAllCursor:function(b,c,a,d){var f=[],e=!1,g=null;b.oncomplete=function(){(e?a:d)(g)};b.onabort=d;b.onerror=d;b=c.openCursor();b.onsuccess=function(a){(a=a.target.result)?(f.push(a.value),a["continue"]()):(e=!0,g=f)};b.onError=d},clear:function(b,c){c||(c=h);b||(b=k);var a=!1,d=null,f=this.db.transaction([this.storeName],this.consts.READ_WRITE);f.oncomplete=function(){(a?b:c)(d)};f.onabort=c;f.onerror=c;var e=f.objectStore(this.storeName).clear();e.onsuccess=
function(b){a=!0;d=b.target.result};e.onerror=c;return f},_addIdPropertyIfNeeded:function(b){!this.features.hasAutoIncrement&&"undefined"==typeof b[this.keyPath]&&(b[this.keyPath]=this._insertIdCount++ +Date.now())},getIndexList:function(){return this.store.indexNames},hasIndex:function(b){return this.store.indexNames.contains(b)},normalizeIndexData:function(b){b.keyPath=b.keyPath||b.name;b.unique=!!b.unique;b.multiEntry=!!b.multiEntry},indexComplies:function(b,c){return["keyPath","unique","multiEntry"].every(function(a){if("multiEntry"==
a&&void 0===b[a]&&!1===c[a])return!0;if("keyPath"==a&&"[object Array]"==Object.prototype.toString.call(c[a])){var a=c.keyPath,d=b.keyPath;if("string"==typeof d)return a.toString()==d;if(!("function"==typeof d.contains||"function"==typeof d.indexOf)||d.length!==a.length)return!1;for(var f=0,e=a.length;f<e;f++)if(!(d.contains&&d.contains(a[f])||d.indexOf(-1!==a[f])))return!1;return!0}return c[a]==b[a]})},iterate:function(b,c){var c=m({index:null,order:"ASC",autoContinue:!0,filterDuplicates:!1,keyRange:null,
writeAccess:!1,onEnd:null,onError:h},c||{}),a="desc"==c.order.toLowerCase()?"PREV":"NEXT";c.filterDuplicates&&(a+="_NO_DUPLICATE");var d=!1,f=this.db.transaction([this.storeName],this.consts[c.writeAccess?"READ_WRITE":"READ_ONLY"]),e=f.objectStore(this.storeName);c.index&&(e=e.index(c.index));f.oncomplete=function(){if(d)if(c.onEnd)c.onEnd();else b(null);else c.onError(null)};f.onabort=c.onError;f.onerror=c.onError;a=e.openCursor(c.keyRange,this.consts[a]);a.onerror=c.onError;a.onsuccess=function(a){if(a=
a.target.result){if(b(a.value,a,f),c.autoContinue)a["continue"]()}else d=!0};return f},query:function(b,c){var a=[],c=c||{};c.onEnd=function(){b(a)};return this.iterate(function(b){a.push(b)},c)},count:function(b,c){var c=m({index:null,keyRange:null},c||{}),a=c.onError||h,d=!1,f=null,e=this.db.transaction([this.storeName],this.consts.READ_ONLY);e.oncomplete=function(){(d?b:a)(f)};e.onabort=a;e.onerror=a;var g=e.objectStore(this.storeName);c.index&&(g=g.index(c.index));g=g.count(c.keyRange);g.onsuccess=
function(a){d=!0;f=a.target.result};g.onError=a;return e},makeKeyRange:function(b){var c="undefined"!=typeof b.lower,a="undefined"!=typeof b.upper,d="undefined"!=typeof b.only;switch(!0){case d:b=this.keyRange.only(b.only);break;case c&&a:b=this.keyRange.bound(b.lower,b.upper,b.excludeLower,b.excludeUpper);break;case c:b=this.keyRange.lowerBound(b.lower,b.excludeLower);break;case a:b=this.keyRange.upperBound(b.upper,b.excludeUpper);break;default:throw Error('Cannot create KeyRange. Provide one or both of "lower" or "upper" value, or an "only" value.');
}return b}};var k=function(){},n={},m=function(b,c){var a,d;for(a in c)d=c[a],d!==n[a]&&d!==b[a]&&(b[a]=d);return b};i.version=i.prototype.version;return i},this);
<commit_msg>Use shimIndexedDB<commit_after>/*
 IDBWrapper - A cross-browser wrapper for IndexedDB
 Copyright (c) 2011 - 2015 Jens Arps
 http://jensarps.de/

 Licensed under the MIT (X11) license
*/
(function(h,j,i){"function"===typeof define?define(j):"undefined"!==typeof module&&module.exports?module.exports=j():i[h]=j()})("IDBStore",function(){var h=function(b){throw b;},j={storeName:"Store",storePrefix:"IDBWrapper-",dbVersion:1,keyPath:"id",autoIncrement:!0,onStoreReady:function(){},onError:h,indexes:[]},i=function(b,c){"undefined"==typeof c&&"function"==typeof b&&(c=b);"[object Object]"!=Object.prototype.toString.call(b)&&(b={});for(var a in j)this[a]="undefined"!=typeof b[a]?b[a]:j[a];
this.dbName=this.storePrefix+this.storeName;this.dbVersion=parseInt(this.dbVersion,10)||1;c&&(this.onStoreReady=c);a="object"==typeof window?window:self;this.idb=a.shimIndexedDB||a.indexedDB||a.webkitIndexedDB||a.mozIndexedDB;this.keyRange=a.IDBKeyRange||a.webkitIDBKeyRange||a.mozIDBKeyRange;this.features={hasAutoIncrement:!a.mozIndexedDB};this.consts={READ_ONLY:"readonly",READ_WRITE:"readwrite",VERSION_CHANGE:"versionchange",NEXT:"next",NEXT_NO_DUPLICATE:"nextunique",PREV:"prev",PREV_NO_DUPLICATE:"prevunique"};this.openDB()};
i.prototype={constructor:i,version:"1.4.1",db:null,dbName:null,dbVersion:null,store:null,storeName:null,keyPath:null,autoIncrement:null,indexes:null,features:null,onStoreReady:null,onError:null,_insertIdCount:0,openDB:function(){var b=this.idb.open(this.dbName,this.dbVersion),c=!1;b.onerror=function(a){var b=!1;"error"in a.target?b="VersionError"==a.target.error.name:"errorCode"in a.target&&(b=12==a.target.errorCode);if(b)this.onError(Error("The version number provided is lower than the existing one."));
else this.onError(a)}.bind(this);b.onsuccess=function(a){if(!c)if(this.db)this.onStoreReady();else if(this.db=a.target.result,"string"==typeof this.db.version)this.onError(Error("The IndexedDB implementation in this browser is outdated. Please upgrade your browser."));else if(this.db.objectStoreNames.contains(this.storeName)){this.store=this.db.transaction([this.storeName],this.consts.READ_ONLY).objectStore(this.storeName);var b=Array.prototype.slice.call(this.getIndexList());this.indexes.forEach(function(a){var e=
a.name;e?(this.normalizeIndexData(a),this.hasIndex(e)?(this.indexComplies(this.store.index(e),a)||(c=!0,this.onError(Error('Cannot modify index "'+e+'" for current version. Please bump version number to '+(this.dbVersion+1)+"."))),b.splice(b.indexOf(e),1)):(c=!0,this.onError(Error('Cannot create new index "'+e+'" for current version. Please bump version number to '+(this.dbVersion+1)+".")))):(c=!0,this.onError(Error("Cannot create index: No index name given.")))},this);b.length&&(c=!0,this.onError(Error('Cannot delete index(es) "'+
b.toString()+'" for current version. Please bump version number to '+(this.dbVersion+1)+".")));c||this.onStoreReady()}else this.onError(Error("Something is wrong with the IndexedDB implementation in this browser. Please upgrade your browser."))}.bind(this);b.onupgradeneeded=function(a){this.db=a.target.result;if(this.db.objectStoreNames.contains(this.storeName))this.store=a.target.transaction.objectStore(this.storeName);else{a={autoIncrement:this.autoIncrement};if(null!==this.keyPath)a.keyPath=this.keyPath;
this.store=this.db.createObjectStore(this.storeName,a)}var b=Array.prototype.slice.call(this.getIndexList());this.indexes.forEach(function(a){var e=a.name;e||(c=!0,this.onError(Error("Cannot create index: No index name given.")));this.normalizeIndexData(a);this.hasIndex(e)?(this.indexComplies(this.store.index(e),a)||(this.store.deleteIndex(e),this.store.createIndex(e,a.keyPath,{unique:a.unique,multiEntry:a.multiEntry})),b.splice(b.indexOf(e),1)):this.store.createIndex(e,a.keyPath,{unique:a.unique,
multiEntry:a.multiEntry})},this);b.length&&b.forEach(function(a){this.store.deleteIndex(a)},this)}.bind(this)},deleteDatabase:function(){this.idb.deleteDatabase&&this.idb.deleteDatabase(this.dbName)},put:function(b,c,a,d){null!==this.keyPath&&(d=a,a=c,c=b);d||(d=h);a||(a=k);var f=!1,e=null,g=this.db.transaction([this.storeName],this.consts.READ_WRITE);g.oncomplete=function(){(f?a:d)(e)};g.onabort=d;g.onerror=d;null!==this.keyPath?(this._addIdPropertyIfNeeded(c),b=g.objectStore(this.storeName).put(c)):
b=g.objectStore(this.storeName).put(c,b);b.onsuccess=function(a){f=!0;e=a.target.result};b.onerror=d;return g},get:function(b,c,a){a||(a=h);c||(c=k);var d=!1,f=null,e=this.db.transaction([this.storeName],this.consts.READ_ONLY);e.oncomplete=function(){(d?c:a)(f)};e.onabort=a;e.onerror=a;b=e.objectStore(this.storeName).get(b);b.onsuccess=function(a){d=!0;f=a.target.result};b.onerror=a;return e},remove:function(b,c,a){a||(a=h);c||(c=k);var d=!1,f=null,e=this.db.transaction([this.storeName],this.consts.READ_WRITE);
e.oncomplete=function(){(d?c:a)(f)};e.onabort=a;e.onerror=a;b=e.objectStore(this.storeName)["delete"](b);b.onsuccess=function(a){d=!0;f=a.target.result};b.onerror=a;return e},batch:function(b,c,a){a||(a=h);c||(c=k);"[object Array]"!=Object.prototype.toString.call(b)&&a(Error("dataArray argument must be of type Array."));var d=this.db.transaction([this.storeName],this.consts.READ_WRITE);d.oncomplete=function(){(g?c:a)(g)};d.onabort=a;d.onerror=a;var f=b.length,e=!1,g=!1,l=function(){f--;0===f&&!e&&
(g=e=!0)};b.forEach(function(b){var c=b.type,f=b.key,g=b.value,b=function(b){d.abort();e||(e=!0,a(b,c,f))};if("remove"==c)g=d.objectStore(this.storeName)["delete"](f),g.onsuccess=l,g.onerror=b;else if("put"==c)null!==this.keyPath?(this._addIdPropertyIfNeeded(g),g=d.objectStore(this.storeName).put(g)):g=d.objectStore(this.storeName).put(g,f),g.onsuccess=l,g.onerror=b},this);return d},putBatch:function(b,c,a){return this.batch(b.map(function(a){return{type:"put",value:a}}),c,a)},removeBatch:function(b,
c,a){return this.batch(b.map(function(a){return{type:"remove",key:a}}),c,a)},getBatch:function(b,c,a,d){a||(a=h);c||(c=k);d||(d="sparse");"[object Array]"!=Object.prototype.toString.call(b)&&a(Error("keyArray argument must be of type Array."));var f=this.db.transaction([this.storeName],this.consts.READ_ONLY);f.oncomplete=function(){(l?c:a)(i)};f.onabort=a;f.onerror=a;var e=[],g=b.length,l=!1,i=null,j=function(a){a.target.result||"dense"==d?e.push(a.target.result):"sparse"==d&&e.length++;g--;0===g&&
(l=!0,i=e)};b.forEach(function(b){b=f.objectStore(this.storeName).get(b);b.onsuccess=j;b.onerror=function(b){i=b;a(b);f.abort()}},this);return f},getAll:function(b,c){c||(c=h);b||(b=k);var a=this.db.transaction([this.storeName],this.consts.READ_ONLY),d=a.objectStore(this.storeName);d.getAll?this._getAllNative(a,d,b,c):this._getAllCursor(a,d,b,c);return a},_getAllNative:function(b,c,a,d){var f=!1,e=null;b.oncomplete=function(){(f?a:d)(e)};b.onabort=d;b.onerror=d;b=c.getAll();b.onsuccess=function(a){f=
!0;e=a.target.result};b.onerror=d},_getAllCursor:function(b,c,a,d){var f=[],e=!1,g=null;b.oncomplete=function(){(e?a:d)(g)};b.onabort=d;b.onerror=d;b=c.openCursor();b.onsuccess=function(a){(a=a.target.result)?(f.push(a.value),a["continue"]()):(e=!0,g=f)};b.onError=d},clear:function(b,c){c||(c=h);b||(b=k);var a=!1,d=null,f=this.db.transaction([this.storeName],this.consts.READ_WRITE);f.oncomplete=function(){(a?b:c)(d)};f.onabort=c;f.onerror=c;var e=f.objectStore(this.storeName).clear();e.onsuccess=
function(b){a=!0;d=b.target.result};e.onerror=c;return f},_addIdPropertyIfNeeded:function(b){!this.features.hasAutoIncrement&&"undefined"==typeof b[this.keyPath]&&(b[this.keyPath]=this._insertIdCount++ +Date.now())},getIndexList:function(){return this.store.indexNames},hasIndex:function(b){return this.store.indexNames.contains(b)},normalizeIndexData:function(b){b.keyPath=b.keyPath||b.name;b.unique=!!b.unique;b.multiEntry=!!b.multiEntry},indexComplies:function(b,c){return["keyPath","unique","multiEntry"].every(function(a){if("multiEntry"==
a&&void 0===b[a]&&!1===c[a])return!0;if("keyPath"==a&&"[object Array]"==Object.prototype.toString.call(c[a])){var a=c.keyPath,d=b.keyPath;if("string"==typeof d)return a.toString()==d;if(!("function"==typeof d.contains||"function"==typeof d.indexOf)||d.length!==a.length)return!1;for(var f=0,e=a.length;f<e;f++)if(!(d.contains&&d.contains(a[f])||d.indexOf(-1!==a[f])))return!1;return!0}return c[a]==b[a]})},iterate:function(b,c){var c=m({index:null,order:"ASC",autoContinue:!0,filterDuplicates:!1,keyRange:null,
writeAccess:!1,onEnd:null,onError:h},c||{}),a="desc"==c.order.toLowerCase()?"PREV":"NEXT";c.filterDuplicates&&(a+="_NO_DUPLICATE");var d=!1,f=this.db.transaction([this.storeName],this.consts[c.writeAccess?"READ_WRITE":"READ_ONLY"]),e=f.objectStore(this.storeName);c.index&&(e=e.index(c.index));f.oncomplete=function(){if(d)if(c.onEnd)c.onEnd();else b(null);else c.onError(null)};f.onabort=c.onError;f.onerror=c.onError;a=e.openCursor(c.keyRange,this.consts[a]);a.onerror=c.onError;a.onsuccess=function(a){if(a=
a.target.result){if(b(a.value,a,f),c.autoContinue)a["continue"]()}else d=!0};return f},query:function(b,c){var a=[],c=c||{};c.onEnd=function(){b(a)};return this.iterate(function(b){a.push(b)},c)},count:function(b,c){var c=m({index:null,keyRange:null},c||{}),a=c.onError||h,d=!1,f=null,e=this.db.transaction([this.storeName],this.consts.READ_ONLY);e.oncomplete=function(){(d?b:a)(f)};e.onabort=a;e.onerror=a;var g=e.objectStore(this.storeName);c.index&&(g=g.index(c.index));g=g.count(c.keyRange);g.onsuccess=
function(a){d=!0;f=a.target.result};g.onError=a;return e},makeKeyRange:function(b){var c="undefined"!=typeof b.lower,a="undefined"!=typeof b.upper,d="undefined"!=typeof b.only;switch(!0){case d:b=this.keyRange.only(b.only);break;case c&&a:b=this.keyRange.bound(b.lower,b.upper,b.excludeLower,b.excludeUpper);break;case c:b=this.keyRange.lowerBound(b.lower,b.excludeLower);break;case a:b=this.keyRange.upperBound(b.upper,b.excludeUpper);break;default:throw Error('Cannot create KeyRange. Provide one or both of "lower" or "upper" value, or an "only" value.');
}return b}};var k=function(){},n={},m=function(b,c){var a,d;for(a in c)d=c[a],d!==n[a]&&d!==b[a]&&(b[a]=d);return b};i.version=i.prototype.version;return i},this);
<|endoftext|>


<commit_before>/**
  * This file is part of taolin project (http://taolin.fbk.eu)
  * Copyright (C) 2008, 2009 FBK Foundation, (http://www.fbk.eu)
  * Authors: SoNet Group (see AUTHORS.txt)
  *
  * Taolin is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
  * the Free Software Foundation version 3 of the License.
  *
  * Taolin is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with Taolin. If not, see <http://www.gnu.org/licenses/>.
  *
  */

Ext.namespace( 'Ext.ux.fbk.sonet' );

Ext.ux.fbk.sonet.Settings = Ext.extend(Ext.form.FormPanel, {
    cls: 'form_settings'
    ,border: false
    ,autoScroll: true
    ,labelAlign: 'top'
    // To prevent sprited images to be shown in the background
    ,defaults: {
        // applied to each contained item
        autoWidth: true
        ,msgTarget: 'side'
    }
    ,monitorValid: true
    ,waitMsgTarget: true
    ,method:'GET'
    ,onRender:function(){
        Ext.ux.fbk.sonet.Settings.superclass.onRender.apply(this, arguments);

        this.form.load();
    }
    ,initComponent: function() {
        var config = {
            trackResetOnLoad: true,
            url: 'users/getusersettings/',
            text: "Loading...",
            timeout: 60,
            scope: this,
            items: [
                {
                    html: '
' +
                            '
' +
                                'View your profile' +
                            '
' +
                            '
' +
                                'Edit your photos' +
                            '
' +
                            '
' +
                                'Edit workplace' +
                            '
' +
                        '
'
                }, {
                    items: new Ext.form.FieldSet({
                        //title: ' Personal',
                        layoutConfig: {
                            // layout-specific configs go here
                            labelSeparator: ''
                        },
                        collapsible:false,
                        autoHeight: true,
                        autoWidth: true,
                        border:false,
                        defaultType: 'textfield',
                        style:'padding:5px 0 0 5px',
                        items: [
                            new Ext.form.DateField({
                                fieldLabel: 'Date of Birth',
                                name: 'date_of_birth',
                                format: 'Y-m-d',
                                editable: false,
                                anchor: '95%'
                            }), {
                                fieldLabel: 'Email',
                                name: 'email',
                                vtype:'email',
                                maxLength: 50,
                                anchor: '95%'
                            }, {
                                fieldLabel: 'Internal Phone
(0461/314xxx)',
                                name: 'phone',
                                maxLength: 3,
                                anchor: '95%'
                            }, {
                                fieldLabel: 'Secondary internal phone
(0461/314xxx)',
                                name: 'phone2',
                                maxLength: 3,
                                anchor: '95%'
                            }, {
                                fieldLabel: 'Personal Page',
                                name: 'personal_page',
                                vtype:'url',
                                maxLength: 80,
                                anchor: '95%'
                            }, {
                                fieldLabel: 'Home address',
                                name: 'home_address',
                                maxLength: 180,
                                anchor: '95%'
                            }, {
                                fieldLabel: 'Available for carpooling?',
                                name: 'carpooling',
                                xtype: 'checkbox',
                                anchor: '95%'
                            }, {
                                fieldLabel: 'About me
(Describe yourself, your interests and your work here at FBK)',
                                name: 'description',
                                xtype: 'textarea',
                                grow: true,
                                anchor: '95%'
                            }, {
                                fieldLabel: ' Linkedin public-profile
Example: set it to nickname if your linkedin public profile url is http://www.linkedin.com/in/nickname'
                                ,name: 'linkedin'
                                ,maxLength: 50
                                ,anchor: '95%'
                            }, {
                                fieldLabel: ' Twitter username'
                                ,name: 'twitter'
                                ,maxLength: 50
                                ,anchor: '95%'
                            }, {
                                fieldLabel: ' Facebook profile Web address
If you have a facebook username, enter http://www.facebook.com/ followed by your username (eg: http://www.facebook.com/foousername). Otherwise, once logged into Facebook, click the Profile button and then copy here the Web address of the resulting page'
                                ,name: 'facebook'
                                ,vtype: 'url'
                                ,maxLength: 120
                                ,anchor: '95%'
                            }]
                    })
                },{
                    buttons: [{
                        text: 'Save',
                        handler: function(){
                            if(this.form.isDirty()){
                                this.form.submit({
                                    url:'users/setusersettings',
                                    success:this.onSuccess,
                                    failure:this.onFailure,
                                    waitMsg:'Saving data...'
                                });
                            }
                            else
                                expandUserPanel();
                        }
                        ,formBind:true
                        ,scope: this
                    },{
                        text: 'Cancel',
                        handler: function(){
                            this.form.reset();
                            expandUserPanel();
                        }
                        ,scope: this
                    }]
                    ,buttonAlign: 'left'
                }
            ]
        };

        Ext.apply(this, Ext.apply(this.initialConfig, config));

        Ext.ux.fbk.sonet.Settings.superclass.initComponent.apply(this, arguments);
    }
    ,onSuccess:function(form, action){
        Ext.example.msg('Settings', 'Your data has been saved');
        form.load();
        showUserInfo();
    },
    onFailure:function(form, action){
        Ext.Msg.show({
            title: 'Error!',
            msg: 'Your data has not been saved!

Please check your data or submit a feedback to us.',
            width: 400,
            buttons: Ext.MessageBox.OK,
            icon: Ext.MessageBox.ERROR
        });
    }
});
<commit_msg>(Extjs) bug fixing (really it's a workaround)<commit_after>/**
  * This file is part of taolin project (http://taolin.fbk.eu)
  * Copyright (C) 2008, 2009 FBK Foundation, (http://www.fbk.eu)
  * Authors: SoNet Group (see AUTHORS.txt)
  *
  * Taolin is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
  * the Free Software Foundation version 3 of the License.
  *
  * Taolin is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with Taolin. If not, see <http://www.gnu.org/licenses/>.
  *
  */

Ext.namespace( 'Ext.ux.fbk.sonet' );

Ext.ux.fbk.sonet.Settings = Ext.extend(Ext.form.FormPanel, {
    cls: 'form_settings'
    ,border: false
    ,autoScroll: true
    ,labelAlign: 'top'
    // To prevent sprited images to be shown in the background
    ,defaults: {
        // applied to each contained item
        autoWidth: true
        ,msgTarget: 'side'
    }
    ,listeners:{
        actioncomplete:function(t, action){ // BAD HACK! (this should be to prevent an ExtJS 3 bug)
            $('#settings .x-panel-btns').css('width', '');
        }
    }
    ,monitorValid: true
    ,waitMsgTarget: true
    ,method:'GET'
    ,onRender:function(){
        Ext.ux.fbk.sonet.Settings.superclass.onRender.apply(this, arguments);

        this.form.load();
    }
    ,initComponent: function() {
        var config = {
            trackResetOnLoad: true,
            url: 'users/getusersettings/',
            text: "Loading...",
            timeout: 60,
            scope: this,
            items: [
                {
                    html: '
' +
                            '
' +
                                'View your profile' +
                            '
' +
                            '
' +
                                'Edit your photos' +
                            '
' +
                            '
' +
                                'Edit workplace' +
                            '
' +
                        '
'
                }, {
                    items: new Ext.form.FieldSet({
                        //title: ' Personal',
                        layoutConfig: {
                            // layout-specific configs go here
                            labelSeparator: ''
                        },
                        collapsible:false,
                        autoHeight: true,
                        autoWidth: true,
                        border:false,
                        defaultType: 'textfield',
                        style:'padding:5px 0 0 5px',
                        items: [
                            new Ext.form.DateField({
                                fieldLabel: 'Date of Birth',
                                name: 'date_of_birth',
                                format: 'Y-m-d',
                                editable: false,
                                anchor: '95%'
                            }), {
                                fieldLabel: 'Email',
                                name: 'email',
                                vtype:'email',
                                maxLength: 50,
                                anchor: '95%'
                            }, {
                                fieldLabel: 'Internal Phone
(0461/314xxx)',
                                name: 'phone',
                                maxLength: 3,
                                anchor: '95%'
                            }, {
                                fieldLabel: 'Secondary internal phone
(0461/314xxx)',
                                name: 'phone2',
                                maxLength: 3,
                                anchor: '95%'
                            }, {
                                fieldLabel: 'Personal Page',
                                name: 'personal_page',
                                vtype:'url',
                                maxLength: 80,
                                anchor: '95%'
                            }, {
                                fieldLabel: 'Home address',
                                name: 'home_address',
                                maxLength: 180,
                                anchor: '95%'
                            }, {
                                fieldLabel: 'Available for carpooling?',
                                name: 'carpooling',
                                xtype: 'checkbox',
                                anchor: '95%'
                            }, {
                                fieldLabel: 'About me
(Describe yourself, your interests and your work here at FBK)',
                                name: 'description',
                                xtype: 'textarea',
                                grow: true,
                                anchor: '95%'
                            }, {
                                fieldLabel: ' Linkedin public-profile
Example: set it to nickname if your linkedin public profile url is http://www.linkedin.com/in/nickname'
                                ,name: 'linkedin'
                                ,maxLength: 50
                                ,anchor: '95%'
                            }, {
                                fieldLabel: ' Twitter username'
                                ,name: 'twitter'
                                ,maxLength: 50
                                ,anchor: '95%'
                            }, {
                                fieldLabel: ' Facebook profile Web address
If you have a facebook username, enter http://www.facebook.com/ followed by your username (eg: http://www.facebook.com/foousername). Otherwise, once logged into Facebook, click the Profile button and then copy here the Web address of the resulting page'
                                ,name: 'facebook'
                                ,vtype: 'url'
                                ,maxLength: 120
                                ,anchor: '95%'
                            }]
                    })
                },{
                    buttons: [{
                        text: 'Save',
                        handler: function(){
                            if(this.form.isDirty()){
                                this.form.submit({
                                    url:'users/setusersettings',
                                    success:this.onSuccess,
                                    failure:this.onFailure,
                                    waitMsg:'Saving data...'
                                });
                            }
                            else
                                expandUserPanel();
                        }
                        ,formBind:true
                        ,scope: this
                    },{
                        text: 'Cancel',
                        handler: function(){
                            this.form.reset();
                            expandUserPanel();
                        }
                        ,scope: this
                    }]
                    ,buttonAlign: 'left'
                }
            ]
        };

        Ext.apply(this, Ext.apply(this.initialConfig, config));

        Ext.ux.fbk.sonet.Settings.superclass.initComponent.apply(this, arguments);
    }
    ,onSuccess:function(form, action){
        Ext.example.msg('Settings', 'Your data has been saved');
        form.load();
        showUserInfo();
    },
    onFailure:function(form, action){
        Ext.Msg.show({
            title: 'Error!',
            msg: 'Your data has not been saved!

Please check your data or submit a feedback to us.',
            width: 400,
            buttons: Ext.MessageBox.OK,
            icon: Ext.MessageBox.ERROR
        });
    }
});
<|endoftext|>


<commit_before>/*global _ Backbone */

/*
 * Backbone.Siren
 *
 * Copyright (c) 2013 Kiva Microfunds
 * Licensed under the MIT license.
 * https://github.com/kiva/backbone.siren/blob/master/license.txt
 */

(function(root, factory) {
    'use strict';

    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['underscore', 'backbone', 'backbone-relational'], function(_, Backbone) {
            Backbone.Siren = factory(_, Backbone);
        });
    } else {
        // Browser globals
        root.Backbone.Siren = factory(_, Backbone);
    }

}(this, function (_, Backbone, undefined) {
    'use strict';

    // The store
    var _store = {}
    , store = {
        key: function (model) {
            return model.url() + '-' +  model.cid;
        }

        , add: function (model) {
            _store[this.key(model)] = model;
        }

        , exists: function (model) {
            return !!_store[this.key(model)];
        }

        , all: function () {
            return _store;
        }
    }
    , warn = function (msg) {
        if (Backbone.Siren.settings.showWarnings && console) {
            console.warn(msg);
        }
    };

    /**
     *
     * @static
     * @param name
     * @param lowercaseFirstChar
     * @return {String}
     */
    function toCamelCase(name, lowercaseFirstChar) {
        return ((lowercaseFirstChar ? '' : '-') + name).replace(/(\-[a-z])/g, function(match){return match.toUpperCase().replace('-','');});
    }

    /**
     *
     * @static
     * @param entity
     * @return {Object}
     */
    function getUrl(entity) {
        var link, url;

        if (entity.href) {
            url = entity.href;
        } else if (entity.links) {
            link = entity.links.filter(function (link) {
                return !!(link.rel && link.rel.filter(function (relType) {
                    return relType == 'self';
                }).length);
            })[0];

            if (link) {
                url = link.href;
            }
        } else {
            warn('Missing href or "self" link');
        }

        return url;
    }

    /**
     * Access to the representation's "self" url, or its "href" if there is one.
     *
     * @return {String}
     */
    function url() {
        return getUrl(this._data);
    }

    /**
     *
     * @static
     * @param {Object} sirenObj Can be a siren entity or siren action object
     * @return {Array}
     */
    function getClassNames(sirenObj) {
        return sirenObj['class'];
    }

    /**
     *
     * @static
     * @param {Array} entities
     * @param {Object} filters
     * @return {Array}
     */
    function filter(entities, filters) {
        return _.filter(entities, function (entity) {
            return modelHasFilterProperties(entity, filters);
        });
    }

    /**
     *
     * @param entity
     * @param filters
     * @return {Boolean}
     */
    function modelHasFilterProperties(entity, filters) {
        var hasProperties = true;

        if (filters.className) {
            hasProperties = entity.hasClass(filters.className);
        }

        if (filters.rel) {
            hasProperties = hasProperties && (entity.rel() == filters.rel);
        }

        return hasProperties;
    }

    /**
     *
     * @param {Object} obj
     * @param {Object} filtersObj
     * @return {Boolean}
     */
    function objectHasFilterProperties(obj, filtersObj) {
        var hasProperties = true;

        if (filtersObj.className) {
            hasProperties = _hasClass(obj, filtersObj.className);
        }

        if (filtersObj.rel) {
            hasProperties = hasRel(obj, filtersObj.rel);
        }

        return hasProperties;
    }

    /**
     *
     * @static
     * @param sirenObj
     * @param className
     * @return {Boolean}
     */
    function _hasClass(sirenObj, className) {
        return _.indexOf(getClassNames(sirenObj), className) > -1;
    }

    /**
     *
     * @param className
     * @return {Boolean}
     */
    function hasClass(className) {
        return _hasClass(this._data, className);
    }

    /**
     *
     * @static
     * @param sirenObj
     * @param relValue
     * @return {Boolean}
     */
    function hasRel(sirenObj, relValue) {
        return getRel(sirenObj) == relValue;
    }

    /**
     * Accesses the "class" property of the Siren Object
     *
     * @return {Array}
     */
    function classes() {
        return getClassNames(this._data);
    }

    /**
     *
     * @static
     * @param sirenObj
     * @param verbose
     * @return {String}
     */
    function getRel(sirenObj, verbose) {
        var _rel = sirenObj.rel;

        if (_rel) {
            _rel = _rel[0];

            if (! verbose) {
                _rel = _rel.slice(_rel.lastIndexOf('/') + 1, _rel.length);
            }
        } else {
            warn('Missing "rel" attribute');
        }

        return _rel;
    }

    /**
     * By default returns characters after the last '/'.  Set to `verbose` to true to return the entire rel string.
     *
     * @param {Boolean} verbose
     * @return {String}
     */
    function rel(verbose) {
        return getRel(this._data, verbose);
    }

    function actions(filters) {
        var actions = this._data.actions;

        if (filters) {
            actions = _.filter(actions, function (action) {
                return objectHasFilterProperties(action, filters);
            });
        }
        return actions;
    }

    /**
     * Access to the representation's "title"
     *
     * @return {String}
     */
    function title() {
        return this._data.title;
    }

    function parseActions(model, options) {
         _.each(model.actions(), function (action) {
            model[toCamelCase(action.name, true)] = function () {

                options.url = action.href;

                if (action.method) {
                    options.method = action.method;
                }

                if (action.type) {
                    options.type = action.type;
                }

                return model.save(arguments, options);
            }
         });
    }

    return {
        store: store

        , settings: {
            showWarnings: true
        }

        , Model: Backbone.Model.extend({

            url: url
            , classes: classes
            , hasClass: hasClass
            , rel: rel
            , title: title
            , actions: actions

            /**
             * http://backbonejs.org/#Model-parse
             *
             * @param {Object} sirenObj
             */
            , parse: function (sirenObj, options) {
                var self = this;

                this.resolveEntities()
                    .done(function (args) {
                        _.each(args, function (entity) {
                            var camelCaseRel, bbSiren;

                            if (_.indexOf(getClassNames(entity), 'collection') == -1) {
                                // Its a model
                                bbSiren = new Backbone.Siren.Model(entity);
                                store.add(bbSiren);
                            } else {
                                // Its a collection
                                bbSiren = new Backbone.Siren.Collection(entity);
                            }

                            camelCaseRel = toCamelCase(bbSiren.rel());
                            self[camelCaseRel] = bbSiren;
                            self._entities.push(camelCaseRel);
                        });
                    });

                parseActions(this, options);

                return sirenObj.properties;
            }

            /**
             * http://backbonejs.org/#Model-toJSON
             *
             * @param {Object} options
             */
            , toJSON: function () {
                var json = _.clone(this.attributes)
                , entities = this.entities();

                _.each(entities, function (entity) {
                    json[entity.rel()] = entity;
                });

                return json;
            }

            /**
             * Filters the entitie's properties and returns only sub-entities
             *
             * @return {Array}
             */
            , entities: function (filters) {
                var self = this
                , entities = _.filter(this, function (val, name) {
                    return _.indexOf(self._entities, name) > -1;
                });

                if (filters) {
                    entities = filter(entities, filters);
                }

                return entities;
            }

            /**
             *
             * @return {jQuery.Deferred}
             */
            , resolveEntities: function () {
                var deferreds = [];

                _.each(this._data.entities, function (entity) {
                    var url = getUrl(entity);

                    if (entity.href && url) {
                        deferreds.push($.getJSON(url));
                    } else if (! entity.href) {
                        deferreds.push(entity);
                    }
                });

                return $.when(deferreds);
            }

            /**
             * http://backbonejs.org/#Model-constructor
             *
             * @param {Object} attributes
             * @param {Object} options
             */
            , constructor: function (sirenObj, options) {
                options = options || {};
                options.parse = true; // Force "parse" to be called on instantiation: http://stackoverflow.com/questions/11068989/backbone-js-using-parse-without-calling-fetch/14950519#14950519

                this._data = sirenObj; // Stores the entire siren object in raw json
                this._entities = []; // Stores sub-entity names

                Backbone.Model.call(this, sirenObj, options);
            }

        })

        , Collection: Backbone.Collection.extend({
            url: url
            , classes: classes
            , hasClass: hasClass
            , title: title
            , rel: rel
            , actions: actions

            /**
             * http://backbonejs.org/#Collection-toJSON
             *
             * @param {Object} options
             */
            , toJSON: function (options) {
                return this.map(function(model){
                    return model.toJSON(options);
                });
            }

            /**
             *
             * @param {Function|Object} arg
             * @return {Array}
             */
            , filter: function (arg) {
                if (typeof arg ==  'function') {
                    return _.filter(this, arg);
                } else {
                    return filter(this, arg);
                }
            }

            /**
             * http://backbonejs.org/#Collection-parse
             *
             * @param {Object} sirenObj
             */
            , parse: function (sirenObj, options) {
                var models = [];
                _.each(sirenObj.entities, function (entity) {
                    var model = new Backbone.Siren.Model(entity);
                    models.push(model);
                    store.add(model);
                });

                parseActions(this, options);

                return models;
            }

            /**
             * http://backbonejs.org/#Collection-constructor
             *
             * @param {Object} attributes
             * @param {Object} options
             */
            , constructor: function (sirenObj, options) {
                options = options || {};
                options.parse = true; // Force "parse" to be called on instantiation: http://stackoverflow.com/questions/11068989/backbone-js-using-parse-without-calling-fetch/14950519#14950519

                this._data = sirenObj;

                Backbone.Collection.call(this, sirenObj, options);
            }
        })
    };

}));<commit_msg>move sub-entities into the attributes object and no longer reference them directly on the model<commit_after>/*global _ Backbone */

/*
 * Backbone.Siren
 *
 * Copyright (c) 2013 Kiva Microfunds
 * Licensed under the MIT license.
 * https://github.com/kiva/backbone.siren/blob/master/license.txt
 */

(function(root, factory) {
    'use strict';

    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['underscore', 'backbone', 'backbone-relational'], function(_, Backbone) {
            Backbone.Siren = factory(_, Backbone);
        });
    } else {
        // Browser globals
        root.Backbone.Siren = factory(_, Backbone);
    }

}(this, function (_, Backbone, undefined) {
    'use strict';

    // The store
    var _store = {}
    , store = {
        key: function (model) {
            return model.url() + '-' +  model.cid;
        }

        , add: function (model) {
            _store[this.key(model)] = model;
        }

        , exists: function (model) {
            return !!_store[this.key(model)];
        }

        , all: function () {
            return _store;
        }
    }
    , warn = function (msg) {
        if (Backbone.Siren.settings.showWarnings && console) {
            console.warn(msg);
        }
    };

    /**
     *
     * @static
     * @param name
     * @return {String}
     */
    function toCamelCase(name) {
        return name.replace(/(\-[a-z])/g, function(match){return match.toUpperCase().replace('-','');});
    }

    /**
     *
     * @static
     * @param entity
     * @return {Object}
     */
    function getUrl(entity) {
        var link, url;

        if (entity.href) {
            url = entity.href;
        } else if (entity.links) {
            link = entity.links.filter(function (link) {
                return !!(link.rel && link.rel.filter(function (relType) {
                    return relType == 'self';
                }).length);
            })[0];

            if (link) {
                url = link.href;
            }
        } else {
            warn('Missing href or "self" link');
        }

        return url;
    }

    /**
     * Access to the representation's "self" url, or its "href" if there is one.
     *
     * @return {String}
     */
    function url() {
        return getUrl(this._data);
    }

    /**
     *
     * @static
     * @param {Object} sirenObj Can be a siren entity or siren action object
     * @return {Array}
     */
    function getClassNames(sirenObj) {
        return sirenObj['class'];
    }

    /**
     *
     * @static
     * @param {Array} entities
     * @param {Object} filters
     * @return {Array}
     */
    function filter(entities, filters) {
        return _.filter(entities, function (entity) {
            return modelHasFilterProperties(entity, filters);
        });
    }

    /**
     *
     * @param entity
     * @param filters
     * @return {Boolean}
     */
    function modelHasFilterProperties(entity, filters) {
        var hasProperties = true;

        if (filters.className) {
            hasProperties = entity.hasClass(filters.className);
        }

        if (filters.rel) {
            hasProperties = hasProperties && (entity.name() == filters.rel);
        }

        return hasProperties;
    }

    /**
     *
     * @param {Object} obj
     * @param {Object} filtersObj
     * @return {Boolean}
     */
    function objectHasFilterProperties(obj, filtersObj) {
        var hasProperties = true;

        if (filtersObj.className) {
            hasProperties = _hasClass(obj, filtersObj.className);
        }

        if (filtersObj.rel) {
            hasProperties = hasRel(obj, filtersObj.rel);
        }

        return hasProperties;
    }

    /**
     *
     * @static
     * @param sirenObj
     * @param className
     * @return {Boolean}
     */
    function _hasClass(sirenObj, className) {
        return _.indexOf(getClassNames(sirenObj), className) > -1;
    }

    /**
     *
     * @param className
     * @return {Boolean}
     */
    function hasClass(className) {
        return _hasClass(this._data, className);
    }

    /**
     *
     * @static
     * @param sirenObj
     * @param relValue
     * @return {Boolean}
     */
    function hasRel(sirenObj, relValue) {
        return getRel(sirenObj) == relValue;
    }

    /**
     * Accesses the "class" property of the Siren Object
     *
     * @return {Array}
     */
    function classes() {
        return getClassNames(this._data);
    }

    /**
     *
     * @static
     * @param sirenObj
     * @return {String}
     */
    function getName(sirenObj) {
        var rel = sirenObj.rel[0];

        if (rel) {
            rel = rel.slice(rel.lastIndexOf('/') + 1, rel.length);
        }

        return rel;
    }

    function name() {
        return getName(this._data);
    }

    function getRel(sirenObj) {
        return sirenObj.rel;
    }

    /**
     * By default returns characters after the last '/'.  Set to `verbose` to true to return the entire rel string.
     *
     * @return {String}
     */
    function rel() {
        return getRel(this._data);
    }

    function actions(filters) {
        var _actions = this._data.actions;

        if (filters) {
            _actions = _.filter(_actions, function (action) {
                return objectHasFilterProperties(action, filters);
            });
        }
        return _actions;
    }

    /**
     * Access to the representation's "title"
     *
     * @return {String}
     */
    function title() {
        return this._data.title;
    }

    function parseActions(model, options) {
         _.each(model.actions(), function (action) {
            model[toCamelCase(action.name)] = function () {

                options.url = action.href;

                if (action.method) {
                    options.method = action.method;
                }

                if (action.type) {
                    options.type = action.type;
                }

                return model.save(arguments, options);
            };
         });
    }

    return {
        store: store

        , settings: {
            showWarnings: true
        }

        , Model: Backbone.Model.extend({

            url: url
            , classes: classes
            , hasClass: hasClass
            , rel: rel
            , name: name
            , title: title
            , actions: actions

            /**
             * http://backbonejs.org/#Model-parse
             *
             * @param {Object} sirenObj
             */
            , parse: function (sirenObj, options) {
                var self = this;

                this.resolveEntities()
                    .done(function (args) {
                        _.each(args, function (entity) {
                            var camelCaseRel, bbSiren;

                            if (_.indexOf(getClassNames(entity), 'collection') == -1) {
                                // Its a model
                                bbSiren = new Backbone.Siren.Model(entity);
                                store.add(bbSiren);
                            } else {
                                // Its a collection
                                bbSiren = new Backbone.Siren.Collection(entity);
                            }

                            camelCaseRel = toCamelCase(bbSiren.name());
                            self.set(camelCaseRel, bbSiren);
                            self._entities.push(camelCaseRel);
                        });
                    });

                parseActions(this, options);

                return sirenObj.properties;
            }

            /**
             * http://backbonejs.org/#Model-toJSON
             *
             * @param {Object} options
             */
            , toJSON: function () {
                var json = _.clone(this.attributes)
                , entities = this.entities();

                _.each(entities, function (entity) {
                    json[entity.name()] = entity;
                });

                return json;
            }

            /**
             * Filters the entitie's properties and returns only sub-entities
             *
             * @return {Array}
             */
            , entities: function (filters) {
                var self = this
                , entities = _.filter(this, function (val, name) {
                    return _.indexOf(self._entities, name) > -1;
                });

                if (filters) {
                    entities = filter(entities, filters);
                }

                return entities;
            }

            /**
             *
             * @return {jQuery.Deferred}
             */
            , resolveEntities: function () {
                var deferreds = [];

                _.each(this._data.entities, function (entity) {
                    var url = getUrl(entity);

                    if (entity.href && url) {
                        deferreds.push($.getJSON(url));
                    } else if (! entity.href) {
                        deferreds.push(entity);
                    }
                });

                return $.when(deferreds);
            }

            /**
             * http://backbonejs.org/#Model-constructor
             *
             * @param {Object} attributes
             * @param {Object} options
             */
            , constructor: function (sirenObj, options) {
                options = options || {};
                options.parse = true; // Force "parse" to be called on instantiation: http://stackoverflow.com/questions/11068989/backbone-js-using-parse-without-calling-fetch/14950519#14950519

                this._data = sirenObj; // Stores the entire siren object in raw json
                this._entities = []; // Stores sub-entity names

                Backbone.Model.call(this, sirenObj, options);
            }

        })

        , Collection: Backbone.Collection.extend({
            url: url
            , classes: classes
            , hasClass: hasClass
            , title: title
            , rel: rel
            , name: name
            , actions: actions

            /**
             * http://backbonejs.org/#Collection-toJSON
             *
             * @param {Object} options
             */
            , toJSON: function (options) {
                return this.map(function(model){
                    return model.toJSON(options);
                });
            }

            /**
             *
             * @param {Function|Object} arg
             * @return {Array}
             */
            , filter: function (arg) {
                if (typeof arg ==  'function') {
                    return _.filter(this, arg);
                } else {
                    return filter(this, arg);
                }
            }

            /**
             * http://backbonejs.org/#Collection-parse
             *
             * @param {Object} sirenObj
             */
            , parse: function (sirenObj, options) {
                var models = [];
                _.each(sirenObj.entities, function (entity) {
                    var model = new Backbone.Siren.Model(entity);
                    models.push(model);
                    store.add(model);
                });

                parseActions(this, options);

                return models;
            }

            /**
             * http://backbonejs.org/#Collection-constructor
             *
             * @param {Object} attributes
             * @param {Object} options
             */
            , constructor: function (sirenObj, options) {
                options = options || {};
                options.parse = true; // Force "parse" to be called on instantiation: http://stackoverflow.com/questions/11068989/backbone-js-using-parse-without-calling-fetch/14950519#14950519

                this._data = sirenObj;

                Backbone.Collection.call(this, sirenObj, options);
            }
        })
    };

}));<|endoftext|>


<commit_before>import 'babel-polyfill'
import express from 'express'
import fallback from 'express-history-api-fallback'
import path from 'path'
import log from './log'
import proxy from 'http-proxy-middleware'

// Properly catch async exceptions, log them, and re-throw them
// on the main process to crash the program
const wrap = (fn) =>
  (...args) =>
    fn(...args)
      .catch((ex) => {
        log.error(ex)
        process.nextTick(() => { throw ex })
      })
const app = express()
const port = process.env.PORT
const publicPath = path.resolve(__dirname, '../../build/frontend')
app.enable('trust proxy')

app.listen(port, () => {
  log.info(`Node app is running on port ${port}`)
})

app.get('/', (req, res) => {
  res.redirect('/home')
})

app.use(express.static(publicPath, {
  maxAge: '180 days'
}))

app.use([
  '/home',
  '/teams',
  '/about'],
  proxy({
    target: 'http://brandnewcongress.nationbuilder.com/',
    changeOrigin: true
  }))

app.use([
  '/abteam',
  '/adteam',
  '/call',
  '/conferencecallteam',
  '/dataentryteam',
  '/faq',
  '/officeteam',
  '/researchteam',
  '/shareteam',
  '/spreadsheetteam',
  '/textingteam',
  '/travelteam'],
  proxy({
    target: 'http://brandnewcongress.github.io/',
    changeOrigin: true
  }))

app.use(fallback('index.html', {
  root: publicPath,
  maxAge: 0
}))
<commit_msg>Redirect to github pages for now<commit_after>import 'babel-polyfill'
import express from 'express'
import fallback from 'express-history-api-fallback'
import path from 'path'
import log from './log'
import proxy from 'http-proxy-middleware'

// Properly catch async exceptions, log them, and re-throw them
// on the main process to crash the program
const wrap = (fn) =>
  (...args) =>
    fn(...args)
      .catch((ex) => {
        log.error(ex)
        process.nextTick(() => { throw ex })
      })
const app = express()
const port = process.env.PORT
const publicPath = path.resolve(__dirname, '../../build/frontend')
app.enable('trust proxy')

app.listen(port, () => {
  log.info(`Node app is running on port ${port}`)
})

app.get('/', (req, res) => {
  res.redirect('/index')
})

app.use(express.static(publicPath, {
  maxAge: '180 days'
}))

app.use([
  '/teams',
  '/about'],
  proxy({
    target: 'http://brandnewcongress.nationbuilder.com/',
    changeOrigin: true
  }))

app.use([
  '/index',
  '/abteam',
  '/adteam',
  '/call',
  '/conferencecallteam',
  '/dataentryteam',
  '/faq',
  '/officeteam',
  '/researchteam',
  '/shareteam',
  '/spreadsheetteam',
  '/textingteam',
  '/travelteam'],
  proxy({
    target: 'http://brandnewcongress.github.io/',
    changeOrigin: true
  }))

app.use(fallback('index.html', {
  root: publicPath,
  maxAge: 0
}))
<|endoftext|>