<commit_before>/** HTML5 localStorage
 * @desc    Cross Browser localStorage,
 * @copy    Jony (www.niceue.com), MIT Licensed
 */
;(function(w,d){if(!w.localStorage){if(d.documentElement.addBehavior){var e=d.createElement("meta"),s="localStorage";e.addBehavior("#default#userdata");d.getElementsByTagName("head")[0].appendChild(e);var w.localStorage={length:0,key:function(n){return(n>=0&&n<this.length)?_a()[n].nodeName:null},setItem:function(k,v){e.load(s);e.setAttribute(k,v);e.save(s);_l();},getItem:function(k){e.load(s);return e.getAttribute(k);},removeItem:function(k){e.load(s);e.removeAttribute(k);e.save(s);_l();},clear:function(){e.load(s);var a,i=0;while(a=_a()[i++])e.removeAttribute(a.nodeName);e.save(s);this.length=0;}},_a=function(){return e.XMLDocument.documentElement.attributes},_l=function(){w.localStorage.length=_a().length};e.load(s);_l();}else if(w.globalStorage){var gs=w['globalStorage'][w.location.hostname],w.localStorage={length:0,key:function(n){return gs.key(n)},setItem:function(k,v){gs.setItem(k,v);this.length=gs.length;},getItem:function(k){return gs.getItem(k)},removeItem:function(k){gs.removeItem(k);this.length=gs.length;},clear:function(){for(var k in gs)this.removeItem(k);this.length=0;}};w.localStorage.length=gs.length;}}})(window,document);<commit_msg>build<commit_after>/** HTML5 localStorage
 * @desc    Cross Browser localStorage,
 * @copy    Jony (www.niceue.com), MIT Licensed
 */
;(function(w,d){if(!w.localStorage){if(d.documentElement.addBehavior){var e=d.createElement("meta"),s="localStorage";e.addBehavior("#default#userdata");d.getElementsByTagName("head")[0].appendChild(e);w.localStorage={length:0,key:function(n){return(n>=0&&n<this.length)?_a()[n].nodeName:null},setItem:function(key,val){e.load(s);e.setAttribute(key,val);e.save(s);_l();},getItem:function(key){e.load(s);return e.getAttribute(key);},removeItem:function(key){e.load(s);e.removeAttribute(key);e.save(s);_l();},clear:function(){e.load(s);var a,i=0;while(a=_a()[i++])e.removeAttribute(a.nodeName);e.save(s);this.length=0;}},_a=function(){return e.XMLDocument.documentElement.attributes},_l=function(){w.localStorage.length=_a().length};e.load(s);_l();}else if(w.globalStorage){var gs=w['globalStorage'][w.location.hostname],w.localStorage={length:0,key:function(n){return gs.key(n)},setItem:function(key,val){gs.setItem(key,val);this.length=gs.length;},getItem:function(key){return gs.getItem(key)},removeItem:function(key){gs.removeItem(key);this.length=gs.length;},clear:function(){for(var key in gs)this.removeItem(key);this.length=0;}};w.localStorage.length=gs.length;}}})(window,document);<|endoftext|>


<commit_before>// Separate Numbers with Commas in JavaScript **Pairing Challenge**

// I worked on this challenge with: Peter L..

// Pseudocode - The function will take in an integer. First split the integer and convert it to a string so that we can iterate over each item. We want to put a comma after every three zeros, so working from the back if the length / 3 leaves no remainders, and the output isn't an empty string, then you want to shovel into the output a comma. Otherwise, you want to shovel into the output  the number at that index. Once you have the array, you need to reverse the array since we started from the back, in order to have the numbers in the correct order, and return that output.

var numCommas = function(number) {
 var num = String(number);
 var output = ""
  for (var i = num.length - 1; i >= 0 ; i --){
    if ((num.length - i - 1) % 3 === 0  && output != ''){
     output = output + ','
    }
    output = output + num[i]
  }
  var output2 = ''
  for (var i = output.length -1; i >= 0; i --) {
    output2 = output2 + output[i]
  }
  return output2
}

// Refactored Solution

var numCommas = function(number) {
 var num = String(number);
 var output = ""
  for (var i = num.length - 1; i >= 0 ; i --){
    if ((num.length - i - 1) % 3 === 0  && output != ''){
     output += ','
    }
    output += num[i]
  }
   return output.split('').reverse().join('')
}

// Your Own Tests (OPTIONAL)
console.log(numCommas(1,000)
console.log(numCommas(10,000)
console.log(numCommas(1,000,000)
console.log(numCommas(10,000,000)
console.log(numCommas(100,000,000)

// Reflection<commit_msg>Add reflection to nums commas challenge<commit_after>// Separate Numbers with Commas in JavaScript **Pairing Challenge**

// I worked on this challenge with: Peter L..

// Pseudocode - The function will take in an integer. First split the integer and convert it to a string so that we can iterate over each item. We want to put a comma after every three zeros, so working from the back if the length / 3 leaves no remainders, and the output isn't an empty string, then you want to shovel into the output a comma. Otherwise, you want to shovel into the output  the number at that index. Once you have the array, you need to reverse the array since we started from the back, in order to have the numbers in the correct order, and return that output.

var numCommas = function(number) {
 var num = String(number);
 var output = ""
  for (var i = num.length - 1; i >= 0 ; i --){
    if ((num.length - i - 1) % 3 === 0  && output != ''){
     output = output + ','
    }
    output = output + num[i]
  }
  var output2 = ''
  for (var i = output.length -1; i >= 0; i --) {
    output2 = output2 + output[i]
  }
  return output2
}

// Refactored Solution

var numCommas = function(number) {
 var num = String(number);
 var output = ""
  for (var i = num.length - 1; i >= 0 ; i --){
    if ((num.length - i - 1) % 3 === 0  && output != ''){
     output += ','
    }
    output += num[i]
  }
   return output.split('').reverse().join('')
}

// Your Own Tests (OPTIONAL)
console.log(numCommas(1,000)
console.log(numCommas(10,000)
console.log(numCommas(1,000,000)
console.log(numCommas(10,000,000)
console.log(numCommas(100,000,000)

// Reflection

// What was it like to approach the problem from the perspective of JavaScript? Did you approach the problem differently? - It required a little different thinking. I forgot how wonderful ruby is with all its built in methods, so we really had to piece apart every step to solve this with limited methods.
// What did you learn about iterating over arrays in JavaScript? - You iterate over the index, so if you want to element, when using a for loop, you need to reference the array. ex array[i] and not just do i to try and access the element in the array.
// What was different about solving this problem in JavaScript? - Using only the index instead of iterating over each item took me a while to wrap my head around the process as I was used to using .each with Ruby. In addition, the syntax is quite different so it's important to remember to add parenthesis, semicolons, and curly braces.
// What built-in methods did you find to incorporate in your refactored solution? - We used .reverse, .split, and .join for the refactored solution.<|endoftext|>


<commit_before>// Tally Votes in JavaScript Pairing Challenge.

// I worked on this challenge with:
// This challenge took me [#] hours.

// These are the votes cast by each student. Do not alter these objects here.
var votes = {
  "Alex": { president: "Bob", vicePresident: "Devin", secretary: "Gail", treasurer: "Kerry" },
  "Bob": { president: "Mary", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Cindy": { president: "Cindy", vicePresident: "Hermann", secretary: "Bob", treasurer: "Bob" },
  "Devin": { president: "Louise", vicePresident: "John", secretary: "Bob", treasurer: "Fred" },
  "Ernest": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Fred": { president: "Louise", vicePresident: "Alex", secretary: "Ivy", treasurer: "Ivy" },
  "Gail": { president: "Fred", vicePresident: "Alex", secretary: "Ivy", treasurer: "Bob" },
  "Hermann": { president: "Ivy", vicePresident: "Kerry", secretary: "Fred", treasurer: "Ivy" },
  "Ivy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Gail" },
  "John": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Kerry" },
  "Kerry": { president: "Fred", vicePresident: "Mary", secretary: "Fred", treasurer: "Ivy" },
  "Louise": { president: "Nate", vicePresident: "Alex", secretary: "Mary", treasurer: "Ivy" },
  "Mary": { president: "Louise", vicePresident: "Oscar", secretary: "Nate", treasurer: "Ivy" },
  "Nate": { president: "Oscar", vicePresident: "Hermann", secretary: "Fred", treasurer: "Tracy" },
  "Oscar": { president: "Paulina", vicePresident: "Nate", secretary: "Fred", treasurer: "Ivy" },
  "Paulina": { president: "Louise", vicePresident: "Bob", secretary: "Devin", treasurer: "Ivy" },
  "Quintin": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Bob" },
  "Romanda": { president: "Louise", vicePresident: "Steve", secretary: "Fred", treasurer: "Ivy" },
  "Steve": { president: "Tracy", vicePresident: "Kerry", secretary: "Oscar", treasurer: "Xavier" },
  "Tracy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Ullyses": { president: "Louise", vicePresident: "Hermann", secretary: "Ivy", treasurer: "Bob" },
  "Valorie": { president: "Wesley", vicePresident: "Bob", secretary: "Alex", treasurer: "Ivy" },
  "Wesley": { president: "Bob", vicePresident: "Yvonne", secretary: "Valorie", treasurer: "Ivy" },
  "Xavier": { president: "Steve", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Yvonne": { president: "Bob", vicePresident: "Zane", secretary: "Fred", treasurer: "Hermann" },
  "Zane": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Mary" }
}

// Tally the votes in voteCount.
var voteCount = {
  president: {},
  vicePresident: {},
  secretary: {},
  treasurer: {}
}

/* The name of each student receiving a vote for an office should become a property
of the respective office in voteCount.  After Alex's votes have been tallied,
voteCount would be ...

  var voteCount = {
    president: { Bob: 1 },
    vicePresident: { Devin: 1 },
    secretary: { Gail: 1 },
    treasurer: { Kerry: 1 }
  }

*/

/* Once the votes have been tallied, assign each officer position the name of the
student who received the most votes. */
var officers = {
  president: undefined,
  vicePresident: undefined,
  secretary: undefined,
  treasurer: undefined
}

// Pseudocode

// __________________________________________
// Initial Solution

// __________________________________________
// Refactored Solution

// __________________________________________
// Reflection

// __________________________________________
// Test Code:  Do not alter code below this line.

function assert(test, message, test_number) {
  if (!test) {
    console.log(test_number + "false");
    throw "ERROR: " + message;
  }
  console.log(test_number + "true");
  return true;
}

assert(
  (voteCount.president["Bob"] === 3),
  "Bob should receive three votes for President.",
  "1. "
)

assert(
  (voteCount.vicePresident["Bob"] === 2),
  "Bob should receive two votes for Vice President.",
  "2. "
)

assert(
  (voteCount.secretary["Bob"] === 2),
  "Bob should receive two votes for Secretary.",
  "3. "
)

assert(
  (voteCount.treasurer["Bob"] === 4),
  "Bob should receive four votes for Treasurer.",
  "4. "
)

assert(
  (officers.president === "Louise"),
  "Louise should be elected President.",
  "5. "
)

assert(
  (officers.vicePresident === "Hermann"),
  "Hermann should be elected Vice President.",
  "6. "
)

assert(
  (officers.secretary === "Fred"),
  "Fred should be elected Secretary.",
  "7. "
)

assert(
  (officers.treasurer === "Ivy"),
  "Ivy should be elected Treasurer.",
  "8. "
)
<commit_msg>add tally votes<commit_after>// Tally Votes in JavaScript Pairing Challenge.

// I worked on this challenge with: Jon Clayton
// This challenge took me 3 hours.

// These are the votes cast by each student. Do not alter these objects here.
var votes = {
  "Alex": { president: "Bob", vicePresident: "Devin", secretary: "Gail", treasurer: "Kerry" },
  "Bob": { president: "Mary", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Cindy": { president: "Cindy", vicePresident: "Hermann", secretary: "Bob", treasurer: "Bob" },
  "Devin": { president: "Louise", vicePresident: "John", secretary: "Bob", treasurer: "Fred" },
  "Ernest": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Fred": { president: "Louise", vicePresident: "Alex", secretary: "Ivy", treasurer: "Ivy" },
  "Gail": { president: "Fred", vicePresident: "Alex", secretary: "Ivy", treasurer: "Bob" },
  "Hermann": { president: "Ivy", vicePresident: "Kerry", secretary: "Fred", treasurer: "Ivy" },
  "Ivy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Gail" },
  "John": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Kerry" },
  "Kerry": { president: "Fred", vicePresident: "Mary", secretary: "Fred", treasurer: "Ivy" },
  "Louise": { president: "Nate", vicePresident: "Alex", secretary: "Mary", treasurer: "Ivy" },
  "Mary": { president: "Louise", vicePresident: "Oscar", secretary: "Nate", treasurer: "Ivy" },
  "Nate": { president: "Oscar", vicePresident: "Hermann", secretary: "Fred", treasurer: "Tracy" },
  "Oscar": { president: "Paulina", vicePresident: "Nate", secretary: "Fred", treasurer: "Ivy" },
  "Paulina": { president: "Louise", vicePresident: "Bob", secretary: "Devin", treasurer: "Ivy" },
  "Quintin": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Bob" },
  "Romanda": { president: "Louise", vicePresident: "Steve", secretary: "Fred", treasurer: "Ivy" },
  "Steve": { president: "Tracy", vicePresident: "Kerry", secretary: "Oscar", treasurer: "Xavier" },
  "Tracy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Ullyses": { president: "Louise", vicePresident: "Hermann", secretary: "Ivy", treasurer: "Bob" },
  "Valorie": { president: "Wesley", vicePresident: "Bob", secretary: "Alex", treasurer: "Ivy" },
  "Wesley": { president: "Bob", vicePresident: "Yvonne", secretary: "Valorie", treasurer: "Ivy" },
  "Xavier": { president: "Steve", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Yvonne": { president: "Bob", vicePresident: "Zane", secretary: "Fred", treasurer: "Hermann" },
  "Zane": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Mary" }
}

// Tally the votes in voteCount.
var voteCount = {
  president: {},
  vicePresident: {},
  secretary: {},
  treasurer: {}
}

/*
Pseudocode for function to populate voteCount

Input: nested object

Output: function doesn't return anything, but changes var voteCount
to tally the votes in the input object.

steps:

Loop outside: iterate over the nested input object, which will allow
us to operate one at a time on each ballot, which includes votes for
multiple offices.

Loop inside: iterate over each ballot, inspecting each individual vote
(a single office/candidate combination).

[create the office/candidate combo within voteCount, if necessary]

Add 1 to the number of votes received by the candidate for the office.

*/

// __________________________________________
// Initial Solution

function tally(votes) {
  for (var voter in votes) {
    for (var office in votes[voter]) {
      if (voteCount[office][votes[voter][office]] == undefined) {
        voteCount[office][votes[voter][office]] = 0
      }
      voteCount[office][votes[voter][office]] += 1;
    }
  }
};

tally(votes);
// console.log(voteCount);

/*

The name of each student receiving a vote for an office should become a property
of the respective office in voteCount.  After Alex's votes have been tallied,
voteCount would be ...

  var voteCount = {
    president: { Bob: 1 },
    vicePresident: { Devin: 1 },
    secretary: { Gail: 1 },
    treasurer: { Kerry: 1 }
  }

*/

/* Once the votes have been tallied, assign each officer position the name of the
student who received the most votes.

*/
var officers = {
  president: undefined,
  vicePresident: undefined,
  secretary: undefined,
  treasurer: undefined
}

/*

pseudocode

input: nested object.
output: pick a property from input nested object based on the steps below.

steps

Outside loop: iterate over nested object, which will allow us to operate on each office individually.

Inside loop: iterate over each office, operating on candidate names.

[Set officers["president"] as undefined]

if office is undefined, give current candidate the office

set office to the current candidate if that candidate has more votes than the current officer.

Identify the highest value associated with the candidate names.

*/

function officeMaker (voteCount) {
  for (var office in voteCount) {
    for (var candidate in voteCount[office]) {
      if (officers[office] == undefined) {officers[office] = candidate};
      if (voteCount[office][candidate] > voteCount[office][officers[office]]) {
        officers[office] = candidate;
      }
    }
  }
}

officeMaker(voteCount);

// __________________________________________
// Refactored Solution

/* We couldn't find any significant ways to refactor these functions.
/* Let us know if anything comes to mind!

// __________________________________________
// Reflection

/*

What did you learn about iterating over nested objects in JavaScript?

The for...in sequence is a useful method for iterating over nested
objects in JS. You can create variables within the for...in sequence,
no need to declare empty variables beforehand.

Were you able to find useful methods to help you with this?

We looked up the for...in statement in the MDS guide, but that was
about the only 'method' we utilized. We used if conditions in the
officeMaker function, but that's not really a method.

What concepts were solidified in the process of working through this challenge?

I'm still getting comfortable with the basics, so it's all good
practice for me. The for...in statement was a good concept to get
practice with, and working with nested objects is also still less than
100% clear and easy for me.

*/

// __________________________________________
// Test Code:  Do not alter code below this line.

function assert(test, message, test_number) {
  if (!test) {
    console.log(test_number + "false");
    throw "ERROR: " + message;
  }
  console.log(test_number + "true");
  return true;
}

assert(
  (voteCount.president["Bob"] === 3),
  "Bob should receive three votes for President.",
  "1. "
)

assert(
  (voteCount.vicePresident["Bob"] === 2),
  "Bob should receive two votes for Vice President.",
  "2. "
)

assert(
  (voteCount.secretary["Bob"] === 2),
  "Bob should receive two votes for Secretary.",
  "3. "
)

assert(
  (voteCount.treasurer["Bob"] === 4),
  "Bob should receive four votes for Treasurer.",
  "4. "
)

assert(
  (officers.president === "Louise"),
  "Louise should be elected President.",
  "5. "
)

assert(
  (officers.vicePresident === "Hermann"),
  "Hermann should be elected Vice President.",
  "6. "
)

assert(
  (officers.secretary === "Fred"),
  "Fred should be elected Secretary.",
  "7. "
)

assert(
  (officers.treasurer === "Ivy"),
  "Ivy should be elected Treasurer.",
  "8. "
)

<|endoftext|>


<commit_before>
// Tally Votes in JavaScript Pairing Challenge.

// I worked on this challenge with:
// This challenge took me [#] hours.

// These are the votes cast by each student. Do not alter these objects here.
var votes = {
  "Alex": { president: "Bob", vicePresident: "Devin", secretary: "Gail", treasurer: "Kerry" },
  "Bob": { president: "Mary", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Cindy": { president: "Cindy", vicePresident: "Hermann", secretary: "Bob", treasurer: "Bob" },
  "Devin": { president: "Louise", vicePresident: "John", secretary: "Bob", treasurer: "Fred" },
  "Ernest": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Fred": { president: "Louise", vicePresident: "Alex", secretary: "Ivy", treasurer: "Ivy" },
  "Gail": { president: "Fred", vicePresident: "Alex", secretary: "Ivy", treasurer: "Bob" },
  "Hermann": { president: "Ivy", vicePresident: "Kerry", secretary: "Fred", treasurer: "Ivy" },
  "Ivy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Gail" },
  "John": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Kerry" },
  "Kerry": { president: "Fred", vicePresident: "Mary", secretary: "Fred", treasurer: "Ivy" },
  "Louise": { president: "Nate", vicePresident: "Alex", secretary: "Mary", treasurer: "Ivy" },
  "Mary": { president: "Louise", vicePresident: "Oscar", secretary: "Nate", treasurer: "Ivy" },
  "Nate": { president: "Oscar", vicePresident: "Hermann", secretary: "Fred", treasurer: "Tracy" },
  "Oscar": { president: "Paulina", vicePresident: "Nate", secretary: "Fred", treasurer: "Ivy" },
  "Paulina": { president: "Louise", vicePresident: "Bob", secretary: "Devin", treasurer: "Ivy" },
  "Quintin": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Bob" },
  "Romanda": { president: "Louise", vicePresident: "Steve", secretary: "Fred", treasurer: "Ivy" },
  "Steve": { president: "Tracy", vicePresident: "Kerry", secretary: "Oscar", treasurer: "Xavier" },
  "Tracy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Ullyses": { president: "Louise", vicePresident: "Hermann", secretary: "Ivy", treasurer: "Bob" },
  "Valorie": { president: "Wesley", vicePresident: "Bob", secretary: "Alex", treasurer: "Ivy" },
  "Wesley": { president: "Bob", vicePresident: "Yvonne", secretary: "Valorie", treasurer: "Ivy" },
  "Xavier": { president: "Steve", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Yvonne": { president: "Bob", vicePresident: "Zane", secretary: "Fred", treasurer: "Hermann" },
  "Zane": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Mary" }
}

// Tally the votes in voteCount.
var voteCount = {
  president: {},
  vicePresident: {},
  secretary: {},
  treasurer: {}
}

/* The name of each student receiving a vote for an office should become a property
of the respective office in voteCount.  After Alex's votes have been tallied,
voteCount would be ...

  var voteCount = {
    president: { Bob: 1 },
    vicePresident: { Devin: 1 },
    secretary: { Gail: 1 },
    treasurer: { Kerry: 1 }
  }

*/

/* Once the votes have been tallied, assign each officer position the name of the
student who received the most votes. */
var officers = {
  president: undefined,
  vicePresident: undefined,
  secretary: undefined,
  treasurer: undefined
}

// Pseudocode

// __________________________________________
// Initial Solution

// __________________________________________
// Refactored Solution

// __________________________________________
// Reflection

// __________________________________________
// Test Code:  Do not alter code below this line.

function assert(test, message, test_number) {
  if (!test) {
    console.log(test_number + "false");
    throw "ERROR: " + message;
  }
  console.log(test_number + "true");
  return true;
}

assert(
  (voteCount.president["Bob"] === 3),
  "Bob should receive three votes for President.",
  "1. "
)

assert(
  (voteCount.vicePresident["Bob"] === 2),
  "Bob should receive two votes for Vice President.",
  "2. "
)

assert(
  (voteCount.secretary["Bob"] === 2),
  "Bob should receive two votes for Secretary.",
  "3. "
)

assert(
  (voteCount.treasurer["Bob"] === 4),
  "Bob should receive four votes for Treasurer.",
  "4. "
)

assert(
  (officers.president === "Louise"),
  "Louise should be elected President.",
  "5. "
)

assert(
  (officers.vicePresident === "Hermann"),
  "Hermann should be elected Vice President.",
  "6. "
)

assert(
  (officers.secretary === "Fred"),
  "Fred should be elected Secretary.",
  "7. "
)

assert(
  (officers.treasurer === "Ivy"),
  "Ivy should be elected Treasurer.",
  "8. "
)<commit_msg>implement methods, add reflection<commit_after>// Tally Votes in JavaScript Pairing Challenge.

// I worked on this challenge with: Heather Conklin
// This challenge took me [2] hours.

// These are the votes cast by each student. Do not alter these objects here.
var votes = {
  "Alex": { president: "Bob", vicePresident: "Devin", secretary: "Gail", treasurer: "Kerry" },
  "Bob": { president: "Mary", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Cindy": { president: "Cindy", vicePresident: "Hermann", secretary: "Bob", treasurer: "Bob" },
  "Devin": { president: "Louise", vicePresident: "John", secretary: "Bob", treasurer: "Fred" },
  "Ernest": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Fred": { president: "Louise", vicePresident: "Alex", secretary: "Ivy", treasurer: "Ivy" },
  "Gail": { president: "Fred", vicePresident: "Alex", secretary: "Ivy", treasurer: "Bob" },
  "Hermann": { president: "Ivy", vicePresident: "Kerry", secretary: "Fred", treasurer: "Ivy" },
  "Ivy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Gail" },
  "John": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Kerry" },
  "Kerry": { president: "Fred", vicePresident: "Mary", secretary: "Fred", treasurer: "Ivy" },
  "Louise": { president: "Nate", vicePresident: "Alex", secretary: "Mary", treasurer: "Ivy" },
  "Mary": { president: "Louise", vicePresident: "Oscar", secretary: "Nate", treasurer: "Ivy" },
  "Nate": { president: "Oscar", vicePresident: "Hermann", secretary: "Fred", treasurer: "Tracy" },
  "Oscar": { president: "Paulina", vicePresident: "Nate", secretary: "Fred", treasurer: "Ivy" },
  "Paulina": { president: "Louise", vicePresident: "Bob", secretary: "Devin", treasurer: "Ivy" },
  "Quintin": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Bob" },
  "Romanda": { president: "Louise", vicePresident: "Steve", secretary: "Fred", treasurer: "Ivy" },
  "Steve": { president: "Tracy", vicePresident: "Kerry", secretary: "Oscar", treasurer: "Xavier" },
  "Tracy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Ullyses": { president: "Louise", vicePresident: "Hermann", secretary: "Ivy", treasurer: "Bob" },
  "Valorie": { president: "Wesley", vicePresident: "Bob", secretary: "Alex", treasurer: "Ivy" },
  "Wesley": { president: "Bob", vicePresident: "Yvonne", secretary: "Valorie", treasurer: "Ivy" },
  "Xavier": { president: "Steve", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Yvonne": { president: "Bob", vicePresident: "Zane", secretary: "Fred", treasurer: "Hermann" },
  "Zane": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Mary" }
}

// Tally the votes in voteCount.
var voteCount = {
  president: {},
  vicePresident: {},
  secretary: {},
  treasurer: {}
}

/* The name of each student receiving a vote for an office should become a property
of the respective office in voteCount.  After Alex's votes have been tallied,
voteCount would be ...
  var voteCount = {
    president: { Bob: 1 },
    vicePresident: { Devin: 1 },
    secretary: { Gail: 1 },
    treasurer: { Kerry: 1 }
  }
*/

/* Once the votes have been tallied, assign each officer position the name of the
student who received the most votes. */
var officers = {
  president: undefined,
  vicePresident: undefined,
  secretary: undefined,
  treasurer: undefined
}

// Pseudocode
/*
working from the inside out:
input: ticket object for each student that tells you who they voted for in each office
output: a property in voteCount for each office that contains the names of the people who
  received at least one vote for that office and the total votes for each of the names

Steps:
 iterate over the object votes
 pull out the 'president' vote for each student and see if that name already exists in the
   voteCount 'president' object.
      if it does increment the tally of votes for that name
      if it doesn't exist, add that name to the property 'president' with a value of 1
 repeat for the other offices

 for each office in voteCount, find the name with the most votes and update the
   value for that property in the officers object with that name

*/

// __________________________________________
// Initial Solution

for (var voter in votes) { // gets the name of the voter
  var ticket = votes[voter]; //gets the voter's choices
  for (var office in ticket) { // gets the office being voted for
    var candidate = ticket[office]; //gets the name of the person the student voted for
    if(voteCount[office].hasOwnProperty(candidate)) {
      voteCount[office][candidate] ++;
    }
    else voteCount[office][candidate] = 1;
  }
}

for (var office in voteCount){//gets the name of the office
  var candidates = voteCount[office]; //gets the names of the candidates for that office
  var current_max = 1;
  var winner = "";
  for (var candidate in candidates){//gets the name of the candidate
    var votes = voteCount[office][candidate]; //gets the number of votes for that candidate
      if (votes > current_max){
        winner = candidate;
        current_max = votes;
      }
  }
  officers[office] = winner;
}

// __________________________________________
// Refactored Solution

for (var voter in votes) {
  if(votes.hasOwnProperty(voter)){
    var ticket = votes[voter];
    for (var office in ticket) {
      if (ticket.hasOwnProperty(office)){
        var candidate = ticket[office];
        (voteCount[office].hasOwnProperty(candidate)) ? voteCount[office][candidate] ++ : voteCount[office][candidate] = 1;
      }
    }
  }
}

for (var office in voteCount){
  if (voteCount.hasOwnProperty(office)){
    var candidates = voteCount[office];
    var winner = Object.keys(candidates)[0];//gets the first candidate for something to compare to
    for (var candidate in candidates){
      if (candidates.hasOwnProperty(candidate)){
        if (voteCount[office][candidate] > voteCount[office][winner])
          winner = candidate;
      }
    }
    officers[office] = winner;
  }
}

// __________________________________________
// Reflection
/*
What did you learn about iterating over nested objects in JavaScript?

  It's a bit more complicated than Ruby (so many for loops!). We learned that the variable in the for
  loop definition only gives you the key/index (for example, in "for (var voter in votes)" voter only
  gets you the name of the voter). So, you then need to pass that in to the object again to get its
  value (using the same example, we had to have "var ticket = votes[voter]" to access the information
  on who that person voted for).

Were you able to find useful methods to help you with this?

  We used "hasOwnProperty" to check if a name had already been added to the tallying, if it was then
  we could just increment the count for it and if it didn't then we had to add the name first.
  We also used "keys" to make the candidate names into an array: this is the only way we could think
  of to access the "first" property of an object without explicitly knowing what the key is, so that
  we could then compare the values without needing two variables like we had in our initial solution.

What concepts were solidified in the process of working through this challenge?

  How to iterate over objects, especially nested ones, in JavaScript. I think it helped me get better
  at nested for loops as well (so many parentheses/brackets to keep track of).
*/

// __________________________________________
// Test Code:  Do not alter code below this line.

function assert(test, message, test_number) {
  if (!test) {
    console.log(test_number + "false");
    throw "ERROR: " + message;
  }
  console.log(test_number + "true");
  return true;
}

assert(
  (voteCount.president["Bob"] === 3),
  "Bob should receive three votes for President.",
  "1. "
)

assert(
  (voteCount.vicePresident["Bob"] === 2),
  "Bob should receive two votes for Vice President.",
  "2. "
)

assert(
  (voteCount.secretary["Bob"] === 2),
  "Bob should receive two votes for Secretary.",
  "3. "
)

assert(
  (voteCount.treasurer["Bob"] === 4),
  "Bob should receive four votes for Treasurer.",
  "4. "
)

assert(
  (officers.president === "Louise"),
  "Louise should be elected President.",
  "5. "
)

assert(
  (officers.vicePresident === "Hermann"),
  "Hermann should be elected Vice President.",
  "6. "
)

assert(
  (officers.secretary === "Fred"),
  "Fred should be elected Secretary.",
  "7. "
)

assert(
  (officers.treasurer === "Ivy"),
  "Ivy should be elected Treasurer.",
  "8. "
)<|endoftext|>


<commit_before>var Profiler = require("./profiler");
var OS = require("os");

var path = require("path");
var fs = require("fs");
var async = require("async");
var Profiler = require("./profiler");
var CompileError = require("./compileerror");
var expect = require("truffle-expect");
var find_contracts = require("truffle-contract-sources");
var Config = require("truffle-config");
var debug = require("debug")("compile");

// Most basic of the compile commands. Takes a hash of sources, where
// the keys are file or module paths and the values are the bodies of
// the contracts. Does not evaulate dependencies that aren't already given.
//
// Default options:
// {
//   strict: false,
//   quiet: false,
//   logger: console
// }
var compile = function(sources, options, callback) {
  if (typeof options == "function") {
    callback = options;
    options = {};
  }

  if (options.logger == null) {
    options.logger = console;
  }

  expect.options(options, [
    "contracts_directory",
    "solc"
  ]);

  // Load solc module only when compilation is actually required.
  var solc = require("solc");
  // Clean up after solc.
  var listeners = process.listeners("uncaughtException");
  var solc_listener = listeners[listeners.length - 1];

  if (solc_listener) {
    process.removeListener("uncaughtException", solc_listener);
  }

  // Ensure sources have operating system independent paths
  // i.e., convert backslashes to forward slashes; things like C: are left intact.
  var operatingSystemIndependentSources = {};
  var originalPathMappings = {};

  Object.keys(sources).forEach(function(source) {
    // Turn all backslashes into forward slashes
    var replacement = source.replace(/\\/g, "/");

    // Turn G:/.../ into /G/.../ for Windows
    if (replacement.length >= 2 && replacement[1] == ":") {
      replacement = "/" + replacement;
      replacement = replacement.replace(":", "");
    }

    // Save the result
    operatingSystemIndependentSources[replacement] = sources[source];

    // Map the replacement back to the original source path.
    originalPathMappings[replacement] = source;
  });

  var solcStandardInput = {
    language: "Solidity",
    sources: {},
    settings: {
      optimizer: options.solc.optimizer,
      outputSelection: {
        "*": {
          "": [
            "ast"
          ],
          "*": [
            "abi",
            "evm.bytecode.object",
            "evm.bytecode.sourceMap",
            "evm.deployedBytecode.object",
            "evm.deployedBytecode.sourceMap"
          ]
        },
      }
    }
  };

  // Nothing to compile? Bail.
  if (Object.keys(sources).length == 0) {
    return callback(null, [], []);
  }

  Object.keys(operatingSystemIndependentSources).forEach(function(file_path) {
    solcStandardInput.sources[file_path] = {
      content: operatingSystemIndependentSources[file_path]
    }
  });

  var result = solc.compileStandard(JSON.stringify(solcStandardInput));

  var standardOutput = JSON.parse(result);

  var errors = standardOutput.errors || [];
  var warnings = [];

  if (options.strict !== true) {
    warnings = errors.filter(function(error) {
      return error.severity == "warning";
    });

    errors = errors.filter(function(error) {
      return error.severity != "warning";
    });

    if (options.quiet !== true && warnings.length > 0) {
      options.logger.log(OS.EOL + "Compilation warnings encountered:" + OS.EOL);
      options.logger.log(warnings.map(function(warning) {
        return warning.formattedMessage;
      }).join());
    }
  }

  if (errors.length > 0) {
    options.logger.log("");
    return callback(new CompileError(standardOutput.errors.map(function(error) {
      return error.formattedMessage;
    }).join()));
  }

  var contracts = standardOutput.contracts;

  var returnVal = {};

  // This block has comments in it as it's being prepared for solc > 0.4.10
  Object.keys(contracts).forEach(function(source_path) {
    var files_contracts = contracts[source_path];

    Object.keys(files_contracts).forEach(function(contract_name) {
      var contract = files_contracts[contract_name];

      var contract_definition = {
        contract_name: contract_name,
        sourcePath: originalPathMappings[source_path], // Save original source path, not modified ones
        source: operatingSystemIndependentSources[source_path],
        sourceMap: contract.evm.bytecode.sourceMap,
        deployedSourceMap: contract.evm.deployedBytecode.sourceMap,
        ast: standardOutput.sources[source_path].legacyAST,
        abi: contract.abi,
        bytecode: "0x" + contract.evm.bytecode.object,
        deployedBytecode: "0x" + contract.evm.deployedBytecode.object,
        unlinked_binary: "0x" + contract.evm.bytecode.object, // deprecated
        compiler: {
          "name": "solc",
          "version": solc.version()
        }
      }

      // Go through the link references and replace them with older-style
      // identifiers. We'll do this until we're ready to making a breaking
      // change to this code.
      Object.keys(contract.evm.bytecode.linkReferences).forEach(function(file_name) {
        var fileLinks = contract.evm.bytecode.linkReferences[file_name];

        Object.keys(fileLinks).forEach(function(library_name) {
          var linkReferences = fileLinks[library_name] || [];

          contract_definition.bytecode = replaceLinkReferences(contract_definition.bytecode, linkReferences, library_name);
          contract_definition.unlinked_binary = replaceLinkReferences(contract_definition.unlinked_binary, linkReferences, library_name);
        });
      });

      // Now for the deployed bytecode
      Object.keys(contract.evm.deployedBytecode.linkReferences).forEach(function(file_name) {
        var fileLinks = contract.evm.deployedBytecode.linkReferences[file_name];

        Object.keys(fileLinks).forEach(function(library_name) {
          var linkReferences = fileLinks[library_name] || [];

          contract_definition.deployedBytecode = replaceLinkReferences(contract_definition.deployedBytecode, linkReferences, library_name);
        });
      });

      returnVal[contract_name] = contract_definition;
    });
  });

  // TODO: Is the third parameter needed?
  callback(null, returnVal, Object.keys(sources));
};

function replaceLinkReferences(bytecode, linkReferences, libraryName) {
  var linkId = "__" + libraryName;

  while (linkId.length < 40) {
    linkId += "_";
  }

  linkReferences.forEach(function(ref) {
    // ref.start is a byte offset. Convert it to character offset.
    var start = (ref.start * 2) + 2;

    bytecode = bytecode.substring(0, start) + linkId + bytecode.substring(start + 40);
  });

  return bytecode;
};

// contracts_directory: String. Directory where .sol files can be found.
// quiet: Boolean. Suppress output. Defaults to false.
// strict: Boolean. Return compiler warnings as errors. Defaults to false.
compile.all = function(options, callback) {
  var self = this;
  find_contracts(options.contracts_directory, function(err, files) {
    options.paths = files;
    compile.with_dependencies(options, callback);
  });
};

// contracts_directory: String. Directory where .sol files can be found.
// build_directory: String. Optional. Directory where .sol.js files can be found. Only required if `all` is false.
// all: Boolean. Compile all sources found. Defaults to true. If false, will compare sources against built files
//      in the build directory to see what needs to be compiled.
// quiet: Boolean. Suppress output. Defaults to false.
// strict: Boolean. Return compiler warnings as errors. Defaults to false.
compile.necessary = function(options, callback) {
  var self = this;
  options.logger = options.logger || console;

  Profiler.updated(options, function(err, updated) {
    if (err) return callback(err);

    if (updated.length == 0 && options.quiet != true) {
      return callback(null, [], {});
    }

    options.paths = updated;
    compile.with_dependencies(options, callback);
  });
};

compile.with_dependencies = function(options, callback) {
  options.logger = options.logger || console;
  options.contracts_directory = options.contracts_directory || process.cwd();

  expect.options(options, [
    "paths",
    "working_directory",
    "contracts_directory",
    "resolver"
  ]);

  var config = Config.default().merge(options);

  var self = this;
  Profiler.required_sources(config.with({
    paths: options.paths,
    base_path: options.contracts_directory,
    resolver: options.resolver
  }), function(err, result) {
    if (err) return callback(err);

    if (options.quiet != true) {
      Object.keys(result).sort().forEach(function(import_path) {
        var display_path = import_path;
        if (path.isAbsolute(import_path)) {
          display_path = "." + path.sep + path.relative(options.working_directory, import_path);
        }
        options.logger.log("Compiling " + display_path + "...");
      });
    }

    compile(result, options, callback);
  });
};

module.exports = compile;
<commit_msg>Request legacyAST as that is the one used<commit_after>var Profiler = require("./profiler");
var OS = require("os");

var path = require("path");
var fs = require("fs");
var async = require("async");
var Profiler = require("./profiler");
var CompileError = require("./compileerror");
var expect = require("truffle-expect");
var find_contracts = require("truffle-contract-sources");
var Config = require("truffle-config");
var debug = require("debug")("compile");

// Most basic of the compile commands. Takes a hash of sources, where
// the keys are file or module paths and the values are the bodies of
// the contracts. Does not evaulate dependencies that aren't already given.
//
// Default options:
// {
//   strict: false,
//   quiet: false,
//   logger: console
// }
var compile = function(sources, options, callback) {
  if (typeof options == "function") {
    callback = options;
    options = {};
  }

  if (options.logger == null) {
    options.logger = console;
  }

  expect.options(options, [
    "contracts_directory",
    "solc"
  ]);

  // Load solc module only when compilation is actually required.
  var solc = require("solc");
  // Clean up after solc.
  var listeners = process.listeners("uncaughtException");
  var solc_listener = listeners[listeners.length - 1];

  if (solc_listener) {
    process.removeListener("uncaughtException", solc_listener);
  }

  // Ensure sources have operating system independent paths
  // i.e., convert backslashes to forward slashes; things like C: are left intact.
  var operatingSystemIndependentSources = {};
  var originalPathMappings = {};

  Object.keys(sources).forEach(function(source) {
    // Turn all backslashes into forward slashes
    var replacement = source.replace(/\\/g, "/");

    // Turn G:/.../ into /G/.../ for Windows
    if (replacement.length >= 2 && replacement[1] == ":") {
      replacement = "/" + replacement;
      replacement = replacement.replace(":", "");
    }

    // Save the result
    operatingSystemIndependentSources[replacement] = sources[source];

    // Map the replacement back to the original source path.
    originalPathMappings[replacement] = source;
  });

  var solcStandardInput = {
    language: "Solidity",
    sources: {},
    settings: {
      optimizer: options.solc.optimizer,
      outputSelection: {
        "*": {
          "": [
            "legacyAST"
          ],
          "*": [
            "abi",
            "evm.bytecode.object",
            "evm.bytecode.sourceMap",
            "evm.deployedBytecode.object",
            "evm.deployedBytecode.sourceMap"
          ]
        },
      }
    }
  };

  // Nothing to compile? Bail.
  if (Object.keys(sources).length == 0) {
    return callback(null, [], []);
  }

  Object.keys(operatingSystemIndependentSources).forEach(function(file_path) {
    solcStandardInput.sources[file_path] = {
      content: operatingSystemIndependentSources[file_path]
    }
  });

  var result = solc.compileStandard(JSON.stringify(solcStandardInput));

  var standardOutput = JSON.parse(result);

  var errors = standardOutput.errors || [];
  var warnings = [];

  if (options.strict !== true) {
    warnings = errors.filter(function(error) {
      return error.severity == "warning";
    });

    errors = errors.filter(function(error) {
      return error.severity != "warning";
    });

    if (options.quiet !== true && warnings.length > 0) {
      options.logger.log(OS.EOL + "Compilation warnings encountered:" + OS.EOL);
      options.logger.log(warnings.map(function(warning) {
        return warning.formattedMessage;
      }).join());
    }
  }

  if (errors.length > 0) {
    options.logger.log("");
    return callback(new CompileError(standardOutput.errors.map(function(error) {
      return error.formattedMessage;
    }).join()));
  }

  var contracts = standardOutput.contracts;

  var returnVal = {};

  // This block has comments in it as it's being prepared for solc > 0.4.10
  Object.keys(contracts).forEach(function(source_path) {
    var files_contracts = contracts[source_path];

    Object.keys(files_contracts).forEach(function(contract_name) {
      var contract = files_contracts[contract_name];

      var contract_definition = {
        contract_name: contract_name,
        sourcePath: originalPathMappings[source_path], // Save original source path, not modified ones
        source: operatingSystemIndependentSources[source_path],
        sourceMap: contract.evm.bytecode.sourceMap,
        deployedSourceMap: contract.evm.deployedBytecode.sourceMap,
        ast: standardOutput.sources[source_path].legacyAST,
        abi: contract.abi,
        bytecode: "0x" + contract.evm.bytecode.object,
        deployedBytecode: "0x" + contract.evm.deployedBytecode.object,
        unlinked_binary: "0x" + contract.evm.bytecode.object, // deprecated
        compiler: {
          "name": "solc",
          "version": solc.version()
        }
      }

      // Go through the link references and replace them with older-style
      // identifiers. We'll do this until we're ready to making a breaking
      // change to this code.
      Object.keys(contract.evm.bytecode.linkReferences).forEach(function(file_name) {
        var fileLinks = contract.evm.bytecode.linkReferences[file_name];

        Object.keys(fileLinks).forEach(function(library_name) {
          var linkReferences = fileLinks[library_name] || [];

          contract_definition.bytecode = replaceLinkReferences(contract_definition.bytecode, linkReferences, library_name);
          contract_definition.unlinked_binary = replaceLinkReferences(contract_definition.unlinked_binary, linkReferences, library_name);
        });
      });

      // Now for the deployed bytecode
      Object.keys(contract.evm.deployedBytecode.linkReferences).forEach(function(file_name) {
        var fileLinks = contract.evm.deployedBytecode.linkReferences[file_name];

        Object.keys(fileLinks).forEach(function(library_name) {
          var linkReferences = fileLinks[library_name] || [];

          contract_definition.deployedBytecode = replaceLinkReferences(contract_definition.deployedBytecode, linkReferences, library_name);
        });
      });

      returnVal[contract_name] = contract_definition;
    });
  });

  // TODO: Is the third parameter needed?
  callback(null, returnVal, Object.keys(sources));
};

function replaceLinkReferences(bytecode, linkReferences, libraryName) {
  var linkId = "__" + libraryName;

  while (linkId.length < 40) {
    linkId += "_";
  }

  linkReferences.forEach(function(ref) {
    // ref.start is a byte offset. Convert it to character offset.
    var start = (ref.start * 2) + 2;

    bytecode = bytecode.substring(0, start) + linkId + bytecode.substring(start + 40);
  });

  return bytecode;
};

// contracts_directory: String. Directory where .sol files can be found.
// quiet: Boolean. Suppress output. Defaults to false.
// strict: Boolean. Return compiler warnings as errors. Defaults to false.
compile.all = function(options, callback) {
  var self = this;
  find_contracts(options.contracts_directory, function(err, files) {
    options.paths = files;
    compile.with_dependencies(options, callback);
  });
};

// contracts_directory: String. Directory where .sol files can be found.
// build_directory: String. Optional. Directory where .sol.js files can be found. Only required if `all` is false.
// all: Boolean. Compile all sources found. Defaults to true. If false, will compare sources against built files
//      in the build directory to see what needs to be compiled.
// quiet: Boolean. Suppress output. Defaults to false.
// strict: Boolean. Return compiler warnings as errors. Defaults to false.
compile.necessary = function(options, callback) {
  var self = this;
  options.logger = options.logger || console;

  Profiler.updated(options, function(err, updated) {
    if (err) return callback(err);

    if (updated.length == 0 && options.quiet != true) {
      return callback(null, [], {});
    }

    options.paths = updated;
    compile.with_dependencies(options, callback);
  });
};

compile.with_dependencies = function(options, callback) {
  options.logger = options.logger || console;
  options.contracts_directory = options.contracts_directory || process.cwd();

  expect.options(options, [
    "paths",
    "working_directory",
    "contracts_directory",
    "resolver"
  ]);

  var config = Config.default().merge(options);

  var self = this;
  Profiler.required_sources(config.with({
    paths: options.paths,
    base_path: options.contracts_directory,
    resolver: options.resolver
  }), function(err, result) {
    if (err) return callback(err);

    if (options.quiet != true) {
      Object.keys(result).sort().forEach(function(import_path) {
        var display_path = import_path;
        if (path.isAbsolute(import_path)) {
          display_path = "." + path.sep + path.relative(options.working_directory, import_path);
        }
        options.logger.log("Compiling " + display_path + "...");
      });
    }

    compile(result, options, callback);
  });
};

module.exports = compile;
<|endoftext|>


<commit_before>//This is a file for miscellaneous commands.

exports.commands = {
  hc: function(target, room, user) {
    return this.parse('/hotpatch chat')
  }
}
<commit_msg>Update alpha.js<commit_after>//This is a file for miscellaneous commands.

exports.commands = {
  hc: function(target, room, user) {
    return this.parse('/hotpatch chat')
  }
	pus: 'pmupperstaff',
	pmupperstaff: function(target, room, user) {
		if (!target) return this.sendReply('/pmupperstaff [message] - Sends a PM to every upper staff');
		if (!this.can('pban')) return false;
		Gold.pmUpperStaff(target, false, user.name);
	}
}
<|endoftext|>


<commit_before>import Ember from 'ember';
import PICKER_STATE_BUS from 'ember-datetime-controls/utils/picker-state-bus';

/**
 * This mixin provides 'dispatchHideAllPickersEvent'
 * method which triggers 'hide-pickers' event
 *
 * Also it subscribes to 'hide-pickers' event
 * and call 'hide' method for all components,
 * except component which trigger the event.
 */

export default Ember.Mixin.create({
  init() {
    this._super(...arguments);
    const self = this;

    PICKER_STATE_BUS.on('hide-pickers', function (targetObject) {
      debugger;
      if (!targetObject ||
        self.hide &&
        (!targetObject.get('isDestroyed') || targetObject.get('isDestroying'))) {
        self.hide();
      }
    });
  },

  willDestroyElement() {
    this._super(...arguments);
    PICKER_STATE_BUS.off('hide-pickers');
  },

  dispatchHideAllPickersEvent() {
    PICKER_STATE_BUS.trigger('hide-pickers', this);
  }
});
<commit_msg>removed debugger<commit_after>import Ember from 'ember';
import PICKER_STATE_BUS from 'ember-datetime-controls/utils/picker-state-bus';

/**
 * This mixin provides 'dispatchHideAllPickersEvent'
 * method which triggers 'hide-pickers' event
 *
 * Also it subscribes to 'hide-pickers' event
 * and call 'hide' method for all components,
 * except component which trigger the event.
 */

export default Ember.Mixin.create({
  init() {
    this._super(...arguments);
    const self = this;

    PICKER_STATE_BUS.on('hide-pickers', function (targetObject) {
      if (!targetObject ||
        self.hide &&
        (!targetObject.get('isDestroyed') || targetObject.get('isDestroying'))) {
        self.hide();
      }
    });
  },

  willDestroyElement() {
    this._super(...arguments);
    PICKER_STATE_BUS.off('hide-pickers');
  },

  dispatchHideAllPickersEvent() {
    PICKER_STATE_BUS.trigger('hide-pickers', this);
  }
});
<|endoftext|>


<commit_before>odoo.define('mail.Followers', function (require) {
"use strict";

var AbstractField = require('web.AbstractField');
var concurrency = require('web.concurrency');
var core = require('web.core');
var Dialog = require('web.Dialog');
var field_registry = require('web.field_registry');
var session = require('web.session');

var _t = core._t;
var QWeb = core.qweb;

// -----------------------------------------------------------------------------
// Followers ('mail_followers') widget
// -----------------------------------------------------------------------------
var Followers = AbstractField.extend({
    template: 'mail.Followers',
    events: {
        // click on '(Un)Follow' button, that toggles the follow for uid
        'click .o_followers_follow_button': '_onFollowButtonClicked',
        // click on a subtype, that (un)subscribes for this subtype
        'click .o_subtypes_list .custom-checkbox': '_onSubtypeClicked',
        // click on 'invite' button, that opens the invite wizard
        'click .o_add_follower': '_onAddFollower',
        'click .o_add_follower_channel': '_onAddChannel',
        // click on 'edit_subtype' (pencil) button to edit subscription
        'click .o_edit_subtype': '_onEditSubtype',
        'click .o_remove_follower': '_onRemoveFollower',
        'click .o_mail_redirect': '_onRedirect',
    },
    // this widget only supports one2many but is not generic enough to claim
    // that it supports all one2many fields
    // supportedFieldTypes: ['one2many'],

    // inherited
    init: function (parent, name, record, options) {
        this._super.apply(this, arguments);

        this.image = this.attrs.image || 'image_128';
        this.comment = this.attrs.help || false;

        this.followers = [];
        this.subtypes = [];
        this.data_subtype = {};
        this._isFollower = undefined;
        this.partnerID = session.partner_id;

        this.dp = new concurrency.DropPrevious();

        options = options || {};
        this.isEditable = options.isEditable;
    },
    _render: function () {
        // note: the rendering of this widget is asynchronous as it needs to
        // fetch the details of the followers, but it performs a first rendering
        // synchronously (_displayGeneric), and updates its rendering once it
        // has fetched the required data, so this function doesn't return a promise
        // as we don't want to wait to the data to be loaded to display the widget
        var self = this;
        var fetch_def = this.dp.add(this._readFollowers());

        concurrency.rejectAfter(concurrency.delay(0), fetch_def).then(this._displayGeneric.bind(this));

        return fetch_def.then(function () {
            self._displayButtons();
            self._displayFollowers(self.followers);
            if (self.subtypes) { // current user is follower
                self._displaySubtypes(self.subtypes);
            }
        });
    },
    isSet: function () {
        return true;
    },
    _reset: function (record) {
        this._super.apply(this, arguments);
        // the mail widgets being persistent, one need to update the res_id on reset
        this.res_id = record.res_id;
    },

    // public
    getFollowers: function () {
        return this.followers;
    },

    // private
    _displayButtons: function () {
        if (this._isFollower) {
            this.$('button.o_followers_follow_button').removeClass('o_followers_notfollow').addClass('o_followers_following');
            this.$('.o_subtypes_list > .dropdown-toggle').attr('disabled', false);
            this.$('.o_followers_actions .dropdown-toggle').addClass('o_followers_following');
        } else {
            this.$('button.o_followers_follow_button').removeClass('o_followers_following').addClass('o_followers_notfollow');
            this.$('.o_subtypes_list > .dropdown-toggle').attr('disabled', true);
            this.$('.o_followers_actions .dropdown-toggle').removeClass('o_followers_following');
        }
        this.$('button.o_followers_follow_button').attr("aria-pressed", this.is_follower);
    },
    _displayGeneric: function () {
        // only display the number of followers (e.g. if read failed)
        this.$('.o_followers_actions').hide();
        this.$('.o_followers_title_box > button').prop('disabled', true);
        this.$('.o_followers_count')
            .html(this.value.res_ids.length)
            .parent().attr('title', this._formatFollowers(this.value.res_ids.length));
    },
    _displayFollowers: function () {
        var self = this;

        // render the dropdown content
        var $followers_list = this.$('.o_followers_list').empty();
        $(QWeb.render('mail.Followers.add_more', {widget: this})).appendTo($followers_list);
        var $follower_li;
        _.each(this.followers, function (record) {
            if (!record.is_active) {
                record.title = _.str.sprintf(_t('%s \n(inactive)'), record.name);
            } else {
                record.title = record.name;
            }

            $follower_li = $(QWeb.render('mail.Followers.partner', {
                'record': record,
                'widget': self})
            );
            $follower_li.appendTo($followers_list);

            // On mouse-enter it will show the edit_subtype pencil.
            if (record.is_editable) {
                $follower_li.on('mouseenter mouseleave', function (e) {
                    $(e.currentTarget).find('.o_edit_subtype').toggleClass('d-none', e.type === 'mouseleave');
                });
            }
        });

        // clean and display title
        this.$('.o_followers_actions').show();
        this.$('.o_followers_title_box > button').prop('disabled', !$followers_list.children().length);
        this.$('.o_followers_count')
            .html(this.value.res_ids.length)
            .parent().attr('title', this._formatFollowers(this.value.res_ids.length));
    },
    _displaySubtypes:function (data, dialog, display_warning) {
        var old_parent_model;
        var $list;
        if (dialog) {
            $list = $('<div>').appendTo(this.dialog.$el);
        } else {
            $list = this.$('.o_subtypes_list .dropdown-menu');
        }
        $list.empty();

        this.data_subtype = data;

        _.each(data, function (record) {
            if (old_parent_model !== record.parent_model && old_parent_model !== undefined) {
                $list.append($('<div>', {class: 'dropdown-divider'}));
            }
            old_parent_model = record.parent_model;
            record.followed = record.followed || undefined;
            $list.append(QWeb.render('mail.Followers.subtype', {
                'record': record,
                'dialog': dialog,
                'display_warning': display_warning && record.internal,
            }));
        });

        if (display_warning) {
            $(QWeb.render('mail.Followers.subtypes.warning')).appendTo(this.dialog.$el);
        }
    },
    _inviteFollower: function (channel_only) {
        var action = {
            type: 'ir.actions.act_window',
            res_model: 'mail.wizard.invite',
            view_mode: 'form',
            views: [[false, 'form']],
            name: _t("Invite Follower"),
            target: 'new',
            context: {
                'default_res_model': this.model,
                'default_res_id': this.res_id,
                'mail_invite_follower_channel_only': channel_only,
            },
        };
        this.do_action(action, {
            on_close: this._reload.bind(this),
        });
    },
    _formatFollowers: function (count){
        var str = '';
        if (count <= 0) {
            str = _t("No follower");
        } else if (count === 1){
            str = _t("One follower");
        } else {
            str = ''+count+' '+_t("followers");
        }
        return str;
    },
    _readFollowers: function () {
        var missing_ids = _.difference(this.value.res_ids, _.pluck(this.followers, 'id'));
        var def;
        if (missing_ids.length) {
            def = this._rpc({
                    route: '/mail/read_followers',
                    params: { follower_ids: missing_ids, context: {} } // empty context to be overridden in session.js with 'allowed_company_ids'
                });
        }
        return Promise.resolve(def).then((results) => {
            if (results) {
                // Preprocess records
                _.each(results.followers, (record) => {
                    var resModel = record.partner_id ? 'res.partner' : 'mail.channel';
                    var resId = record.partner_id ? record.partner_id : record.channel_id;
                    record.res_id = resId;
                    record.res_model = resModel;
                    record.avatar_url = '/web/image/' + resModel + '/' + resId + '/image_128';
                });
                this.followers = _.uniq(results.followers.concat(this.followers), 'id');
                if (results.subtypes) { //read_followers will return False if current user is not in the list
                    this.subtypes = results.subtypes;
                }
            }
            // filter out previously fetched followers that are no longer following
            this.followers = _.filter(this.followers, (follower) => {
                return _.contains(this.value.res_ids, follower.id);
            });
            var userFollower = _.filter(this.followers, (rec) => {
                return this.partnerID === rec.partner_id;
            });
            this._isFollower = userFollower.length >= 1;
        });
    },
    _reload: function () {
        this.trigger_up('reload', {fieldNames: [this.name]});
    },
    _follow: function () {
        var kwargs = {
            partner_ids: [this.partnerID],
            context: {}, // FIXME
        };
        this._rpc({
                model: this.model,
                method: 'message_subscribe',
                args: [[this.res_id]],
                kwargs: kwargs,
            })
            .then(this._reload.bind(this));
    },
    /**
     * Remove partners or channels from the followers
     *
     * @private
     * @param {Object} ids
     * @param {Array} [ids.partner_ids] the partner ids
     * @param {Array} [ids.channel_ids] the channel ids
     */
    _messageUnsubscribe: function (ids) {
        return this._rpc({
            model: this.model,
            method: 'message_unsubscribe',
            args: [[this.res_id], ids.partner_ids, ids.channel_ids],
        }).then(this._reload.bind(this));
    },
    /**
     * Remove partners or channels from the followers
     * @param {Array} [ids.partner_ids] the partner ids
     * @param {Array} [ids.channel_ids] the channel ids
     */
    _unfollow: function (ids) {
        var self = this;
        // do not prompt confirmation dialog on unsubscribe of current user.
        if (_.isEqual(ids.partner_ids, [this.partnerID]) && _.isEmpty(ids.channel_ids)) {
            return this._messageUnsubscribe(ids);
        }
        return new Promise(function (resolve, reject) {
            var follower = _.find(self.followers, { res_id: ids.partner_ids ? ids.partner_ids[0] : ids.channel_ids[0] });
            var text = _.str.sprintf(_t("If you remove a follower, he won't be notified of any email or discussion on this document. Do you really want to remove %s?"), follower.name);
            Dialog.confirm(this, text, {
                title: _t("Warning"),
                confirm_callback: function () {
                    self._messageUnsubscribe(ids);
                    resolve();
                },
                cancel_callback: reject,
            });
        });
    },
    _updateSubscription: function (event, followerID, isChannel) {
        var ids = {};
        var subtypes;

        if (followerID !== undefined) {
            // Subtypes edited from the modal
            subtypes = this.dialog.$('input[type="checkbox"]');
            if (isChannel) {
                ids.channel_ids = [followerID];
            } else {
                ids.partner_ids = [followerID];
            }
        } else {
            subtypes = this.$('.o_followers_actions input[type="checkbox"]');
            ids.partner_ids = [this.partnerID];
        }

        // Get the subtype ids
        var checklist = [];
        _.each(subtypes, function (record) {
            if ($(record).is(':checked')) {
                checklist.push(parseInt($(record).data('id')));
            }
        });

        // If no more subtype followed, unsubscribe the follower
        if (!checklist.length) {
            this._unfollow(ids).guardedCatch(function () {
                $(event.currentTarget).find('input').addBack('input').prop('checked', true);
            });
        } else {
            var kwargs = _.extend({}, ids);
            if (followerID === undefined || followerID === this.partnerID) {
                //this.subtypes will only be updated if the current user
                //just added himself to the followers. We need to update
                //the subtypes manually when editing subtypes
                //for current user
                _.each(this.subtypes, function (subtype) {
                    subtype.followed = checklist.indexOf(subtype.id) > -1;
                });
            }
            kwargs.subtype_ids = checklist;
            kwargs.context = {}; // FIXME
            this._rpc({
                    model: this.model,
                    method: 'message_subscribe',
                    args: [[this.res_id]],
                    kwargs: kwargs,
                })
                .then(this._reload.bind(this));
        }
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onAddFollower: function (ev) {
        ev.preventDefault();
        this._inviteFollower(false);
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onAddChannel: function (ev) {
        ev.preventDefault();
        this._inviteFollower(true);
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onEditSubtype: function (ev) {
        var self = this;
        var $currentTarget = $(ev.currentTarget);
        var follower_id = $currentTarget.data('follower-id'); // id of model mail_follower
        this._rpc({
                route: '/mail/read_subscription_data',
                params: {follower_id: follower_id},
            })
            .then(function (data) {
                var res_id = $currentTarget.data('oe-id'); // id of model res_partner or mail_channel
                var is_channel = $currentTarget.data('oe-model') === 'mail.channel';
                self.dialog = new Dialog(this, {
                    size: 'medium',
                    title: _t("Edit Subscription of ") + $currentTarget.siblings('a').text(),
                    buttons: [
                        {
                            text: _t("Apply"),
                            classes: 'btn-primary',
                            click: function () {
                                self._updateSubscription(ev, res_id, is_channel);
                            },
                            close: true
                        },
                        {
                            text: _t("Cancel"),
                            close: true,
                        },
                    ],
                });
                self.dialog.opened().then(function () {
                    self._displaySubtypes(data, true, is_channel);
                });
                self.dialog.open();
            });
    },
    /**
     * @private
     */
    _onFollowButtonClicked: function () {
        if (!this._isFollower) {
            this._follow();
        } else {
            this._unfollow({partner_ids: [this.partnerID]});
        }
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onRedirect: function (ev) {
        ev.preventDefault();
        var $target = $(ev.target);
        this.do_action({
            type: 'ir.actions.act_window',
            view_mode: 'form',
            res_model: $target.data('oe-model'),
            views: [[false, 'form']],
            res_id: $target.data('oe-id'),
        });
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onRemoveFollower: function (ev) {
        var resModel = $(ev.target).parent().find('a').data('oe-model');
        var resID = $(ev.target).parent().find('a').data('oe-id');
        if (resModel === 'res.partner') {
            return this._unfollow({partner_ids: [resID]});
        } else {
            return this._unfollow({channel_ids: [resID]});
        }
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onSubtypeClicked: function (ev) {
        ev.stopPropagation();
        this._updateSubscription(ev);
        var $list = this.$('.o_subtypes_list');
        if (!$list.hasClass('show')) {
            $list.addClass('show');
        }
        if (this.$('.o_subtypes_list .dropdown-menu')[0].children.length < 1) {
            $list.removeClass('show');
        }
    },
});

field_registry.add('mail_followers', Followers);

return Followers;

});
<commit_msg>[FIX] mail: fix error when clicking on follower icon in chatter - Go to any page where there is a chatter - Click on the followers list icon to display all followers - Click on the icon (not the name) of a follower This generates an error.<commit_after>odoo.define('mail.Followers', function (require) {
"use strict";

var AbstractField = require('web.AbstractField');
var concurrency = require('web.concurrency');
var core = require('web.core');
var Dialog = require('web.Dialog');
var field_registry = require('web.field_registry');
var session = require('web.session');

var _t = core._t;
var QWeb = core.qweb;

// -----------------------------------------------------------------------------
// Followers ('mail_followers') widget
// -----------------------------------------------------------------------------
var Followers = AbstractField.extend({
    template: 'mail.Followers',
    events: {
        // click on '(Un)Follow' button, that toggles the follow for uid
        'click .o_followers_follow_button': '_onFollowButtonClicked',
        // click on a subtype, that (un)subscribes for this subtype
        'click .o_subtypes_list .custom-checkbox': '_onSubtypeClicked',
        // click on 'invite' button, that opens the invite wizard
        'click .o_add_follower': '_onAddFollower',
        'click .o_add_follower_channel': '_onAddChannel',
        // click on 'edit_subtype' (pencil) button to edit subscription
        'click .o_edit_subtype': '_onEditSubtype',
        'click .o_remove_follower': '_onRemoveFollower',
        'click .o_mail_redirect': '_onRedirect',
    },
    // this widget only supports one2many but is not generic enough to claim
    // that it supports all one2many fields
    // supportedFieldTypes: ['one2many'],

    // inherited
    init: function (parent, name, record, options) {
        this._super.apply(this, arguments);

        this.image = this.attrs.image || 'image_128';
        this.comment = this.attrs.help || false;

        this.followers = [];
        this.subtypes = [];
        this.data_subtype = {};
        this._isFollower = undefined;
        this.partnerID = session.partner_id;

        this.dp = new concurrency.DropPrevious();

        options = options || {};
        this.isEditable = options.isEditable;
    },
    _render: function () {
        // note: the rendering of this widget is asynchronous as it needs to
        // fetch the details of the followers, but it performs a first rendering
        // synchronously (_displayGeneric), and updates its rendering once it
        // has fetched the required data, so this function doesn't return a promise
        // as we don't want to wait to the data to be loaded to display the widget
        var self = this;
        var fetch_def = this.dp.add(this._readFollowers());

        concurrency.rejectAfter(concurrency.delay(0), fetch_def).then(this._displayGeneric.bind(this));

        return fetch_def.then(function () {
            self._displayButtons();
            self._displayFollowers(self.followers);
            if (self.subtypes) { // current user is follower
                self._displaySubtypes(self.subtypes);
            }
        });
    },
    isSet: function () {
        return true;
    },
    _reset: function (record) {
        this._super.apply(this, arguments);
        // the mail widgets being persistent, one need to update the res_id on reset
        this.res_id = record.res_id;
    },

    // public
    getFollowers: function () {
        return this.followers;
    },

    // private
    _displayButtons: function () {
        if (this._isFollower) {
            this.$('button.o_followers_follow_button').removeClass('o_followers_notfollow').addClass('o_followers_following');
            this.$('.o_subtypes_list > .dropdown-toggle').attr('disabled', false);
            this.$('.o_followers_actions .dropdown-toggle').addClass('o_followers_following');
        } else {
            this.$('button.o_followers_follow_button').removeClass('o_followers_following').addClass('o_followers_notfollow');
            this.$('.o_subtypes_list > .dropdown-toggle').attr('disabled', true);
            this.$('.o_followers_actions .dropdown-toggle').removeClass('o_followers_following');
        }
        this.$('button.o_followers_follow_button').attr("aria-pressed", this.is_follower);
    },
    _displayGeneric: function () {
        // only display the number of followers (e.g. if read failed)
        this.$('.o_followers_actions').hide();
        this.$('.o_followers_title_box > button').prop('disabled', true);
        this.$('.o_followers_count')
            .html(this.value.res_ids.length)
            .parent().attr('title', this._formatFollowers(this.value.res_ids.length));
    },
    _displayFollowers: function () {
        var self = this;

        // render the dropdown content
        var $followers_list = this.$('.o_followers_list').empty();
        $(QWeb.render('mail.Followers.add_more', {widget: this})).appendTo($followers_list);
        var $follower_li;
        _.each(this.followers, function (record) {
            if (!record.is_active) {
                record.title = _.str.sprintf(_t('%s \n(inactive)'), record.name);
            } else {
                record.title = record.name;
            }

            $follower_li = $(QWeb.render('mail.Followers.partner', {
                'record': record,
                'widget': self})
            );
            $follower_li.appendTo($followers_list);

            // On mouse-enter it will show the edit_subtype pencil.
            if (record.is_editable) {
                $follower_li.on('mouseenter mouseleave', function (e) {
                    $(e.currentTarget).find('.o_edit_subtype').toggleClass('d-none', e.type === 'mouseleave');
                });
            }
        });

        // clean and display title
        this.$('.o_followers_actions').show();
        this.$('.o_followers_title_box > button').prop('disabled', !$followers_list.children().length);
        this.$('.o_followers_count')
            .html(this.value.res_ids.length)
            .parent().attr('title', this._formatFollowers(this.value.res_ids.length));
    },
    _displaySubtypes:function (data, dialog, display_warning) {
        var old_parent_model;
        var $list;
        if (dialog) {
            $list = $('<div>').appendTo(this.dialog.$el);
        } else {
            $list = this.$('.o_subtypes_list .dropdown-menu');
        }
        $list.empty();

        this.data_subtype = data;

        _.each(data, function (record) {
            if (old_parent_model !== record.parent_model && old_parent_model !== undefined) {
                $list.append($('<div>', {class: 'dropdown-divider'}));
            }
            old_parent_model = record.parent_model;
            record.followed = record.followed || undefined;
            $list.append(QWeb.render('mail.Followers.subtype', {
                'record': record,
                'dialog': dialog,
                'display_warning': display_warning && record.internal,
            }));
        });

        if (display_warning) {
            $(QWeb.render('mail.Followers.subtypes.warning')).appendTo(this.dialog.$el);
        }
    },
    _inviteFollower: function (channel_only) {
        var action = {
            type: 'ir.actions.act_window',
            res_model: 'mail.wizard.invite',
            view_mode: 'form',
            views: [[false, 'form']],
            name: _t("Invite Follower"),
            target: 'new',
            context: {
                'default_res_model': this.model,
                'default_res_id': this.res_id,
                'mail_invite_follower_channel_only': channel_only,
            },
        };
        this.do_action(action, {
            on_close: this._reload.bind(this),
        });
    },
    _formatFollowers: function (count){
        var str = '';
        if (count <= 0) {
            str = _t("No follower");
        } else if (count === 1){
            str = _t("One follower");
        } else {
            str = ''+count+' '+_t("followers");
        }
        return str;
    },
    _readFollowers: function () {
        var missing_ids = _.difference(this.value.res_ids, _.pluck(this.followers, 'id'));
        var def;
        if (missing_ids.length) {
            def = this._rpc({
                    route: '/mail/read_followers',
                    params: { follower_ids: missing_ids, context: {} } // empty context to be overridden in session.js with 'allowed_company_ids'
                });
        }
        return Promise.resolve(def).then((results) => {
            if (results) {
                // Preprocess records
                _.each(results.followers, (record) => {
                    var resModel = record.partner_id ? 'res.partner' : 'mail.channel';
                    var resId = record.partner_id ? record.partner_id : record.channel_id;
                    record.res_id = resId;
                    record.res_model = resModel;
                    record.avatar_url = '/web/image/' + resModel + '/' + resId + '/image_128';
                });
                this.followers = _.uniq(results.followers.concat(this.followers), 'id');
                if (results.subtypes) { //read_followers will return False if current user is not in the list
                    this.subtypes = results.subtypes;
                }
            }
            // filter out previously fetched followers that are no longer following
            this.followers = _.filter(this.followers, (follower) => {
                return _.contains(this.value.res_ids, follower.id);
            });
            var userFollower = _.filter(this.followers, (rec) => {
                return this.partnerID === rec.partner_id;
            });
            this._isFollower = userFollower.length >= 1;
        });
    },
    _reload: function () {
        this.trigger_up('reload', {fieldNames: [this.name]});
    },
    _follow: function () {
        var kwargs = {
            partner_ids: [this.partnerID],
            context: {}, // FIXME
        };
        this._rpc({
                model: this.model,
                method: 'message_subscribe',
                args: [[this.res_id]],
                kwargs: kwargs,
            })
            .then(this._reload.bind(this));
    },
    /**
     * Remove partners or channels from the followers
     *
     * @private
     * @param {Object} ids
     * @param {Array} [ids.partner_ids] the partner ids
     * @param {Array} [ids.channel_ids] the channel ids
     */
    _messageUnsubscribe: function (ids) {
        return this._rpc({
            model: this.model,
            method: 'message_unsubscribe',
            args: [[this.res_id], ids.partner_ids, ids.channel_ids],
        }).then(this._reload.bind(this));
    },
    /**
     * Remove partners or channels from the followers
     * @param {Array} [ids.partner_ids] the partner ids
     * @param {Array} [ids.channel_ids] the channel ids
     */
    _unfollow: function (ids) {
        var self = this;
        // do not prompt confirmation dialog on unsubscribe of current user.
        if (_.isEqual(ids.partner_ids, [this.partnerID]) && _.isEmpty(ids.channel_ids)) {
            return this._messageUnsubscribe(ids);
        }
        return new Promise(function (resolve, reject) {
            var follower = _.find(self.followers, { res_id: ids.partner_ids ? ids.partner_ids[0] : ids.channel_ids[0] });
            var text = _.str.sprintf(_t("If you remove a follower, he won't be notified of any email or discussion on this document. Do you really want to remove %s?"), follower.name);
            Dialog.confirm(this, text, {
                title: _t("Warning"),
                confirm_callback: function () {
                    self._messageUnsubscribe(ids);
                    resolve();
                },
                cancel_callback: reject,
            });
        });
    },
    _updateSubscription: function (event, followerID, isChannel) {
        var ids = {};
        var subtypes;

        if (followerID !== undefined) {
            // Subtypes edited from the modal
            subtypes = this.dialog.$('input[type="checkbox"]');
            if (isChannel) {
                ids.channel_ids = [followerID];
            } else {
                ids.partner_ids = [followerID];
            }
        } else {
            subtypes = this.$('.o_followers_actions input[type="checkbox"]');
            ids.partner_ids = [this.partnerID];
        }

        // Get the subtype ids
        var checklist = [];
        _.each(subtypes, function (record) {
            if ($(record).is(':checked')) {
                checklist.push(parseInt($(record).data('id')));
            }
        });

        // If no more subtype followed, unsubscribe the follower
        if (!checklist.length) {
            this._unfollow(ids).guardedCatch(function () {
                $(event.currentTarget).find('input').addBack('input').prop('checked', true);
            });
        } else {
            var kwargs = _.extend({}, ids);
            if (followerID === undefined || followerID === this.partnerID) {
                //this.subtypes will only be updated if the current user
                //just added himself to the followers. We need to update
                //the subtypes manually when editing subtypes
                //for current user
                _.each(this.subtypes, function (subtype) {
                    subtype.followed = checklist.indexOf(subtype.id) > -1;
                });
            }
            kwargs.subtype_ids = checklist;
            kwargs.context = {}; // FIXME
            this._rpc({
                    model: this.model,
                    method: 'message_subscribe',
                    args: [[this.res_id]],
                    kwargs: kwargs,
                })
                .then(this._reload.bind(this));
        }
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onAddFollower: function (ev) {
        ev.preventDefault();
        this._inviteFollower(false);
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onAddChannel: function (ev) {
        ev.preventDefault();
        this._inviteFollower(true);
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onEditSubtype: function (ev) {
        var self = this;
        var $currentTarget = $(ev.currentTarget);
        var follower_id = $currentTarget.data('follower-id'); // id of model mail_follower
        this._rpc({
                route: '/mail/read_subscription_data',
                params: {follower_id: follower_id},
            })
            .then(function (data) {
                var res_id = $currentTarget.data('oe-id'); // id of model res_partner or mail_channel
                var is_channel = $currentTarget.data('oe-model') === 'mail.channel';
                self.dialog = new Dialog(this, {
                    size: 'medium',
                    title: _t("Edit Subscription of ") + $currentTarget.siblings('a').text(),
                    buttons: [
                        {
                            text: _t("Apply"),
                            classes: 'btn-primary',
                            click: function () {
                                self._updateSubscription(ev, res_id, is_channel);
                            },
                            close: true
                        },
                        {
                            text: _t("Cancel"),
                            close: true,
                        },
                    ],
                });
                self.dialog.opened().then(function () {
                    self._displaySubtypes(data, true, is_channel);
                });
                self.dialog.open();
            });
    },
    /**
     * @private
     */
    _onFollowButtonClicked: function () {
        if (!this._isFollower) {
            this._follow();
        } else {
            this._unfollow({partner_ids: [this.partnerID]});
        }
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onRedirect: function (ev) {
        ev.preventDefault();
        var $target = $(ev.target).closest('.o_mail_redirect');
        this.do_action({
            type: 'ir.actions.act_window',
            view_mode: 'form',
            res_model: $target.data('oe-model'),
            views: [[false, 'form']],
            res_id: $target.data('oe-id'),
        });
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onRemoveFollower: function (ev) {
        var resModel = $(ev.target).parent().find('a').data('oe-model');
        var resID = $(ev.target).parent().find('a').data('oe-id');
        if (resModel === 'res.partner') {
            return this._unfollow({partner_ids: [resID]});
        } else {
            return this._unfollow({channel_ids: [resID]});
        }
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onSubtypeClicked: function (ev) {
        ev.stopPropagation();
        this._updateSubscription(ev);
        var $list = this.$('.o_subtypes_list');
        if (!$list.hasClass('show')) {
            $list.addClass('show');
        }
        if (this.$('.o_subtypes_list .dropdown-menu')[0].children.length < 1) {
            $list.removeClass('show');
        }
    },
});

field_registry.add('mail_followers', Followers);

return Followers;

});
<|endoftext|>