<commit_before>'use strict';

function Project (portfolioDataObj) {
  this.name = portfolioDataObj.name;
  this.link = portfolioDataObj.link;
  this.description = portfolioDataObj.description;
  this.image = 'images/' + portfolioDataObj.image;
}

Project.all = [];

Project.prototype.toHtml = function() {
  var source   = $('#entry-template').html();
  var template = Handlebars.compile(source);
  return(template(this));
}

var articleView = {};

articleView.init = function() {
  Project.all.forEach(function(article){
    $('#projects').append(article.toHtml());
  });
};

articleView.handleMainNav = function() {
  $('.tab').on('click', function() {
    var selection = $(this).attr('data-content');
    $('section.tab-content').hide();
    $('section.tab-content[id="' + selection + '"]').fadeIn();
  })
  $('.navigation .tab:first').click();
};

articleView.handleMainNav();

Project.loadAll = function(projectData) {
  projectData.forEach(function(object){
    Project.all.push(new Project(object))
  });
};

Project.getAll = function() {
  if(localStorage.projectData){
    Project.loadAll(JSON.parse(localStorage.projectData));
    articleView.init();
  }else{
    $.getJSON('../data/data.json').then(function (data) {
      localStorage.setItem('projectData', JSON.stringify(data)) ;
      Project.loadAll(data);
      articleView.init();
    }, function(err) {
      console.log(err);
    });
  }
}
<commit_msg>added todos for when i am catching up<commit_after>'use strict';
//TODO;  Use of at least one .map() method.
 // TODO Use of at least one .reduce() method.
 // TODO IIFE present on at least one .js file.
function Project (portfolioDataObj) {
  this.name = portfolioDataObj.name;
  this.link = portfolioDataObj.link;
  this.description = portfolioDataObj.description;
  this.image = 'images/' + portfolioDataObj.image;
}

Project.all = [];

Project.prototype.toHtml = function() {
  var source   = $('#entry-template').html();
  var template = Handlebars.compile(source);
  return(template(this));
}

var articleView = {};

articleView.init = function() {
  Project.all.forEach(function(article){
    $('#projects').append(article.toHtml());
  });
};

articleView.handleMainNav = function() {
  $('.tab').on('click', function() {
    var selection = $(this).attr('data-content');
    $('section.tab-content').hide();
    $('section.tab-content[id="' + selection + '"]').fadeIn();
  })
  $('.navigation .tab:first').click();
};

articleView.handleMainNav();

Project.loadAll = function(projectData) {
  projectData.forEach(function(object){
    Project.all.push(new Project(object))
  });
};

Project.getAll = function() {
  if(localStorage.projectData){
    Project.loadAll(JSON.parse(localStorage.projectData));
    articleView.init();
  }else{
    $.getJSON('../data/data.json').then(function (data) {
      localStorage.setItem('projectData', JSON.stringify(data)) ;
      Project.loadAll(data);
      articleView.init();
    }, function(err) {
      console.log(err);
    });
  }
}
<|endoftext|>


<commit_before>/*
  A general purpose user CordovaPush
  ios, android, mail, twitter?, facebook?, sms?, snailMail? :)

  Phonegap generic :
  https://github.com/phonegap-build/PushPlugin
 */

// getText / getBinary

Push.setBadge = function(id, count) {
    // throw new Error('Push.setBadge not implemented on the server');
};

Push.init = function(options) {
    var self = this;
    // https://npmjs.org/package/apn

    // After requesting the certificate from Apple, export your private key as a .p12 file and download the .cer file from the iOS Provisioning Portal.

    // gateway.push.apple.com, port 2195
    // gateway.sandbox.push.apple.com, port 2195

    // Now, in the directory containing cert.cer and key.p12 execute the following commands to generate your .pem files:
    // $ openssl x509 -in cert.cer -inform DER -outform PEM -out cert.pem
    // $ openssl pkcs12 -in key.p12 -out key.pem -nodes

    // This function is called when a token is replaced on a device - normally
    // this should not happen, but if it does we should take action on it
    _replaceToken = function(currentToken, newToken) {
        // console.log('Replace token: ' + currentToken + ' -- ' + newToken);
        // If the server gets a token event its passing in the current token and
        // the new value - if new value is undefined this empty the token
        self.emit('token', currentToken, newToken);
    };

    // Rig the removeToken callback
    _removeToken = function(token) {
        // console.log('Remove token: ' + token);
        // Invalidate the token
        self.emit('token', token, null);
    };

    if (options.apn) {
        if (Push.debug) console.log('Push: APN configured');

        // Allow production to be a general option for push notifications
        if (options.production)
          options.apn.production = true;

        // We check the apn gateway i the options, we could risk shipping
        // server into production while using the production configuration.
        // On the other hand we could be in development but using the production
        // configuration. And finally we could have configured an unknown apn
        // gateway (this could change in the future - but a warning about typos
        // can save hours of debugging)
        //
        // Warn about gateway configurations - it's more a guide
        if (options.apn.gateway) {

            if (options.apn.gateway == 'gateway.sandbox.push.apple.com') {
                // Using the development sandbox
                console.warn('WARNING: Push APN is in development mode');
            } else if (options.apn.gateway == 'gateway.push.apple.com') {
                // In production - but warn if we are running on localhost
                if (/http:\/\/localhost/.test(Meteor.absoluteUrl())) {
                    console.warn('WARNING: Push APN is configured to production mode - but server is running from localhost');
                }
            } else {
                // Warn about gateways we dont know about
                console.warn('WARNING: Push APN unkown gateway "' + options.apn.gateway + '"');
            }

        } else {
            if (options.apn.production) {
                if (/http:\/\/localhost/.test(Meteor.absoluteUrl())) {
                    console.warn('WARNING: Push APN is configured to production mode - but server is running from localhost');
                }
            } else {
                console.warn('WARNING: Push APN is in development mode');
            }
        }

        // Check certificate data
        if (!options.apn['certData'] || !options.apn['certData'].length)
            console.error('ERROR: Push server could not find certData');

        // Check key data
        if (!options.apn['keyData'] || !options.apn['keyData'].length)
            console.error('ERROR: Push server could not find keyData');

        // Rig apn connection
        var apn = Npm.require('apn');
        var apnConnection = new apn.Connection( options.apn );

        // XXX: should we do a test of the connection? It would be nice to know
        // That the server/certificates/network are correct configured

        // apnConnection.connect().then(function() {
        //     console.info('CHECK: Push APN connection OK');
        // }, function(err) {
        //     console.warn('CHECK: Push APN connection FAILURE');
        // });
        // Note: the above code spoils the connection - investigate how to
        // shutdown/close it.

        // (cert.pem and key.pem)
        self.sendAPN = function(from, userToken, title, text, count, priority) {
            // console.log('sendAPN', from, userToken, title, text, count, priority);
            priority = (priority || priority === 0)? priority : 10;

            var myDevice = new apn.Device(userToken);

            var note = new apn.Notification();

            note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
            note.badge = count;
            note.sound = ""; // XXX: Does this work?
            note.alert = text;
            note.payload = {'messageFrom': from };
            note.priority = priority;

            // console.log('I:Send message to: ' + userToken + ' count=' + count);

            apnConnection.pushNotification(note, myDevice);

        };

        var initFeedback = function() {
            var apn = Npm.require('apn');
            // console.log('Init feedback');
            var feedbackOptions = {
                "batchFeedback": true,
                "interval": 1000,
                'address': 'feedback.push.apple.com'
            };

            var feedback = new apn.Feedback(feedbackOptions);
            feedback.on("feedback", function(devices) {
                devices.forEach(function(item) {
                    // Do something with item.device and item.time;
                    // console.log('A:PUSH FEEDBACK ' + item.device + ' - ' + item.time);
                    // The app is most likely removed from the device, we should
                    // remove the token
                    _removeToken({ apn: item.device});
                });
            });
        };

        // Init feedback from apn server
        // This will help keep the appCollection up-to-date, it will help update
        // and remove token from appCollection.
        initFeedback();

    } // EO ios options

    if (options.gcm && options.gcm.apiKey) {
        if (Push.debug) console.log('GCM configured');
        self.sendGCM = function(from, userTokens, title, text, count, priority) {
            // console.log('sendGCM', from, userToken, title, text, count, priority);
            var gcm = Npm.require('node-gcm');
            var Fiber = Npm.require('fibers');

            //var message = new gcm.Message();
            var message = new gcm.Message({
                collapseKey: from,
            //    delayWhileIdle: true,
            //    timeToLive: 4,
            //    restricted_package_name: 'dk.gi2.app'
                data: {
                    title: title,
                    message: text,
                    msgcnt: count
                }
            });
            var sender = new gcm.Sender(options.gcm.apiKey);

            _.each(userTokens, function(value, key) {
                // console.log('A:Send message to: ' + value + ' count=' + count);
            });

            /*message.addData('title', title);
            message.addData('message', text);
            message.addData('msgcnt', '1');
            message.collapseKey = 'sitDrift';
            message.delayWhileIdle = true;
            message.timeToLive = 3;*/

            // /**
            //  * Parameters: message-literal, userTokens-array, No. of retries, callback-function
            //  */

            var userToken = (userTokens.length === 1)?userTokens[0]:null;

            sender.send(message, userTokens, 5, function (err, result) {
                if (err) {
                    // console.log('ANDROID ERROR: result of sender: ' + result);
                } else {
                    // console.log('ANDROID: Result of sender: ' + JSON.stringify(result));
                    if (result.canonical_ids === 1 && userToken) {

                        // This is an old device, token is replaced
                        Fiber(function(self) {
                            // Run in fiber
                            try {
                                self.callback(self.oldToken, self.newToken);
                            } catch(err) {

                            }

                        }).run({
                            oldToken: { gcm: userToken },
                            newToken: { gcm: result.results[0].registration_id },
                            callback: _replaceToken
                        });
                        //_replaceToken({ gcm: userToken }, { gcm: result.results[0].registration_id });

                    }
                    // We cant send to that token - might not be registred
                    // ask the user to remove the token from the list
                    if (result.failure !== 0 && userToken) {

                        // This is an old device, token is replaced
                        Fiber(function(self) {
                            // Run in fiber
                            try {
                                self.callback(self.token);
                            } catch(err) {

                            }

                        }).run({
                            token: { gcm: userToken },
                            callback: _removeToken
                        });
                        //_replaceToken({ gcm: userToken }, { gcm: result.results[0].registration_id });

                    }

                }
            });
            // /** Use the following line if you want to send the message without retries
            // sender.sendNoRetry(message, userTokens, function (result) {
            //     console.log('ANDROID: ' + JSON.stringify(result));
            // });
            // **/
        }; // EO sendAndroid

    } // EO Android

    // Universal send function
    var _querySend = function(from, query, title, text, count, priority) {

      var countApn = [];
      var countGcm = [];

        Push.appCollection.find(query).forEach(function(app) {

          if (Push.debug) console.log('send to token', app.token);

            if (app.token.apn) {
              countApn.push(app._id);
                // Send to APN
                if (self.sendAPN) self.sendAPN(from, app.token.apn, title, text, count, priority);

            } else if (app.token.gcm) {
              countGcm.push(app._id);

                // Send to GCM
                // We do support multiple here - so we should construct an array
                // and send it bulk - Investigate limit count of id's
                if (self.sendGCM) self.sendGCM(from, app.token.gcm, title, text, count, priority);

            } else {
                throw new Error('Push.send got a faulty query');
            }

        });

        if (Push.debug) console.log('Push: Sent message "' + title + '" to ' + countApn.length + ' ios apps ' + countGcm.length + ' android apps');

        return {
          apn: countApn,
          gcm: countGcm
        };
    };

    self.serverSend = function(options) {
      options = options || { count: 0 };
      var query;

      // Check basic options
      if (options.from !== ''+options.from)
        throw new Error('Push.send: option "from" not a string');

      if (options.title !== ''+options.title)
        throw new Error('Push.send: option "title" not a string');

      if (options.text !== ''+options.text)
        throw new Error('Push.send: option "text" not a string');

      if (options.token || options.tokens) {

        // The user set one token or array of tokens
        var tokenList = (options.token)? [options.token] : options.tokens;

        if (Push.debug) console.log('Push: Send message "' + options.title + '" via token(s)', tokenList);

        query = {
          $or: [
              // XXX: Test this query: can we hand in a list of push tokens?
              { token: { $in: tokenList } },
              // XXX: Test this query: does this work on app id?
              { $and: [
                  { _in: { $in: tokenList } }, // one of the app ids
                  { $or: [
                      { 'token.apn': { $exists: true }  }, // got apn token
                      { 'token.gcm': { $exists: true }  }  // got gcm token
                  ]}
              ]}
          ]
        };

      } else if (options.query) {

        if (Push.debug) console.log('Push: Send message "' + options.title + '" via query', options.query);

        query = {
          $and: [
              options.query, // query object
              { $or: [
                  { 'token.apn': { $exists: true }  }, // got apn token
                  { 'token.gcm': { $exists: true }  }  // got gcm token
              ]}
          ]
        };
      }

      if (query) {

        // Convert to querySend and return status
        return _querySend(options.from, query, options.title, options.text, options.count, options.priority)

      } else {
        throw new Error('Push.send: please set option "token"/"tokens" or "query"');
      }

    };

    var isSendingNotification = false;

    Meteor.setInterval(function() {

        if (!isSendingNotification) {
            // Set send fence
            isSendingNotification = true;

            // Find one notification
            var notification = Push.notifications.findOne({ sent : { $ne: true } }, { sort: { createdAt: 1 } });

            // Check if we got any notifications to send
            if (notification) {

                // Send the notification
                var result = Push.serverSend(notification);

                if (!options.keepNotifications) {
                    // Pr. Default we will remove notifications
                    Push.notifications.remove({ _id: notification._id });
                } else {

                    // Update the notification
                    Push.notifications.update({ _id: notification._id }, {
                        $set: {
                            sent: true,
                            sentAt: new Date(),
                            count: result
                        }
                    });

                }

                // Emit the send
                self.emit('send', { notification: notification._id, result: result });
            }

            // Remove the send fence
            isSendingNotification = false;
        }
    }, options.sendInterval || 15000); // Default every 15'the sec

};
<commit_msg>Fix android sends - convert string to array of strings<commit_after>/*
  A general purpose user CordovaPush
  ios, android, mail, twitter?, facebook?, sms?, snailMail? :)

  Phonegap generic :
  https://github.com/phonegap-build/PushPlugin
 */

// getText / getBinary

Push.setBadge = function(id, count) {
    // throw new Error('Push.setBadge not implemented on the server');
};

Push.init = function(options) {
    var self = this;
    // https://npmjs.org/package/apn

    // After requesting the certificate from Apple, export your private key as a .p12 file and download the .cer file from the iOS Provisioning Portal.

    // gateway.push.apple.com, port 2195
    // gateway.sandbox.push.apple.com, port 2195

    // Now, in the directory containing cert.cer and key.p12 execute the following commands to generate your .pem files:
    // $ openssl x509 -in cert.cer -inform DER -outform PEM -out cert.pem
    // $ openssl pkcs12 -in key.p12 -out key.pem -nodes

    // This function is called when a token is replaced on a device - normally
    // this should not happen, but if it does we should take action on it
    _replaceToken = function(currentToken, newToken) {
        // console.log('Replace token: ' + currentToken + ' -- ' + newToken);
        // If the server gets a token event its passing in the current token and
        // the new value - if new value is undefined this empty the token
        self.emit('token', currentToken, newToken);
    };

    // Rig the removeToken callback
    _removeToken = function(token) {
        // console.log('Remove token: ' + token);
        // Invalidate the token
        self.emit('token', token, null);
    };

    if (options.apn) {
        if (Push.debug) console.log('Push: APN configured');

        // Allow production to be a general option for push notifications
        if (options.production)
          options.apn.production = true;

        // We check the apn gateway i the options, we could risk shipping
        // server into production while using the production configuration.
        // On the other hand we could be in development but using the production
        // configuration. And finally we could have configured an unknown apn
        // gateway (this could change in the future - but a warning about typos
        // can save hours of debugging)
        //
        // Warn about gateway configurations - it's more a guide
        if (options.apn.gateway) {

            if (options.apn.gateway == 'gateway.sandbox.push.apple.com') {
                // Using the development sandbox
                console.warn('WARNING: Push APN is in development mode');
            } else if (options.apn.gateway == 'gateway.push.apple.com') {
                // In production - but warn if we are running on localhost
                if (/http:\/\/localhost/.test(Meteor.absoluteUrl())) {
                    console.warn('WARNING: Push APN is configured to production mode - but server is running from localhost');
                }
            } else {
                // Warn about gateways we dont know about
                console.warn('WARNING: Push APN unkown gateway "' + options.apn.gateway + '"');
            }

        } else {
            if (options.apn.production) {
                if (/http:\/\/localhost/.test(Meteor.absoluteUrl())) {
                    console.warn('WARNING: Push APN is configured to production mode - but server is running from localhost');
                }
            } else {
                console.warn('WARNING: Push APN is in development mode');
            }
        }

        // Check certificate data
        if (!options.apn['certData'] || !options.apn['certData'].length)
            console.error('ERROR: Push server could not find certData');

        // Check key data
        if (!options.apn['keyData'] || !options.apn['keyData'].length)
            console.error('ERROR: Push server could not find keyData');

        // Rig apn connection
        var apn = Npm.require('apn');
        var apnConnection = new apn.Connection( options.apn );

        // XXX: should we do a test of the connection? It would be nice to know
        // That the server/certificates/network are correct configured

        // apnConnection.connect().then(function() {
        //     console.info('CHECK: Push APN connection OK');
        // }, function(err) {
        //     console.warn('CHECK: Push APN connection FAILURE');
        // });
        // Note: the above code spoils the connection - investigate how to
        // shutdown/close it.

        // (cert.pem and key.pem)
        self.sendAPN = function(from, userToken, title, text, count, priority) {
            // console.log('sendAPN', from, userToken, title, text, count, priority);
            priority = (priority || priority === 0)? priority : 10;

            var myDevice = new apn.Device(userToken);

            var note = new apn.Notification();

            note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
            note.badge = count;
            note.sound = ""; // XXX: Does this work?
            note.alert = text;
            note.payload = {'messageFrom': from };
            note.priority = priority;

            // console.log('I:Send message to: ' + userToken + ' count=' + count);

            apnConnection.pushNotification(note, myDevice);

        };

        var initFeedback = function() {
            var apn = Npm.require('apn');
            // console.log('Init feedback');
            var feedbackOptions = {
                "batchFeedback": true,
                "interval": 1000,
                'address': 'feedback.push.apple.com'
            };

            var feedback = new apn.Feedback(feedbackOptions);
            feedback.on("feedback", function(devices) {
                devices.forEach(function(item) {
                    // Do something with item.device and item.time;
                    // console.log('A:PUSH FEEDBACK ' + item.device + ' - ' + item.time);
                    // The app is most likely removed from the device, we should
                    // remove the token
                    _removeToken({ apn: item.device});
                });
            });
        };

        // Init feedback from apn server
        // This will help keep the appCollection up-to-date, it will help update
        // and remove token from appCollection.
        initFeedback();

    } // EO ios options

    if (options.gcm && options.gcm.apiKey) {
        if (Push.debug) console.log('GCM configured');
        self.sendGCM = function(from, userTokens, title, text, count, priority) {
            // Make sure userTokens are an array of strings
            if (userTokens === ''+userTokens) userTokens = [userTokens];

            // Check if any tokens in there to send
            if (!userTokens.length) {
                if (Push.debug) console.log('sendGCM no push tokens found');
                return;
            }

            if (Push.debug)
                console.log('sendGCM', from, userToken, title, text, count, priority);

            var gcm = Npm.require('node-gcm');
            var Fiber = Npm.require('fibers');

            //var message = new gcm.Message();
            var message = new gcm.Message({
                collapseKey: from,
            //    delayWhileIdle: true,
            //    timeToLive: 4,
            //    restricted_package_name: 'dk.gi2.app'
                data: {
                    title: title,
                    message: text,
                    msgcnt: count
                }
            });
            var sender = new gcm.Sender(options.gcm.apiKey);

            _.each(userTokens, function(value, key) {
                // console.log('A:Send message to: ' + value + ' count=' + count);
            });

            /*message.addData('title', title);
            message.addData('message', text);
            message.addData('msgcnt', '1');
            message.collapseKey = 'sitDrift';
            message.delayWhileIdle = true;
            message.timeToLive = 3;*/

            // /**
            //  * Parameters: message-literal, userTokens-array, No. of retries, callback-function
            //  */

            var userToken = (userTokens.length === 1)?userTokens[0]:null;

            sender.send(message, userTokens, 5, function (err, result) {
                if (err) {
                    // console.log('ANDROID ERROR: result of sender: ' + result);
                } else {
                    // console.log('ANDROID: Result of sender: ' + JSON.stringify(result));
                    if (result.canonical_ids === 1 && userToken) {

                        // This is an old device, token is replaced
                        Fiber(function(self) {
                            // Run in fiber
                            try {
                                self.callback(self.oldToken, self.newToken);
                            } catch(err) {

                            }

                        }).run({
                            oldToken: { gcm: userToken },
                            newToken: { gcm: result.results[0].registration_id },
                            callback: _replaceToken
                        });
                        //_replaceToken({ gcm: userToken }, { gcm: result.results[0].registration_id });

                    }
                    // We cant send to that token - might not be registred
                    // ask the user to remove the token from the list
                    if (result.failure !== 0 && userToken) {

                        // This is an old device, token is replaced
                        Fiber(function(self) {
                            // Run in fiber
                            try {
                                self.callback(self.token);
                            } catch(err) {

                            }

                        }).run({
                            token: { gcm: userToken },
                            callback: _removeToken
                        });
                        //_replaceToken({ gcm: userToken }, { gcm: result.results[0].registration_id });

                    }

                }
            });
            // /** Use the following line if you want to send the message without retries
            // sender.sendNoRetry(message, userTokens, function (result) {
            //     console.log('ANDROID: ' + JSON.stringify(result));
            // });
            // **/
        }; // EO sendAndroid

    } // EO Android

    // Universal send function
    var _querySend = function(from, query, title, text, count, priority) {

      var countApn = [];
      var countGcm = [];

        Push.appCollection.find(query).forEach(function(app) {

          if (Push.debug) console.log('send to token', app.token);

            if (app.token.apn) {
              countApn.push(app._id);
                // Send to APN
                if (self.sendAPN) self.sendAPN(from, app.token.apn, title, text, count, priority);

            } else if (app.token.gcm) {
              countGcm.push(app._id);

                // Send to GCM
                // We do support multiple here - so we should construct an array
                // and send it bulk - Investigate limit count of id's
                if (self.sendGCM) self.sendGCM(from, app.token.gcm, title, text, count, priority);

            } else {
                throw new Error('Push.send got a faulty query');
            }

        });

        if (Push.debug) console.log('Push: Sent message "' + title + '" to ' + countApn.length + ' ios apps ' + countGcm.length + ' android apps');

        return {
          apn: countApn,
          gcm: countGcm
        };
    };

    self.serverSend = function(options) {
      options = options || { count: 0 };
      var query;

      // Check basic options
      if (options.from !== ''+options.from)
        throw new Error('Push.send: option "from" not a string');

      if (options.title !== ''+options.title)
        throw new Error('Push.send: option "title" not a string');

      if (options.text !== ''+options.text)
        throw new Error('Push.send: option "text" not a string');

      if (options.token || options.tokens) {

        // The user set one token or array of tokens
        var tokenList = (options.token)? [options.token] : options.tokens;

        if (Push.debug) console.log('Push: Send message "' + options.title + '" via token(s)', tokenList);

        query = {
          $or: [
              // XXX: Test this query: can we hand in a list of push tokens?
              { token: { $in: tokenList } },
              // XXX: Test this query: does this work on app id?
              { $and: [
                  { _in: { $in: tokenList } }, // one of the app ids
                  { $or: [
                      { 'token.apn': { $exists: true }  }, // got apn token
                      { 'token.gcm': { $exists: true }  }  // got gcm token
                  ]}
              ]}
          ]
        };

      } else if (options.query) {

        if (Push.debug) console.log('Push: Send message "' + options.title + '" via query', options.query);

        query = {
          $and: [
              options.query, // query object
              { $or: [
                  { 'token.apn': { $exists: true }  }, // got apn token
                  { 'token.gcm': { $exists: true }  }  // got gcm token
              ]}
          ]
        };
      }

      if (query) {

        // Convert to querySend and return status
        return _querySend(options.from, query, options.title, options.text, options.count, options.priority)

      } else {
        throw new Error('Push.send: please set option "token"/"tokens" or "query"');
      }

    };

    var isSendingNotification = false;

    Meteor.setInterval(function() {

        if (!isSendingNotification) {
            // Set send fence
            isSendingNotification = true;

            // Find one notification
            var notification = Push.notifications.findOne({ sent : { $ne: true } }, { sort: { createdAt: 1 } });

            // Check if we got any notifications to send
            if (notification) {

                // Send the notification
                var result = Push.serverSend(notification);

                if (!options.keepNotifications) {
                    // Pr. Default we will remove notifications
                    Push.notifications.remove({ _id: notification._id });
                } else {

                    // Update the notification
                    Push.notifications.update({ _id: notification._id }, {
                        $set: {
                            sent: true,
                            sentAt: new Date(),
                            count: result
                        }
                    });

                }

                // Emit the send
                self.emit('send', { notification: notification._id, result: result });
            }

            // Remove the send fence
            isSendingNotification = false;
        }
    }, options.sendInterval || 15000); // Default every 15'the sec

};
<|endoftext|>


<commit_before>// ui functions
ZenPen = window.ZenPen || {};
ZenPen.ui = (function() {

	// Base elements
	var body, article, uiContainer, overlay, aboutButton, descriptionModal, header;

	// Buttons
	var screenSizeElement, colorLayoutElement, targetElement, saveElement;

	// Word Counter
	var wordCountValue, wordCountBox, wordCountElement, wordCounter, wordCounterProgress;

	//save support
	var supportsSave, saveFormat, textToWrite;

	var expandScreenIcon = '';
	var shrinkScreenIcon = '';

	var darkLayout = false;

	function init() {

		supportsSave = !!new Blob()?true:false;

		bindElements();

		wordCountActive = false;

		if ( ZenPen.util.supportsHtmlStorage() ) {
			loadState();
		}

		console.log( "Checkin under the hood eh? We've probably got a lot in common. You should totally check out ZenPen on github! (https://github.com/tholman/zenpen)." );
	}

	function loadState() {

		// Activate word counter
		if ( localStorage['wordCount'] && localStorage['wordCount'] !== "0") {
			wordCountValue = parseInt(localStorage['wordCount']);
			wordCountElement.value = localStorage['wordCount'];
			wordCounter.className = "word-counter active";
			updateWordCount();
		}

		// Activate color switch
		if ( localStorage['darkLayout'] === 'true' ) {
			if ( darkLayout === false ) {
				document.body.className = 'yang';
			} else {
				document.body.className = 'yin';
			}
			darkLayout = !darkLayout;
		}

	}

	function saveState() {

		if ( ZenPen.util.supportsHtmlStorage() ) {
			localStorage[ 'darkLayout' ] = darkLayout;
			localStorage[ 'wordCount' ] = wordCountElement.value;
		}
	}

	function bindElements() {

		// Body element for light/dark styles
		body = document.body;

		uiContainer = document.querySelector( '.ui' );

		// UI element for color flip
		colorLayoutElement = document.querySelector( '.color-flip' );
		colorLayoutElement.onclick = onColorLayoutClick;

		// UI element for full screen
		screenSizeElement = document.querySelector( '.fullscreen' );
		screenSizeElement.onclick = onScreenSizeClick;

		targetElement = document.querySelector( '.target ');
		targetElement.onclick = onTargetClick;

		//init event listeners only if browser can save
		if (supportsSave) {

			saveElement = document.querySelector( '.save' );
			saveElement.onclick = onSaveClick;

			var formatSelectors = document.querySelectorAll( '.saveselection span' );
			for( var i in formatSelectors ) {
				formatSelectors[i].onclick = selectFormat;
			}

			document.querySelector('.savebutton').onclick = saveText;
		} else {
			document.querySelector('.save.useicons').style.display = "none";
		}

		if (firebase) {
			loadElement = document.querySelector( '.load' );
			loadElement.onclick = onLoadClick;
			document.querySelector('.loadbutton').onclick = loadArticle;
		}

		// Overlay when modals are active
		overlay = document.querySelector( '.overlay' );
		overlay.onclick = onOverlayClick;

		article = document.querySelector( '.content' );
		article.onkeyup = onArticleKeyUp;

		wordCountBox = overlay.querySelector( '.wordcount' );
		wordCountElement = wordCountBox.querySelector( 'input' );
		wordCountElement.onchange = onWordCountChange;
		wordCountElement.onkeyup = onWordCountKeyUp;

		descriptionModal = overlay.querySelector( '.description' );

		saveModal = overlay.querySelector('.saveoverlay');

		loadModal = overlay.querySelector('.load-from-title');
		articleTitle = document.querySelector( '.article-title' );

		wordCounter = document.querySelector( '.word-counter' );
		wordCounterProgress = wordCounter.querySelector( '.progress' );

		aboutButton = document.querySelector( '.about' );
		aboutButton.onclick = onAboutButtonClick;

		header = document.querySelector( '.header' );
		header.onkeypress = onHeaderKeyPress;
	}

	function onScreenSizeClick( event ) {

		screenfull.toggle();
   		if ( screenfull.enabled ) {
			document.addEventListener( screenfull.raw.fullscreenchange, function () {
				if ( screenfull.isFullscreen ) {
					screenSizeElement.innerHTML = shrinkScreenIcon;
				} else {
					screenSizeElement.innerHTML = expandScreenIcon;
				}
    		});
    	}
	};

	function onColorLayoutClick( event ) {
		if ( darkLayout === false ) {
			document.body.className = 'yang';
		} else {
			document.body.className = 'yin';
		}
		darkLayout = !darkLayout;

		saveState();
	}

	function onTargetClick( event ) {
		overlay.style.display = "block";
		wordCountBox.style.display = "block";
		wordCountElement.focus();
	}

	function onAboutButtonClick( event ) {
		overlay.style.display = "block";
		descriptionModal.style.display = "block";
	}

	function onLoadClick( event ) {
		overlay.style.display = "block";
		loadModal.style.display = "block";
	}

	function onSaveClick( event ) {
		overlay.style.display = "block";
		saveModal.style.display = "block";
	}

	function loadArticle( event ) {
		if (articleTitle.value){
			return firebase.database().ref('/articles/' + articleTitle.value).once('value').then(function(snapshot) {
				if(snapshot.val()){
					var bodyField = document.querySelector('article.content');
					header.innerHTML = articleTitle.value;
					bodyField.innerHTML = snapshot.val().content;
					removeOverlay();
				}else{
					alert(articleTitle.value + " not found")
				}

			});
		}
	}

	function saveText( event ) {

		if (typeof saveFormat != 'undefined' && saveFormat != '') {
			var blob = new Blob([textToWrite], {type: "text/plain;charset=utf-8"});
			/* remove tabs and line breaks from header */
			var headerText = header.innerHTML.replace(/(\t|\n|\r)/gm,"");
			if (headerText === "") {
			    headerText = "ZenPen";
			}
			saveAs(blob, headerText + '.txt');
		} else {
			document.querySelector('.saveoverlay h1').style.color = '#FC1E1E';
		}
	}

	/* Allows the user to press enter to tab from the title */
	function onHeaderKeyPress( event ) {

		if ( event.keyCode === 13 ) {
			event.preventDefault();
			article.focus();
		}
	}

	/* Allows the user to press enter to tab from the word count modal */
	function onWordCountKeyUp( event ) {

		if ( event.keyCode === 13 ) {
			event.preventDefault();

			setWordCount( parseInt(this.value) );

			removeOverlay();

			article.focus();
		}
	}

	function onWordCountChange( event ) {

		setWordCount( parseInt(this.value) );
	}

	function setWordCount( count ) {

		// Set wordcount ui to active
		if ( count > 0) {

			wordCountValue = count;
			wordCounter.className = "word-counter active";
			updateWordCount();

		} else {

			wordCountValue = 0;
			wordCounter.className = "word-counter";
		}

		saveState();
	}

	function onArticleKeyUp( event ) {

		if ( wordCountValue > 0 ) {
			updateWordCount();
		}
	}

	function updateWordCount() {

		var wordCount = ZenPen.editor.getWordCount();
		var percentageComplete = wordCount / wordCountValue;
		wordCounterProgress.style.height = percentageComplete * 100 + '%';

		if ( percentageComplete >= 1 ) {
			wordCounterProgress.className = "progress complete";
		} else {
			wordCounterProgress.className = "progress";
		}
	}

	function selectFormat( e ) {

		if ( document.querySelectorAll('span.activesave').length > 0 ) {
			document.querySelector('span.activesave').className = '';
		}

		document.querySelector('.saveoverlay h1').style.cssText = '';

		var targ;
		if (!e) var e = window.event;
		if (e.target) targ = e.target;
		else if (e.srcElement) targ = e.srcElement;

		// defeat Safari bug
		if (targ.nodeType == 3) {
			targ = targ.parentNode;
		}

		targ.className ='activesave';

		saveFormat = targ.getAttribute('data-format');

		var header = document.querySelector('header.header');
		var headerText = header.innerHTML.replace(/(\r\n|\n|\r)/gm,"") + "\n";

		var body = document.querySelector('article.content');
		var bodyText = body.innerHTML;

		textToWrite = formatText(saveFormat,headerText,bodyText);

		var textArea = document.querySelector('.hiddentextbox');
		textArea.value = textToWrite;
		textArea.focus();
		textArea.select();

	}

	function formatText( type, header, body ) {

		var text;
		switch( type ) {

			case 'html':
				header = "
" + header + "
";
				text = header + body;
				text = text.replace(/\t/g, '');
			break;

			case 'markdown':
				header = header.replace(/\t/g, '');
				header = header.replace(/\n$/, '');
				header = "#" + header + "#";

				text = body.replace(/\t/g, '');

				text = text.replace(/|<\/b>/g,"**")
					.replace(/\r\n+|\r+|\n+|\t+/ig,"")
					.replace(/|<\/i>/g,"_")
					.replace(/
/g,"> ")
					.replace(/<\/blockquote>/g,"")
					.replace(/
|<\/p>/gi,"\n")
					.replace(/
/g,"\n");

				var links = text.match(/(.+)<\/a>/gi);

                                if (links !== null) {
                                        for ( var i = 0; i<links.length; i++ ) {
                                                var tmpparent = document.createElement('div');
                                                tmpparent.innerHTML = links[i];

                                                var tmp = tmpparent.firstChild;

                                                var href = tmp.getAttribute('href');
                                                var linktext = tmp.textContent || tmp.innerText || "";

                                                text = text.replace(links[i],'['+linktext+']('+href+')');
                                        }
                                }

				text = header +"\n\n"+ text;
			break;

			case 'plain':
				header = header.replace(/\t/g, '');

				var tmp = document.createElement('div');
				tmp.innerHTML = body;
				text = tmp.textContent || tmp.innerText || "";

				text = text.replace(/\t/g, '')
					.replace(/\n{3}/g,"\n")
					.replace(/\n/,""); //replace the opening line break

				text = header + text;
			break;
			default:
			break;
		}

		return text;
	}

	function onOverlayClick( event ) {

		if ( event.target.className === "overlay" ) {
			removeOverlay();
		}
	}

	function removeOverlay() {

		overlay.style.display = "none";
		wordCountBox.style.display = "none";
		descriptionModal.style.display = "none";
		saveModal.style.display = "none";
		loadModal.style.display = "none"
		if ( document.querySelectorAll('span.activesave' ).length > 0) {
			document.querySelector('span.activesave').className = '';
		}

		saveFormat='';
	}

	return {
		init: init
	}

})();
<commit_msg>title redefine<commit_after>// ui functions
ZenPen = window.ZenPen || {};
ZenPen.ui = (function() {

	// Base elements
	var body, article, uiContainer, overlay, aboutButton, descriptionModal, header;

	// Buttons
	var screenSizeElement, colorLayoutElement, targetElement, saveElement;

	// Word Counter
	var wordCountValue, wordCountBox, wordCountElement, wordCounter, wordCounterProgress;

	//save support
	var supportsSave, saveFormat, textToWrite;

	var expandScreenIcon = '';
	var shrinkScreenIcon = '';

	var darkLayout = false;

	function init() {

		supportsSave = !!new Blob()?true:false;

		bindElements();

		wordCountActive = false;

		if ( ZenPen.util.supportsHtmlStorage() ) {
			loadState();
		}

		console.log( "Checkin under the hood eh? We've probably got a lot in common. You should totally check out ZenPen on github! (https://github.com/tholman/zenpen)." );
	}

	function loadState() {

		// Activate word counter
		if ( localStorage['wordCount'] && localStorage['wordCount'] !== "0") {
			wordCountValue = parseInt(localStorage['wordCount']);
			wordCountElement.value = localStorage['wordCount'];
			wordCounter.className = "word-counter active";
			updateWordCount();
		}

		// Activate color switch
		if ( localStorage['darkLayout'] === 'true' ) {
			if ( darkLayout === false ) {
				document.body.className = 'yang';
			} else {
				document.body.className = 'yin';
			}
			darkLayout = !darkLayout;
		}

	}

	function saveState() {

		if ( ZenPen.util.supportsHtmlStorage() ) {
			localStorage[ 'darkLayout' ] = darkLayout;
			localStorage[ 'wordCount' ] = wordCountElement.value;
		}
	}

	function bindElements() {

		// Body element for light/dark styles
		body = document.body;

		uiContainer = document.querySelector( '.ui' );

		// UI element for color flip
		colorLayoutElement = document.querySelector( '.color-flip' );
		colorLayoutElement.onclick = onColorLayoutClick;

		// UI element for full screen
		screenSizeElement = document.querySelector( '.fullscreen' );
		screenSizeElement.onclick = onScreenSizeClick;

		targetElement = document.querySelector( '.target ');
		targetElement.onclick = onTargetClick;

		//init event listeners only if browser can save
		if (supportsSave) {

			saveElement = document.querySelector( '.save' );
			saveElement.onclick = onSaveClick;

			var formatSelectors = document.querySelectorAll( '.saveselection span' );
			for( var i in formatSelectors ) {
				formatSelectors[i].onclick = selectFormat;
			}

			document.querySelector('.savebutton').onclick = saveText;
		} else {
			document.querySelector('.save.useicons').style.display = "none";
		}

		if (firebase) {
			loadElement = document.querySelector( '.load' );
			loadElement.onclick = onLoadClick;
			document.querySelector('.loadbutton').onclick = loadArticle;
		}

		// Overlay when modals are active
		overlay = document.querySelector( '.overlay' );
		overlay.onclick = onOverlayClick;

		article = document.querySelector( '.content' );
		article.onkeyup = onArticleKeyUp;

		wordCountBox = overlay.querySelector( '.wordcount' );
		wordCountElement = wordCountBox.querySelector( 'input' );
		wordCountElement.onchange = onWordCountChange;
		wordCountElement.onkeyup = onWordCountKeyUp;

		descriptionModal = overlay.querySelector( '.description' );

		saveModal = overlay.querySelector('.saveoverlay');

		loadModal = overlay.querySelector('.load-from-title');
		articleTitle = document.querySelector( '.article-title' );

		wordCounter = document.querySelector( '.word-counter' );
		wordCounterProgress = wordCounter.querySelector( '.progress' );

		aboutButton = document.querySelector( '.about' );
		aboutButton.onclick = onAboutButtonClick;

		header = document.querySelector( '.header' );
		header.onkeypress = onHeaderKeyPress;
	}

	function onScreenSizeClick( event ) {

		screenfull.toggle();
   		if ( screenfull.enabled ) {
			document.addEventListener( screenfull.raw.fullscreenchange, function () {
				if ( screenfull.isFullscreen ) {
					screenSizeElement.innerHTML = shrinkScreenIcon;
				} else {
					screenSizeElement.innerHTML = expandScreenIcon;
				}
    		});
    	}
	};

	function onColorLayoutClick( event ) {
		if ( darkLayout === false ) {
			document.body.className = 'yang';
		} else {
			document.body.className = 'yin';
		}
		darkLayout = !darkLayout;

		saveState();
	}

	function onTargetClick( event ) {
		overlay.style.display = "block";
		wordCountBox.style.display = "block";
		wordCountElement.focus();
	}

	function onAboutButtonClick( event ) {
		overlay.style.display = "block";
		descriptionModal.style.display = "block";
	}

	function onLoadClick( event ) {
		overlay.style.display = "block";
		loadModal.style.display = "block";
	}

	function onSaveClick( event ) {
		overlay.style.display = "block";
		saveModal.style.display = "block";
	}

	function loadArticle( event ) {
		if (articleTitle.value){
			return firebase.database().ref('/articles/' + articleTitle.value).once('value').then(function(snapshot) {
				if(snapshot.val()){
					var bodyField = document.querySelector('article.content');
					header.innerHTML = articleTitle.value;
					bodyField.innerHTML = snapshot.val().content;
					removeOverlay();
				}else{
					alert(articleTitle.value + " not found")
				}

			});
		}
	}

	function saveText( event ) {

		if (typeof saveFormat != 'undefined' && saveFormat != '') {
			var blob = new Blob([textToWrite], {type: "text/plain;charset=utf-8"});
			/* remove tabs and line breaks from header */
			var headerText = header.innerHTML.replace(/(\t|\n|\r)/gm,"");
			if (headerText === "") {
			    headerText = "Beary Writer";
			}
			saveAs(blob, headerText + '.txt');
		} else {
			document.querySelector('.saveoverlay h1').style.color = '#FC1E1E';
		}
	}

	/* Allows the user to press enter to tab from the title */
	function onHeaderKeyPress( event ) {

		if ( event.keyCode === 13 ) {
			event.preventDefault();
			article.focus();
		}
	}

	/* Allows the user to press enter to tab from the word count modal */
	function onWordCountKeyUp( event ) {

		if ( event.keyCode === 13 ) {
			event.preventDefault();

			setWordCount( parseInt(this.value) );

			removeOverlay();

			article.focus();
		}
	}

	function onWordCountChange( event ) {

		setWordCount( parseInt(this.value) );
	}

	function setWordCount( count ) {

		// Set wordcount ui to active
		if ( count > 0) {

			wordCountValue = count;
			wordCounter.className = "word-counter active";
			updateWordCount();

		} else {

			wordCountValue = 0;
			wordCounter.className = "word-counter";
		}

		saveState();
	}

	function onArticleKeyUp( event ) {

		if ( wordCountValue > 0 ) {
			updateWordCount();
		}
	}

	function updateWordCount() {

		var wordCount = ZenPen.editor.getWordCount();
		var percentageComplete = wordCount / wordCountValue;
		wordCounterProgress.style.height = percentageComplete * 100 + '%';

		if ( percentageComplete >= 1 ) {
			wordCounterProgress.className = "progress complete";
		} else {
			wordCounterProgress.className = "progress";
		}
	}

	function selectFormat( e ) {

		if ( document.querySelectorAll('span.activesave').length > 0 ) {
			document.querySelector('span.activesave').className = '';
		}

		document.querySelector('.saveoverlay h1').style.cssText = '';

		var targ;
		if (!e) var e = window.event;
		if (e.target) targ = e.target;
		else if (e.srcElement) targ = e.srcElement;

		// defeat Safari bug
		if (targ.nodeType == 3) {
			targ = targ.parentNode;
		}

		targ.className ='activesave';

		saveFormat = targ.getAttribute('data-format');

		var header = document.querySelector('header.header');
		var headerText = header.innerHTML.replace(/(\r\n|\n|\r)/gm,"") + "\n";

		var body = document.querySelector('article.content');
		var bodyText = body.innerHTML;

		textToWrite = formatText(saveFormat,headerText,bodyText);

		var textArea = document.querySelector('.hiddentextbox');
		textArea.value = textToWrite;
		textArea.focus();
		textArea.select();

	}

	function formatText( type, header, body ) {

		var text;
		switch( type ) {

			case 'html':
				header = "
" + header + "
";
				text = header + body;
				text = text.replace(/\t/g, '');
			break;

			case 'markdown':
				header = header.replace(/\t/g, '');
				header = header.replace(/\n$/, '');
				header = "#" + header + "#";

				text = body.replace(/\t/g, '');

				text = text.replace(/|<\/b>/g,"**")
					.replace(/\r\n+|\r+|\n+|\t+/ig,"")
					.replace(/|<\/i>/g,"_")
					.replace(/
/g,"> ")
					.replace(/<\/blockquote>/g,"")
					.replace(/
|<\/p>/gi,"\n")
					.replace(/
/g,"\n");

				var links = text.match(/(.+)<\/a>/gi);

                                if (links !== null) {
                                        for ( var i = 0; i<links.length; i++ ) {
                                                var tmpparent = document.createElement('div');
                                                tmpparent.innerHTML = links[i];

                                                var tmp = tmpparent.firstChild;

                                                var href = tmp.getAttribute('href');
                                                var linktext = tmp.textContent || tmp.innerText || "";

                                                text = text.replace(links[i],'['+linktext+']('+href+')');
                                        }
                                }

				text = header +"\n\n"+ text;
			break;

			case 'plain':
				header = header.replace(/\t/g, '');

				var tmp = document.createElement('div');
				tmp.innerHTML = body;
				text = tmp.textContent || tmp.innerText || "";

				text = text.replace(/\t/g, '')
					.replace(/\n{3}/g,"\n")
					.replace(/\n/,""); //replace the opening line break

				text = header + text;
			break;
			default:
			break;
		}

		return text;
	}

	function onOverlayClick( event ) {

		if ( event.target.className === "overlay" ) {
			removeOverlay();
		}
	}

	function removeOverlay() {

		overlay.style.display = "none";
		wordCountBox.style.display = "none";
		descriptionModal.style.display = "none";
		saveModal.style.display = "none";
		loadModal.style.display = "none"
		if ( document.querySelectorAll('span.activesave' ).length > 0) {
			document.querySelector('span.activesave').className = '';
		}

		saveFormat='';
	}

	return {
		init: init
	}

})();
<|endoftext|>


<commit_before>load("util.js");

var io = {
	print: function(s) {
		return print(s);
	},
	quit: function() {
		return quit();
	},
};

var connector = {
	selector: java.nio.channels.Selector.open(),
	read_buf: java.nio.ByteBuffer.allocate(1024),
	write_bufs: {},
	events: [],

	/* Register/unregister descriptors and operations with the selector.
	   These assume that only one ops of each type is registered per
	   descriptor; for example, you can't register two read events, because
	   when one is unregistered it will unregister the other. The exception
	   to this is that there may be multiple write events per descriptor. */
	register: function(sd, ops) {
		var key = sd.keyFor(this.selector);
		var current_ops = 0;
		if (key)
			current_ops = key.interestOps();
		sd.register(this.selector, current_ops | ops);
	},
	unregister: function(sd, ops) {
		var key = sd.keyFor(this.selector);
		if (key && key.isValid())
			sd.register(this.selector, key.interestOps() & ~ops);
	},

	bytebuffer_to_string: function(bb) {
		return String(new java.lang.String(java.util.Arrays.copyOf(bb.array(), bb.position()), "ISO-8859-1"));
	},
	string_to_bytebuffer: function(s) {
		return java.nio.ByteBuffer.wrap(new java.lang.String(s).getBytes("ISO-8859-1"));
	},

	s_sd: function(sd) {
		var s = sd.socket();
		return [s.getLocalAddress().getHostAddress(),
			s.getLocalPort(),
			s.getInetAddress().getHostAddress(),
			s.getPort()].join("\0");
	},

	handle_selection_key: function(key) {
		var channel = key.channel();
		if ((key.readyOps() & java.nio.channels.SelectionKey.OP_ACCEPT)
			== java.nio.channels.SelectionKey.OP_ACCEPT) {
			this.op_accept(channel);
		} else if ((key.readyOps() & java.nio.channels.SelectionKey.OP_CONNECT)
			== java.nio.channels.SelectionKey.OP_CONNECT) {
			this.op_connect(channel);
		} else if ((key.readyOps() & java.nio.channels.SelectionKey.OP_READ)
			== java.nio.channels.SelectionKey.OP_READ) {
			this.op_read(channel);
		} else if ((key.readyOps() & java.nio.channels.SelectionKey.OP_WRITE)
			== java.nio.channels.SelectionKey.OP_WRITE) {
			this.op_write(channel);
		} else {
			throw new Error("Unknown selection key op.");
		}
	},
	op_accept: function(channel) {
		var c = channel.socket().accept();
		this.events.push({
			type: "accept",
			sd: channel,
			address: c.getInetAddress().getHostAddress(),
			port: c.getPort(),
			client: c.channel,
		});
		/* For accept only, do not unregister the selection key. */
	},
	op_connect: function(channel) {
		try {
			if (channel.isConnectionPending())
				channel.finishConnect();
		} catch (error) {
			this.events.push({
				type: "connect",
				sd: channel,
				error: error,
			});
			return;
		}
		this.events.push({
			type: "connect",
			sd: channel,
			address: channel.socket().getInetAddress().getHostAddress(),
			port: channel.socket().getPort(),
		});
		this.unregister(channel, java.nio.channels.SelectionKey.OP_CONNECT);
	},
	op_read: function(channel) {
		this.read_buf.clear();
		try {
			var n = channel.read(this.read_buf);
		} catch (error) {
			this.events.push({
				type: "recv",
				sd: channel,
				error: error,
			});
			return;
		}
		var data;
		if (n == -1)
			data = undefined;
		else
			data = this.bytebuffer_to_string(this.read_buf);
		this.events.push({
			type: "recv",
			sd: channel,
			data: data,
		});
		this.unregister(channel, java.nio.channels.SelectionKey.OP_READ);
	},
	op_write: function(channel) {
		var write_bufs = this.write_bufs[this.s_sd(channel)];
		try {
			var n = channel.write(write_bufs[0]);
		} catch (error) {
			this.events.push({
				type: "send",
				sd: channel,
				error: error,
			});
			return;
		}
		/* Each buffer in our write_bufs queue corresponds to one write
		   event. Don't signal completion until an entire buffer is
		   exhausted. */
		if (write_bufs[0].remaining() == 0) {
			this.events.push({
				type: "send",
				sd: channel,
				n: write_bufs[0].position(),
			});
			write_bufs.shift();
		}
		if (write_bufs.length == 0)
			this.unregister(channel, java.nio.channels.SelectionKey.OP_WRITE);
	},

	wait_for_event: function() {
		while (true) {
			if (this.events.length > 0)
				return this.events.shift();

			var n;
			do {
				n = this.selector.select();
			} while (n == 0);

			var iter = this.selector.selectedKeys().iterator();
			while (iter.hasNext())
				this.handle_selection_key(iter.next());
			this.selector.selectedKeys().clear();
		}
	},
	listen: function(address, port) {
		var sd = java.nio.channels.ServerSocketChannel.open();
		sd.configureBlocking(false);
		try {
			sd.socket().bind(java.net.InetSocketAddress(port));
			this.register(sd, java.nio.channels.SelectionKey.OP_ACCEPT);
		} catch (error) {
			this.events.push({
				type: "accept",
				sd: sd,
				error: error,
				address: address,
				port: port,
			});
		}
		return sd;
	},
	connect: function(address, port) {
		var sd = java.nio.channels.SocketChannel.open();
		sd.configureBlocking(false);
		sd.connect(java.net.InetSocketAddress(address, port));
		this.register(sd, java.nio.channels.SelectionKey.OP_CONNECT);
		return sd;
	},
	recv: function(sd) {
		sd.configureBlocking(false);
		this.register(sd, java.nio.channels.SelectionKey.OP_READ);
		return sd;
	},
	send: function(sd, data) {
		sd.configureBlocking(false);
		var key = this.s_sd(sd);
		this.write_bufs[key] = this.write_bufs[key] || [];
		this.write_bufs[key].push(this.string_to_bytebuffer(data));
		this.register(sd, java.nio.channels.SelectionKey.OP_WRITE);
		return sd;
	},
	close: function(sd) {
		delete this.write_bufs[this.s_sd(sd)];
		sd.close();
	},
};

accept_pending = [];
connect_pending = [];
recv_pending = [];
send_pending = [];

function add_pending(pending_list, id, data) {
	pending_list.push({ id: id, data: data });
}

function get_pending(pending_list, id, keep) {
	for (var i = 0; i < pending_list.length; i++) {
		if (pending_list[i].id == id) {
			var pending = pending_list[i];
			if (!keep)
				pending_list.splice(i, 1)[0];
			return pending.data;
		}
	}
}

function listen(address, port, callback, userdata) {
	var sd = connector.listen(address, port);
	add_pending(accept_pending, sd, { callback: callback, userdata: userdata });
	return sd;
}

function connect(address, port, callback, userdata) {
	var sd = connector.connect(address, port);
	add_pending(connect_pending, sd, { callback: callback, userdata: userdata });
	return sd;
}

function recv(sd, callback, userdata) {
	var sd = connector.recv(sd);
	add_pending(recv_pending, sd, { callback: callback, userdata: userdata });
	return sd;
}

function send(sd, data, callback, userdata) {
	var sd = connector.send(sd, data);
	add_pending(send_pending, sd, { callback: callback, userdata: userdata });
	return sd;
}

function close(sd) {
	while (get_pending(accept_pending, sd))
		;
	while (get_pending(connect_pending, sd))
		;
	while (get_pending(recv_pending, sd))
		;
	while (get_pending(send_pending, sd))
		;
	connector.close(sd);
};

function event_loop() {
	while (true) {
		var ev = connector.wait_for_event();
		io.print("ev: " + repr(ev));

		var pending, args;
		switch (ev.type) {
		case "accept":
			/* accept events are persistent; don't remove pending
			   except on error. */
			pending = this.get_pending(this.accept_pending, ev.sd, true);
			args = [ev.client, ev.address, ev.port];
			break;
		case "connect":
			pending = this.get_pending(this.connect_pending, ev.sd);
			args = [ev.address, ev.port];
			break;
		case "recv":
			pending = this.get_pending(this.recv_pending, ev.sd);
			args = [ev.data];
			break;
		case "send":
			pending = this.get_pending(this.send_pending, ev.sd);
			args = [];
			break;
		default:
			throw new Error("Unknown event type \"" + ev.type + "\".");
		}
		/* Add standard arguments. */
		args = [ev.sd, ev.error, pending.userdata].concat(args);
		if (pending.callback)
			pending.callback.apply(null, args);
	}
}

io.print("jscat starting.")

var LOCAL_ADDRESS = "0.0.0.0";
var LOCAL_PORT = 9998;

var DIRECTORY_ADDRESS = "localhost";
var DIRECTORY_PORT = 9999;

function accept_handler(sd, error, userdata, client, address, port) {
	if (error) {
		io.print("Error listening on " + address + ":" + port + ": " + error.message);
		return;
	}
	io.print("Connection from " + address + ":" + port + ".");
	/* Pass the client as userdata. */
	connect(DIRECTORY_ADDRESS, DIRECTORY_PORT, connect_handler, client);
}

function connect_handler(sd, error, userdata, address, port) {
	if (error) {
		io.print("Error in connect: " + error.message);
		close(userdata);
		return;
	}
	io.print("Connection to " + address + ":" + port + ".");
	/* Queue initial read events. */
	recv(sd, recv_handler, userdata);
	recv(userdata, recv_handler, sd);
}

function recv_handler(sd, error, userdata, data) {
	if (error) {
		io.print("Error in recv: " + error.message);
		close(sd);
		close(userdata);
		return;
	}
	if (data == undefined) {
		close(sd);
		close(userdata);
	} else {
		send(userdata, data);
		recv(sd, recv_handler, userdata);
	}
}

listen(LOCAL_ADDRESS, LOCAL_PORT, accept_handler);

event_loop();
<commit_msg>Better comment.<commit_after>load("util.js");

var io = {
	print: function(s) {
		return print(s);
	},
	quit: function() {
		return quit();
	},
};

var connector = {
	selector: java.nio.channels.Selector.open(),
	read_buf: java.nio.ByteBuffer.allocate(1024),
	write_bufs: {},
	events: [],

	/* Register/unregister descriptors and operations with the selector.
	   These assume that only one ops of each type is registered per
	   descriptor; for example, you can't register two read events, because
	   when one is unregistered it will unregister the other. The exception
	   to this is that there may be multiple write events per descriptor. */
	register: function(sd, ops) {
		var key = sd.keyFor(this.selector);
		var current_ops = 0;
		if (key)
			current_ops = key.interestOps();
		sd.register(this.selector, current_ops | ops);
	},
	unregister: function(sd, ops) {
		var key = sd.keyFor(this.selector);
		if (key && key.isValid())
			sd.register(this.selector, key.interestOps() & ~ops);
	},

	bytebuffer_to_string: function(bb) {
		return String(new java.lang.String(java.util.Arrays.copyOf(bb.array(), bb.position()), "ISO-8859-1"));
	},
	string_to_bytebuffer: function(s) {
		return java.nio.ByteBuffer.wrap(new java.lang.String(s).getBytes("ISO-8859-1"));
	},

	s_sd: function(sd) {
		var s = sd.socket();
		return [s.getLocalAddress().getHostAddress(),
			s.getLocalPort(),
			s.getInetAddress().getHostAddress(),
			s.getPort()].join("\0");
	},

	handle_selection_key: function(key) {
		var channel = key.channel();
		if ((key.readyOps() & java.nio.channels.SelectionKey.OP_ACCEPT)
			== java.nio.channels.SelectionKey.OP_ACCEPT) {
			this.op_accept(channel);
		} else if ((key.readyOps() & java.nio.channels.SelectionKey.OP_CONNECT)
			== java.nio.channels.SelectionKey.OP_CONNECT) {
			this.op_connect(channel);
		} else if ((key.readyOps() & java.nio.channels.SelectionKey.OP_READ)
			== java.nio.channels.SelectionKey.OP_READ) {
			this.op_read(channel);
		} else if ((key.readyOps() & java.nio.channels.SelectionKey.OP_WRITE)
			== java.nio.channels.SelectionKey.OP_WRITE) {
			this.op_write(channel);
		} else {
			throw new Error("Unknown selection key op.");
		}
	},
	op_accept: function(channel) {
		var c = channel.socket().accept();
		this.events.push({
			type: "accept",
			sd: channel,
			address: c.getInetAddress().getHostAddress(),
			port: c.getPort(),
			client: c.channel,
		});
		/* For accept only, do not unregister the selection key. */
	},
	op_connect: function(channel) {
		try {
			if (channel.isConnectionPending())
				channel.finishConnect();
		} catch (error) {
			this.events.push({
				type: "connect",
				sd: channel,
				error: error,
			});
			return;
		}
		this.events.push({
			type: "connect",
			sd: channel,
			address: channel.socket().getInetAddress().getHostAddress(),
			port: channel.socket().getPort(),
		});
		this.unregister(channel, java.nio.channels.SelectionKey.OP_CONNECT);
	},
	op_read: function(channel) {
		this.read_buf.clear();
		try {
			var n = channel.read(this.read_buf);
		} catch (error) {
			this.events.push({
				type: "recv",
				sd: channel,
				error: error,
			});
			return;
		}
		var data;
		if (n == -1)
			data = undefined;
		else
			data = this.bytebuffer_to_string(this.read_buf);
		this.events.push({
			type: "recv",
			sd: channel,
			data: data,
		});
		this.unregister(channel, java.nio.channels.SelectionKey.OP_READ);
	},
	op_write: function(channel) {
		var write_bufs = this.write_bufs[this.s_sd(channel)];
		try {
			var n = channel.write(write_bufs[0]);
		} catch (error) {
			this.events.push({
				type: "send",
				sd: channel,
				error: error,
			});
			return;
		}
		/* Each buffer in our write_bufs queue corresponds to one write
		   event. Don't signal completion until an entire buffer is
		   exhausted. */
		if (write_bufs[0].remaining() == 0) {
			this.events.push({
				type: "send",
				sd: channel,
				n: write_bufs[0].position(),
			});
			write_bufs.shift();
		}
		if (write_bufs.length == 0)
			this.unregister(channel, java.nio.channels.SelectionKey.OP_WRITE);
	},

	wait_for_event: function() {
		while (true) {
			if (this.events.length > 0)
				return this.events.shift();

			var n;
			do {
				n = this.selector.select();
			} while (n == 0);

			var iter = this.selector.selectedKeys().iterator();
			while (iter.hasNext())
				this.handle_selection_key(iter.next());
			this.selector.selectedKeys().clear();
		}
	},
	listen: function(address, port) {
		var sd = java.nio.channels.ServerSocketChannel.open();
		sd.configureBlocking(false);
		try {
			sd.socket().bind(java.net.InetSocketAddress(port));
			this.register(sd, java.nio.channels.SelectionKey.OP_ACCEPT);
		} catch (error) {
			this.events.push({
				type: "accept",
				sd: sd,
				error: error,
				address: address,
				port: port,
			});
		}
		return sd;
	},
	connect: function(address, port) {
		var sd = java.nio.channels.SocketChannel.open();
		sd.configureBlocking(false);
		sd.connect(java.net.InetSocketAddress(address, port));
		this.register(sd, java.nio.channels.SelectionKey.OP_CONNECT);
		return sd;
	},
	recv: function(sd) {
		sd.configureBlocking(false);
		this.register(sd, java.nio.channels.SelectionKey.OP_READ);
		return sd;
	},
	send: function(sd, data) {
		sd.configureBlocking(false);
		var key = this.s_sd(sd);
		this.write_bufs[key] = this.write_bufs[key] || [];
		this.write_bufs[key].push(this.string_to_bytebuffer(data));
		this.register(sd, java.nio.channels.SelectionKey.OP_WRITE);
		return sd;
	},
	close: function(sd) {
		delete this.write_bufs[this.s_sd(sd)];
		sd.close();
	},
};

accept_pending = [];
connect_pending = [];
recv_pending = [];
send_pending = [];

function add_pending(pending_list, id, data) {
	pending_list.push({ id: id, data: data });
}

function get_pending(pending_list, id, keep) {
	for (var i = 0; i < pending_list.length; i++) {
		if (pending_list[i].id == id) {
			var pending = pending_list[i];
			if (!keep)
				pending_list.splice(i, 1)[0];
			return pending.data;
		}
	}
}

function listen(address, port, callback, userdata) {
	var sd = connector.listen(address, port);
	add_pending(accept_pending, sd, { callback: callback, userdata: userdata });
	return sd;
}

function connect(address, port, callback, userdata) {
	var sd = connector.connect(address, port);
	add_pending(connect_pending, sd, { callback: callback, userdata: userdata });
	return sd;
}

function recv(sd, callback, userdata) {
	var sd = connector.recv(sd);
	add_pending(recv_pending, sd, { callback: callback, userdata: userdata });
	return sd;
}

function send(sd, data, callback, userdata) {
	var sd = connector.send(sd, data);
	add_pending(send_pending, sd, { callback: callback, userdata: userdata });
	return sd;
}

function close(sd) {
	while (get_pending(accept_pending, sd))
		;
	while (get_pending(connect_pending, sd))
		;
	while (get_pending(recv_pending, sd))
		;
	while (get_pending(send_pending, sd))
		;
	connector.close(sd);
};

function event_loop() {
	while (true) {
		var ev = connector.wait_for_event();
		io.print("ev: " + repr(ev));

		var pending, args;
		switch (ev.type) {
		case "accept":
			/* "accept" events are persistent; don't remove pending
			   except on closing. */
			pending = this.get_pending(this.accept_pending, ev.sd, true);
			args = [ev.client, ev.address, ev.port];
			break;
		case "connect":
			pending = this.get_pending(this.connect_pending, ev.sd);
			args = [ev.address, ev.port];
			break;
		case "recv":
			pending = this.get_pending(this.recv_pending, ev.sd);
			args = [ev.data];
			break;
		case "send":
			pending = this.get_pending(this.send_pending, ev.sd);
			args = [];
			break;
		default:
			throw new Error("Unknown event type \"" + ev.type + "\".");
		}
		/* Add standard arguments. */
		args = [ev.sd, ev.error, pending.userdata].concat(args);
		if (pending.callback)
			pending.callback.apply(null, args);
	}
}

io.print("jscat starting.")

var LOCAL_ADDRESS = "0.0.0.0";
var LOCAL_PORT = 9998;

var DIRECTORY_ADDRESS = "localhost";
var DIRECTORY_PORT = 9999;

function accept_handler(sd, error, userdata, client, address, port) {
	if (error) {
		io.print("Error listening on " + address + ":" + port + ": " + error.message);
		return;
	}
	io.print("Connection from " + address + ":" + port + ".");
	/* Pass the client as userdata. */
	connect(DIRECTORY_ADDRESS, DIRECTORY_PORT, connect_handler, client);
}

function connect_handler(sd, error, userdata, address, port) {
	if (error) {
		io.print("Error in connect: " + error.message);
		close(userdata);
		return;
	}
	io.print("Connection to " + address + ":" + port + ".");
	/* Queue initial read events. */
	recv(sd, recv_handler, userdata);
	recv(userdata, recv_handler, sd);
}

function recv_handler(sd, error, userdata, data) {
	if (error) {
		io.print("Error in recv: " + error.message);
		close(sd);
		close(userdata);
		return;
	}
	if (data == undefined) {
		close(sd);
		close(userdata);
	} else {
		send(userdata, data);
		recv(sd, recv_handler, userdata);
	}
}

listen(LOCAL_ADDRESS, LOCAL_PORT, accept_handler);

event_loop();
<|endoftext|>


<commit_before>(function($, d3, PetitionMap) {
  PetitionMap.current_petition = PetitionMap.current_petition || undefined;
  PetitionMap.mp_data = PetitionMap.mp_data || undefined;

  var width, height;

  var projection, svg, path, g;
  var boundaries, units;

  // Zoom variables
  var zoom = d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoomed);
  var translate_saved = [0, 0];
  var scale_saved = 1;

  var active = d3.select(null);

  // Party colour class names
  var parties = [
      "Conservative",
      "Green",
      "Independent",
      "Labour",
      "LabourCooperative",
      "LiberalDemocrat",
      "PlaidCymru",
      "ScottishNationalParty",
      "Speaker",
      "UKIP"
  ];

  computeSize();
  init(width, height);

  // Compute size for map
  function computeSize() {
    width = parseInt(d3.select("#map").style("width"));
    height = $('main').innerHeight();
  }

  // Initialise map
  function init(width, height) {
    projection = d3.geo.albers()
      .rotate([0, 0]);

    path = d3.geo.path()
      .projection(projection);

    svg = d3.select("#map").append("svg")
      .attr("width", width)
      .attr("height", height)
      .append("g")
      .call(zoom)
      .on("click", stopped, true);

    g = svg.append("g");
  }

  // Draw map on SVG element
  function draw(boundaries) {
    projection
      .scale(1)
      .translate([0,0]);

    // Compute the correct bounds and scaling from the topoJSON
    var b = path.bounds(topojson.feature(boundaries, boundaries.objects[units])),
      s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
      t,
      area = $("input[name='area']:checked").val();
    if (area === "lon") {
      t = [((width - s * (b[1][0] + b[0][0])) / 2.25), (height - s * (b[1][1] + b[0][1])) / 2];
    } else if (area === "gb") {
      t = [((width - s * (b[1][0] + b[0][0])) / 1.95), (height - s * (b[1][1] + b[0][1])) / 2];
    } else {
      t = [((width - s * (b[1][0] + b[0][0])) / 1.85), (height - s * (b[1][1] + b[0][1])) / 2];
    }

    projection
      .scale(s)
      .translate(t);

    // Add an area for each feature in the topoJSON (constituency)
    g.selectAll(".area")
      .data(topojson.feature(boundaries, boundaries.objects[units]).features)
      .enter().append("path")
      .attr("class", "area")
      .attr("id", function(d) {return d.id})
      .attr("d", path)
      .attr('vector-effect', 'non-scaling-stroke')
      .on("mouseenter", function(constituency){ $(window).trigger('petitionmap:constituency-on', constituency); })
      .on("mouseleave", function(constituency){ $(window).trigger('petitionmap:constituency-off', constituency); });

    // Add a boundary between areas
    g.append("path")
      .datum(topojson.mesh(boundaries, boundaries.objects[units], function(a, b){ return a !== b }))
      .attr('d', path)
      .attr('class', 'boundary')
      .attr('vector-effect', 'non-scaling-stroke');
  }

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

  // Redraw the map - remove map completely and start from scratch
  function redraw() {
    computeSize();

    d3.select("svg").remove();

    init(width, height);
    draw(boundaries);
    recolourMap();
    svg.attr("transform", "translate(" + translate_saved + ")scale(" + scale_saved + ")");
  }

  // Loads data from the given file and redraws and recolours the map
  function loadMapData(filename, new_units) {
    units = new_units;

    return $.getJSON(filename)
      .done(function(data) {
        boundaries = data;
        redraw();
        recolourMap();
        interpolateZoomAndPan(translate_saved, scale_saved);
      })
      .fail(function(error) {
        console.error(error);
      });
  }

  // Recolour the map for a given petition
  function recolourMap() {
    var highest_count = getHighestCount();
    slices = calculateSlices(highest_count);
    colourClasses(slices);
  }

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

  // Get the highest constituency signature count
  function getHighestCount() {
    var highest_count = 0,
      constituencies = PetitionMap.current_petition.data.attributes.signatures_by_constituency;
    $.each(constituencies, function (index, item) {
      if (item.signature_count >= highest_count) {
          highest_count = item.signature_count;
      }
    });

    return highest_count;
  }

  // Calculate the ranges for signature colouring based on highest count
  function calculateSlices(highest_count) {
    var goalBinSize = Math.floor(highest_count / 8)
    var roundBy = Math.pow(10, Math.floor(goalBinSize.toString().length / 2))
    var binSize = Math.round(goalBinSize/ roundBy) * roundBy;

    slices = {};
    for (i = 0; i <= 8; i++) {
      slices[i] = i * Math.round(goalBinSize / roundBy) * roundBy;
    }

    for (i = 0; i <= 8; i++) {
      $('#t' + (i+1)).html("");
      if (i === 0) {
        $('#t' + (i+1)).html("1 - " +  slices[i + 1]);
      } else if (i === 7) {
        $('#t' + (i + 1)).html(slices[i] + " +");
      } else {
        $('#t' + (i + 1)).html(slices[i] + " - " +  slices[i + 1]);
      }
    }

    return slices;
  }

  // Colour the areas on the map based on their place in the ranges
  function colourClasses(slices) {
    var constituencies = PetitionMap.current_petition.data.attributes.signatures_by_constituency;

    d3.selectAll(".coloured").attr("class", "area");
    $.each(constituencies, function (index, item) {
      var id = "#" + item.ons_code;
      var index = placeInArray(slices, item.signature_count);
      var colour_class = "c" + index + " coloured";
      d3.select(id)
        .attr("class", colour_class);
    });
  }

  // Find the place of constituency in the slices array
  function placeInArray(slices, count) {
    var slice = slices[1];
    for (i = 0; i < 8; i++) {
      if (count >= slices[i] && count < (slices[i] + slice)) {
        return i+1;
      }
      if (count >= slice * 8) {
        return 8;
      }
    }
  }

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

  // Show constituency info and party colours on select
  // (hover on desktop or click on mobile)
  function highlightConstituencyOnMap(_event, constituency) {
    var mpForConstituency = PetitionMap.mp_data[constituency.id],
      party_class = stripWhitespace(mpForConstituency.party);
    deselectPartyColours();
    d3.select("#" + constituency.id).classed(party_class, true);
    d3.select("#" + constituency.id).classed("selected_boundary", true);
  }

  // Remove classes from other constituencies on deselect
  function dehighlightConstituencyOnMap(_event, constituency) {
    // var party_class = stripWhitespace(PetitionMap.mp_data[constituency.id].party);
    // d3.select("#" + constituency.id).classed(party_class, false);
    // d3.select("#" + constituency.id).classed("selected_boundary", false);
  }

  // Removes all other party colour classes from constituencies
  function deselectPartyColours() {
    $.each(parties, function (index, item) {
      d3.selectAll(".area").classed(item, false);
      d3.selectAll(".coloured").classed(item, false);
    });
    d3.selectAll(".selected_boundary").classed("selected_boundary", false);
  }

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

  // Strips whitespace from a string
  function stripWhitespace(string) {
    return string.replace(/[^a-zA-Z]/g, '');
  }

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

  // Zoom and pan transition
  function interpolateZoomAndPan(translate, scale) {
    translate_saved = translate;
    scale_saved = scale;
    var self = this;
    return d3.transition().duration(350).tween("zoom", function () {
      var iTranslate = d3.interpolate(zoom.translate(), translate),
          iScale = d3.interpolate(zoom.scale(), scale);
      return function (t) {
        zoom
          .scale(iScale(t))
          .translate(iTranslate(t));
        zoomed();
      };
    });
  }

  // Zoom in and out based on plus or minus button
  function zoomButton() {
    var clicked = d3.event.target,
      direction = 1,
      factor = 0.2,
      target_zoom = 1,
      center = [width / 2, height / 2],
      extent = zoom.scaleExtent(),
      translate = zoom.translate(),
      translate0 = [],
      l = [],
      view = {x: translate[0], y: translate[1], k: zoom.scale()};

    d3.event.preventDefault();
    direction = (this.id === 'zoom_in') ? 1 : -1;
    target_zoom = zoom.scale() * (1 + factor * direction);

    if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }

    translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
    view.k = target_zoom;
    l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];

    view.x += center[0] - l[0];
    view.y += center[1] - l[1];

    interpolateZoomAndPan([view.x, view.y], view.k);
  }

  function zoomed() {
    svg.attr("transform", "translate(" + zoom.translate() + ")scale(" + zoom.scale() + ")");
  }

  function stopped() {
    if (d3.event.defaultPrevented) d3.event.stopPropagation();
  }

  // Reset scale and translation
  function resetMapState() {
    active.classed("active", false);
    active = d3.select(null);

    svg.transition()
      .call(zoom.translate([0, 0]).scale(1).event);

    translate_saved = [0, 0];
    scale_saved = 1;
  }

  // Pan around based on N, E, S, W buttons
  function panButton() {
    var clicked = d3.event.target,
      offsetX = 0,
      offsetY = 0,
      center = [width / 2, height / 2],
      translate = zoom.translate(),
      translate0 = [],
      l = [],
      view = {x: translate[0], y: translate[1], k: zoom.scale()};

    d3.event.preventDefault();
    if (this.id == 'pan_west') {
      offsetX -= 50;
    } else if (this.id === 'pan_north') {
      offsetY -= 50;
    } else if (this.id === 'pan_south') {
      offsetY += 50;
    } else if (this.id === 'pan_east') {
      offsetX += 50;
    }

    translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
    l = [translate0[0] * view.k + view.x + offsetX, translate0[1] * view.k + view.y + offsetY];

    view.x += center[0] - l[0];
    view.y += center[1] - l[1];

    interpolateZoomAndPan([view.x, view.y], view.k);
  }

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

  // React to constituency highlight events
  $(window).on('petitionmap:constituency-off', dehighlightConstituencyOnMap);
  $(window).on('petitionmap:constituency-on', highlightConstituencyOnMap);

  // when the window is resized, redraw the map
  $(window).on('resize', redraw);

  // Button to reset zoom
  $("#reset").on('click', resetMapState);

  // Buttons to zoom in and out
  d3.selectAll('.zoom').on('click', zoomButton);

  // Buttons to pan around
  d3.selectAll('.pan').on('click', panButton);

  PetitionMap.loadMapData = loadMapData;
  PetitionMap.resetMapState = resetMapState;

})(window.jQuery, window.d3, window.PetitionMap = window.PetitionMap || {});
<commit_msg>Simplify redraw and loadMapData<commit_after>(function($, d3, PetitionMap) {
  PetitionMap.current_petition = PetitionMap.current_petition || undefined;
  PetitionMap.mp_data = PetitionMap.mp_data || undefined;

  var width, height;

  var projection, svg, path, g;
  var boundaries, units;

  // Zoom variables
  var zoom = d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", applyZoomAndPan);
  var translate_saved = [0, 0];
  var scale_saved = 1;

  var active = d3.select(null);

  // Party colour class names
  var parties = [
      "Conservative",
      "Green",
      "Independent",
      "Labour",
      "LabourCooperative",
      "LiberalDemocrat",
      "PlaidCymru",
      "ScottishNationalParty",
      "Speaker",
      "UKIP"
  ];

  computeSize();
  init(width, height);

  // Compute size for map
  function computeSize() {
    width = parseInt(d3.select("#map").style("width"));
    height = $('main').innerHeight();
  }

  // Initialise map
  function init(width, height) {
    projection = d3.geo.albers()
      .rotate([0, 0]);

    path = d3.geo.path()
      .projection(projection);

    svg = d3.select("#map").append("svg")
      .attr("width", width)
      .attr("height", height)
      .append("g")
      .call(zoom)
      .on("click", stopped, true);

    g = svg.append("g");
  }

  // Draw map on SVG element
  function draw(boundaries) {
    projection
      .scale(1)
      .translate([0,0]);

    // Compute the correct bounds and scaling from the topoJSON
    var b = path.bounds(topojson.feature(boundaries, boundaries.objects[units])),
      s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
      t,
      area = $("input[name='area']:checked").val();
    if (area === "lon") {
      t = [((width - s * (b[1][0] + b[0][0])) / 2.25), (height - s * (b[1][1] + b[0][1])) / 2];
    } else if (area === "gb") {
      t = [((width - s * (b[1][0] + b[0][0])) / 1.95), (height - s * (b[1][1] + b[0][1])) / 2];
    } else {
      t = [((width - s * (b[1][0] + b[0][0])) / 1.85), (height - s * (b[1][1] + b[0][1])) / 2];
    }

    projection
      .scale(s)
      .translate(t);

    // Add an area for each feature in the topoJSON (constituency)
    g.selectAll(".area")
      .data(topojson.feature(boundaries, boundaries.objects[units]).features)
      .enter().append("path")
      .attr("class", "area")
      .attr("id", function(d) {return d.id})
      .attr("d", path)
      .attr('vector-effect', 'non-scaling-stroke')
      .on("mouseenter", function(constituency){ $(window).trigger('petitionmap:constituency-on', constituency); })
      .on("mouseleave", function(constituency){ $(window).trigger('petitionmap:constituency-off', constituency); });

    // Add a boundary between areas
    g.append("path")
      .datum(topojson.mesh(boundaries, boundaries.objects[units], function(a, b){ return a !== b }))
      .attr('d', path)
      .attr('class', 'boundary')
      .attr('vector-effect', 'non-scaling-stroke');
  }

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

  // Redraw the map - remove map completely and start from scratch
  function redraw() {
    computeSize();

    d3.select("svg").remove();

    init(width, height);
    draw(boundaries);
    recolourMap();
    applyZoomAndPan();
  }

  // Loads data from the given file and redraws and recolours the map
  function loadMapData(filename, new_units) {
    units = new_units;

    return $.getJSON(filename)
      .done(function(data) {
        boundaries = data;
        redraw();
      })
      .fail(function(error) {
        console.error(error);
      });
  }

  // Recolour the map for a given petition
  function recolourMap() {
    var highest_count = getHighestCount();
    slices = calculateSlices(highest_count);
    colourClasses(slices);
  }

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

  // Get the highest constituency signature count
  function getHighestCount() {
    var highest_count = 0,
      constituencies = PetitionMap.current_petition.data.attributes.signatures_by_constituency;
    $.each(constituencies, function (index, item) {
      if (item.signature_count >= highest_count) {
          highest_count = item.signature_count;
      }
    });

    return highest_count;
  }

  // Calculate the ranges for signature colouring based on highest count
  function calculateSlices(highest_count) {
    var goalBinSize = Math.floor(highest_count / 8)
    var roundBy = Math.pow(10, Math.floor(goalBinSize.toString().length / 2))
    var binSize = Math.round(goalBinSize/ roundBy) * roundBy;

    slices = {};
    for (i = 0; i <= 8; i++) {
      slices[i] = i * Math.round(goalBinSize / roundBy) * roundBy;
    }

    for (i = 0; i <= 8; i++) {
      $('#t' + (i+1)).html("");
      if (i === 0) {
        $('#t' + (i+1)).html("1 - " +  slices[i + 1]);
      } else if (i === 7) {
        $('#t' + (i + 1)).html(slices[i] + " +");
      } else {
        $('#t' + (i + 1)).html(slices[i] + " - " +  slices[i + 1]);
      }
    }

    return slices;
  }

  // Colour the areas on the map based on their place in the ranges
  function colourClasses(slices) {
    var constituencies = PetitionMap.current_petition.data.attributes.signatures_by_constituency;

    d3.selectAll(".coloured").attr("class", "area");
    $.each(constituencies, function (index, item) {
      var id = "#" + item.ons_code;
      var index = placeInArray(slices, item.signature_count);
      var colour_class = "c" + index + " coloured";
      d3.select(id)
        .attr("class", colour_class);
    });
  }

  // Find the place of constituency in the slices array
  function placeInArray(slices, count) {
    var slice = slices[1];
    for (i = 0; i < 8; i++) {
      if (count >= slices[i] && count < (slices[i] + slice)) {
        return i+1;
      }
      if (count >= slice * 8) {
        return 8;
      }
    }
  }

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

  // Show constituency info and party colours on select
  // (hover on desktop or click on mobile)
  function highlightConstituencyOnMap(_event, constituency) {
    var mpForConstituency = PetitionMap.mp_data[constituency.id],
      party_class = stripWhitespace(mpForConstituency.party);
    deselectPartyColours();
    d3.select("#" + constituency.id).classed(party_class, true);
    d3.select("#" + constituency.id).classed("selected_boundary", true);
  }

  // Remove classes from other constituencies on deselect
  function dehighlightConstituencyOnMap(_event, constituency) {
    // var party_class = stripWhitespace(PetitionMap.mp_data[constituency.id].party);
    // d3.select("#" + constituency.id).classed(party_class, false);
    // d3.select("#" + constituency.id).classed("selected_boundary", false);
  }

  // Removes all other party colour classes from constituencies
  function deselectPartyColours() {
    $.each(parties, function (index, item) {
      d3.selectAll(".area").classed(item, false);
      d3.selectAll(".coloured").classed(item, false);
    });
    d3.selectAll(".selected_boundary").classed("selected_boundary", false);
  }

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

  // Strips whitespace from a string
  function stripWhitespace(string) {
    return string.replace(/[^a-zA-Z]/g, '');
  }

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

  // Zoom and pan transition
  function interpolateZoomAndPan(translate, scale) {
    translate_saved = translate;
    scale_saved = scale;
    var self = this;
    return d3.transition().duration(350).tween("zoom", function () {
      var iTranslate = d3.interpolate(zoom.translate(), translate),
          iScale = d3.interpolate(zoom.scale(), scale);
      return function (t) {
        zoom
          .scale(iScale(t))
          .translate(iTranslate(t));
        applyZoomAndPan();
      };
    });
  }

  // Zoom in and out based on plus or minus button
  function zoomButton() {
    var clicked = d3.event.target,
      direction = 1,
      factor = 0.2,
      target_zoom = 1,
      center = [width / 2, height / 2],
      extent = zoom.scaleExtent(),
      translate = zoom.translate(),
      translate0 = [],
      l = [],
      view = {x: translate[0], y: translate[1], k: zoom.scale()};

    d3.event.preventDefault();
    direction = (this.id === 'zoom_in') ? 1 : -1;
    target_zoom = zoom.scale() * (1 + factor * direction);

    if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }

    translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
    view.k = target_zoom;
    l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];

    view.x += center[0] - l[0];
    view.y += center[1] - l[1];

    interpolateZoomAndPan([view.x, view.y], view.k);
  }

  function applyZoomAndPan() {
    svg.attr("transform", "translate(" + zoom.translate() + ")scale(" + zoom.scale() + ")");
  }

  function stopped() {
    if (d3.event.defaultPrevented) d3.event.stopPropagation();
  }

  // Reset scale and translation
  function resetMapState() {
    active.classed("active", false);
    active = d3.select(null);

    svg.transition()
      .call(zoom.translate([0, 0]).scale(1).event);

    translate_saved = [0, 0];
    scale_saved = 1;
  }

  // Pan around based on N, E, S, W buttons
  function panButton() {
    var clicked = d3.event.target,
      offsetX = 0,
      offsetY = 0,
      center = [width / 2, height / 2],
      translate = zoom.translate(),
      translate0 = [],
      l = [],
      view = {x: translate[0], y: translate[1], k: zoom.scale()};

    d3.event.preventDefault();
    if (this.id == 'pan_west') {
      offsetX -= 50;
    } else if (this.id === 'pan_north') {
      offsetY -= 50;
    } else if (this.id === 'pan_south') {
      offsetY += 50;
    } else if (this.id === 'pan_east') {
      offsetX += 50;
    }

    translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
    l = [translate0[0] * view.k + view.x + offsetX, translate0[1] * view.k + view.y + offsetY];

    view.x += center[0] - l[0];
    view.y += center[1] - l[1];

    interpolateZoomAndPan([view.x, view.y], view.k);
  }

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

  // React to constituency highlight events
  $(window).on('petitionmap:constituency-off', dehighlightConstituencyOnMap);
  $(window).on('petitionmap:constituency-on', highlightConstituencyOnMap);

  // when the window is resized, redraw the map
  $(window).on('resize', redraw);

  // Button to reset zoom
  $("#reset").on('click', resetMapState);

  // Buttons to zoom in and out
  d3.selectAll('.zoom').on('click', zoomButton);

  // Buttons to pan around
  d3.selectAll('.pan').on('click', panButton);

  PetitionMap.loadMapData = loadMapData;
  PetitionMap.resetMapState = resetMapState;

})(window.jQuery, window.d3, window.PetitionMap = window.PetitionMap || {});
<|endoftext|>