<commit_before>(function() {
  'use strict';

  angular.module('dimApp')
    .factory('dimEngramFarmingService', EngramFarmingService);

  EngramFarmingService.$inject = ['$rootScope', '$q', 'dimBungieService', 'dimItemService', 'dimStoreService', '$interval', 'toaster'];

  /**
   * A service for "farming" engrams by moving them continuously off a character,
   * so that they don't go to the Postmaster.
   */
  function EngramFarmingService($rootScope, $q, dimBungieService, dimItemService, dimStoreService, $interval, toaster) {
    var intervalId;
    var cancelReloadListener;
    return {
      active: false,
      store: null,
      engramsMoved: 0,
      movingEngrams: false,
      makingRoom: false,
      // Move all engrams on the selected character to the vault.
      moveItemsToVault: function(items, incrementCounter) {
        var self = this;
        var vault = dimStoreService.getVault();
        return _.reduce(items, function(promise, item) {
          return promise
            .then(function() {
              return dimItemService.moveTo(item, vault, false, item.amount, items);
            })
            .then(function() {
              if (incrementCounter) {
                // TODO: whoops
                self.engramsMoved++;
              }
            });
        }, $q.resolve())
          .catch(function(e) {
            toaster.pop('error', item.name, e.message);
          });
      },
      moveEngramsToVault: function() {
        var self = this;
        var store = dimStoreService.getStore(self.store.id);
        var engrams = _.select(store.items, function(i) {
          return i.isEngram() && i.sort !== 'Postmaster';
        });

        if (engrams.length === 0) {
          return $q.resolve();
        }

        self.movingEngrams = true;
        return self.moveItemsToVault(engrams, true)
          .finally(function() {
            self.movingEngrams = false;
          });
      },
      // Ensure that there's one open space in each category that could
      // hold an engram, so they don't go to the postmaster.
      makeRoomForEngrams: function() {
        var self = this;

        var store = dimStoreService.getStore(self.store.id);
        // TODO: this'll be easier with buckets
        // These types can have engrams
        var engramTypes = ['Primary',
                          'Special',
                          'Heavy',
                          'Helmet',
                          'Gauntlets',
                          'Chest',
                          'Leg',
                          'ClassItem'];

        var applicableItems = _.select(store.items, function(i) {
          return !i.equipped &&
            !i.notransfer &&
            i.sort !== 'Postmaster' &&
            _.contains(engramTypes, i.type);
        });
        var itemsByType = _.groupBy(applicableItems, 'type');

        // If any category is full, we'll move one aside
        var itemsToMove = [];
        _.each(itemsByType, function(items, type) {
          // subtract 1 from capacity because we excluded the equipped item
          if (items.length > 0 && items.length >= (store.capacityForItem(items[0]) - 1)) {
            // We'll move the lowest-value item to the vault.
            itemsToMove.push(_.min(items, function(i) {
              var value = {
                Common: 0,
                Uncommon: 1,
                Rare: 2,
                Legendary: 3,
                Exotic: 4
              }[i.tier];
              // And low-stat
              if (i.primStat) {
                value += i.primStat.value / 1000.0;
              }
              return value;
            }));
          }
        });

        if (itemsToMove.length === 0) {
          return $q.resolve();
        }

        self.makingRoom = true;
        return self.moveItemsToVault(itemsToMove, false)
          .finally(function() {
            self.makingRoom = false;
          });
      },
      start: function(store) {
        if (!this.active) {
          this.active = true;
          this.store = store;
          this.engramsMoved = 0;
          this.movingEngrams = false;
          this.makingRoom = false;
          var self = this;

          function farm() {
            self.moveEngramsToVault().then(function() {
              self.makeRoomForEngrams();
            });
          }

          // Whenever the store is reloaded, run the farming algo
          // That way folks can reload manually too
          cancelReloadListener = $rootScope.$on('dim-stores-updated', function() {
            // prevent some recursion...
            if (self.active && !self.movingEngrams && !self.makingRoom) {
              farm();
            }
          });
          intervalId = $interval(function() {
            // just start reloading stores more often
            dimStoreService.reloadStores();
          }, 60000);
          farm();
        }
      },
      stop: function() {
        if (intervalId) {
          $interval.cancel(intervalId);
        }
        if (cancelReloadListener) {
          cancelReloadListener();
        }
        this.active = false;
      }
    };
  }
})();
<commit_msg>Behave properly in the presence of notransfers.<commit_after>(function() {
  'use strict';

  angular.module('dimApp')
    .factory('dimEngramFarmingService', EngramFarmingService);

  EngramFarmingService.$inject = ['$rootScope', '$q', 'dimBungieService', 'dimItemService', 'dimStoreService', '$interval', 'toaster'];

  /**
   * A service for "farming" engrams by moving them continuously off a character,
   * so that they don't go to the Postmaster.
   */
  function EngramFarmingService($rootScope, $q, dimBungieService, dimItemService, dimStoreService, $interval, toaster) {
    var intervalId;
    var cancelReloadListener;
    return {
      active: false,
      store: null,
      engramsMoved: 0,
      movingEngrams: false,
      makingRoom: false,
      // Move all engrams on the selected character to the vault.
      moveItemsToVault: function(items, incrementCounter) {
        var self = this;
        var vault = dimStoreService.getVault();
        return _.reduce(items, function(promise, item) {
          return promise
            .then(function() {
              return dimItemService.moveTo(item, vault, false, item.amount, items);
            })
            .then(function() {
              if (incrementCounter) {
                // TODO: whoops
                self.engramsMoved++;
              }
            });
        }, $q.resolve())
          .catch(function(e) {
            toaster.pop('error', item.name, e.message);
          });
      },
      moveEngramsToVault: function() {
        var self = this;
        var store = dimStoreService.getStore(self.store.id);
        var engrams = _.select(store.items, function(i) {
          return i.isEngram() && i.sort !== 'Postmaster';
        });

        if (engrams.length === 0) {
          return $q.resolve();
        }

        self.movingEngrams = true;
        return self.moveItemsToVault(engrams, true)
          .finally(function() {
            self.movingEngrams = false;
          });
      },
      // Ensure that there's one open space in each category that could
      // hold an engram, so they don't go to the postmaster.
      makeRoomForEngrams: function() {
        var self = this;

        var store = dimStoreService.getStore(self.store.id);
        // TODO: this'll be easier with buckets
        // These types can have engrams
        var engramTypes = ['Primary',
                          'Special',
                          'Heavy',
                          'Helmet',
                          'Gauntlets',
                          'Chest',
                          'Leg',
                          'ClassItem'];

        var applicableItems = _.select(store.items, function(i) {
          return !i.equipped &&
            i.sort !== 'Postmaster' &&
            _.contains(engramTypes, i.type);
        });
        var itemsByType = _.groupBy(applicableItems, 'type');

        // If any category is full, we'll move one aside
        var itemsToMove = [];
        _.each(itemsByType, function(items, type) {
          // subtract 1 from capacity because we excluded the equipped item
          if (items.length > 0 && items.length >= (store.capacityForItem(items[0]) - 1)) {
            // We'll move the lowest-value item to the vault.
            itemsToMove.push(_.min(_.select(items, { notransfer: false}), function(i) {
              var value = {
                Common: 0,
                Uncommon: 1,
                Rare: 2,
                Legendary: 3,
                Exotic: 4
              }[i.tier];
              // And low-stat
              if (i.primStat) {
                value += i.primStat.value / 1000.0;
              }
              return value;
            }));
          }
        });

        if (itemsToMove.length === 0) {
          return $q.resolve();
        }

        self.makingRoom = true;
        return self.moveItemsToVault(itemsToMove, false)
          .finally(function() {
            self.makingRoom = false;
          });
      },
      start: function(store) {
        if (!this.active) {
          this.active = true;
          this.store = store;
          this.engramsMoved = 0;
          this.movingEngrams = false;
          this.makingRoom = false;
          var self = this;

          function farm() {
            self.moveEngramsToVault().then(function() {
              self.makeRoomForEngrams();
            });
          }

          // Whenever the store is reloaded, run the farming algo
          // That way folks can reload manually too
          cancelReloadListener = $rootScope.$on('dim-stores-updated', function() {
            // prevent some recursion...
            if (self.active && !self.movingEngrams && !self.makingRoom) {
              farm();
            }
          });
          intervalId = $interval(function() {
            // just start reloading stores more often
            dimStoreService.reloadStores();
          }, 60000);
          farm();
        }
      },
      stop: function() {
        if (intervalId) {
          $interval.cancel(intervalId);
        }
        if (cancelReloadListener) {
          cancelReloadListener();
        }
        this.active = false;
      }
    };
  }
})();
<|endoftext|>


<commit_before>(function () {
    var console = window.console;
    if (!console) {
        console = {
            log: function () {
            },
            error: function () {
            }
        }
    }

    var time = (function () {
        if (Date.now) {
            return Date.now;
        } else {
            return function () {
                return +new Date();
            };
        }
    }());

    var requestAnimationFrame;
    var cancelAnimationFrame;
    (function () {
        if (window.requestAnimationFrame) {
            requestAnimationFrame = window.requestAnimationFrame;
            cancelAnimationFrame = window.cancelAnimationFrame;
        } else if (window.mozRequestAnimationFrame) {
            requestAnimationFrame = window.mozRequestAnimationFrame;
            cancelAnimationFrame = window.mozCancelAnimationFrame;
        } else if (window.webkitRequestAnimationFrame) {
            requestAnimationFrame = window.webkitRequestAnimationFrame;
            cancelAnimationFrame = window.webkitCancelAnimationFrame;
        } else if (window.oRequestAnimationFrame) {
            requestAnimationFrame = window.oRequestAnimationFrame;
            cancelAnimationFrame = window.oCancelAnimationFrame;
        } else {
            var interval = 1000 / 60;
            var cancelAfterNothingTimes = 60;
            var nothingTimes = 0;
            var intervalId;
            var callbacksA = [];
            var callbacksB = [];

            function onInterval() {
                var now = time();
                var callbacks = callbacksA;
                callbacksA = callbacksB;
                callbacksB = callbacks;

                if (callbacks.length == 0) {
                    if (++nothingTimes >= cancelAfterNothingTimes) {
                        nothingTimes = 0;
                        clearInterval(intervalId);
                        intervalId = null;
                    }
                } else {
                    nothingTimes = 0;

                    for (var i = 0; i < callbacks.length; ++i) {
                        var callback = callbacks[i];
                        if (callback) {
                            try {
                                callback(now);
                            } catch (e) {
                                console.error(e);
                            }
                        }
                    }
                }

                callbacks.length = 0;
            }

            requestAnimationFrame = function (callback) {
                if (!intervalId) {
                    intervalId = setInterval(onInterval, interval);
                }

                var id = callbacksA.length;
                callbacksA.push(callback);
                return id;
            };

            cancelAnimationFrame = function (id) {
                callbacksA[id] = null;
            };
        }
    }());

    var mainElement = (function() {
        var mainElement = document.getElementsByClassName('words-purple')[0];

        while (mainElement.firstChild) {
            mainElement.removeChild(mainElement.firstChild);
        }

        return mainElement;
    }());

    function loadSound(name, loop) {
        var audio = document.createElement("audio");
        var source = document.createElement("source");
        source.src = name + ".ogg";
        audio.appendChild(source);
        source = document.createElement("source");
        source.src = name + ".mp3";
        audio.appendChild(source);
        audio.loop = !!loop;
        return {
            play: function () {
                audio.load();
                audio.play();
            },
            stop: function () {
                audio.pause();
            }
        }
    }

    (function() {
        var background = document.createElement("div");
        background.className = "background";

        for (var i=0; i<40; ++i) {
            (function() {
                var square = document.createElement("div");
                square.style.position = "absolute";
                var startTime, size, speed;

                function start() {
                    startTime = time();
                    size = Math.random() * 256 + 16;
                    speed = Math.random() * 15 + 1;
                    square.style.top = Math.random() * (600 + size + size) - size + "px";
                    square.style.width = size + "px";
                    square.style.height = size + "px";
                    square.style.opacity = Math.random() * 0.1;
                    onAnimationFrame();
                }

                function onAnimationFrame() {
                    var f = (time() - startTime) / (1000 / 60);
                    var left = 800 - speed * f;
                    square.style.left = left + "px";

                    if (left <= -size) {
                        start();
                    } else {
                        requestAnimationFrame(onAnimationFrame);
                    }
                }

                start();

                background.appendChild(square);
            }());
        }

        mainElement.appendChild(background);
    }());

    var game = (function() {
        var gameElement = document.createElement("div");
        gameElement.className = "game";
        gameElement.style.display = "none";

        var counter = (function() {
            var counterElement = document.createElement("div");
            counterElement.className = "counter";
            gameElement.appendChild(counterElement);

            function start() {
                var startTime = time();

                function onAnimationFrame() {
                    var seconds = (time() - startTime) / 1000;

                    if (seconds < 11) {
                        counterElement.textContent = Math.ceil(10 - seconds).toString();
                        counterElement.style.opacity = 1 - (seconds % 1);

                        requestAnimationFrame(onAnimationFrame);
                    } else {
                        score.end();
                        gameElement.style.display = "none";
                        titleScreen.start();
                    }
                }

                onAnimationFrame();
            }

            return {
                start: start
            };
        }());

        (function() {
            var instruction = document.createElement("div");
            instruction.className = "instruction";
            instruction.appendChild(document.createTextNode("Write down as many words as you can that rhyme with ‘purple’!!!!!"));
            gameElement.appendChild(instruction);

            setInterval(function() {
                instruction.style.paddingTop = (Math.random() * 32) + "px";
                instruction.style.textIndent = (Math.random() * 128) + "px";
                instruction.style.fontSize = (Math.random() * 16 - 8 + 32) + "px";
                instruction.style.lineHeight = (Math.random() * 16 - 8 + 48) + "px";
            }, 500);

            setInterval(function() {
                instruction.style.color = "hsl(" + (Math.random() * 80 + 245) + ",50%,40%)";
            }, 1000/6)
        }());

        var wordEntry = (function() {
            var allowedWords = {
                "curple": true,
                "hirple": true
            };

            var wordEntry = document.createElement("div");
            wordEntry.className = "word-entry";

            var input = document.createElement("input");
            wordEntry.appendChild(input);

            var startTime = time();
            function onAnimationFrame() {
                var f = (time() - startTime) / (1000 / 60);

                var v = (Math.sin(f * 6 * Math.PI / 180) * 16 + 16);
                var h = (Math.sin(1.2 + f * 5.5 * Math.PI / 180) * 16 + 24);
                wordEntry.style.top = (240 - v) + 'px';
                wordEntry.style.left = (64 - h) + 'px';
                wordEntry.style.right = (64 - h) + 'px';
                wordEntry.style.padding =  v + 'px ' + h + 'px';

                requestAnimationFrame(onAnimationFrame);
            }

            onAnimationFrame();

            function onInputChange(event) {
                setTimeout(function () {
                    if (event.key === "Enter" ||
                            event.charCode === 0xa ||
                            event.charCode === 0xd ||
                            event.keyCode === 0xa ||
                            event.keyCode === 0xd ||
                            event.which === 0xa ||
                            event.which === 0xd ||
                            /\s/.test(input.value)) {
                        var words = input.value.split(/\s+/);
                        for (var i = 0; i < words.length; ++i) {
                            var word = words[i].toLowerCase();
                            var wordSpan = document.createElement("span");
                            wordSpan.textContent = word + " ";
                            if (allowedWords[word]) {
                                score.score(1);
                                wordSpan.className = "good-word";
                                wordEntry.insertBefore(wordSpan, input);
                            } else if (word.length > 0) {
                                wordSpan.className = "bad-word";
                                wordEntry.insertBefore(wordSpan, input);
                            }
                        }
                        input.value = "";
                    }
                }, 0);
            }

            function onInputFocus() {
                wordEntry.className = "word-entry focus";
            }

            function onInputBlur() {
                wordEntry.className = "word-entry";
            }

            function onClick(event) {
                if (event.target !== input) {
                    input.focus();
                    input.selectionStart = input.value.length;
                    input.selectionEnd = input.value.length;
                }
            }

            input.addEventListener("change", onInputChange, true);
            input.addEventListener("keydown", onInputChange, true);
            input.addEventListener("focus", onInputFocus, true);
            input.addEventListener("blur", onInputBlur, true);
            wordEntry.addEventListener("click", onClick, true);

            gameElement.appendChild(wordEntry);

            function start() {
                while (wordEntry.firstChild !== input) {
                    wordEntry.removeChild(wordEntry.firstChild);
                }

                input.value = "";
                input.focus();
            }

            return {
                start: start
            };
        }());

        mainElement.appendChild(gameElement);

        var gameMusic = loadSound("game-music", false);

        function start() {
            gameElement.style.display = "block";
            counter.start();
            wordEntry.start();
            score.start();
            gameMusic.play();
        }

        return {
            start: start
        };
    }());

    var titleScreen = (function() {
        var titleScreenElement = document.createElement("div");
        titleScreenElement.className = "title-screen";
        titleScreenElement.style.display = "block";

        (function() {
            var titleElement = document.createElement("h1");
            titleElement.appendChild(document.createTextNode("Words that Rhyme\xa0 with ‘Purple’\xa0"));
            titleScreenElement.appendChild(titleElement);

            titleElement.style.position = 'relative';

            var startTime = time();

            function onAnimationFrame() {
                var f = (time() - startTime) / (1000 / 60);
                titleElement.style.top = (Math.sin(f * 6 * Math.PI / 180) * 24) + 'px';
                titleElement.style.lineHeight = (96 + Math.sin(3 + f * 5 * Math.PI / 180) * 6) + 'px';
                var transform = 'scale(' + (1 + 0.1 * Math.sin(9 + f * 2 * Math.PI / 180)) + ')';
                titleElement.style.webkitTransform = transform;
                titleElement.style.mozTransform = transform;
                titleElement.style.transform = transform;
                requestAnimationFrame(onAnimationFrame);
            }

            onAnimationFrame();
        }());

        var playButton = (function () {
            var playButton = document.createElement('button');
            playButton.style.position = "relative";
            playButton.className = "play-button";
            playButton.appendChild(document.createTextNode('Play Game'));
            titleScreenElement.appendChild(playButton);

            function onClickPlay() {
                titleMusic.stop();
                letsPlaySound.play();

                playButton.disabled = true;
                playButton.className = "play-button active";

                var startTime = time();

                function onAnimationFrame() {
                    var f = (time() - startTime) / (1000 / 60);

                    playButton.style.top = Math.pow(2, f * 0.2) + 'px';
                    titleScreenElement.style.opacity = (1 - f / 45).toString();

                    if (f >= 10) {
                        playButton.className = "play-button";
                    }

                    if (++f < 45) {
                        requestAnimationFrame(onAnimationFrame);
                    } else {
                        titleScreenElement.style.display = "none";

                        game.start();
                    }
                }

                onAnimationFrame();
            }

            playButton.addEventListener("click", onClickPlay, true);

            return {
                start: function() {
                    playButton.style.top = "0";
                    playButton.disabled = false;
                }
            };
        }());

        var small = document.createElement("small");
        small.appendChild(document.createTextNode("© 2013 Smartarse Industries"));
        titleScreenElement.appendChild(small);

        mainElement.appendChild(titleScreenElement);

        var titleMusic = loadSound("title-music", true);
        var letsPlaySound = loadSound("lets-play", false);

        function start() {
            titleMusic.play();

            titleScreenElement.style.display = "block";
            titleScreenElement.style.opacity = "1";
            playButton.start();
        }

        start();

        return {
            start: start
        };
    }());

    var score = (function() {
        var currentScore = 0;
        var highScore = 0;

        var scoreElement = document.createElement("div");
        scoreElement.className = "score";
        mainElement.appendChild(scoreElement);

        function start() {
            currentScore = 0;
            score(0);
        }

        function score(points) {
            currentScore += points;
            scoreElement.textContent = "Score: " + currentScore;
            if (currentScore > highScore) {
                highScore = currentScore;
            }
        }

        function end() {
            scoreElement.textContent = "High Score: " + highScore;
        }

        return {
            start: start,
            score: score,
            end: end
        };
    }());

    titleScreen.start();

    // Your earth-word ‘purple’ confuses and infuriates us!
    // Give us the rhymes that we demand, or be destroyed!
}());<commit_msg>Refactorings in preparation for adding a win and lose condition.<commit_after>(function () {
    var console = window.console;
    if (!console) {
        console = {
            log: function () {
            },
            error: function () {
            }
        }
    }

    var time = (function () {
        if (Date.now) {
            return Date.now;
        } else {
            return function () {
                return +new Date();
            };
        }
    }());

    var requestAnimationFrame;
    var cancelAnimationFrame;
    (function () {
        if (window.requestAnimationFrame) {
            requestAnimationFrame = window.requestAnimationFrame;
            cancelAnimationFrame = window.cancelAnimationFrame;
        } else if (window.mozRequestAnimationFrame) {
            requestAnimationFrame = window.mozRequestAnimationFrame;
            cancelAnimationFrame = window.mozCancelAnimationFrame;
        } else if (window.webkitRequestAnimationFrame) {
            requestAnimationFrame = window.webkitRequestAnimationFrame;
            cancelAnimationFrame = window.webkitCancelAnimationFrame;
        } else if (window.oRequestAnimationFrame) {
            requestAnimationFrame = window.oRequestAnimationFrame;
            cancelAnimationFrame = window.oCancelAnimationFrame;
        } else {
            var interval = 1000 / 60;
            var cancelAfterNothingTimes = 60;
            var nothingTimes = 0;
            var intervalId;
            var callbacksA = [];
            var callbacksB = [];

            function onInterval() {
                var now = time();
                var callbacks = callbacksA;
                callbacksA = callbacksB;
                callbacksB = callbacks;

                if (callbacks.length == 0) {
                    if (++nothingTimes >= cancelAfterNothingTimes) {
                        nothingTimes = 0;
                        clearInterval(intervalId);
                        intervalId = null;
                    }
                } else {
                    nothingTimes = 0;

                    for (var i = 0; i < callbacks.length; ++i) {
                        var callback = callbacks[i];
                        if (callback) {
                            try {
                                callback(now);
                            } catch (e) {
                                console.error(e);
                            }
                        }
                    }
                }

                callbacks.length = 0;
            }

            requestAnimationFrame = function (callback) {
                if (!intervalId) {
                    intervalId = setInterval(onInterval, interval);
                }

                var id = callbacksA.length;
                callbacksA.push(callback);
                return id;
            };

            cancelAnimationFrame = function (id) {
                callbacksA[id] = null;
            };
        }
    }());

    var mainElement = (function() {
        var mainElement = document.getElementsByClassName('words-purple')[0];

        while (mainElement.firstChild) {
            mainElement.removeChild(mainElement.firstChild);
        }

        return mainElement;
    }());

    function loadSound(name, loop) {
        var audio = document.createElement("audio");
        var source = document.createElement("source");
        source.src = name + ".ogg";
        audio.appendChild(source);
        source = document.createElement("source");
        source.src = name + ".mp3";
        audio.appendChild(source);
        audio.loop = !!loop;
        return {
            play: function () {
                audio.load();
                audio.play();
            },
            stop: function () {
                audio.pause();
            }
        }
    }

    (function() {
        var background = document.createElement("div");
        background.className = "background";

        for (var i=0; i<40; ++i) {
            (function() {
                var square = document.createElement("div");
                square.style.position = "absolute";
                var startTime, size, speed;

                function start() {
                    startTime = time();
                    size = Math.random() * 256 + 16;
                    speed = Math.random() * 15 + 1;
                    square.style.top = Math.random() * (600 + size + size) - size + "px";
                    square.style.width = size + "px";
                    square.style.height = size + "px";
                    square.style.opacity = Math.random() * 0.1;
                    onAnimationFrame();
                }

                function onAnimationFrame() {
                    var f = (time() - startTime) / (1000 / 60);
                    var left = 800 - speed * f;
                    square.style.left = left + "px";

                    if (left <= -size) {
                        start();
                    } else {
                        requestAnimationFrame(onAnimationFrame);
                    }
                }

                start();

                background.appendChild(square);
            }());
        }

        mainElement.appendChild(background);
    }());

    var game = (function() {
        var gameElement = document.createElement("div");
        gameElement.className = "game";
        gameElement.style.display = "none";

        var counter = (function() {
            var counterElement = document.createElement("div");
            var won = false;
            counterElement.className = "counter";
            gameElement.appendChild(counterElement);

            function start() {
                var startTime = time();
                var signalledTimeUp = false;
                won = false;

                function onAnimationFrame() {
                    if (!won) {
                        var seconds = (time() - startTime) / 1000;

                        if (seconds < 11) {
                            counterElement.textContent = Math.ceil(10 - seconds).toString();
                            counterElement.style.opacity = 1 - (seconds % 1);

                            requestAnimationFrame(onAnimationFrame);
                        } else {
                            end();
                        }

                        if (seconds >= 10 && !signalledTimeUp) {
                            signalledTimeUp = true;
                            lose();
                        }
                    }
                }

                onAnimationFrame();
            }

            function win() {
                won = true;
            }

            return {
                start: start,
                win: win
            };
        }());

        (function() {
            var instruction = document.createElement("div");
            instruction.className = "instruction";
            instruction.appendChild(document.createTextNode("Write down as many words as you can that rhyme with ‘purple’!!!!!"));
            gameElement.appendChild(instruction);

            setInterval(function() {
                instruction.style.paddingTop = (Math.random() * 32) + "px";
                instruction.style.textIndent = (Math.random() * 128) + "px";
                instruction.style.fontSize = (Math.random() * 16 - 8 + 32) + "px";
                instruction.style.lineHeight = (Math.random() * 16 - 8 + 48) + "px";
            }, 500);

            setInterval(function() {
                instruction.style.color = "hsl(" + (Math.random() * 80 + 245) + ",50%,40%)";
            }, 1000/6)
        }());

        var wordEntry = (function() {
            var allowedWords = {
                "curple": true,
                "hirple": true
            };

            var goodWordCount = 0;
            var allowedWordCount = 2;

            var wordEntry = document.createElement("div");
            wordEntry.className = "word-entry";

            var input = document.createElement("input");
            wordEntry.appendChild(input);

            var startTime = time();
            function onAnimationFrame() {
                var f = (time() - startTime) / (1000 / 60);

                var v = (Math.sin(f * 6 * Math.PI / 180) * 16 + 16);
                var h = (Math.sin(1.2 + f * 5.5 * Math.PI / 180) * 16 + 24);
                wordEntry.style.top = (240 - v) + 'px';
                wordEntry.style.left = (64 - h) + 'px';
                wordEntry.style.right = (64 - h) + 'px';
                wordEntry.style.padding =  v + 'px ' + h + 'px';

                requestAnimationFrame(onAnimationFrame);
            }

            onAnimationFrame();

            function onInputChange(event) {
                setTimeout(function () {
                    if (event.key === "Enter" ||
                            event.charCode === 0xa ||
                            event.charCode === 0xd ||
                            event.keyCode === 0xa ||
                            event.keyCode === 0xd ||
                            event.which === 0xa ||
                            event.which === 0xd ||
                            /\s/.test(input.value)) {
                        var words = input.value.split(/\s+/);
                        for (var i = 0; i < words.length; ++i) {
                            var word = words[i].toLowerCase();
                            var wordSpan = document.createElement("span");
                            wordSpan.textContent = word + " ";
                            if (allowedWords[word]) {
                                score.score(1);
                                wordSpan.className = "good-word";
                                wordEntry.insertBefore(wordSpan, input);

                                if (++goodWordCount == allowedWordCount) {
                                    win();
                                }
                            } else if (word.length > 0) {
                                wordSpan.className = "bad-word";
                                wordEntry.insertBefore(wordSpan, input);
                            }
                        }
                        input.value = "";
                    }
                }, 0);
            }

            function onInputFocus() {
                wordEntry.className = "word-entry focus";
            }

            function onInputBlur() {
                wordEntry.className = "word-entry";
            }

            function onClick(event) {
                if (event.target !== input) {
                    input.focus();
                    input.selectionStart = input.value.length;
                    input.selectionEnd = input.value.length;
                }
            }

            input.addEventListener("change", onInputChange, true);
            input.addEventListener("keydown", onInputChange, true);
            input.addEventListener("focus", onInputFocus, true);
            input.addEventListener("blur", onInputBlur, true);
            wordEntry.addEventListener("click", onClick, true);

            gameElement.appendChild(wordEntry);

            function start() {
                while (wordEntry.firstChild !== input) {
                    wordEntry.removeChild(wordEntry.firstChild);
                }

                goodWordCount = 0;

                input.value = "";
                input.focus();
            }

            function end() {
                // TODO
            }

            return {
                start: start,
                end: end
            };
        }());

        mainElement.appendChild(gameElement);

        var gameMusic = loadSound("game-music", false);

        function start() {
            gameElement.style.display = "block";
            counter.start();
            wordEntry.start();
            score.start();
            gameMusic.play();
        }

        function end() {
            wordEntry.end();
            score.end();
            gameElement.style.display = "none";
            titleScreen.start();
        }

        function win() {
            counter.win();
            // TODO
        }

        function lose() {
            // TODO
        }

        return {
            start: start
        };
    }());

    var titleScreen = (function() {
        var titleScreenElement = document.createElement("div");
        titleScreenElement.className = "title-screen";
        titleScreenElement.style.display = "block";

        (function() {
            var titleElement = document.createElement("h1");
            titleElement.appendChild(document.createTextNode("Words that Rhyme\xa0 with ‘Purple’\xa0"));
            titleScreenElement.appendChild(titleElement);

            titleElement.style.position = 'relative';

            var startTime = time();

            function onAnimationFrame() {
                var f = (time() - startTime) / (1000 / 60);
                titleElement.style.top = (Math.sin(f * 6 * Math.PI / 180) * 24) + 'px';
                titleElement.style.lineHeight = (96 + Math.sin(3 + f * 5 * Math.PI / 180) * 6) + 'px';
                var transform = 'scale(' + (1 + 0.1 * Math.sin(9 + f * 2 * Math.PI / 180)) + ')';
                titleElement.style.webkitTransform = transform;
                titleElement.style.mozTransform = transform;
                titleElement.style.transform = transform;
                requestAnimationFrame(onAnimationFrame);
            }

            onAnimationFrame();
        }());

        var playButton = (function () {
            var playButton = document.createElement('button');
            playButton.style.position = "relative";
            playButton.className = "play-button";
            playButton.appendChild(document.createTextNode('Play Game'));
            titleScreenElement.appendChild(playButton);

            function onClickPlay() {
                titleMusic.stop();
                letsPlaySound.play();

                playButton.disabled = true;
                playButton.className = "play-button active";

                var startTime = time();

                function onAnimationFrame() {
                    var f = (time() - startTime) / (1000 / 60);

                    playButton.style.top = Math.pow(2, f * 0.2) + 'px';
                    titleScreenElement.style.opacity = (1 - f / 45).toString();

                    if (f >= 10) {
                        playButton.className = "play-button";
                    }

                    if (++f < 45) {
                        requestAnimationFrame(onAnimationFrame);
                    } else {
                        titleScreenElement.style.display = "none";

                        game.start();
                    }
                }

                onAnimationFrame();
            }

            playButton.addEventListener("click", onClickPlay, true);

            return {
                start: function() {
                    playButton.style.top = "0";
                    playButton.disabled = false;
                }
            };
        }());

        var small = document.createElement("small");
        small.appendChild(document.createTextNode("© 2013 Smartarse Industries"));
        titleScreenElement.appendChild(small);

        mainElement.appendChild(titleScreenElement);

        var titleMusic = loadSound("title-music", true);
        var letsPlaySound = loadSound("lets-play", false);

        function start() {
            titleMusic.play();

            titleScreenElement.style.display = "block";
            titleScreenElement.style.opacity = "1";
            playButton.start();
        }

        start();

        return {
            start: start
        };
    }());

    var score = (function() {
        var currentScore = 0;
        var highScore = 0;

        var scoreElement = document.createElement("div");
        scoreElement.className = "score";
        mainElement.appendChild(scoreElement);

        function start() {
            currentScore = 0;
            score(0);
        }

        function score(points) {
            currentScore += points;
            scoreElement.textContent = "Score: " + currentScore;
            if (currentScore > highScore) {
                highScore = currentScore;
            }
        }

        function end() {
            scoreElement.textContent = "High Score: " + highScore;
        }

        return {
            start: start,
            score: score,
            end: end
        };
    }());

    titleScreen.start();

    // Your earth-word ‘purple’ confuses and infuriates us!
    // Give us the rhymes that we demand, or be destroyed!
}());<|endoftext|>


<commit_before>var request = require('request').defaults({json: true});
var HttpStatus = require('http-status-codes');
var async = require('async');
var auth = require('../helper/auth');
var api = require('../helper/api').skvsApiUrl;
var hubApi = require('../helper/api').dockerHubApiUrl;
var error_helper = require('../helper/error').errorHelper;
var request_handler = require('../helper/error').requestHandler;

module.exports = function (router) {
  // TODO: return system data: IP, hostname, hardware revision, software revision, etc.
  router.get('/system', auth, function (req, res, next) {
    res.json({status: "okay"});
  });

  router.get('/system/update', auth, function (req, res, next) {
    /*
     ┌────────────────────────────┐
     │      Frontend Request      │
     │GET /admin/api/system/update│
     └────────────────────────────┘
     │
     ▼
     ┌──────────────────────────┐
     │ skvs GET /system/channel │
     └──────────────────────────┘
     │ eg. 'development'
     ▼
     ┌──────────────────────────┐
     │ skvs GET /system/images  │
     └──────────────────────────┘
     │     Keys: key1...keyN
     ┌──────────────────────────────┐    │    ┌──────────────────────────────┐
     ┌──│skvs GET /system/images/<key1>│◀───┼───▶│hub GET /<key1>/tag/<channel> │──┐
     │  └──────────────────────────────┘    │    └──────────────────────────────┘  │
     │                   .                  │                     .                │
     │                   .          Parallel Requests             .                │
     │                   .                  │                     .                │
     │  ┌──────────────────────────────┐    │    ┌──────────────────────────────┐  │
     ├──│skvs GET /system/images/<keyN>│◀───┴───▶│hub GET /<keyN>/tag/<channel> │──┤
     │  └──────────────────────────────┘         └──────────────────────────────┘  │
     │                                                                             │
     │   Local image1...imageN           ┌─────┐          Remote image1...imageN   │
     └──────────────────────────────────▶│MERGE│◀──────────────────────────────────┘
     └─────┘
     │
     ▼
     ┌──────────────────┐
     │Frontend Response │
     └──────────────────┘
     */
    // get the currently configured channel to use
    request(api('/system/channel'), request_handler(function (response, result) {
      if (response.statusCode == HttpStatus.OK) {
        var channel = result.value.trim();
        // get a list of all installed images
        request(api('/system/images'), request_handler(function (response, result) {
          if (response.statusCode == HttpStatus.OK && result.namespace) {
            var get_image_id_for_key = result.keys.reduce(function (obj, key) {
              // only compare images belonging to the current channel
              if (key.indexOf(channel) >= 0) {
                obj[key] = function (callback) {
                  // Get all Image Ids via skvs/dockerhub
                  async.parallel({
                    local: function (c) {
                      request(api('/system/images/' + key), request_handler(function (response, result) {
                        if (result.value == undefined || result.value == null) {
                          result.value = ""; // always return empty string.
                        }
                        c(null, result.value.trim());
                      }), function () {
                        c(null, ""); // empty string if something went wrong
                      });
                    },
                    remote: function (c) {
                      var key_splitted = key.split(":");
                      var name = key_splitted[0];
                      var tag = key_splitted[1];
                      request(hubApi(name + '/tags/' + tag), request_handler(function (response, result) {
                        var latest_layer = result[0] || {id: ""};
                        c(null, latest_layer.id);
                      }), function () {
                        c(null, ""); // empty string if something went wrong
                      });
                    }
                  }, function (err, results) {
                    results = results || {}; // no error handling here, if something went wrong, handle it outside.
                    callback(null, results);
                  });
                };
              }
              return obj;
            }, {});
            async.parallel(get_image_id_for_key, function (err, images) {
              images = images || {};
              var result = {};
              result['images'] = images;
              result['channel'] = channel;
              // TODO: # TODO: handle images w/ slashes like ibuildthecloud/systemd-docker:latest
              // TODO: race condition in trigger-update-protonet.{service, path}
              var image_keys = Object.keys(images);
              if (image_keys.length == 0) {
                result.up_to_date = false; // No Images => update needed!
                console.log('Found no images, update required.');
              } else {
                result.up_to_date = image_keys.every(function (currentKey, i, arr) {
                  var currentImage = images[currentKey];
                  var result = currentImage.local.indexOf(currentImage.remote) === 0;
                  if (!result) {
                    console.log('Remote "' + currentImage.remote + '" differs from local and will trigger an update.');
                  }
                  return result;
                });
              }
              res.json(result);
            });
          } else {
            next(error_helper(HttpStatus.INTERNAL_SERVER_ERROR, "No image states found."));
          }
        }, next));
      } else {
        next(error_helper(HttpStatus.INTERNAL_SERVER_ERROR, "No channel found!"));
      }
    }, next));
  });

  router.post('/system/update', auth, function (req, res, next) {
    var channel = req.body['channel'];
    if (channel == undefined || channel == null || channel == "") {
      next(error_helper(HttpStatus.BAD_REQUEST, "No channel given!"));
    } else {
      var options = {
        url: api('/system/channel'),
        method: 'PUT',
        form: {
          value: channel
        }
      };
      request(options, request_handler(function (response, result) {
        if (response.statusCode == HttpStatus.OK) {
          res.json({status: "okay", channel: channel});
        } else {
          next(error_helper(HttpStatus.INTERNAL_SERVER_ERROR));
        }
      }), next);
    }
  });
};
<commit_msg>Trigger the CI<commit_after>var request = require('request').defaults({json: true});
var HttpStatus = require('http-status-codes');
var async = require('async');
var auth = require('../helper/auth');
var api = require('../helper/api').skvsApiUrl;
var hubApi = require('../helper/api').dockerHubApiUrl;
var error_helper = require('../helper/error').errorHelper;
var request_handler = require('../helper/error').requestHandler;

module.exports = function (router) {
  // TODO: return system data: IP, hostname, hardware revision, software revision, etc.
  router.get('/system', auth, function (req, res, next) {
    res.json({status: "okay"});
  });

  router.get('/system/update', auth, function (req, res, next) {
    /*
     ┌────────────────────────────┐
     │      Frontend Request      │
     │GET /admin/api/system/update│
     └────────────────────────────┘
     │
     ▼
     ┌──────────────────────────┐
     │ skvs GET /system/channel │
     └──────────────────────────┘
     │ eg. 'development'
     ▼
     ┌──────────────────────────┐
     │ skvs GET /system/images  │
     └──────────────────────────┘
     │     Keys: key1...keyN
     ┌──────────────────────────────┐    │    ┌──────────────────────────────┐
     ┌──│skvs GET /system/images/<key1>│◀───┼───▶│hub GET /<key1>/tag/<channel> │──┐
     │  └──────────────────────────────┘    │    └──────────────────────────────┘  │
     │                   .                  │                     .                │
     │                   .          Parallel Requests             .                │
     │                   .                  │                     .                │
     │  ┌──────────────────────────────┐    │    ┌──────────────────────────────┐  │
     ├──│skvs GET /system/images/<keyN>│◀───┴───▶│hub GET /<keyN>/tag/<channel> │──┤
     │  └──────────────────────────────┘         └──────────────────────────────┘  │
     │                                                                             │
     │   Local image1...imageN           ┌─────┐          Remote image1...imageN   │
     └──────────────────────────────────▶│MERGE│◀──────────────────────────────────┘
     └─────┘
     │
     ▼
     ┌──────────────────┐
     │Frontend Response │
     └──────────────────┘
     */
    // get the currently configured channel to use
    request(api('/system/channel'), request_handler(function (response, result) {
      if (response.statusCode == HttpStatus.OK) {
        var channel = result.value.trim();
        // get a list of all installed images
        request(api('/system/images'), request_handler(function (response, result) {
          if (response.statusCode == HttpStatus.OK && result.namespace) {
            var get_image_id_for_key = result.keys.reduce(function (obj, key) {
              // only compare images belonging to the current channel
              // this takes care of images like ibuildthecloud/..., too
              if (key.indexOf(channel) >= 0) {
                obj[key] = function (callback) {
                  // Get all Image Ids via skvs/dockerhub
                  async.parallel({
                    local: function (c) {
                      request(api('/system/images/' + key), request_handler(function (response, result) {
                        if (result.value == undefined || result.value == null) {
                          result.value = ""; // always return empty string.
                        }
                        c(null, result.value.trim());
                      }), function () {
                        c(null, ""); // empty string if something went wrong
                      });
                    },
                    remote: function (c) {
                      var key_splitted = key.split(":");
                      var name = key_splitted[0];
                      var tag = key_splitted[1];
                      request(hubApi(name + '/tags/' + tag), request_handler(function (response, result) {
                        var latest_layer = result[0] || {id: ""};
                        c(null, latest_layer.id);
                      }), function () {
                        c(null, ""); // empty string if something went wrong
                      });
                    }
                  }, function (err, results) {
                    results = results || {}; // no error handling here, if something went wrong, handle it outside.
                    callback(null, results);
                  });
                };
              }
              return obj;
            }, {});
            async.parallel(get_image_id_for_key, function (err, images) {
              images = images || {};
              var result = {};
              result['images'] = images;
              result['channel'] = channel;
              // TODO: # TODO: handle images w/ slashes like ibuildthecloud/systemd-docker:latest
              // TODO: race condition in trigger-update-protonet.{service, path}
              var image_keys = Object.keys(images);
              if (image_keys.length == 0) {
                result.up_to_date = false; // No Images => update needed!
                console.log('Found no images, update required.');
              } else {
                result.up_to_date = image_keys.every(function (currentKey, i, arr) {
                  var currentImage = images[currentKey];
                  var result = currentImage.local.indexOf(currentImage.remote) === 0;
                  if (!result) {
                    console.log('Remote "' + currentImage.remote + '" differs from local and will trigger an update.');
                  }
                  return result;
                });
              }
              res.json(result);
            });
          } else {
            next(error_helper(HttpStatus.INTERNAL_SERVER_ERROR, "No image states found."));
          }
        }, next));
      } else {
        next(error_helper(HttpStatus.INTERNAL_SERVER_ERROR, "No channel found!"));
      }
    }, next));
  });

  router.post('/system/update', auth, function (req, res, next) {
    var channel = req.body['channel'];
    if (channel == undefined || channel == null || channel == "") {
      next(error_helper(HttpStatus.BAD_REQUEST, "No channel given!"));
    } else {
      var options = {
        url: api('/system/channel'),
        method: 'PUT',
        form: {
          value: channel
        }
      };
      request(options, request_handler(function (response, result) {
        if (response.statusCode == HttpStatus.OK) {
          res.json({status: "okay", channel: channel});
        } else {
          next(error_helper(HttpStatus.INTERNAL_SERVER_ERROR));
        }
      }), next);
    }
  });
};
<|endoftext|>


<commit_before>/*

Utility Belt

*/
if (typeof UtilityBelt === 'undefined') UtilityBelt = UB = {};

UtilityBelt = UB = (function () {

    /**
     * Private
     */

    return {
        /**
         * @cfg regExp
         * Regular expression library
         */
        regExp: {
            email : /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
        },

        /**
         * @Method getRegExp
         * Getter for Regular Expression library
         * @param email {string}
         * @return boolean
         */
        getRegExp: function (exp) {

            if (!this.regExp[exp]) {
                consol.warn('The regular expression', exp, 'was not found');
                return null;
            }

            return this.regExp[exp] ? this.regExp[exp] : null;
        },

        /**
         * Returns `true` if the passed value is email, `false` otherwise
         * @param {String} value The value to test.
         * @return {Boolean}
         */
        isEmail: function (value) {
            return this.getRegExp('email').test(value);
        },

		/**
         * Returns the given value itself if it's not empty, returns the default
         * value (second argument) otherwise.
         *
         * @param {Object} value The value to test.
         * @param {Object} defaultValue The value to return if the original value is empty.
         * @param {Boolean} [allowBlank=false] (optional) `true` to allow zero length strings to qualify as non-empty.
         * @return {Object} `value`, if non-empty, else `defaultValue`.
         */
        valueFrom: function(value, defaultValue, allowBlank){
            return this.isEmpty(value, allowBlank) ? defaultValue : value;
        },

        /**
         * Returns the type of the given variable in string format. List of possible values are:
         *
         * - `undefined`: If the given value is `undefined`
         * - `null`: If the given value is `null`
         * - `string`: If the given value is a string
         * - `number`: If the given value is a number
         * - `boolean`: If the given value is a boolean value
         * - `date`: If the given value is a `Date` object
         * - `function`: If the given value is a function reference
         * - `object`: If the given value is an object
         * - `array`: If the given value is an array
         * - `regexp`: If the given value is a regular expression
         * - `element`: If the given value is a DOM Element
         * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
         * - `whitespace`: If the given value is a DOM text node and contains only whitespace
         *
         * @param {Object} value
         * @return {String}
         */
        typeOf: function(value) {
            if (value === null) {
                return 'null';
            }

            var type = typeof value;

            if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
                return type;
            }

            var typeToString = toString.call(value);

            switch(typeToString) {
                case '[object Array]':
                    return 'array';
                case '[object Date]':
                    return 'date';
                case '[object Boolean]':
                    return 'boolean';
                case '[object Number]':
                    return 'number';
                case '[object RegExp]':
                    return 'regexp';
            }

            if (type === 'function') {
                return 'function';
            }

            if (type === 'object') {
                if (value.nodeType !== undefined) {
                    if (value.nodeType === 3) {
                        return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
                    }
                    else {
                        return 'element';
                    }
                }

                return 'object';
            }

            //<debug error>
            console.warn('Failed to determine the type of the specified value "' + value + '". This is most likely a bug.');
            //</debug>
        },

        /**
         * Returns `true` if the passed value is empty, `false` otherwise. The value is deemed to be empty if it is either:
         *
         * - `null`
         * - `undefined`
         * - a zero-length array.
         * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`).
         *
         * @param {Object} value The value to test.
         * @param {Boolean} [allowEmptyString=false] (optional) `true` to allow empty strings.
         * @return {Boolean}
         */
        isEmpty: function(value, allowEmptyString) {
            return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
        },

        /**
         * Returns `true` if the passed value is a JavaScript Array, `false` otherwise.
         *
         * @param {Object} target The target to test.
         * @return {Boolean}
         * @method
         */
        isArray: ('isArray' in Array) ? Array.isArray : function(value) {
            return toString.call(value) === '[object Array]';
        },

        /**
         * Returns `true` if the passed value is a JavaScript Date object, `false` otherwise.
         * @param {Object} object The object to test.
         * @return {Boolean}
         */
        isDate: function(value) {
            return toString.call(value) === '[object Date]';
        },

        /**
         * Returns 'true' if the passed value is a String that matches the MS Date JSON encoding format
         * @param {String} value The string to test
         * @return {Boolean}
         */
        isMSDate: function(value) {
            if (!Ext.isString(value)) {
                return false;
            } else {
                return value.match("\\\\?/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\\\?/") !== null;
            }
        },

        /**
         * Returns `true` if the passed value is a JavaScript Object, `false` otherwise.
         * @param {Object} value The value to test.
         * @return {Boolean}
         * @method
         */
        isObject: (toString.call(null) === '[object Object]') ?
        function(value) {
            // check ownerDocument here as well to exclude DOM nodes
            return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
        } :
        function(value) {
            return toString.call(value) === '[object Object]';
        },

        /**
         * @private
         */
        isSimpleObject: function(value) {
            return value instanceof Object && value.constructor === Object;
        },
        /**
         * Returns `true` if the passed value is a JavaScript 'primitive', a string, number or Boolean.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isPrimitive: function(value) {
            var type = typeof value;

            return type === 'string' || type === 'number' || type === 'boolean';
        },

        /**
         * Returns `true` if the passed value is a JavaScript Function, `false` otherwise.
         * @param {Object} value The value to test.
         * @return {Boolean}
         * @method
         */
        isFunction:
        // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
        // Object.prorotype.toString (slower)
        (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
            return toString.call(value) === '[object Function]';
        } : function(value) {
            return typeof value === 'function';
        },

        /**
         * Returns `true` if the passed value is a number. Returns `false` for non-finite numbers.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isNumber: function(value) {
            return typeof value === 'number' && isFinite(value);
        },

        /**
         * Validates that a value is numeric.
         * @param {Object} value Examples: 1, '1', '2.34'
         * @return {Boolean} `true` if numeric, `false` otherwise.
         */
        isNumeric: function(value) {
            return !isNaN(parseFloat(value)) && isFinite(value);
        },

        /**
         * Returns `true` if the passed value is a string.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isString: function(value) {
            return typeof value === 'string';
        },

        /**
         * Returns `true` if the passed value is a Boolean.
         *
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isBoolean: function(value) {
            return typeof value === 'boolean';
        },

        /**
         * Returns `true` if the passed value is an HTMLElement.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isElement: function(value) {
            return value ? value.nodeType === 1 : false;
        },

        /**
         * Returns `true` if the passed value is a TextNode.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isTextNode: function(value) {
            return value ? value.nodeName === "#text" : false;
        },

        /**
         * Returns `true` if the passed value is defined.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isDefined: function(value) {
            return typeof value !== 'undefined';
        },

        /**
         * Returns `true` if the passed value is iterable, `false` otherwise.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isIterable: function(value) {
            return (value && typeof value !== 'string') ? value.length !== undefined : false;
        },

		/**
         * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference.
         * @param {Object} item The variable to clone.
         * @return {Object} clone
         */
		clone: function(item) {
            if (item === null || item === undefined) {
                return item;
            }

            // DOM nodes
            if (item.nodeType && item.cloneNode) {
                return item.cloneNode(true);
            }

            // Strings
            var type = toString.call(item);

            // Dates
            if (type === '[object Date]') {
                return new Date(item.getTime());
            }

            var i, j, k, clone, key;

            // Arrays
            if (type === '[object Array]') {
                i = item.length;

                clone = [];

                while (i--) {
                    clone[i] = this.clone(item[i]);
                }
            }
            // Objects
            else if (type === '[object Object]' && item.constructor === Object) {
                clone = {};

                for (key in item) {
                    clone[key] = this.clone(item[key]);
                }

                if (enumerables) {
                    for (j = enumerables.length; j--;) {
                        k = enumerables[j];
                        clone[k] = item[k];
                    }
                }
            }

            return clone || item;
        }
    }
})();
<commit_msg>Update UtilityBelt-1.0.0.js<commit_after>/*

Utility Belt

*/
if (typeof UtilityBelt === 'undefined') UtilityBelt = UB = {};

UtilityBelt = UB = (function () {

    /**
     * Private
     */

    return {
        /**
         * @cfg regExp
         * Regular expression library
         */
        regExp: {
            email : /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
        },

        /**
         * @Method getRegExp
         * Getter for Regular Expression library
         * @param email {string}
         * @return boolean
         */
        getRegExp: function (exp) {

            if (!this.regExp[exp]) {
                consol.warn('The regular expression', exp, 'was not found');
                return null;
            }

            return this.regExp[exp] ? this.regExp[exp] : null;
        },

        /**
         * Returns `true` if the passed value is email, `false` otherwise
         * @param {String} value The value to test.
         * @return {Boolean}
         */
        isEmail: function (value) {
            return this.getRegExp('email').test(value);
        },

		/**
         * Returns the given value itself if it's not empty, returns the default
         * value (second argument) otherwise.
         *
         * @param {Object} value The value to test.
         * @param {Object} defaultValue The value to return if the original value is empty.
         * @param {Boolean} [allowBlank=false] (optional) `true` to allow zero length strings to qualify as non-empty.
         * @return {Object} `value`, if non-empty, else `defaultValue`.
         */
        valueFrom: function(value, defaultValue, allowBlank){
            return this.isEmpty(value, allowBlank) ? defaultValue : value;
        },

        /**
         * Returns the type of the given variable in string format. List of possible values are:
         *
         * - `undefined`: If the given value is `undefined`
         * - `null`: If the given value is `null`
         * - `string`: If the given value is a string
         * - `number`: If the given value is a number
         * - `boolean`: If the given value is a boolean value
         * - `date`: If the given value is a `Date` object
         * - `function`: If the given value is a function reference
         * - `object`: If the given value is an object
         * - `array`: If the given value is an array
         * - `regexp`: If the given value is a regular expression
         * - `element`: If the given value is a DOM Element
         * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
         * - `whitespace`: If the given value is a DOM text node and contains only whitespace
         *
         * @param {Object} value
         * @return {String}
         */
        typeOf: function(value) {
            if (value === null) {
                return 'null';
            }

            var type = typeof value;

            if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
                return type;
            }

            var typeToString = toString.call(value);

            switch(typeToString) {
                case '[object Array]':
                    return 'array';
                case '[object Date]':
                    return 'date';
                case '[object Boolean]':
                    return 'boolean';
                case '[object Number]':
                    return 'number';
                case '[object RegExp]':
                    return 'regexp';
            }

            if (type === 'function') {
                return 'function';
            }

            if (type === 'object') {
                if (value.nodeType !== undefined) {
                    if (value.nodeType === 3) {
                        return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
                    }
                    else {
                        return 'element';
                    }
                }

                return 'object';
            }

            //<debug error>
            console.warn('Failed to determine the type of the specified value "' + value + '". This is most likely a bug.');
            //</debug>
        },

        /**
         * Returns `true` if the passed value is empty, `false` otherwise. The value is deemed to be empty if it is either:
         *
         * - `null`
         * - `undefined`
         * - a zero-length array.
         * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`).
         *
         * @param {Object} value The value to test.
         * @param {Boolean} [allowEmptyString=false] (optional) `true` to allow empty strings.
         * @return {Boolean}
         */
        isEmpty: function(value, allowEmptyString) {
            return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
        },

        /**
         * Returns `true` if the passed value is a JavaScript Array, `false` otherwise.
         *
         * @param {Object} target The target to test.
         * @return {Boolean}
         * @method
         */
        isArray: ('isArray' in Array) ? Array.isArray : function(value) {
            return toString.call(value) === '[object Array]';
        },

        /**
         * Returns 'true' if the passed value is a JavaScript Date object, 'false' otherwise.
         * @param {Object} object The object to test.
         * @return {Boolean}
         */
        isDate: function(value) {
            return toString.call(value) === '[object Date]';
        },

		/**
         * Returns week 'number' if the passed value is a JavaScript Date object, '-1' otherwise.
         * @param {Object} object The object to test.
         * @return {Boolean}
         */
		getWeek: function(value) {

			if(!this.isDate(value)) return -1;

			// Create a copy of this date object
            var target = this.clone(value),

            // ISO week date weeks start on monday so correct the day number
            dayNr = (value.getDay() + 6) % 7;

            target.setDate(target.getDate() - dayNr + 3);
            var firstThursday = target.valueOf();

            // Set the target to the first thursday of the year
            // First set the target to january first
            target.setMonth(0, 1);

            // Not a thursday? Correct the date to the next thursday
            if (target.getDay() != 4) {
                target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7);
            }

            // The weeknumber is the number of weeks between the
            // first thursday of the year and the thursday in the target week
            return 1 + Math.ceil((firstThursday - target) / 604800000);
		},
        /**
         * Returns 'true' if the passed value is a String that matches the MS Date JSON encoding format
         * @param {String} value The string to test
         * @return {Boolean}
         */
        isMSDate: function(value) {
            if (!Ext.isString(value)) {
                return false;
            } else {
                return value.match("\\\\?/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\\\?/") !== null;
            }
        },

        /**
         * Returns `true` if the passed value is a JavaScript Object, `false` otherwise.
         * @param {Object} value The value to test.
         * @return {Boolean}
         * @method
         */
        isObject: (toString.call(null) === '[object Object]') ?
        function(value) {
            // check ownerDocument here as well to exclude DOM nodes
            return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
        } :
        function(value) {
            return toString.call(value) === '[object Object]';
        },

        /**
         * @private
         */
        isSimpleObject: function(value) {
            return value instanceof Object && value.constructor === Object;
        },
        /**
         * Returns `true` if the passed value is a JavaScript 'primitive', a string, number or Boolean.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isPrimitive: function(value) {
            var type = typeof value;

            return type === 'string' || type === 'number' || type === 'boolean';
        },

        /**
         * Returns `true` if the passed value is a JavaScript Function, `false` otherwise.
         * @param {Object} value The value to test.
         * @return {Boolean}
         * @method
         */
        isFunction:
        // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
        // Object.prorotype.toString (slower)
        (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
            return toString.call(value) === '[object Function]';
        } : function(value) {
            return typeof value === 'function';
        },

        /**
         * Returns `true` if the passed value is a number. Returns `false` for non-finite numbers.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isNumber: function(value) {
            return typeof value === 'number' && isFinite(value);
        },

        /**
         * Validates that a value is numeric.
         * @param {Object} value Examples: 1, '1', '2.34'
         * @return {Boolean} `true` if numeric, `false` otherwise.
         */
        isNumeric: function(value) {
            return !isNaN(parseFloat(value)) && isFinite(value);
        },

        /**
         * Returns `true` if the passed value is a string.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isString: function(value) {
            return typeof value === 'string';
        },

        /**
         * Returns `true` if the passed value is a Boolean.
         *
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isBoolean: function(value) {
            return typeof value === 'boolean';
        },

        /**
         * Returns `true` if the passed value is an HTMLElement.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isElement: function(value) {
            return value ? value.nodeType === 1 : false;
        },

        /**
         * Returns `true` if the passed value is a TextNode.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isTextNode: function(value) {
            return value ? value.nodeName === "#text" : false;
        },

        /**
         * Returns `true` if the passed value is defined.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isDefined: function(value) {
            return typeof value !== 'undefined';
        },

        /**
         * Returns `true` if the passed value is iterable, `false` otherwise.
         * @param {Object} value The value to test.
         * @return {Boolean}
         */
        isIterable: function(value) {
            return (value && typeof value !== 'string') ? value.length !== undefined : false;
        },

		/**
         * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference.
         * @param {Object} item The variable to clone.
         * @return {Object} clone
         */
		clone: function(item) {
            if (item === null || item === undefined) {
                return item;
            }

            // DOM nodes
            if (item.nodeType && item.cloneNode) {
                return item.cloneNode(true);
            }

            // Strings
            var type = toString.call(item);

            // Dates
            if (type === '[object Date]') {
                return new Date(item.getTime());
            }

            var i, j, k, clone, key;

            // Arrays
            if (type === '[object Array]') {
                i = item.length;

                clone = [];

                while (i--) {
                    clone[i] = this.clone(item[i]);
                }
            }
            // Objects
            else if (type === '[object Object]' && item.constructor === Object) {
                clone = {};

                for (key in item) {
                    clone[key] = this.clone(item[key]);
                }

                if (enumerables) {
                    for (j = enumerables.length; j--;) {
                        k = enumerables[j];
                        clone[k] = item[k];
                    }
                }
            }

            return clone || item;
        }
    }
})();
<|endoftext|>


<commit_before>define(['jquery', 'underscore', 'backbone',
	'text!./app/views/templates/main.html',
	'text!./app/views/templates/item-listing.html',
	'text!./app/views/templates/item-listing-empty.html',
	'text!./app/views/templates/person-info.html',
	'text!./app/views/templates/loading.html',
	'text!./app/views/templates/delete-modal.html',
	'models', 'utils', 'facebook', 'view_common'
], function($, _, Backbone, mainTemplate, itemListingTemplate, itemListingEmptyTemplate, personInfoTemplate, defaultLoadingTemplate,
	deleteModalTemplate, Models, Utils) {
	var Views = {};

	Views.GenericCollectionView = Backbone.View.extend({
		loadingTemplate: defaultLoadingTemplate,

		initialize: function() {
			this.listenTo(this.collection, 'add', this.collectionAdded);
			this.listenTo(this.collection, 'change', this.collectionChanged);
			this.listenTo(this.collection, 'remove', this.collectionRemoved);

			var that = this;
			this.listenTo(this.collection, 'fetched', function() {
				that._loaded = true;
				that.render();
			});

			this.childViews = [];
			this.render();
		},

		render: function() {
			var fragment = document.createDocumentFragment();

			if (!this._loaded) {
				$(this.loadingTemplate).appendTo(fragment);
			} else if (this.childViews.length > 0) {
				_(this.childViews).each(function(currentView) {
					fragment.appendChild(currentView.render().el);
				});
			} else {
				$(itemListingEmptyTemplate).appendTo(fragment);
			}

			this._appendFragmentToDocument(fragment);
			return this;
		},

		collectionAdded: function(item) {
			var that = this;
			item.set({
				userId: that.options.userId
			});
			this._addViewForModel(item);
			this.render();
		},

		collectionRemoved: function(item) {
			this.childViews = this.childViews.filter(function(view) {
				return view.model.id !== item.id;
			});
			this.render();
		},

		collectionChanged: function(item) {
			if (!item.changed.userId) {
				this.childViews = [];
				this._addAllModels();
			}
		},

		_addViewForModel: function(item) {
			this.childViews.push(new this.subView({
				model: item
			}));
		},

		_addAllModels: function() {
			var that = this;
			this.collection.each(function(item) {
				item.set({
					userId: that.options.userId
				});
				that._addViewForModel(item);
			});
			this.render();
		}
	});

	Views.ItemView = Backbone.View.extend({
		template: _.template(itemListingTemplate),
		personInfoTemplate: _.template(personInfoTemplate),

		events: {
			"click button#btn-pledge": 'pledgeClicked',
			"click button#btn-unpledge": 'unpledgeClicked',
			"click button#btn-delete": 'deleteClicked',
			"click div.item-buyers": "buyersClicked"
		},

		initialize: function() {
			this.listenTo(this.model, "change", this.render);
			this.id = "item-" + this.model.attributes.id;
		},

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

		pledgeClicked: function() {
			var that = this;
			$.ajax({
				url: '/service/me/want',
				dataType: 'json',
				type: 'POST',
				data: {
					product_listing_id: this.model.attributes.id
				}
			}).success(function() {
				FB.api('me/crowdbuyfb:want_to_purchase', 'post',
					{
					  item: 'http://fb.sapuan.org/service/products/og/' + that.model.attributes.productId
					},
					function(response) {
					  // handle the response
					}
				  );
			});
			this.model.fetch();
		},

		unpledgeClicked: function() {
			$.ajax({
				url: '/service/me/dontWant',
				dataType: 'json',
				type: 'POST',
				data: {
					product_listing_id: this.model.attributes.id
				}
			});
			this.model.fetch();
		},

		buyersClicked: function() {
			if (this._buyerListPopover) {
				//We only handle this once.
				return;
			}

			var buyerIds = this.model.attributes.buyers;
			buyerIds.push(this.model.attributes.owner);
			var buyerQueries = [];
			var buyerObjects = new Backbone.Collection([], {

			});
			_.each(buyerIds, function(currentId) {
				var requestUrl = Utils.getFacebookApiLink(currentId);
				var pictureUrl = requestUrl + '/picture';
				buyerQueries.push($.ajax({
					url: requestUrl,
					dataType: 'json',
					type: 'GET',
					success: function(response) {
						buyerObjects.add({
							name: response.name,
							link: response.link || '//www.facebook.com/' + response.id,
							picture: pictureUrl
						});
					}
				}));
			});

			var that = this;
			$.when.apply($, buyerQueries)
				.done(function() {
					function handlePopoverClick(e) {
						if (!that._buyerListPopover) {
							return;
						}

						var target = e.target || e.toElement;
						if (target === that._buyerListPopover[0]) {
							//We are going to ourself.
							that._buyerListPopover.popover('toggle');
						} else {
							dismissPopover();
						}
					}

					function dismissPopover() {
						if (that._buyerListPopover) {
							that._buyerListPopover.popover('hide');
						}
					}

					var buyerFragment = document.createElement('div');
					for (var i = 0; i < buyerObjects.length; ++i) {
						$(that.personInfoTemplate(buyerObjects.models[i].attributes)).
						appendTo(buyerFragment);
					}

					$('a', buyerFragment).tooltip();
					that._buyerListPopover = $('div.item-buyers span.item-buyers-list', that.$el);
					that._buyerListPopover.parents('div.item-listing').on('scroll', dismissPopover);
					$(document).on('click', handlePopoverClick);
					that._buyerListPopover.popover({
						selector: '#' + this.id,
						html: true,
						content: buyerFragment,
						container: that._buyerListPopover,
						toggle: 'trigger'
					});
					that._buyerListPopover.popover('show');
				});
		},

		deleteClicked: function() {
			if (this.modal) {
				this.disposeModal();
			}

			this.modal = new Views.DeleteListingView({
				model: this.model,
				el: '#modal-container'
			});
			this.listenTo(this.modal, 'viewClosed', this.disposeModal);
		},

		disposeModal: function() {
			this.modal.undelegateEvents();
		}
	});

	Views.DeleteListingView = Backbone.View.extend({
		template: _.template(deleteModalTemplate),

		initialize: function() {
			this.render();
		},

		render: function() {
			this.$el.html(this.template(this.model.attributes));
			$('#delete-warning-modal').modal('show');
		},

		events: {
			"click button.btn-danger": "deleteClicked",
			"click button.btn-success": "keepClicked"
		},

		deleteClicked: function() {
			var serviceUrl = '/service/listings/delete/' + this.model.attributes.id;

			var that = this;
			$.ajax({
				url: serviceUrl,
				dataType: 'json',
				type: 'GET',
				success: function() {
					that.dismissModal();
				},
				error: function() {
					alert("Oops. We couldn't delete the listing - try again later!");
				}
			});
		},

		keepClicked: function() {
			this.dismissModal();
		},

		dismissModal: function() {
			$('#delete-warning-modal').modal('hide');
			this.trigger("viewClosed");
		}
	});

	Views.ListingView = Views.GenericCollectionView.extend({
		_appendFragmentToDocument: function(fragment) {
			this.$('.item-listing').html(fragment);
		},
		subView: Views.ItemView
	});

	return Views;
});<commit_msg>Remove listings when deleted.<commit_after>define(['jquery', 'underscore', 'backbone',
	'text!./app/views/templates/main.html',
	'text!./app/views/templates/item-listing.html',
	'text!./app/views/templates/item-listing-empty.html',
	'text!./app/views/templates/person-info.html',
	'text!./app/views/templates/loading.html',
	'text!./app/views/templates/delete-modal.html',
	'models', 'utils', 'facebook', 'view_common'
], function($, _, Backbone, mainTemplate, itemListingTemplate, itemListingEmptyTemplate, personInfoTemplate, defaultLoadingTemplate,
	deleteModalTemplate, Models, Utils) {
	var Views = {};

	Views.GenericCollectionView = Backbone.View.extend({
		loadingTemplate: defaultLoadingTemplate,

		initialize: function() {
			this.listenTo(this.collection, 'add', this.collectionAdded);
			this.listenTo(this.collection, 'change', this.collectionChanged);
			this.listenTo(this.collection, 'remove', this.collectionRemoved);

			var that = this;
			this.listenTo(this.collection, 'fetched', function() {
				that._loaded = true;
				that.render();
			});

			this.childViews = [];
			this.render();
		},

		render: function() {
			var fragment = document.createDocumentFragment();

			if (!this._loaded) {
				$(this.loadingTemplate).appendTo(fragment);
			} else if (this.childViews.length > 0) {
				_(this.childViews).each(function(currentView) {
					fragment.appendChild(currentView.render().el);
				});
			} else {
				$(itemListingEmptyTemplate).appendTo(fragment);
			}

			this._appendFragmentToDocument(fragment);
			return this;
		},

		collectionAdded: function(item) {
			var that = this;
			item.set({
				userId: that.options.userId
			});
			this._addViewForModel(item);
			this.render();
		},

		collectionRemoved: function(item) {
			this.childViews = this.childViews.filter(function(view) {
				return view.model.id !== item.id;
			});
			this.render();
		},

		collectionChanged: function(item) {
			if (!item.changed.userId) {
				this.childViews = [];
				this._addAllModels();
			}
		},

		_addViewForModel: function(item) {
			this.childViews.push(new this.subView({
				model: item
			}));
		},

		_addAllModels: function() {
			var that = this;
			this.collection.each(function(item) {
				item.set({
					userId: that.options.userId
				});
				that._addViewForModel(item);
			});
			this.render();
		}
	});

	Views.ItemView = Backbone.View.extend({
		template: _.template(itemListingTemplate),
		personInfoTemplate: _.template(personInfoTemplate),

		events: {
			"click button#btn-pledge": 'pledgeClicked',
			"click button#btn-unpledge": 'unpledgeClicked',
			"click button#btn-delete": 'deleteClicked',
			"click div.item-buyers": "buyersClicked"
		},

		initialize: function() {
			this.listenTo(this.model, "change", this.render);
			this.id = "item-" + this.model.attributes.id;
		},

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

		pledgeClicked: function() {
			var that = this;
			$.ajax({
				url: '/service/me/want',
				dataType: 'json',
				type: 'POST',
				data: {
					product_listing_id: this.model.attributes.id
				}
			}).success(function() {
				FB.api('me/crowdbuyfb:want_to_purchase', 'post',
					{
					  item: 'http://fb.sapuan.org/service/products/og/' + that.model.attributes.productId
					},
					function(response) {
					  // handle the response
					}
				  );
			});
			this.model.fetch();
		},

		unpledgeClicked: function() {
			$.ajax({
				url: '/service/me/dontWant',
				dataType: 'json',
				type: 'POST',
				data: {
					product_listing_id: this.model.attributes.id
				}
			});
			this.model.fetch();
		},

		buyersClicked: function() {
			if (this._buyerListPopover) {
				//We only handle this once.
				return;
			}

			var buyerIds = this.model.attributes.buyers;
			buyerIds.push(this.model.attributes.owner);
			var buyerQueries = [];
			var buyerObjects = new Backbone.Collection([], {

			});
			_.each(buyerIds, function(currentId) {
				var requestUrl = Utils.getFacebookApiLink(currentId);
				var pictureUrl = requestUrl + '/picture';
				buyerQueries.push($.ajax({
					url: requestUrl,
					dataType: 'json',
					type: 'GET',
					success: function(response) {
						buyerObjects.add({
							name: response.name,
							link: response.link || '//www.facebook.com/' + response.id,
							picture: pictureUrl
						});
					}
				}));
			});

			var that = this;
			$.when.apply($, buyerQueries)
				.done(function() {
					function handlePopoverClick(e) {
						if (!that._buyerListPopover) {
							return;
						}

						var target = e.target || e.toElement;
						if (target === that._buyerListPopover[0]) {
							//We are going to ourself.
							that._buyerListPopover.popover('toggle');
						} else {
							dismissPopover();
						}
					}

					function dismissPopover() {
						if (that._buyerListPopover) {
							that._buyerListPopover.popover('hide');
						}
					}

					var buyerFragment = document.createElement('div');
					for (var i = 0; i < buyerObjects.length; ++i) {
						$(that.personInfoTemplate(buyerObjects.models[i].attributes)).
						appendTo(buyerFragment);
					}

					$('a', buyerFragment).tooltip();
					that._buyerListPopover = $('div.item-buyers span.item-buyers-list', that.$el);
					that._buyerListPopover.parents('div.item-listing').on('scroll', dismissPopover);
					$(document).on('click', handlePopoverClick);
					that._buyerListPopover.popover({
						selector: '#' + this.id,
						html: true,
						content: buyerFragment,
						container: that._buyerListPopover,
						toggle: 'trigger'
					});
					that._buyerListPopover.popover('show');
				});
		},

		deleteClicked: function() {
			if (this.modal) {
				this.disposeModal();
			}

			this.modal = new Views.DeleteListingView({
				model: this.model,
				el: '#modal-container'
			});
			this.listenTo(this.modal, 'viewClosed', this.disposeModal);
		},

		disposeModal: function(isDeleted) {
			this.modal.undelegateEvents();

			if (isDeleted) {
				this.model.trigger("destroy");
			}
		}
	});

	Views.DeleteListingView = Backbone.View.extend({
		template: _.template(deleteModalTemplate),

		initialize: function() {
			this.render();
		},

		render: function() {
			this.$el.html(this.template(this.model.attributes));
			$('#delete-warning-modal').modal('show');
		},

		events: {
			"click button.btn-danger": "deleteClicked",
			"click button.btn-success": "keepClicked"
		},

		deleteClicked: function() {
			var serviceUrl = '/service/listings/delete/' + this.model.attributes.id;

			var that = this;
			$.ajax({
				url: serviceUrl,
				dataType: 'json',
				type: 'GET',
				success: function() {
					that.dismissModal(true);
				},
				error: function() {
					alert("Oops. We couldn't delete the listing - try again later!");
				}
			});
		},

		keepClicked: function() {
			this.dismissModal(false);
		},

		dismissModal: function(isDeleted) {
			$('#delete-warning-modal').modal('hide');
			this.trigger("viewClosed", isDeleted);
		}
	});

	Views.ListingView = Views.GenericCollectionView.extend({
		_appendFragmentToDocument: function(fragment) {
			this.$('.item-listing').html(fragment);
		},
		subView: Views.ItemView
	});

	return Views;
});<|endoftext|>