<commit_before>'use strict';

// Defines the main endpoint for receiving and parsing incoming
// text messages

var twilio = require('twilio');

var User = require('../models/users');
var secrets = require('../config/secrets');
var fs = require('fs');
var spawn = require('child_process').spawn;
var requests = require('requests');

var client = new twilio.RestClient(
  secrets.twilio.twilio_account_id,
  secrets.twilio.twilio_auth_token
);

// Called by twilio upon receiving text messages to user's
// API number
exports.receiveText = function(request, response) {
  // TODO: Add check to make sure this is actually from a text
  var textResp = new twilio.TwimlResponse();
  var sendingNumber = request.body.From;
  var query = {'local.phone': sendingNumber};

  User.findOne(query, function(err, user) {
    if(err) {
      textResp.message('Error occurred. Please try again.');
      response.send(textResp.toString());
    } else {
      if(!user) {
        console.log("Unknown user detected");
        textResp.message("Unknown number: " + sendingNumber);
        response.send(textResp.toString());
      } else {
        var parsedText = parseTextMessage(request.body.Body);
        var command = parsedText[0];
        if (isHelpRequest(command)) {
          var commandsList = listUserCommands(user);
          textResp.message('You have configured the following commands:\n' + commandsList);
          response.send(textResp.toString());
        } else {
          if(isTranslateRequest(command)) {
            // Handle translation
            // TODO: Get this working lol
            console.log("Translating given string");

            var language = '';
            if (parsedText.length >= 3) {
              language = parsedText[2];
            }
            var text = parsedText[1];
           // var translation = translate(language, parsedText[1]);

            var transStr="mt-";
            transStr+=langMatch("english")+"-";
            transStr+=langMatch(language);

            requests.post(
              'https://gateway.watsonplatform.net/machine-translation-beta/api',
              {"sid":transStr,"txt":text},
              function (error, response, body) {
              if (!error && response.statusCode == 200) {
                textResp.message(translation);
                response.send(textResp.toString());
              } else {
                textResp.message('Translation failed.');
                response.send(textResp.toString());
              }
            });
          } else {

            // Otherwise must be a user command
              var userScript = findScript(user, command);

            if (!userScript) {
              console.log('Invalid command name');
              textResp.message('Invalid command');
              response.send(textResp.toString());
            } else {

              var args = parsedText;
              args.shift() // Shift array to get only arguments

              console.log('Executing user command: ' + userScript.name);
              fs.writeFile('../tmp/' + userScript.name + '.py', userScript.code, function(err) {
                console.log(userScript.code);
                if (err){
                  textResp.message('Error occurred running the command.');
                  response.send(textResp.toString());
                } else {
                  args.unshift('../tmp/' + userScript.name + '.py');
                  console.log('Passing in the following args: ' + args.join(' '));
                  var process = spawn('python', args);
                  process.stdout.setEncoding('utf8');

                  // Set up callback to catch all script output
                  var output = '';
                  process.stdout.on('data', function (data) {
                    output += data.toString();
                  });

                  // Send text response once script is done
                  process.on('close', function (code) {
                    if (output) {
                      forwardToOtherRecipients(user.name, userScript, output);
                      textResp.message('Result: ' + output);
                      response.send(textResp.toString());
                    } else {
                      textResp.message('Script done, exited with code ' + code);
                      response.send(textResp.toString());
                    }
                  });
                }
              });
            }
          }
        }
      }
    }
  });
}

// Parses the given text message, extracting the command name
// and any arguments for the command
// TODO: Configure this to take in arguments w/ spaces
// Maybe use quotes as delimiters?
function parseTextMessage(textContent) {
  console.log("Parsing text message");
  if (textContent === undefined) {
    console.log("Textcontent is undefined!");
  }
  var contents = textContent.split(' ');
  console.log(contents);
  return contents;
}

function findScript(user, command) {
  for(var i = 0; i < user.programs.length; i++){
    var script = user.programs[i];
    if (script.name === command) {
      return script;
    }
  }
  return null;
}

function isHelpRequest(command) {
  console.log('Checking if command ' + command + ' is a help request');
  return command === '.help';
}

function isTranslateRequest(command) {
  return command === '.translate'
}

function sendHelpMessage(textResp, response, user) {
  var commandsList = listUserCommands(user);
  textResp.message('You have configured the following commands: ' + commandsList);
  response.send(textResp.toString());
}

function listUserCommands(user) {
  var result = [];
  for (var i = 0; i < user.programs.length; i++) {
    var script = user.programs[i];
    var entry = script.name + ': ' + script.description;
    result.push(entry);
  }
  return result.join('\n');
}

function sendUnknownNumberMsg(sendingNumber, textResp, response) {
  textResp.message("Unknown number: " + sendingNumber);
  response.send(textResp.toString());
}

function forwardToOtherRecipients(creatorName, script, output) {
  for(var i = 0; i < script.recipients.length; i++){
    var recipientNumber = script.recipients[i];
    var message = 'The following was generated and sent to you by ' + creatorName + '\n\n';
    message += output;
    console.log('Sending to the following number: ' + recipientNumber);
    console.log('Sending the message: ' + message);
    client.sendSms({
      to: recipientNumber,
      from: secrets.twilio.twilio_phone_number,
      body: message
    }, function(err, message) {
      if (err) {
        console.log("An error occurred sending the message to other recipients.");
      } else {
        console.log("Message successfully forwarded to recipient");
      }
    });
  }
  return;
}

// Translation Functions

var request = require('request');

function translate(lang, text) {
  var transStr="mt-";
  transStr+=langMatch("english")+"-";
  transStr+=langMatch(lang);

  request.post(
    'https://gateway.watsonplatform.net/machine-translation-beta/api',
    {"sid":transStr,"txt":text },
    function (error, response, body) {
      if (!error && response.statusCode == 200) {
        return body;
      } else {
        return '';
      }
    });
}

function langMatch(lang) {
  switch(lang) {
  case "english":
    return "enus";
  case "Portuguese":
    return "ptbr";
  case "Spanish":
    return "eses";
  case "french":
    return "frfr";
  default:
    return "";
  }
}
<commit_msg>Bug fix<commit_after>'use strict';

// Defines the main endpoint for receiving and parsing incoming
// text messages

var twilio = require('twilio');

var User = require('../models/users');
var secrets = require('../config/secrets');
var fs = require('fs');
var spawn = require('child_process').spawn;
var requests = require('request');

var client = new twilio.RestClient(
  secrets.twilio.twilio_account_id,
  secrets.twilio.twilio_auth_token
);

// Called by twilio upon receiving text messages to user's
// API number
exports.receiveText = function(request, response) {
  // TODO: Add check to make sure this is actually from a text
  var textResp = new twilio.TwimlResponse();
  var sendingNumber = request.body.From;
  var query = {'local.phone': sendingNumber};

  User.findOne(query, function(err, user) {
    if(err) {
      textResp.message('Error occurred. Please try again.');
      response.send(textResp.toString());
    } else {
      if(!user) {
        console.log("Unknown user detected");
        textResp.message("Unknown number: " + sendingNumber);
        response.send(textResp.toString());
      } else {
        var parsedText = parseTextMessage(request.body.Body);
        var command = parsedText[0];
        if (isHelpRequest(command)) {
          var commandsList = listUserCommands(user);
          textResp.message('You have configured the following commands:\n' + commandsList);
          response.send(textResp.toString());
        } else {
          if(isTranslateRequest(command)) {
            // Handle translation
            // TODO: Get this working lol
            console.log("Translating given string");

            var language = '';
            if (parsedText.length >= 3) {
              language = parsedText[2];
            }
            var text = parsedText[1];
           // var translation = translate(language, parsedText[1]);

            var transStr="mt-";
            transStr+=langMatch("english")+"-";
            transStr+=langMatch(language);

            requests.post(
              'https://gateway.watsonplatform.net/machine-translation-beta/api',
              {"sid":transStr,"txt":text},
              function (error, response, body) {
              if (!error && response.statusCode == 200) {
                textResp.message(translation);
                response.send(textResp.toString());
              } else {
                textResp.message('Translation failed.');
                response.send(textResp.toString());
              }
            });
          } else {

            // Otherwise must be a user command
              var userScript = findScript(user, command);

            if (!userScript) {
              console.log('Invalid command name');
              textResp.message('Invalid command');
              response.send(textResp.toString());
            } else {

              var args = parsedText;
              args.shift() // Shift array to get only arguments

              console.log('Executing user command: ' + userScript.name);
              fs.writeFile('../tmp/' + userScript.name + '.py', userScript.code, function(err) {
                console.log(userScript.code);
                if (err){
                  textResp.message('Error occurred running the command.');
                  response.send(textResp.toString());
                } else {
                  args.unshift('../tmp/' + userScript.name + '.py');
                  console.log('Passing in the following args: ' + args.join(' '));
                  var process = spawn('python', args);
                  process.stdout.setEncoding('utf8');

                  // Set up callback to catch all script output
                  var output = '';
                  process.stdout.on('data', function (data) {
                    output += data.toString();
                  });

                  // Send text response once script is done
                  process.on('close', function (code) {
                    if (output) {
                      forwardToOtherRecipients(user.name, userScript, output);
                      textResp.message('Result: ' + output);
                      response.send(textResp.toString());
                    } else {
                      textResp.message('Script done, exited with code ' + code);
                      response.send(textResp.toString());
                    }
                  });
                }
              });
            }
          }
        }
      }
    }
  });
}

// Parses the given text message, extracting the command name
// and any arguments for the command
// TODO: Configure this to take in arguments w/ spaces
// Maybe use quotes as delimiters?
function parseTextMessage(textContent) {
  console.log("Parsing text message");
  if (textContent === undefined) {
    console.log("Textcontent is undefined!");
  }
  var contents = textContent.split(' ');
  console.log(contents);
  return contents;
}

function findScript(user, command) {
  for(var i = 0; i < user.programs.length; i++){
    var script = user.programs[i];
    if (script.name === command) {
      return script;
    }
  }
  return null;
}

function isHelpRequest(command) {
  console.log('Checking if command ' + command + ' is a help request');
  return command === '.help';
}

function isTranslateRequest(command) {
  return command === '.translate'
}

function sendHelpMessage(textResp, response, user) {
  var commandsList = listUserCommands(user);
  textResp.message('You have configured the following commands: ' + commandsList);
  response.send(textResp.toString());
}

function listUserCommands(user) {
  var result = [];
  for (var i = 0; i < user.programs.length; i++) {
    var script = user.programs[i];
    var entry = script.name + ': ' + script.description;
    result.push(entry);
  }
  return result.join('\n');
}

function sendUnknownNumberMsg(sendingNumber, textResp, response) {
  textResp.message("Unknown number: " + sendingNumber);
  response.send(textResp.toString());
}

function forwardToOtherRecipients(creatorName, script, output) {
  for(var i = 0; i < script.recipients.length; i++){
    var recipientNumber = script.recipients[i];
    var message = 'The following was generated and sent to you by ' + creatorName + '\n\n';
    message += output;
    console.log('Sending to the following number: ' + recipientNumber);
    console.log('Sending the message: ' + message);
    client.sendSms({
      to: recipientNumber,
      from: secrets.twilio.twilio_phone_number,
      body: message
    }, function(err, message) {
      if (err) {
        console.log("An error occurred sending the message to other recipients.");
      } else {
        console.log("Message successfully forwarded to recipient");
      }
    });
  }
  return;
}

// Translation Functions

var request = require('request');

function translate(lang, text) {
  var transStr="mt-";
  transStr+=langMatch("english")+"-";
  transStr+=langMatch(lang);

  request.post(
    'https://gateway.watsonplatform.net/machine-translation-beta/api',
    {"sid":transStr,"txt":text },
    function (error, response, body) {
      if (!error && response.statusCode == 200) {
        return body;
      } else {
        return '';
      }
    });
}

function langMatch(lang) {
  switch(lang) {
  case "english":
    return "enus";
  case "Portuguese":
    return "ptbr";
  case "Spanish":
    return "eses";
  case "french":
    return "frfr";
  default:
    return "";
  }
}
<|endoftext|>


<commit_before>/**
 * Views for simpleshelf
 */

// set underscore to use mustache-style interpolation
_.templateSettings = {
    interpolate : /\{\{(.+?)\}\}/g
};

/**
 * Show basic info about entire library; based on Library [collection]
 */
window.LibraryInfoView = Backbone.View.extend({
    template: _.template('

book count: {{ bookCount }}

'),
    tagName: "div",
    className: "info-view",

    initialize: function(){
        _.bindAll(this, "render");
        this.collection.bind('reset', this.render);
    },

    render: function() {
        $(this.el).html(this.template({bookCount: this.collection.length}));
        return this;
    }
});

/**
 * Navigation for entire app
 */
window.NavigationView = Backbone.View.extend({
    template: _.template(
        '

'
    ),
    tagName: "div",
    className: "navigation-view",
    events: {
      'click .newbook': 'addBook',
      'click .index': 'goIndex'
    },

    initialize: function(){
        _.bindAll(this, "render", "addBook", "goIndex");
    },

    render: function(){
        // get template
        var $parent = $(this.template());

        // add links
        var links = {
            "newbook": "New book",
            "index": "Index"
            }

        var linkTemplate = _.template('
{{name}}
');
        _.each(links, function(value, key, list){
            $parent.append(linkTemplate({'key': key, 'name': value}));
        });

        $(this.el).html($parent);
        return this;
    },

    goIndex: function(event){
        event.preventDefault();
        // return home
        this.trigger('navigation:index');
    },

    addBook: function(event){
        event.preventDefault();
        // show new book form
        this.trigger('navigation:newbook');
    }
});

/**
 * Container for simple list of titles
 */
window.SpineListView = Backbone.View.extend({
    template: _.template('
All books

'),
    tagName: 'div',
    className: 'spine-list-view',

    initialize: function(){
        _.bindAll(this, 'render', 'addAll', 'addOne', 'updateTag', 'bookSelected');
        this.collection.bind('add', this.addOne);
        this.collection.bind('reset', this.render);
    },

    render: function(){
        console.log('rendering window.SpineListView');
        $(this.el).html(this.template());
        this.addAll();
        return this;
    },

    addAll: function() {
        console.log('SpineListView.addAll: this.collection.length==', this.collection.length)
        this.collection.each(this.addOne);
    },

    addOne: function(model) {
        view = new SpineView({model: model});
        view.render();
        $('ul', this.el).append(view.el);
        model.bind('remove', view.remove);
        view.bind('spineview:selected', this.bookSelected)
    },

    updateTag: function(msgArgs){
        console.log('SpineListView:updateTag', msgArgs);
        this.collection.filterByTag(msgArgs);
    },

    bookSelected: function(msgArgs){
        console.log('SpineListView:bookSelected', msgArgs);
        this.trigger('spinelistview:bookSelected', msgArgs.bookId);
    }
});

/**
 * Simple representation of book
 */
window.SpineView = Backbone.View.extend({
    className: 'spine-view',
    tagName: 'li',
    template: _.template('{{title}}'),
    events: {
      'click': 'bookSelected'
    },

    initialize: function(properties){
        _.bindAll(this, 'render', 'remove', 'bookSelected');
        this.model.bind('change', this.render);
        this.model.bind('destroy', this.remove);
    },

    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        return this;
    },

    remove: function() {
        $(this.el).remove();
    },

    bookSelected: function(evt){
        console.log('SpineView: selected book', this.options.model)
        evt.preventDefault();
        // signal to switch to full view for this book
        this.model.select();
        this.trigger('spineview:selected', {'bookId': this.model.get('id')});
    }
});

/**
 * Show individual tag
 */
window.TagView = Backbone.View.extend({
    className: 'tag',
    tagName: 'li',
    template: _.template('{{ tag }}, {{ count }}'),

    events: {
        'click': 'tagSelected'
    },

    initialize: function(properties){
        _.bindAll(this, 'render', 'remove', 'highlightIfMatch');
        this.model.bind('change', this.render);
        this.model.bind('destroy', this.remove);
        this.model.bind('tag:highlight', this.highlightIfMatch);
    },

    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        if (this.model.get('selected'))
            $(this.el).addClass('selected');
        return this;
    },

    remove: function() {
        $(this.el).remove();
    },

    tagSelected: function(){
        console.log('TagView: click evt for tag==' + this.model.get('tag'));
        this.model.select();
        this.trigger('tagview:selected', {'tag': this.model.get('tag')});
    },

    highlightIfMatch: function(tag){
        $(this.el).toggleClass('selected', (this.model.get('tag') == tag));
    }
});

/**
 * Show tag cloud
 */
window.TagCloudView = Backbone.View.extend({
    className: 'tagcloud',
    tagName: 'div',
    template: _.template('
Tags

'),
    events: {
        'click #tagcloudviewheader': 'resetTags'
    },

    initialize: function(properties) {
        _.bindAll(this, 'render', 'addAll', 'addOne', 'tagSelected', 'resetTags');
        this.collection.bind('add', this.addOne);
        this.collection.bind('reset', this.render);
    },

    render: function() {
        console.log('rendering window.TagCloudView');
        $(this.el).html(this.template());
        $('.tagheader', this.el).attr('title', 'Click to show all tags');
        this.addAll();
        $('.' + this.className + ' .tagheader').on('click', this.resetTags);
        return this;
    },

    addAll: function() {
        console.log('TagCloudView.addAll: this.collection.length==', this.collection.length)
        this.collection.each(this.addOne);
    },

    addOne: function(model) {
        var view = new TagView({model: model});
        view.render();
        $('ul', this.el).append(view.el);
        model.bind('remove', view.remove);
        view.bind('tagview:selected', this.tagSelected)
    },

    resetTags: function(){
        console.log('TagCloudView.resetTags');
        this.collection.selectTag(null);
        this.trigger('tagcloud:tagselected', {'tag': null});
    },

    tagSelected: function(tag){
        console.log('TagCloudView.tagSelected event', tag);
        this.trigger('tagcloud:tagselected', tag);
    }
});

/**
 * Show all information for single book
 */
window.BookView = Backbone.View.extend({
    className: 'book',
    tagName: 'div',
    template: _.template(
        '
Book
' +
        '
'
    ),
    // templates for the different values in a book
    // TODO: custom templates for status, notes
    simpleTemplates: {
        'simpleField': _.template(
            '
{{title}}	' +
            '{{value}}
'
        ),
        'tags': _.template(
            '
{{title}}	' +
            '{{value}}
'
        )
    },

    initialize: function(options){
        _.bindAll(this, 'render');
    },

    render: function(){
        console.log('BookView: rendering');
        $(this.el).html(this.template());

        // build lines programmatically
        var dataKeys = ['title', 'author', 'isbn', 'openlibrary', 'publisher', 'tags'];
        var htmlSnippets = {'tags': this.simpleTemplates.tags};
        var bookinfoEl = $('.bookinfo', this.el);
        var table = $('
');
        var me = this;

        // for each data element (in specified order), render as TR
        _.each(dataKeys, function(element, index, list){
            if (htmlSnippets.hasOwnProperty(element)){
                // render specific field
                table.append(htmlSnippets[element]({title: element, value: me.model.get(element)}));
            } else {
                // render element in generic way
                table.append(me._addSimpleField(element, me.model.get(element)));
            }
        });

        bookinfoEl.append(table);
        return this;
    },

    _addSimpleField: function(fieldTitle, fieldValue){
        return this.simpleTemplates.simpleField({title: fieldTitle, value: fieldValue});
    }
});

/**
 * Add/edit book
 */
window.EditBookView = Backbone.View.extend({
    className: 'editBook',
    tagName: 'div',
    template: _.template(
        '
Book
' +
        '<form class="bookinfo"/>'
    ),

    dataKeys: ['title', 'author', 'isbn', 'openlibrary', 'publisher', 'tags'],
    simpleTemplates: {
        'simpleField': _.template(
            '
{{title}}	' +
            '<input type="text" name="{{key}}">
'
        ),
        'tags': _.template(
            '
{{title}}	' +
            '<input type="text" name="{{key}}">
'
        )
    },

    events: {
        'click .submit': 'save'
    },

    initialize: function(options){
        _.bindAll(this, 'render');
    },

    render: function(){
        console.log('EditBookView: rendering');
        $(this.el).html(this.template());

        // build lines programmatically
        var normalInputs = ['title', 'author', 'isbn', 'openlibrary', 'publisher'];
        var htmlSnippets = {'tags': this.simpleTemplates.tags};
        var bookinfoEl = $('.bookinfo', this.el);
        var table = $('
');
        var me = this;

        // for each data element (in specified order), render as TR
        _.each(this.dataKeys, function(element, index, list){
            if (_.indexOf(normalInputs, element) != -1) {
                // render element in generic way
                table.append(me._addSimpleField(element, element));
            } else if (htmlSnippets.hasOwnProperty(element)){
                table.append(me.simpleTemplates[element]({title: element, key: element}));
            }
        });

        bookinfoEl.append(table).append('<input type="submit" value="Submit" class="submit">');

        return this;
    },

    save: function(event){
        console.log("EditBookView:save", this.model.isNew());
        event.preventDefault();
        var newAttributes = {};
        var me = this;
        if (this.model.isNew()){
            // save everything
            // newAttributes = $('form', this.el).serializeArray();
            _.each($('form', this.el).serializeArray(), function(element, index, list){
                if (_.indexOf(me.dataKeys, element.name) > -1){
                    newAttributes[element.name] = element.value;
                }
            });
        }

        // TODO: handle validation
        this.model.save(newAttributes);
    },

    _addSimpleField: function(fieldKey, fieldTitle){
        return this.simpleTemplates.simpleField({title: fieldTitle, key: fieldKey});
    }
});
<commit_msg>Bind model.change event to router navigation<commit_after>/**
 * Views for simpleshelf
 */

// set underscore to use mustache-style interpolation
_.templateSettings = {
    interpolate : /\{\{(.+?)\}\}/g
};

/**
 * Show basic info about entire library; based on Library [collection]
 */
window.LibraryInfoView = Backbone.View.extend({
    template: _.template('

book count: {{ bookCount }}

'),
    tagName: "div",
    className: "info-view",

    initialize: function(){
        _.bindAll(this, "render");
        this.collection.bind('reset', this.render);
    },

    render: function() {
        $(this.el).html(this.template({bookCount: this.collection.length}));
        return this;
    }
});

/**
 * Navigation for entire app
 */
window.NavigationView = Backbone.View.extend({
    template: _.template(
        '

'
    ),
    tagName: "div",
    className: "navigation-view",
    events: {
      'click .newbook': 'addBook',
      'click .index': 'goIndex'
    },

    initialize: function(){
        _.bindAll(this, "render", "addBook", "goIndex");
    },

    render: function(){
        // get template
        var $parent = $(this.template());

        // add links
        var links = {
            "newbook": "New book",
            "index": "Index"
            }

        var linkTemplate = _.template('
{{name}}
');
        _.each(links, function(value, key, list){
            $parent.append(linkTemplate({'key': key, 'name': value}));
        });

        $(this.el).html($parent);
        return this;
    },

    goIndex: function(event){
        event.preventDefault();
        // return home
        this.trigger('navigation:index');
    },

    addBook: function(event){
        event.preventDefault();
        // show new book form
        this.trigger('navigation:newbook');
    }
});

/**
 * Container for simple list of titles
 */
window.SpineListView = Backbone.View.extend({
    template: _.template('
All books

'),
    tagName: 'div',
    className: 'spine-list-view',

    initialize: function(){
        _.bindAll(this, 'render', 'addAll', 'addOne', 'updateTag', 'bookSelected');
        this.collection.bind('add', this.addOne);
        this.collection.bind('reset', this.render);
    },

    render: function(){
        console.log('rendering window.SpineListView');
        $(this.el).html(this.template());
        this.addAll();
        return this;
    },

    addAll: function() {
        console.log('SpineListView.addAll: this.collection.length==', this.collection.length)
        this.collection.each(this.addOne);
    },

    addOne: function(model) {
        view = new SpineView({model: model});
        view.render();
        $('ul', this.el).append(view.el);
        model.bind('remove', view.remove);
        view.bind('spineview:selected', this.bookSelected)
    },

    updateTag: function(msgArgs){
        console.log('SpineListView:updateTag', msgArgs);
        this.collection.filterByTag(msgArgs);
    },

    bookSelected: function(msgArgs){
        console.log('SpineListView:bookSelected', msgArgs);
        this.trigger('spinelistview:bookSelected', msgArgs.bookId);
    }
});

/**
 * Simple representation of book
 */
window.SpineView = Backbone.View.extend({
    className: 'spine-view',
    tagName: 'li',
    template: _.template('{{title}}'),
    events: {
      'click': 'bookSelected'
    },

    initialize: function(properties){
        _.bindAll(this, 'render', 'remove', 'bookSelected');
        this.model.bind('change', this.render);
        this.model.bind('destroy', this.remove);
    },

    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        return this;
    },

    remove: function() {
        $(this.el).remove();
    },

    bookSelected: function(evt){
        console.log('SpineView: selected book', this.options.model)
        evt.preventDefault();
        // signal to switch to full view for this book
        this.model.select();
        this.trigger('spineview:selected', {'bookId': this.model.get('id')});
    }
});

/**
 * Show individual tag
 */
window.TagView = Backbone.View.extend({
    className: 'tag',
    tagName: 'li',
    template: _.template('{{ tag }}, {{ count }}'),

    events: {
        'click': 'tagSelected'
    },

    initialize: function(properties){
        _.bindAll(this, 'render', 'remove', 'highlightIfMatch');
        this.model.bind('change', this.render);
        this.model.bind('destroy', this.remove);
        this.model.bind('tag:highlight', this.highlightIfMatch);
    },

    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        if (this.model.get('selected'))
            $(this.el).addClass('selected');
        return this;
    },

    remove: function() {
        $(this.el).remove();
    },

    tagSelected: function(){
        console.log('TagView: click evt for tag==' + this.model.get('tag'));
        this.model.select();
        this.trigger('tagview:selected', {'tag': this.model.get('tag')});
    },

    highlightIfMatch: function(tag){
        $(this.el).toggleClass('selected', (this.model.get('tag') == tag));
    }
});

/**
 * Show tag cloud
 */
window.TagCloudView = Backbone.View.extend({
    className: 'tagcloud',
    tagName: 'div',
    template: _.template('
Tags

'),
    events: {
        'click #tagcloudviewheader': 'resetTags'
    },

    initialize: function(properties) {
        _.bindAll(this, 'render', 'addAll', 'addOne', 'tagSelected', 'resetTags');
        this.collection.bind('add', this.addOne);
        this.collection.bind('reset', this.render);
    },

    render: function() {
        console.log('rendering window.TagCloudView');
        $(this.el).html(this.template());
        $('.tagheader', this.el).attr('title', 'Click to show all tags');
        this.addAll();
        $('.' + this.className + ' .tagheader').on('click', this.resetTags);
        return this;
    },

    addAll: function() {
        console.log('TagCloudView.addAll: this.collection.length==', this.collection.length)
        this.collection.each(this.addOne);
    },

    addOne: function(model) {
        var view = new TagView({model: model});
        view.render();
        $('ul', this.el).append(view.el);
        model.bind('remove', view.remove);
        view.bind('tagview:selected', this.tagSelected)
    },

    resetTags: function(){
        console.log('TagCloudView.resetTags');
        this.collection.selectTag(null);
        this.trigger('tagcloud:tagselected', {'tag': null});
    },

    tagSelected: function(tag){
        console.log('TagCloudView.tagSelected event', tag);
        this.trigger('tagcloud:tagselected', tag);
    }
});

/**
 * Show all information for single book
 */
window.BookView = Backbone.View.extend({
    className: 'book',
    tagName: 'div',
    template: _.template(
        '
Book
' +
        '
'
    ),
    // templates for the different values in a book
    // TODO: custom templates for status, notes
    simpleTemplates: {
        'simpleField': _.template(
            '
{{title}}	' +
            '{{value}}
'
        ),
        'tags': _.template(
            '
{{title}}	' +
            '{{value}}
'
        )
    },

    initialize: function(options){
        _.bindAll(this, 'render');
    },

    render: function(){
        console.log('BookView: rendering');
        $(this.el).html(this.template());

        // build lines programmatically
        var dataKeys = ['title', 'author', 'isbn', 'openlibrary', 'publisher', 'tags'];
        var htmlSnippets = {'tags': this.simpleTemplates.tags};
        var bookinfoEl = $('.bookinfo', this.el);
        var table = $('
');
        var me = this;

        // for each data element (in specified order), render as TR
        _.each(dataKeys, function(element, index, list){
            if (htmlSnippets.hasOwnProperty(element)){
                // render specific field
                table.append(htmlSnippets[element]({title: element, value: me.model.get(element)}));
            } else {
                // render element in generic way
                table.append(me._addSimpleField(element, me.model.get(element)));
            }
        });

        bookinfoEl.append(table);
        return this;
    },

    _addSimpleField: function(fieldTitle, fieldValue){
        return this.simpleTemplates.simpleField({title: fieldTitle, value: fieldValue});
    }
});

/**
 * Add/edit book
 */
window.EditBookView = Backbone.View.extend({
    className: 'editBook',
    tagName: 'div',
    template: _.template(
        '
Book
' +
        '<form class="bookinfo"/>'
    ),

    dataKeys: ['title', 'author', 'isbn', 'openlibrary', 'publisher', 'tags'],
    simpleTemplates: {
        'simpleField': _.template(
            '
{{title}}	' +
            '<input type="text" name="{{key}}">
'
        ),
        'tags': _.template(
            '
{{title}}	' +
            '<input type="text" name="{{key}}">
'
        )
    },

    events: {
        'click .submit': 'save'
    },

    initialize: function(options){
        _.bindAll(this, 'render', 'dataChanged');
        this.model.bind('change', this.dataChanged);
    },

    render: function(){
        console.log('EditBookView: rendering');
        $(this.el).html(this.template());

        // build lines programmatically
        var normalInputs = ['title', 'author', 'isbn', 'openlibrary', 'publisher'];
        var htmlSnippets = {'tags': this.simpleTemplates.tags};
        var bookinfoEl = $('.bookinfo', this.el);
        var table = $('
');
        var me = this;

        // for each data element (in specified order), render as TR
        _.each(this.dataKeys, function(element, index, list){
            if (_.indexOf(normalInputs, element) != -1) {
                // render element in generic way
                table.append(me._addSimpleField(element, element));
            } else if (htmlSnippets.hasOwnProperty(element)){
                table.append(me.simpleTemplates[element]({title: element, key: element}));
            }
        });

        bookinfoEl.append(table).append('<input type="submit" value="Submit" class="submit">');

        return this;
    },

    save: function(event){
        console.log("EditBookView:save", this.model.isNew());
        event.preventDefault();
        var newAttributes = {};
        var me = this;
        if (this.model.isNew()){
            // save everything
            // newAttributes = $('form', this.el).serializeArray();
            _.each($('form', this.el).serializeArray(), function(element, index, list){
                if (_.indexOf(me.dataKeys, element.name) > -1){
                    newAttributes[element.name] = element.value;
                }
            });
        }

        // TODO: handle validation
        this.model.save(newAttributes);
    },

    dataChanged: function(event){
        console.log("model's data has changed");
        // goto bookview
        if (this.model.isNew()){
            this.trigger('editbookview:bookChanged');
        } else {
            this.trigger('editbookview:bookChanged', this.model.id);
            window.app.books(this.model.id);
        }
    },

    _addSimpleField: function(fieldKey, fieldTitle){
        return this.simpleTemplates.simpleField({title: fieldTitle, key: fieldKey});
    }
});
<|endoftext|>


<commit_before>/**
 * At least we all get to write some Javascript, eh?
 *
 * JD woz 'ere 2k15
 *
 * Based entirely off http://www.webcodo.net/comments-system-using-php-ajax/
 */
var commentsThingy = function EmptyCommentsThingy() {
	console.error("Failed to build the native Comments function.");
};

var deleteComment = function emptyDeleteComment() {
	console.error("Failed to build the native Delete Comments function.");
};

(function () {
	var commentRoot, containerDiv, containerId, newCommentCount, writeBox;
	newCommentCount = 250;
	writeBox = [
		'
',
		'
',
		'Write a comment ...',
		'
',
		'
',
		'

',
		'<textarea id="newCommentBody"></textarea>',
		'
You have ', newCommentCount, ' characters left.
',
		'
Post comment
',
		'
Cancel
',
		'
',
		'
'
	].join('');

	function createComment(data) {
		containerDiv.innerHTML += [
			'
',
			'',
			'
', '
',
			'
',
			data.author.name,
			'
',
			'', data.created, '',
			((data.permissions.delete) ? 'Delete  ' : ''),
			'
',
			'
', data.comment, '
',
			'
',
			'
'
		].join('');
	}

	function initWriteBox() {
		containerDiv.innerHTML += writeBox;

		setTimeout(function () {
			document.querySelector('#commentWriteBox .new-com-bt').onclick = function OnNewCommentButtonClick() {
				document.querySelector('#commentWriteBox .new-com-bt').style.display = 'none';
				document.querySelector('#commentWriteBox .new-com-cnt').style.display = 'block';
				document.getElementById('newCommentBody').focus();
			};

			document.getElementById('newCommentBody').onkeyup = function OnNewCommentBodyKeyUp() {
				var commentLength, opacity;
				commentLength = document.getElementById('newCommentBody').value.length;

				if (commentLength > 0) {
					if (commentLength > newCommentCount) {
						opacity = 0.6;
						document.getElementById('commentError').innerText = 'Please enter a shorter comment.';
						document.getElementById('newCommentCount').innerText = "-" + (commentLength - newCommentCount);
					}
					else {
						opacity = 1;
						document.getElementById('commentError').innerText = '';
						document.getElementById('newCommentCount').innerText = '' + (newCommentCount - commentLength);
					}
				}
				else {
					opacity = 0.6;
					document.getElementById('newCommentCount').innerText = '' + newCommentCount;
				}

				document.querySelector('#commentWriteBox .bt-add-com').style.opacity = opacity;
			};

			document.querySelector('#commentWriteBox .bt-cancel-com').onclick = function OnNewCommentCancelClick() {
				document.getElementById('newCommentBody').value = '';
				document.getElementById('newCommentCount').innerText = '' + newCommentCount;
				document.getElementById('commentError').innerText = '';
				document.querySelector('#' + containerId + ' .new-com-cnt').style.display = 'none';
				document.querySelector('#' + containerId + ' .new-com-bt').style.display = 'block';
			};

			document.querySelector('#commentWriteBox .bt-add-com').onclick = function OnNewCommentSubmit() {
				var commentBody, writeBoxElem;

				commentBody = document.getElementById('newCommentBody').value;
				if ((commentBody.length == 0) || (commentBody.length > newCommentCount)) {
					return;
				}

				writeBoxElem = document.getElementById('commentWriteBox');
				writeBoxElem.innerHTML = '
Sending...
';
				API.POST(
					"/comment", {root: commentRoot, comment: commentBody},
					function onCommentCreate(data) {
						writeBoxElem.parentNode.removeChild(writeBoxElem);
						createComment(data.body);
						initWriteBox();
					},
					function onCommentCreateError(data) {
						console.error("onCommentCreateError", data);
					}
				);
			};
		}, 200);
	}

	deleteComment = function deleteComment(id) {
		API.DELETE("/comment/" + id, {},
			function Success() {
				var element = document.getElementById("comment_" + id);
				element.parentNode.removeChild(element);

			},
			function Error() {
				console.error("Failed to delete comment with ID " + id);
			}
		);
	};

	commentsThingy = function CommentsThingy(commentBodyId, root) {
		containerId = commentBodyId;
		containerDiv = document.getElementById(commentBodyId);
		commentRoot = root;
		API.GET(
			"/comment/thread", {root: root},
			function onCommentGet(data) {
				if (data.body && data.body.length) {
					for (var i = 0; i < data.body.length; i++) {
						createComment(data.body[i]);
					}
				}
				initWriteBox();
			},
			function onCommentGetError(data) {
				console.error("onCommentGetError", data);
			}
		);
	};
})();<commit_msg>Add confirm for deleting comments.<commit_after>/**
 * At least we all get to write some Javascript, eh?
 *
 * JD woz 'ere 2k15
 *
 * Based entirely off http://www.webcodo.net/comments-system-using-php-ajax/
 */
var commentsThingy = function EmptyCommentsThingy() {
	console.error("Failed to build the native Comments function.");
};

var deleteComment = function emptyDeleteComment() {
	console.error("Failed to build the native Delete Comments function.");
};

(function () {
	var commentRoot, containerDiv, containerId, newCommentCount, writeBox;
	newCommentCount = 250;
	writeBox = [
		'
',
		'
',
		'Write a comment ...',
		'
',
		'
',
		'

',
		'<textarea id="newCommentBody"></textarea>',
		'
You have ', newCommentCount, ' characters left.
',
		'
Post comment
',
		'
Cancel
',
		'
',
		'
'
	].join('');

	function createComment(data) {
		containerDiv.innerHTML += [
			'
',
			'',
			'
', '
',
			'
',
			data.author.name,
			'
',
			'', data.created, '',
			((data.permissions.delete) ? 'Delete  ' : ''),
			'
',
			'
', data.comment, '
',
			'
',
			'
'
		].join('');
	}

	function initWriteBox() {
		containerDiv.innerHTML += writeBox;

		setTimeout(function () {
			document.querySelector('#commentWriteBox .new-com-bt').onclick = function OnNewCommentButtonClick() {
				document.querySelector('#commentWriteBox .new-com-bt').style.display = 'none';
				document.querySelector('#commentWriteBox .new-com-cnt').style.display = 'block';
				document.getElementById('newCommentBody').focus();
			};

			document.getElementById('newCommentBody').onkeyup = function OnNewCommentBodyKeyUp() {
				var commentLength, opacity;
				commentLength = document.getElementById('newCommentBody').value.length;

				if (commentLength > 0) {
					if (commentLength > newCommentCount) {
						opacity = 0.6;
						document.getElementById('commentError').innerText = 'Please enter a shorter comment.';
						document.getElementById('newCommentCount').innerText = "-" + (commentLength - newCommentCount);
					}
					else {
						opacity = 1;
						document.getElementById('commentError').innerText = '';
						document.getElementById('newCommentCount').innerText = '' + (newCommentCount - commentLength);
					}
				}
				else {
					opacity = 0.6;
					document.getElementById('newCommentCount').innerText = '' + newCommentCount;
				}

				document.querySelector('#commentWriteBox .bt-add-com').style.opacity = opacity;
			};

			document.querySelector('#commentWriteBox .bt-cancel-com').onclick = function OnNewCommentCancelClick() {
				document.getElementById('newCommentBody').value = '';
				document.getElementById('newCommentCount').innerText = '' + newCommentCount;
				document.getElementById('commentError').innerText = '';
				document.querySelector('#' + containerId + ' .new-com-cnt').style.display = 'none';
				document.querySelector('#' + containerId + ' .new-com-bt').style.display = 'block';
			};

			document.querySelector('#commentWriteBox .bt-add-com').onclick = function OnNewCommentSubmit() {
				var commentBody, writeBoxElem;

				commentBody = document.getElementById('newCommentBody').value;
				if ((commentBody.length == 0) || (commentBody.length > newCommentCount)) {
					return;
				}

				writeBoxElem = document.getElementById('commentWriteBox');
				writeBoxElem.innerHTML = '
Sending...
';
				API.POST(
					"/comment", {root: commentRoot, comment: commentBody},
					function onCommentCreate(data) {
						writeBoxElem.parentNode.removeChild(writeBoxElem);
						createComment(data.body);
						initWriteBox();
					},
					function onCommentCreateError(data) {
						console.error("onCommentCreateError", data);
					}
				);
			};
		}, 200);
	}

	deleteComment = function deleteComment(id) {
		if (confirm("Are you sure you wish to delete this comment?")) {
			API.DELETE("/comment/" + id, {},
				function Success() {
					var element = document.getElementById("comment_" + id);
					element.parentNode.removeChild(element);
				},
				function Error() {
					console.error("Failed to delete comment with ID " + id);
				}
			);
		}
	};

	commentsThingy = function CommentsThingy(commentBodyId, root) {
		containerId = commentBodyId;
		containerDiv = document.getElementById(commentBodyId);
		commentRoot = root;
		API.GET(
			"/comment/thread", {root: root},
			function onCommentGet(data) {
				if (data.body && data.body.length) {
					for (var i = 0; i < data.body.length; i++) {
						createComment(data.body[i]);
					}
				}
				initWriteBox();
			},
			function onCommentGetError(data) {
				console.error("onCommentGetError", data);
			}
		);
	};
})();<|endoftext|>


<commit_before>//Angular code

'use strict';

/* App Module */

var onsApp = angular.module('onsApp', [
    'ngRoute',
    'onsControllers'
]);

onsApp.config(['$routeProvider','$locationProvider',
    function($routeProvider, $locationProvider) {
		//$locationProvider.hashPrefix('!');
//			 .html5Mode(false)
        $routeProvider.
        when('/', {
            redirectTo: '/home'
        }).
        when('/searchresults', {
            templateUrl: 'templates/searchresults.html',
            controller: 'SearchCtrl'
        }).
        when('/methodology', {
            templateUrl: 'templates/methodology.html',
            controller: 'MethodologyCtrl'
        }).
        when('/article', {
            templateUrl: 'templates/article.html',
            controller: 'ArticleCtrl'
        }).
        when('/home/economy/inflationandpriceindices/bulletin', {
            templateUrl: 'templates/bulletin.html',
            controller: 'BulletinCtrl'
        }).
        when('/bulletin', {
            redirectTo: '/home/economy/inflationandpriceindices/bulletin'
        }).
        when(':home*\/bulletins', {
            templateUrl: 'templates/bulletin.html',
            controller: 'BulletinCtrl'
        }).
        when('/release', {
            templateUrl: 'templates/release.html'
        }).
        when('/collection', {
            templateUrl: 'templates/collection.html',
            controller: "CollectionCtrl"
        }).
        otherwise({
            templateUrl: 'templates/template.html',
            controller: 'TemplateCtrl'
        });
    }
]);

/*Filters for ng-repeat*/

onsApp.filter('slice', function() {
    return function(arr, start, end) {
        return arr.slice(start, end);
    };
});

onsApp.filter('range', function() {
    return function(input, start, end) {
        var start = parseInt(start);
        var end = parseInt(end);
        for (var i = start; i <= end; i++)
            input.push(i);
        return input;
    };
});

/*Custom Directives*/
onsApp.directive('onsFooter', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/footer.html'
  }

})

onsApp.directive('releaseCalendarFooter', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/releasecalendarfooter.html'
  }

})

onsApp.directive('onsHeader', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/header.html'

  }

})
onsApp.directive('paginator', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/paginator.html'
  }
})

onsApp.directive('onsNav', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/onsnav.html'
  }
})

onsApp.directive('onsBreadcrumb', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/breadcrumb.html'
  }
})

onsApp.directive('searchBox', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/searchbox.html'
  }
})

onsApp.directive('autoComplete', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/autocomplete.html',
    controller: 'AutocompleteCtrl'
  }
})

onsApp.directive('topBar', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/top.html'
  }
})

onsApp.directive('onsContent', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/content.html'
  }
})

onsApp.directive('relatedArticles', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/relatedarticles.html'
  }
})

onsApp.directive('contactDetails', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/contactdetails.html'
  }
})

onsApp.directive('tools', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/tools.html'
  }
})

onsApp.directive('latestBulletins', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/latestbulletins.html'
  }
})

onsApp.directive('stopEvent', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            element.bind('click', function (e) {
              e.stopPropagation();
            });
        }
    };
 });

onsApp.directive('markdown', function($http) {
    var converter = new Showdown.converter();
    return {
        restrict: 'A',
        scope: {
            link: '@'
        },
        link: function(scope, element, attrs) {
            attrs.$observe('link', function(link) {
                var htmlText = converter.makeHtml(link);
                element.html(htmlText);
            });
        }
    };
});

        onsApp.factory('Page', function() {
            var title = 'Office Of National Statistics';
            return {
                title: function() {
                    return title;
                },
                setTitle: function(newTitle) {
                    title = newTitle
                }
            };
        });

onsApp.service('anchorSmoothScroll', function(){

    this.scrollTo = function(eID) {

        // This scrolling function
        // is from http://www.itnewb.com/tutorial/Creating-the-Smooth-Scroll-Effect-with-JavaScript

        var startY = currentYPosition();
        var stopY = elmYPosition(eID);
        var distance = stopY > startY ? stopY - startY : startY - stopY;
        if (distance < 100) {
            scrollTo(0, stopY); return;
        }
        var speed = Math.round(distance / 100);
        if (speed >= 20) speed = 20;
        var step = Math.round(distance / 25);
        var leapY = stopY > startY ? startY + step : startY - step;
        var timer = 0;
        if (stopY > startY) {
            for ( var i=startY; i<stopY; i+=step ) {
                setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
                leapY += step; if (leapY > stopY) leapY = stopY; timer++;
            } return;
        }
        for ( var i=startY; i>stopY; i-=step ) {
            setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
            leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
        }

        function currentYPosition() {
            // Firefox, Chrome, Opera, Safari
            if (self.pageYOffset) return self.pageYOffset;
            // Internet Explorer 6 - standards mode
            if (document.documentElement && document.documentElement.scrollTop)
                return document.documentElement.scrollTop;
            // Internet Explorer 6, 7 and 8
            if (document.body.scrollTop) return document.body.scrollTop;
            return 0;
        }

        function elmYPosition(eID) {
            var elm = document.getElementById(eID);
            var y = elm.offsetTop;
            var node = elm;
            while (node.offsetParent && node.offsetParent != document.body) {
                node = node.offsetParent;
                y += node.offsetTop;
            } return y;
        }

    }
});

function AccordionCtrl($scope) {
    //initiate an array to hold all active tabs
    $scope.activeTabs = [];

    //------ If you want to have only one opened at a time ------
    //
    // $scope.openTab = function (tab) {
    //     $scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1);
    //     //check if tab is already open
    //     if ($scope.isOpenTab(tab)) {
    //         //if it is, remove it from the activeTabs array
    //         $scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1);
    //     } else {
    //         //if it's not, add it!
    //         $scope.activeTabs.push(tab);
    //     }
    // }
    //

    // check if the tab is active
    $scope.isOpenTab = function (tab) {
        //check if this tab is already in the activeTabs array
        if ($scope.activeTabs.indexOf(tab) > -1) {
            //if so, return true
            return true;
        } else {
            //if not, return false
            return false;
        }
    }

    //function to 'open' a tab
    $scope.openTab = function (tab) {
        //check if tab is already open
        if ($scope.isOpenTab(tab)) {
            //if it is, remove it from the activeTabs array
            $scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1);
        } else {
            //if it's not, add it!
            $scope.activeTabs.push(tab);
        }
    }
}

<commit_msg>Cleanup of spacing and indent.<commit_after>//Angular code

'use strict';

/* App Module */

var onsApp = angular.module('onsApp', [
    'ngRoute',
    'onsControllers'
]);

onsApp.config(['$routeProvider','$locationProvider',
    function($routeProvider, $locationProvider) {
		//$locationProvider.hashPrefix('!');
//			 .html5Mode(false)
        $routeProvider.
        when('/', {
            redirectTo: '/home'
        }).
        when('/searchresults', {
            templateUrl: 'templates/searchresults.html',
            controller: 'SearchCtrl'
        }).
        when('/methodology', {
            templateUrl: 'templates/methodology.html',
            controller: 'MethodologyCtrl'
        }).
        when('/article', {
            templateUrl: 'templates/article.html',
            controller: 'ArticleCtrl'
        }).
        when('/home/economy/inflationandpriceindices/bulletin', {
            templateUrl: 'templates/bulletin.html',
            controller: 'BulletinCtrl'
        }).
        when('/bulletin', {
            redirectTo: '/home/economy/inflationandpriceindices/bulletin'
        }).
        when(':home*\/bulletins', {
            templateUrl: 'templates/bulletin.html',
            controller: 'BulletinCtrl'
        }).
        when('/release', {
            templateUrl: 'templates/release.html'
        }).
        when('/collection', {
            templateUrl: 'templates/collection.html',
            controller: "CollectionCtrl"
        }).
        otherwise({
            templateUrl: 'templates/template.html',
            controller: 'TemplateCtrl'
        });
    }
]);

/*Filters for ng-repeat*/

onsApp.filter('slice', function() {
    return function(arr, start, end) {
        return arr.slice(start, end);
    };
});

onsApp.filter('range', function() {
    return function(input, start, end) {
        var start = parseInt(start);
        var end = parseInt(end);
        for (var i = start; i <= end; i++)
            input.push(i);
        return input;
    };
});

/*Custom Directives*/
onsApp.directive('onsFooter', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/footer.html'
  }

})

onsApp.directive('releaseCalendarFooter', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/releasecalendarfooter.html'
  }

})

onsApp.directive('onsHeader', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/header.html'

  }

})

onsApp.directive('paginator', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/paginator.html'
  }
})

onsApp.directive('onsNav', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/onsnav.html'
  }
})

onsApp.directive('onsBreadcrumb', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/breadcrumb.html'
  }
})

onsApp.directive('searchBox', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/searchbox.html'
  }
})

onsApp.directive('autoComplete', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/autocomplete.html',
    controller: 'AutocompleteCtrl'
  }
})

onsApp.directive('topBar', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/top.html'
  }
})

onsApp.directive('onsContent', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/content.html'
  }
})

onsApp.directive('relatedArticles', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/relatedarticles.html'
  }
})

onsApp.directive('contactDetails', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/contactdetails.html'
  }
})

onsApp.directive('tools', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/tools.html'
  }
})

onsApp.directive('latestBulletins', function() {
  return {
    restrict: 'E',
    templateUrl: 'templates/latestbulletins.html'
  }
})

onsApp.directive('stopEvent', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            element.bind('click', function (e) {
              e.stopPropagation();
            });
        }
    };
 });

onsApp.directive('markdown', function($http) {
    var converter = new Showdown.converter();
    return {
        restrict: 'A',
        scope: {
            link: '@'
        },
        link: function(scope, element, attrs) {
            attrs.$observe('link', function(link) {
                var htmlText = converter.makeHtml(link);
                element.html(htmlText);
            });
        }
    };
});

onsApp.factory('Page', function() {
    var title = 'Office Of National Statistics';
    return {
        title: function() {
            return title;
        },
        setTitle: function(newTitle) {
            title = newTitle
        }
    };
});

onsApp.service('anchorSmoothScroll', function(){

    this.scrollTo = function(eID) {

        // This scrolling function
        // is from http://www.itnewb.com/tutorial/Creating-the-Smooth-Scroll-Effect-with-JavaScript

        var startY = currentYPosition();
        var stopY = elmYPosition(eID);
        var distance = stopY > startY ? stopY - startY : startY - stopY;
        if (distance < 100) {
            scrollTo(0, stopY); return;
        }
        var speed = Math.round(distance / 100);
        if (speed >= 20) speed = 20;
        var step = Math.round(distance / 25);
        var leapY = stopY > startY ? startY + step : startY - step;
        var timer = 0;
        if (stopY > startY) {
            for ( var i=startY; i<stopY; i+=step ) {
                setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
                leapY += step; if (leapY > stopY) leapY = stopY; timer++;
            } return;
        }
        for ( var i=startY; i>stopY; i-=step ) {
            setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
            leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
        }

        function currentYPosition() {
            // Firefox, Chrome, Opera, Safari
            if (self.pageYOffset) return self.pageYOffset;
            // Internet Explorer 6 - standards mode
            if (document.documentElement && document.documentElement.scrollTop)
                return document.documentElement.scrollTop;
            // Internet Explorer 6, 7 and 8
            if (document.body.scrollTop) return document.body.scrollTop;
            return 0;
        }

        function elmYPosition(eID) {
            var elm = document.getElementById(eID);
            var y = elm.offsetTop;
            var node = elm;
            while (node.offsetParent && node.offsetParent != document.body) {
                node = node.offsetParent;
                y += node.offsetTop;
            } return y;
        }

    }
});

function AccordionCtrl($scope) {
    //initiate an array to hold all active tabs
    $scope.activeTabs = [];

    //------ If you want to have only one opened at a time ------
    //
    // $scope.openTab = function (tab) {
    //     $scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1);
    //     //check if tab is already open
    //     if ($scope.isOpenTab(tab)) {
    //         //if it is, remove it from the activeTabs array
    //         $scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1);
    //     } else {
    //         //if it's not, add it!
    //         $scope.activeTabs.push(tab);
    //     }
    // }
    //

    // check if the tab is active
    $scope.isOpenTab = function (tab) {
        //check if this tab is already in the activeTabs array
        if ($scope.activeTabs.indexOf(tab) > -1) {
            //if so, return true
            return true;
        } else {
            //if not, return false
            return false;
        }
    }

    //function to 'open' a tab
    $scope.openTab = function (tab) {
        //check if tab is already open
        if ($scope.isOpenTab(tab)) {
            //if it is, remove it from the activeTabs array
            $scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1);
        } else {
            //if it's not, add it!
            $scope.activeTabs.push(tab);
        }
    }
}

<|endoftext|>


<commit_before>angular.module('huntEdu.services')
    .service('SearchService', ['$http', '$q', '_', 'JobList', 'User',
    function searchService($http, $q, _, JobList, User) {

        var forEach = _.forEach,
            isEmpty = _.isEmpty;

        /**
         * Populate JobList service with jobs containing keyword, then
         * return the results.
         * @param keyword {String} search keyword
         * @param limit {Integer} max number of results
         * @return [Job1, Job2, ...]
         */

        this.search = function(keyword, limit) {
            var deferred = $q.defer();

            limit = limit || 100;

            if(JobList.hasResults(keyword)) {
                deferred.resolve(JobList.getResults(keyword));
                return deferred.promise;
            }

            $http.get('/search' + '/' + keyword)
                .then(function(res) {
                    var jobPostsRaw = res.data,
                        count = 0;
                    forEach(jobPostsRaw, function(jobPostRaw){
                        if(count < limit) {
                            JobList.addJob(jobPostRaw, keyword);
                        }
                        count += 1;
                    });

                    deferred.resolve(JobList.getResults(keyword));
                }, function(err) {
                    deferred.reject(err);
                });

            return deferred.promise;
        };

        this.getRecommendedFocusJobs = function() {
             var deferred = $q.defer(),
                keyword = User.getMajor(),
                limit = 3;

             if(!keyword) {
                deferred.resolve([]);
                return deferred.promise;
             }

             this.search(keyword, limit)
                .then(function(jobs) {
                    deferred.resolve(jobs);
                }, deferred.reject);

             return deferred.promise;
        };

        this.getRecommendedInterestJobs = function() {
            var deferred = $q.defer(),
                interests = User.getInterests(),
                limit = 3;

             if(isEmpty(interests)) {
                deferred.resolve([]);
                return deferred.promise;
             }

             var interestsStr = interests.join(" ");

             this.search(interestsStr, limit)
                .then(function(jobs) {
                    console.log(jobs);
                    deferred.resolve(jobs);
                }, deferred.reject);

             return deferred.promise;

        };

        this.findJobById = function(id) {
            var deferred = $q.defer();

            if(JobList.hasJob(id)) {
                deferred.resolve(JobList.getJobById(id));
            } else {
                $http.get('/post/id/' + id)
                    .then(function(res) {
                        var jobPostRaw = res.data;
                        JobList.addJob(jobPostRaw);
                        deferred.resolve(JobList.getJobById(jobPostRaw.meta.id));
                    }, deferred.reject);
           }
           return deferred.promise;
        };

    }]);
<commit_msg>not caching job results for now<commit_after>angular.module('huntEdu.services')
    .service('SearchService', ['$http', '$q', '_', 'JobList', 'User',
    function searchService($http, $q, _, JobList, User) {

        var forEach = _.forEach,
            isEmpty = _.isEmpty;

        /**
         * Populate JobList service with jobs containing keyword, then
         * return the results.
         * @param keyword {String} search keyword
         * @param limit {Integer} max number of results
         * @return [Job1, Job2, ...]
         */

        this.search = function(keyword, limit) {
            var deferred = $q.defer();

            limit = limit || 100;

            $http.get('/search' + '/' + keyword)
                .then(function(res) {
                    var jobPostsRaw = res.data,
                        count = 0;
                    forEach(jobPostsRaw, function(jobPostRaw){
                        if(count < limit) {
                            JobList.addJob(jobPostRaw, keyword);
                        }
                        count += 1;
                    });

                    deferred.resolve(JobList.getResults(keyword));
                }, function(err) {
                    deferred.reject(err);
                });

            return deferred.promise;
        };

        this.getRecommendedFocusJobs = function() {
             var deferred = $q.defer(),
                keyword = User.getMajor(),
                limit = 3;

             if(!keyword) {
                deferred.resolve([]);
                return deferred.promise;
             }

             this.search(keyword, limit)
                .then(function(jobs) {
                    deferred.resolve(jobs);
                }, deferred.reject);

             return deferred.promise;
        };

        this.getRecommendedInterestJobs = function() {
            var deferred = $q.defer(),
                interests = User.getInterests(),
                limit = 3;

             if(isEmpty(interests)) {
                deferred.resolve([]);
                return deferred.promise;
             }

             var interestsStr = interests.join(" ");

             this.search(interestsStr, limit)
                .then(function(jobs) {
                    console.log(jobs);
                    deferred.resolve(jobs);
                }, deferred.reject);

             return deferred.promise;

        };

        this.findJobById = function(id) {
            var deferred = $q.defer();

            if(JobList.hasJob(id)) {
                deferred.resolve(JobList.getJobById(id));
            } else {
                $http.get('/post/id/' + id)
                    .then(function(res) {
                        var jobPostRaw = res.data;
                        JobList.addJob(jobPostRaw);
                        deferred.resolve(JobList.getJobById(jobPostRaw.meta.id));
                    }, deferred.reject);
           }
           return deferred.promise;
        };

    }]);
<|endoftext|>


<commit_before>function forEachIn(obj, callback) {
    for (key in obj) {
        if (obj.hasOwnProperty(key)) {
            callback(key, obj[key]);
        }
    }
}

var _pageItems = [];
var _selectedIds = [];
function removeItemFromSelectedList(id) {
    for (var i = 0; i < _selectedIds.length; i++) {
        if (_selectedIds[i] == id) {
            _selectedIds.splice(i, 1);
        }
    }
}

function distanceInsideRect(area, x, y) {
    if (x >= area[0] && x <= area[0] + area[2]
        && y >= area[1] && y <= area[1] + area[3]) {

        var distance = Math.min(x - area[0], area[0] + area[2] - x)
            + Math.min(y - area[1], area[1] + area[3] - y);
        return distance;
    }
    else return -1;
}
function sortItemsByName(items) {
    for (var i=0; i<items.length - 1; i++) {
        for(var j = i+1; j<items.length; j++) {
            if (items[i].name  > items[j].name) {
                var temp = items[i];
                items[i] = items[j];
                items[j] = temp;
            }
        }
    }
    return items;
}
function initGalenPageDump(pageData) {

    var canvas = $(".image .canvas");

    for (objectName in pageData.items) {
        if (pageData.items.hasOwnProperty(objectName)) {
            var item = pageData.items[objectName];
            item.name = objectName;
            item.id = _pageItems.length;
            item.selected = false;
            _pageItems.push(item);
        }
    }

    //sorting items by name
    var sortedItems = sortItemsByName(_pageItems);
    for (var i = 0; i<sortedItems.length; i++) {
        sortedItems[i].id = i;
        renderPageItem(canvas, sortedItems[i]);
    }

    canvas.click(function (event) {
        var offset = $(this).offset();
        var x = event.pageX - offset.left;
        var y = event.pageY - offset.top;

        //Finding the closest to the rect

        var minDistance = 1000000;
        var selectedId = -1;
        for (var i = 0; i < _pageItems.length; i++) {
           var distance = distanceInsideRect(_pageItems[i].area, x, y);
           if (distance > -1 && distance < minDistance) {
               selectedId = i;
               minDistance = distance;
           }
        }

        if (selectedId >=0 ) {
            selectItem(selectedId);
        }
    });
}

function selectItem(id) {

    _pageItems[id].selected = !_pageItems[id].selected;
    var item = _pageItems[id];

    var $item = $("#canvas-brick-" + item.id);
    var $listItem = $("#object-list-item-" + item.id);

    if (item.selected) {
        _selectedIds.push(id);

        $item.addClass("selected");
        $listItem.addClass("selected");

    } else {
        removeItemFromSelectedList(id);
        $item.removeClass("selected");
        $listItem.removeClass("selected");
    }

    if (_selectedIds.length >= 2) {
        showSpecSuggestions(_selectedIds);
    }
    else if (_selectedIds.length == 1) {
        showObjectDetails(_pageItems[_selectedIds[0]]);
        hideSpecSuggestions();
    }
    else if (_selectedIds.length == 0) {
        hideObjectDetails();
        hideSpecSuggestions();
    }
}

function hideSpecSuggestions() {
    $("#object-suggestions").hide();
}

function Point(x, y) {
    this.x = x;
    this.y = y;
}
Point.prototype.isInsideArea = function (area) {
    return this.x >= area[0] && this.x < area[0] + area[2]
        && this.y >= area[1] && this.y < area[1] + area[3];
}
function areaToPoints(area) {
    return [
        new Point(area[0], area[1]),
        new Point(area[0] + area[2], area[1]),
        new Point(area[0] + area[2], area[1] + area[3]),
        new Point(area[0], area[1] + area[3])
    ];
}

function Spec(objectName, specText) {
    this.objectName = objectName;
    this.specText = specText;
}

function __galen_textForLocationsOfInsideSpec(itemA, itemB) {
    var sides = {
        top: itemA.area[1] - itemB.area[1],
        left: itemA.area[0] - itemB.area[0],
        right: itemB.area[0] + itemB.area[2] - itemA.area[0] - itemA.area[2],
        bottom: itemB.area[1] + itemB.area[3] - itemA.area[1] - itemA.area[3]
    }

    var text = "";
    var haveFirst = false;
    forEachIn(sides, function (sideName, value) {
        if (value > 0) {
            if (haveFirst) {
                text = text + ",";
            }
            haveFirst = true;

            text = text + " " + value + "px " + sideName;
        }
    });

    return text;
}
function Suggest() {
    this.specs = {};
}
Suggest.prototype.addSpec = function (spec) {
    if (this.specs[spec.objectName] == undefined) {
        this.specs[spec.objectName] = [spec];
    }
    else this.specs[spec.objectName].push(spec);
};
Suggest.prototype.generateSuggestions = function (items) {
    var thisSuggest = this;
    var propose = function(spec) {
        if (spec != null) {
            thisSuggest.addSpec(spec);
        }
    };
    forEachIn(this._suggestions, function (name, suggestion) {
        for ( var i =0; i < items.length - 1; i++) {
            for (var j = i + 1; j < items.length; j++) {
                propose(suggestion(items[i], items[j]));
                propose(suggestion(items[j], items[i]));
            }
        }
    });

    var html = "";

    for (var objectName in this.specs) {
        if (this.specs.hasOwnProperty(objectName)) {
            html += "\n" + objectName + "\n";
            var specs = this.specs[objectName];
            for (var i = 0; i < specs.length; i++) {
                html += "    " + specs[i].specText + "\n";
            }
        }
    }

    return "
" + html + "
";
};
Suggest.prototype._suggestions = {};
Suggest.prototype._suggestions.inside = function (itemA, itemB){
    var points = areaToPoints(itemA.area);
    for (var i = 0; i<points.length; i++) {
        if (!points[i].isInsideArea(itemB.area)) {
            return null;
        }
    }
    return new Spec(itemA.name, "inside: " + itemB.name + " " + __galen_textForLocationsOfInsideSpec(itemA, itemB));
};
Suggest.prototype._suggestions.insidePartly = function (itemA, itemB){
    var points = areaToPoints(itemA.area);

    var amountOfInsidePoints = 0;
    for (var i = 0; i<points.length; i++) {
        if (points[i].isInsideArea(itemB.area)) {
            amountOfInsidePoints++;
        }
    }

    if (amountOfInsidePoints > 0 && amountOfInsidePoints < 4) {
        return new Spec(itemA.name, "inside partly: " + itemB.name + " " + __galen_textForLocationsOfInsideSpec(itemA, itemB));
    }
    return null;
};
Suggest.prototype._suggestions.alignedHorizontally = function (itemA, itemB) {
    var dTop = Math.abs(itemA.area[1] - itemB.area[1]);
    var dBottom = Math.abs(itemA.area[1] + itemA.area[3] - itemB.area[1] - itemB.area[3]);

    if (dTop < 5 && dBottom < 5) {
        var errorRate = "";
        if (dTop > 0 || dBottom > 0) {
            errorRate = " " + Math.max(dTop, dBottom) + "px";
        }
        return new Spec(itemA.name, "aligned horizontally all: " + itemB.name + errorRate);
    }
    else if (dTop < 5) {
        var errorRate = "";
        if (dTop > 0) {
            errorRate = " " + dTop + "px";
        }
        return new Spec(itemA.name, "aligned horizontally top: " + itemB.name + errorRate);
    }
    else if (dBottom < 5) {
        var errorRate = "";
        if (dBottom > 0) {
            errorRate = " " + dBottom + "px";
        }
        return new Spec(itemA.name, "aligned horizontally bottom: " + itemB.name + errorRate);
    }
    return null;
};
Suggest.prototype._suggestions.alignedVertically = function (itemA, itemB) {
    var dLeft = Math.abs(itemA.area[0] - itemB.area[0]);
    var dRight = Math.abs(itemA.area[0] + itemA.area[2] - itemB.area[0] - itemB.area[2]);

    if (dLeft < 5 && dRight < 5) {
        var errorRate = "";
        if (dLeft > 0 || dRight > 0) {
            errorRate = " " + Math.max(dLeft, dRight) + "px";
        }
        return new Spec(itemA.name, "aligned vertically all: " + itemB.name + errorRate);
    }
    else if (dLeft < 5) {
        var errorRate = "";
        if (dLeft > 0) {
            errorRate = " " + dLeft + "px";
        }
        return new Spec(itemA.name, "aligned vertically left: " + itemB.name + errorRate);
    }
    else if (dRight < 5) {
        var errorRate = "";
        if (dRight > 0) {
            errorRate = " " + dRight + "px";
        }
        return new Spec(itemA.name, "aligned vertically right: " + itemB.name + errorRate);
    }
    return null;
};
Suggest.prototype._suggestions.nearLeft = function (a, b) {
    var diff = b.area[0] - a.area[0] - a.area[2];
    if (diff >= 0) {
        return new Spec(a.name, "near: " + b.name + " " + diff + "px left");
    }
    return null;
};
Suggest.prototype._suggestions.nearRight = function (a, b) {
    var diff = a.area[0] - b.area[0] - b.area[2];
    if (diff >= 0) {
        return new Spec(a.name, "near: " + b.name + " " + diff + "px right");
    }
    return null;
};
Suggest.prototype._suggestions.above = function (a, b) {
    var diff = b.area[1] - a.area[1] - a.area[3];
    if (diff >= 0) {
        return new Spec(a.name, "above: " + b.name + " " + diff + "px");
    }
    return null;
};
Suggest.prototype._suggestions.below = function (a, b) {
    var diff = a.area[1] - b.area[1] - b.area[3];
    if (diff >= 0) {
        return new Spec(a.name, "below: " + b.name + " " + diff + "px");
    }
    return null;
};

Suggest.prototype._suggestions.centered = function (a, b) {
    //centered all inside
    //centered hor in
    //cent v in
    //cent hor on
    //cent v on
    var dt = a.area[1] - b.area[1];
    var db = b.area[1] + b.area[3] - a.area[1] - a.area[3];

    var dl = a.area[0] - b.area[0];
    var dr = b.area[0] + b.area[2] - a.area[0] - a.area[2];

    var similar = function (value1, value2) {
        return Math.abs(value1 - value2) < 5;
    };

    var errorRate = function (diff) {
        var absDiff = Math.abs(diff);
        if (absDiff > 0) {
            return " " + absDiff + "px";
        }
        return "";
    };

    if (similar(dt, db) && dt > 0 ) {
        if (similar(dl, dr) && dr > 0) {
            return new Spec(a.name, "centered all inside: " + b.name + errorRate(Math.max(Math.abs(dt - db), Math.abs(dl - dr))));
        }
        else {
            return new Spec(a.name, "centered vertically inside: " + b.name + errorRate(dt - db));
        }
    }
    else if (similar(dr, dl) && dr > 0) {
        return new Spec(a.name, "centered horizontally inside: " + b.name + errorRate(dl - dr));
    }
    else if (similar(dt, db) && dt < 0) {
        return new Spec(a.name, "centered vertically on: " + b.name + errorRate(dt - db));
    }
    else if (similar(dr, dl) && dr < 0) {
        return new Spec(a.name, "centered horizontally on: " + b.name + errorRate(dr - dl));
    }
    return null;
};

function showSpecSuggestions(selectedIds) {

    var suggest = new Suggest();

    var items = [];

    for (var i=0; i<selectedIds.length; i++) {
        items.push(_pageItems[selectedIds[i]]);
    }

    $("#object-suggestions .spec-list").html(suggest.generateSuggestions(items));
    $("#object-suggestions").show();
}

function showObjectDetails(item) {
    $("#object-details .xf-object-name").text(item.name);
    $("#object-details .xf-object-area").text(item.area);
    $("#object-details .xf-object-text").text(item.text);

    if (item.hasImage) {
        $("#object-details .image").html("").show();
    }
    else $("#object-details .image").hide();

    $("#object-details").show();
}

function hideObjectDetails() {
    $("#object-details").hide();
}

function renderPageItem(canvas, item) {
    $("#object-list ul").append($("
" + item.name + "
").click(function () {
        var id = $(this).attr("data-id");
        selectItem(id);
    }));

    canvas.append("
"
        + "" + item.name + ""
        + "
");
}
<commit_msg>Fix for #84 added suggestions for 'left of' and 'right of' specs in page dump<commit_after>function forEachIn(obj, callback) {
    for (key in obj) {
        if (obj.hasOwnProperty(key)) {
            callback(key, obj[key]);
        }
    }
}

var _pageItems = [];
var _selectedIds = [];
function removeItemFromSelectedList(id) {
    for (var i = 0; i < _selectedIds.length; i++) {
        if (_selectedIds[i] == id) {
            _selectedIds.splice(i, 1);
        }
    }
}

function distanceInsideRect(area, x, y) {
    if (x >= area[0] && x <= area[0] + area[2]
        && y >= area[1] && y <= area[1] + area[3]) {

        var distance = Math.min(x - area[0], area[0] + area[2] - x)
            + Math.min(y - area[1], area[1] + area[3] - y);
        return distance;
    }
    else return -1;
}
function sortItemsByName(items) {
    for (var i=0; i<items.length - 1; i++) {
        for(var j = i+1; j<items.length; j++) {
            if (items[i].name  > items[j].name) {
                var temp = items[i];
                items[i] = items[j];
                items[j] = temp;
            }
        }
    }
    return items;
}
function initGalenPageDump(pageData) {

    var canvas = $(".image .canvas");

    for (objectName in pageData.items) {
        if (pageData.items.hasOwnProperty(objectName)) {
            var item = pageData.items[objectName];
            item.name = objectName;
            item.id = _pageItems.length;
            item.selected = false;
            _pageItems.push(item);
        }
    }

    //sorting items by name
    var sortedItems = sortItemsByName(_pageItems);
    for (var i = 0; i<sortedItems.length; i++) {
        sortedItems[i].id = i;
        renderPageItem(canvas, sortedItems[i]);
    }

    canvas.click(function (event) {
        var offset = $(this).offset();
        var x = event.pageX - offset.left;
        var y = event.pageY - offset.top;

        //Finding the closest to the rect

        var minDistance = 1000000;
        var selectedId = -1;
        for (var i = 0; i < _pageItems.length; i++) {
           var distance = distanceInsideRect(_pageItems[i].area, x, y);
           if (distance > -1 && distance < minDistance) {
               selectedId = i;
               minDistance = distance;
           }
        }

        if (selectedId >=0 ) {
            selectItem(selectedId);
        }
    });
}

function selectItem(id) {

    _pageItems[id].selected = !_pageItems[id].selected;
    var item = _pageItems[id];

    var $item = $("#canvas-brick-" + item.id);
    var $listItem = $("#object-list-item-" + item.id);

    if (item.selected) {
        _selectedIds.push(id);

        $item.addClass("selected");
        $listItem.addClass("selected");

    } else {
        removeItemFromSelectedList(id);
        $item.removeClass("selected");
        $listItem.removeClass("selected");
    }

    if (_selectedIds.length >= 2) {
        showSpecSuggestions(_selectedIds);
    }
    else if (_selectedIds.length == 1) {
        showObjectDetails(_pageItems[_selectedIds[0]]);
        hideSpecSuggestions();
    }
    else if (_selectedIds.length == 0) {
        hideObjectDetails();
        hideSpecSuggestions();
    }
}

function hideSpecSuggestions() {
    $("#object-suggestions").hide();
}

function Point(x, y) {
    this.x = x;
    this.y = y;
}
Point.prototype.isInsideArea = function (area) {
    return this.x >= area[0] && this.x < area[0] + area[2]
        && this.y >= area[1] && this.y < area[1] + area[3];
}
function areaToPoints(area) {
    return [
        new Point(area[0], area[1]),
        new Point(area[0] + area[2], area[1]),
        new Point(area[0] + area[2], area[1] + area[3]),
        new Point(area[0], area[1] + area[3])
    ];
}

function Spec(objectName, specText) {
    this.objectName = objectName;
    this.specText = specText;
}

function __galen_textForLocationsOfInsideSpec(itemA, itemB) {
    var sides = {
        top: itemA.area[1] - itemB.area[1],
        left: itemA.area[0] - itemB.area[0],
        right: itemB.area[0] + itemB.area[2] - itemA.area[0] - itemA.area[2],
        bottom: itemB.area[1] + itemB.area[3] - itemA.area[1] - itemA.area[3]
    }

    var text = "";
    var haveFirst = false;
    forEachIn(sides, function (sideName, value) {
        if (value > 0) {
            if (haveFirst) {
                text = text + ",";
            }
            haveFirst = true;

            text = text + " " + value + "px " + sideName;
        }
    });

    return text;
}
function Suggest() {
    this.specs = {};
}
Suggest.prototype.addSpec = function (spec) {
    if (this.specs[spec.objectName] == undefined) {
        this.specs[spec.objectName] = [spec];
    }
    else this.specs[spec.objectName].push(spec);
};
Suggest.prototype.generateSuggestions = function (items) {
    var thisSuggest = this;
    var propose = function(spec) {
        if (spec != null) {
            thisSuggest.addSpec(spec);
        }
    };
    forEachIn(this._suggestions, function (name, suggestion) {
        for ( var i =0; i < items.length - 1; i++) {
            for (var j = i + 1; j < items.length; j++) {
                propose(suggestion(items[i], items[j]));
                propose(suggestion(items[j], items[i]));
            }
        }
    });

    var html = "";

    for (var objectName in this.specs) {
        if (this.specs.hasOwnProperty(objectName)) {
            html += "\n" + objectName + "\n";
            var specs = this.specs[objectName];
            for (var i = 0; i < specs.length; i++) {
                html += "    " + specs[i].specText + "\n";
            }
        }
    }

    return "
" + html + "
";
};
Suggest.prototype._suggestions = {};
Suggest.prototype._suggestions.inside = function (itemA, itemB){
    var points = areaToPoints(itemA.area);
    for (var i = 0; i<points.length; i++) {
        if (!points[i].isInsideArea(itemB.area)) {
            return null;
        }
    }
    return new Spec(itemA.name, "inside: " + itemB.name + " " + __galen_textForLocationsOfInsideSpec(itemA, itemB));
};
Suggest.prototype._suggestions.insidePartly = function (itemA, itemB){
    var points = areaToPoints(itemA.area);

    var amountOfInsidePoints = 0;
    for (var i = 0; i<points.length; i++) {
        if (points[i].isInsideArea(itemB.area)) {
            amountOfInsidePoints++;
        }
    }

    if (amountOfInsidePoints > 0 && amountOfInsidePoints < 4) {
        return new Spec(itemA.name, "inside partly: " + itemB.name + " " + __galen_textForLocationsOfInsideSpec(itemA, itemB));
    }
    return null;
};
Suggest.prototype._suggestions.alignedHorizontally = function (itemA, itemB) {
    var dTop = Math.abs(itemA.area[1] - itemB.area[1]);
    var dBottom = Math.abs(itemA.area[1] + itemA.area[3] - itemB.area[1] - itemB.area[3]);

    if (dTop < 5 && dBottom < 5) {
        var errorRate = "";
        if (dTop > 0 || dBottom > 0) {
            errorRate = " " + Math.max(dTop, dBottom) + "px";
        }
        return new Spec(itemA.name, "aligned horizontally all: " + itemB.name + errorRate);
    }
    else if (dTop < 5) {
        var errorRate = "";
        if (dTop > 0) {
            errorRate = " " + dTop + "px";
        }
        return new Spec(itemA.name, "aligned horizontally top: " + itemB.name + errorRate);
    }
    else if (dBottom < 5) {
        var errorRate = "";
        if (dBottom > 0) {
            errorRate = " " + dBottom + "px";
        }
        return new Spec(itemA.name, "aligned horizontally bottom: " + itemB.name + errorRate);
    }
    return null;
};
Suggest.prototype._suggestions.alignedVertically = function (itemA, itemB) {
    var dLeft = Math.abs(itemA.area[0] - itemB.area[0]);
    var dRight = Math.abs(itemA.area[0] + itemA.area[2] - itemB.area[0] - itemB.area[2]);

    if (dLeft < 5 && dRight < 5) {
        var errorRate = "";
        if (dLeft > 0 || dRight > 0) {
            errorRate = " " + Math.max(dLeft, dRight) + "px";
        }
        return new Spec(itemA.name, "aligned vertically all: " + itemB.name + errorRate);
    }
    else if (dLeft < 5) {
        var errorRate = "";
        if (dLeft > 0) {
            errorRate = " " + dLeft + "px";
        }
        return new Spec(itemA.name, "aligned vertically left: " + itemB.name + errorRate);
    }
    else if (dRight < 5) {
        var errorRate = "";
        if (dRight > 0) {
            errorRate = " " + dRight + "px";
        }
        return new Spec(itemA.name, "aligned vertically right: " + itemB.name + errorRate);
    }
    return null;
};
Suggest.prototype._suggestions.leftOf = function (a, b) {
    var diff = b.area[0] - a.area[0] - a.area[2];
    if (diff >= 0) {
        return new Spec(a.name, "left of: " + b.name + " " + diff + "px");
    }
    return null;
};
Suggest.prototype._suggestions.rightOf = function (a, b) {
    var diff = a.area[0] - b.area[0] - b.area[2];
    if (diff >= 0) {
        return new Spec(a.name, "right of: " + b.name + " " + diff + "px");
    }
    return null;
};
Suggest.prototype._suggestions.above = function (a, b) {
    var diff = b.area[1] - a.area[1] - a.area[3];
    if (diff >= 0) {
        return new Spec(a.name, "above: " + b.name + " " + diff + "px");
    }
    return null;
};
Suggest.prototype._suggestions.below = function (a, b) {
    var diff = a.area[1] - b.area[1] - b.area[3];
    if (diff >= 0) {
        return new Spec(a.name, "below: " + b.name + " " + diff + "px");
    }
    return null;
};

Suggest.prototype._suggestions.centered = function (a, b) {
    //centered all inside
    //centered hor in
    //cent v in
    //cent hor on
    //cent v on
    var dt = a.area[1] - b.area[1];
    var db = b.area[1] + b.area[3] - a.area[1] - a.area[3];

    var dl = a.area[0] - b.area[0];
    var dr = b.area[0] + b.area[2] - a.area[0] - a.area[2];

    var similar = function (value1, value2) {
        return Math.abs(value1 - value2) < 5;
    };

    var errorRate = function (diff) {
        var absDiff = Math.abs(diff);
        if (absDiff > 0) {
            return " " + absDiff + "px";
        }
        return "";
    };

    if (similar(dt, db) && dt > 0 ) {
        if (similar(dl, dr) && dr > 0) {
            return new Spec(a.name, "centered all inside: " + b.name + errorRate(Math.max(Math.abs(dt - db), Math.abs(dl - dr))));
        }
        else {
            return new Spec(a.name, "centered vertically inside: " + b.name + errorRate(dt - db));
        }
    }
    else if (similar(dr, dl) && dr > 0) {
        return new Spec(a.name, "centered horizontally inside: " + b.name + errorRate(dl - dr));
    }
    else if (similar(dt, db) && dt < 0) {
        return new Spec(a.name, "centered vertically on: " + b.name + errorRate(dt - db));
    }
    else if (similar(dr, dl) && dr < 0) {
        return new Spec(a.name, "centered horizontally on: " + b.name + errorRate(dr - dl));
    }
    return null;
};

function showSpecSuggestions(selectedIds) {

    var suggest = new Suggest();

    var items = [];

    for (var i=0; i<selectedIds.length; i++) {
        items.push(_pageItems[selectedIds[i]]);
    }

    $("#object-suggestions .spec-list").html(suggest.generateSuggestions(items));
    $("#object-suggestions").show();
}

function showObjectDetails(item) {
    $("#object-details .xf-object-name").text(item.name);
    $("#object-details .xf-object-area").text(item.area);
    $("#object-details .xf-object-text").text(item.text);

    if (item.hasImage) {
        $("#object-details .image").html("").show();
    }
    else $("#object-details .image").hide();

    $("#object-details").show();
}

function hideObjectDetails() {
    $("#object-details").hide();
}

function renderPageItem(canvas, item) {
    $("#object-list ul").append($("
" + item.name + "
").click(function () {
        var id = $(this).attr("data-id");
        selectItem(id);
    }));

    canvas.append("
"
        + "" + item.name + ""
        + "
");
}
<|endoftext|>