<commit_before>;(function(){//IFEE

angular.module('brewKeeper')
      .controller('recipeDetail', function($scope, $http, $location, $routeParams, $rootScope){
          var id = $routeParams.id;
          var username = $routeParams.username;
          $scope.username = $routeParams.username;
          $scope.id = $routeParams.id;
          $(document).scrollTop(0);

          $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + "/")
            .then(function(response){
              $rootScope.detail = response.data;
              $rootScope.steps = response.data.steps;
              $rootScope.notes = response.data.brewnotes;
              var currentRating = $rootScope.detail.rating;
              $scope.rating = 0;
              $scope.ratings = [{
                  current: currentRating,
                  max: 5
              }];

          }) //end http.get

          $scope.rateRecipe = function (rating) {
            var newRating = {"rating": rating}
            $http.patch("https://brew-keeper-api.herokuapp.com/api/users/"+ username + "/recipes/"+ id + "/", newRating)
          }

          $scope.EliminateRecipe = function() {
            if (window.confirm("Are you sure you want to delete " + $scope.detail.title + "?")){
              $http.delete('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + '/').then(function(){
                $location.path('/'+ username);
              })
            };
          }; //end Eliminate function

          $scope.deleteStep = function(stepNumber, stepId){
            if (window.confirm("Are you sure you want to delete step " + stepNumber + "?")){
              $http.delete("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ id +"/steps/"+ stepId + "/").then(function(){
                $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id +'/')
                  .then(function(response){
                    $scope.steps = response.data.steps;
                    if(response.data.steps.length == 0){
                      $(".brew-it-button").addClass("hidden");
                      $(".no-steps").removeClass("hidden");
                    }
                  })
              })
            }
          } //end deleteStep function

          // $scope.showSteps = function(stepId){
          //   stepId= "div." + stepId.toString()
          //   $(stepId).removeClass("hidden")
          // };

          $scope.hideEditStep = function(stepId){
            stepId = "div." + stepId.toString();
            $(stepId).toggleClass("hidden")
          }

          $scope.editStep = function(step){
            $http.patch("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ id +"/steps/"+ step.id + "/", step)
              .then(function() {
                $scope.hideEditStep(step.id)
                // $scope.showEditSteps(step.id)
              });
          } //end editStep function

          $scope.showEditStep = function(stepId){
            stepId = "div." + stepId.toString();
            $(stepId).toggleClass("hidden");
          }

          $scope.increaseStep = function(step){
            if(step.step_number >= $rootScope.steps.length){
              return
            }
            step.step_number++
            $http.patch("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ id +"/steps/"+ step.id + "/", step).then(function(){
              $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + '/')
                .then(function(response){
                  $rootScope.steps = response.data.steps;
                })
            })
          } //end increaseStep function

          $scope.decreaseStep = function(step){
            if(step.step_number <= 1){
              return
            }
            step.step_number--
            $http.patch("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ id +"/steps/"+ step.id + "/", step).then(function(){
              $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + '/')
                .then(function(response){
                  $rootScope.steps = response.data.steps;
                })
            })
          } //end decreaseStep function

          $scope.step = { }//Might need to prepopulate this with empty strings for each key... Maybe...
          $scope.addStep=function(){ //add step function
            $scope.step.step_number = $scope.steps.length + 1;
            $http.post("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ id +"/steps/", $scope.step).then(function(){
              $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + '/')
                .then(function(response){
                  $(".brew-it-button").removeClass("hidden");
                  $(".no-steps").addClass("hidden");
                  $rootScope.steps = response.data.steps;
                })
            })
            $scope.step= { };
          } //end new step submit function

        $scope.showAddSteps = function(){
          $("form.create-new-step").toggleClass("hidden");
        };//Reveal "Add Step" when new recipe form is submitted.
        // $scope.hideAddSteps = function(){
        //   $("form.create-new-step").addClass("hidden");
        //   $("button.done-adding").addClass("hidden");
        // }; //hides the add step form

        $('.edit-button').on('click', function(){
          $('.edit-recipe').removeClass("hidden");
          $('.recipe-view').addClass("hidden");
        });

        $(".no-steps").click(function(){
          $scope.showAddSteps()
        })

      $scope.editRecipe = function(recipe){
        $http.patch("https://brew-keeper-api.herokuapp.com/api/users/"+ username + "/recipes/"+ id + "/", recipe)
        .then( function () {
          $('.edit-recipe').addClass("hidden");
          $('.recipe-view').removeClass("hidden");
        })
      } //end editRecipe function
        $('.cancel-recipe-edit').on('click', function(){
          $('.recipe-view').removeClass("hidden");
          $('.edit-recipe').addClass("hidden");
        });

        // Function for cloning recipes
        $scope.cloneRecipe = function(){
          if (!window.confirm("Are you sure you want to clone "+ $scope.detail.title +" ?")){
            return;
          };
          var cloneData = {}
          cloneData.title = "Clone of: " + $scope.detail.title;
          cloneData.bean_name = $scope.detail.bean_name;
          cloneData.roast = $scope.detail.roast;
          cloneData.orientation = $scope.detail.orientation;
          cloneData.general_recipe_comment = $scope.detail.general_recipe_comment;
          cloneData.grind = $scope.detail.grind;
          cloneData.total_bean_amount = $scope.detail.total_bean_amount;
          cloneData.bean_units = $scope.detail.bean_units;
          cloneData.water_type = $scope.detail.water_type;
          cloneData.total_water_amount = $scope.detail.total_water_amount;
          cloneData.water_units = $scope.detail.water_units;
          cloneData.temp = $scope.detail.temp;
          cloneData.steps = [];

          $http.post("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/", cloneData).success(function(response){

            newRecipeId = response.id;
            steps = [];
            for(step in $scope.detail.steps){
              steps[step] = {};
              steps[step].step_number = $scope.detail.steps[step].step_number;
              steps[step].step_title = $scope.detail.steps[step].step_title;
              steps[step].step_body = $scope.detail.steps[step].step_body;
              steps[step].duration = $scope.detail.steps[step].duration;
              steps[step].water_amount = $scope.detail.steps[step].water_amount;

              $http.post("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ newRecipeId +"/steps/", steps[step])//end step post
            };//end loop to clone steps
          })
          .then(function(){
            $location.path("/"+ username +"/clone/"+ newRecipeId);
          })//end post new recipe
        }; //end recipe clone function

        $scope.showEditNote = function(noteId) {
          noteView = "div.note-view" + noteId.toString();
          editNote = "article.edit-note" + noteId.toString();
          $(noteView).addClass("hidden")
          $(editNote).removeClass("hidden")
        }
          // $scope.showEditStep = function(stepId){
          //   stepId = "div." + stepId.toString();
          //   $(stepId).removeClass("hidden")
          // }

        $scope.editNote = function(note){
          var note_id = note.id
          $http.put("https://brew-keeper-api.herokuapp.com/api/users/"+ username + "/recipes/"+ id + "/brewnotes/" + note_id + "/", note)
          .then( function () {
            $(editNote).addClass("hidden");
            $(noteView).removeClass("hidden");
          })
        } //end editNote function

        $scope.deleteNote = function(noteId) {
          var noteId = noteId
          if (window.confirm("Are you sure you want to delete this note?")){
            $http.delete("https://brew-keeper-api.herokuapp.com/api/users/"+ username + "/recipes/"+ id + "/brewnotes/" + noteId + "/")
            .then(function(){var id = $scope.id;
            $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + "/")
              .then(function(response){
                $rootScope.notes = response.data.brewnotes;
              })
            })
          };
        }; //end deleteNote function

        $scope.showAddBrewNote = function(){
          $(".brew-form").toggleClass("hidden");
        };

        $scope.addBrewNote=function(){
          $http.post('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + '/brewnotes/', $scope.brewnote)
          .success(function (data) {
            $(".brew-form").toggleClass("hidden");
            var id = $scope.id;
            $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + "/")
              .then(function(response){
                $rootScope.notes = response.data.brewnotes;
              })
          })
        $scope.brewnote = { };
        $scope.addNote = false;
        }//Add Brew Note Form

        $scope.showNoteIcons = function(noteId){
          $(".note-icons").filter($("."+ noteId)).toggleClass("hidden");
        }

        $scope.makePublic = function(){ //makes recipes public
          if (!window.confirm("Are you sure you want to clone "+ $scope.detail.title +" ?")){
            return;
          };

          var publicData = {}  //build the recipe
          publicData.title = "Public Copy of: " + $scope.detail.title;
          publicData.bean_name = $scope.detail.bean_name;
          publicData.roast = $scope.detail.roast;
          publicData.orientation = $scope.detail.orientation;
          publicData.general_recipe_comment = $scope.detail.general_recipe_comment;
          publicData.grind = $scope.detail.grind;
          publicData.total_bean_amount = $scope.detail.total_bean_amount;
          publicData.bean_units = $scope.detail.bean_units;
          publicData.water_type = $scope.detail.water_type;
          publicData.total_water_amount = $scope.detail.total_water_amount;
          publicData.water_units = $scope.detail.water_units;
          publicData.temp = $scope.detail.temp;
          publicData.steps = [];

          $http.post("https://brew-keeper-api.herokuapp.com/api/users/public/recipes/", publicData).success(function(response){

            newRecipeId = response.id;

            steps = []; //build the steps
            for(step in $scope.detail.steps){
              steps[step] = {};
              steps[step].step_number = $scope.detail.steps[step].step_number;
              steps[step].step_title = $scope.detail.steps[step].step_title;
              steps[step].step_body = $scope.detail.steps[step].step_body;
              steps[step].duration = $scope.detail.steps[step].duration;
              steps[step].water_amount = $scope.detail.steps[step].water_amount;

              $http.post("https://brew-keeper-api.herokuapp.com/api/users/pubic/recipes/"+ newRecipeId +"/steps/", steps[step])//end step post

            } //end for loop to build steps
          }) //end .success for posting new recipe to public
          .then(function(){
            window.alert("Thank you for sharing " + $scope.detail.title)
          })

      }; // end makePublic function

      }) //end recipDetail controller

})();//END Angular IFEE
<commit_msg>add logic to prevent making recipes with no steps public<commit_after>;(function(){//IFEE

angular.module('brewKeeper')
      .controller('recipeDetail', function($scope, $http, $location, $routeParams, $rootScope){
          var id = $routeParams.id;
          var username = $routeParams.username;
          $scope.username = $routeParams.username;
          $scope.id = $routeParams.id;
          $(document).scrollTop(0);

          $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + "/")
            .then(function(response){
              $rootScope.detail = response.data;
              $rootScope.steps = response.data.steps;
              $rootScope.notes = response.data.brewnotes;
              var currentRating = $rootScope.detail.rating;
              $scope.rating = 0;
              $scope.ratings = [{
                  current: currentRating,
                  max: 5
              }];

          }) //end http.get

          $scope.rateRecipe = function (rating) {
            var newRating = {"rating": rating}
            $http.patch("https://brew-keeper-api.herokuapp.com/api/users/"+ username + "/recipes/"+ id + "/", newRating)
          }

          $scope.EliminateRecipe = function() {
            if (window.confirm("Are you sure you want to delete " + $scope.detail.title + "?")){
              $http.delete('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + '/').then(function(){
                $location.path('/'+ username);
              })
            };
          }; //end Eliminate function

          $scope.deleteStep = function(stepNumber, stepId){
            if (window.confirm("Are you sure you want to delete step " + stepNumber + "?")){
              $http.delete("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ id +"/steps/"+ stepId + "/").then(function(){
                $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id +'/')
                  .then(function(response){
                    $scope.steps = response.data.steps;
                    if(response.data.steps.length == 0){
                      $(".brew-it-button").addClass("hidden");
                      $(".no-steps").removeClass("hidden");
                    }
                  })
              })
            }
          } //end deleteStep function

          // $scope.showSteps = function(stepId){
          //   stepId= "div." + stepId.toString()
          //   $(stepId).removeClass("hidden")
          // };

          $scope.hideEditStep = function(stepId){
            stepId = "div." + stepId.toString();
            $(stepId).toggleClass("hidden")
          }

          $scope.editStep = function(step){
            $http.patch("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ id +"/steps/"+ step.id + "/", step)
              .then(function() {
                $scope.hideEditStep(step.id)
                // $scope.showEditSteps(step.id)
              });
          } //end editStep function

          $scope.showEditStep = function(stepId){
            stepId = "div." + stepId.toString();
            $(stepId).toggleClass("hidden");
          }

          $scope.increaseStep = function(step){
            if(step.step_number >= $rootScope.steps.length){
              return
            }
            step.step_number++
            $http.patch("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ id +"/steps/"+ step.id + "/", step).then(function(){
              $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + '/')
                .then(function(response){
                  $rootScope.steps = response.data.steps;
                })
            })
          } //end increaseStep function

          $scope.decreaseStep = function(step){
            if(step.step_number <= 1){
              return
            }
            step.step_number--
            $http.patch("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ id +"/steps/"+ step.id + "/", step).then(function(){
              $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + '/')
                .then(function(response){
                  $rootScope.steps = response.data.steps;
                })
            })
          } //end decreaseStep function

          $scope.step = { }//Might need to prepopulate this with empty strings for each key... Maybe...
          $scope.addStep=function(){ //add step function
            $scope.step.step_number = $scope.steps.length + 1;
            $http.post("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ id +"/steps/", $scope.step).then(function(){
              $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + '/')
                .then(function(response){
                  $(".brew-it-button").removeClass("hidden");
                  $(".no-steps").addClass("hidden");
                  $rootScope.steps = response.data.steps;
                })
            })
            $scope.step= { };
          } //end new step submit function

        $scope.showAddSteps = function(){
          $("form.create-new-step").toggleClass("hidden");
        };//Reveal "Add Step" when new recipe form is submitted.
        // $scope.hideAddSteps = function(){
        //   $("form.create-new-step").addClass("hidden");
        //   $("button.done-adding").addClass("hidden");
        // }; //hides the add step form

        $('.edit-button').on('click', function(){
          $('.edit-recipe').removeClass("hidden");
          $('.recipe-view').addClass("hidden");
        });

        $(".no-steps").click(function(){
          $scope.showAddSteps()
        })

      $scope.editRecipe = function(recipe){
        $http.patch("https://brew-keeper-api.herokuapp.com/api/users/"+ username + "/recipes/"+ id + "/", recipe)
        .then( function () {
          $('.edit-recipe').addClass("hidden");
          $('.recipe-view').removeClass("hidden");
        })
      } //end editRecipe function
        $('.cancel-recipe-edit').on('click', function(){
          $('.recipe-view').removeClass("hidden");
          $('.edit-recipe').addClass("hidden");
        });

        // Function for cloning recipes
        $scope.cloneRecipe = function(){
          if (!window.confirm("Are you sure you want to clone "+ $scope.detail.title +" ?")){
            return;
          };
          var cloneData = {}
          cloneData.title = "Clone of: " + $scope.detail.title;
          cloneData.bean_name = $scope.detail.bean_name;
          cloneData.roast = $scope.detail.roast;
          cloneData.orientation = $scope.detail.orientation;
          cloneData.general_recipe_comment = $scope.detail.general_recipe_comment;
          cloneData.grind = $scope.detail.grind;
          cloneData.total_bean_amount = $scope.detail.total_bean_amount;
          cloneData.bean_units = $scope.detail.bean_units;
          cloneData.water_type = $scope.detail.water_type;
          cloneData.total_water_amount = $scope.detail.total_water_amount;
          cloneData.water_units = $scope.detail.water_units;
          cloneData.temp = $scope.detail.temp;
          cloneData.steps = [];

          $http.post("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/", cloneData).success(function(response){

            newRecipeId = response.id;
            steps = [];
            for(step in $scope.detail.steps){
              steps[step] = {};
              steps[step].step_number = $scope.detail.steps[step].step_number;
              steps[step].step_title = $scope.detail.steps[step].step_title;
              steps[step].step_body = $scope.detail.steps[step].step_body;
              steps[step].duration = $scope.detail.steps[step].duration;
              steps[step].water_amount = $scope.detail.steps[step].water_amount;

              $http.post("https://brew-keeper-api.herokuapp.com/api/users/"+ username +"/recipes/"+ newRecipeId +"/steps/", steps[step])//end step post
            };//end loop to clone steps
          })
          .then(function(){
            $location.path("/"+ username +"/clone/"+ newRecipeId);
          })//end post new recipe
        }; //end recipe clone function

        $scope.showEditNote = function(noteId) {
          noteView = "div.note-view" + noteId.toString();
          editNote = "article.edit-note" + noteId.toString();
          $(noteView).addClass("hidden")
          $(editNote).removeClass("hidden")
        }
          // $scope.showEditStep = function(stepId){
          //   stepId = "div." + stepId.toString();
          //   $(stepId).removeClass("hidden")
          // }

        $scope.editNote = function(note){
          var note_id = note.id
          $http.put("https://brew-keeper-api.herokuapp.com/api/users/"+ username + "/recipes/"+ id + "/brewnotes/" + note_id + "/", note)
          .then( function () {
            $(editNote).addClass("hidden");
            $(noteView).removeClass("hidden");
          })
        } //end editNote function

        $scope.deleteNote = function(noteId) {
          var noteId = noteId
          if (window.confirm("Are you sure you want to delete this note?")){
            $http.delete("https://brew-keeper-api.herokuapp.com/api/users/"+ username + "/recipes/"+ id + "/brewnotes/" + noteId + "/")
            .then(function(){var id = $scope.id;
            $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + "/")
              .then(function(response){
                $rootScope.notes = response.data.brewnotes;
              })
            })
          };
        }; //end deleteNote function

        $scope.showAddBrewNote = function(){
          $(".brew-form").toggleClass("hidden");
        };

        $scope.addBrewNote=function(){
          $http.post('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + '/brewnotes/', $scope.brewnote)
          .success(function (data) {
            $(".brew-form").toggleClass("hidden");
            var id = $scope.id;
            $http.get('https://brew-keeper-api.herokuapp.com/api/users/' + username + '/recipes/' + id + "/")
              .then(function(response){
                $rootScope.notes = response.data.brewnotes;
              })
          })
        $scope.brewnote = { };
        $scope.addNote = false;
        }//Add Brew Note Form

        $scope.showNoteIcons = function(noteId){
          $(".note-icons").filter($("."+ noteId)).toggleClass("hidden");
        }

        $scope.makePublic = function(){ //makes recipes public
          if ($rootScope.steps.length < 1) {
            window.alert("Plese add steps to recipe before making it public.")
            return;
          };

          if (!window.confirm("Are you sure you want to make this recipe public?")){
            return;
          };

          var publicData = {}  //build the recipe
          publicData.title = "Public Copy of: " + $scope.detail.title;
          publicData.bean_name = $scope.detail.bean_name;
          publicData.roast = $scope.detail.roast;
          publicData.orientation = $scope.detail.orientation;
          publicData.general_recipe_comment = $scope.detail.general_recipe_comment;
          publicData.grind = $scope.detail.grind;
          publicData.total_bean_amount = $scope.detail.total_bean_amount;
          publicData.bean_units = $scope.detail.bean_units;
          publicData.water_type = $scope.detail.water_type;
          publicData.total_water_amount = $scope.detail.total_water_amount;
          publicData.water_units = $scope.detail.water_units;
          publicData.temp = $scope.detail.temp;
          publicData.steps = [];

          $http.post("https://brew-keeper-api.herokuapp.com/api/users/public/recipes/", publicData).success(function(response){

            newRecipeId = response.id;

            steps = []; //build the steps
            for(step in $scope.detail.steps){
              steps[step] = {};
              steps[step].step_number = $scope.detail.steps[step].step_number;
              steps[step].step_title = $scope.detail.steps[step].step_title;
              steps[step].step_body = $scope.detail.steps[step].step_body;
              steps[step].duration = $scope.detail.steps[step].duration;
              steps[step].water_amount = $scope.detail.steps[step].water_amount;

              $http.post("https://brew-keeper-api.herokuapp.com/api/users/pubic/recipes/"+ newRecipeId +"/steps/", steps[step])//end step post

            } //end for loop to build steps
          }) //end .success for posting new recipe to public
          .then(function(){
            window.alert("Thank you for sharing " + $scope.detail.title)
          })

      }; // end makePublic function

      }) //end recipDetail controller

})();//END Angular IFEE
<|endoftext|>


<commit_before>var util = require('util');
var webutil = require('../util/web');
var Tab = require('../client/tab').Tab;

var AdvancedTab = function ()
{
  Tab.call(this);
};

util.inherits(AdvancedTab, Tab);

AdvancedTab.prototype.tabName = 'advanced';
AdvancedTab.prototype.mainMenu = 'advanced';

AdvancedTab.prototype.generateHtml = function ()
{
  return require('../../jade/tabs/advanced.jade')();
};

AdvancedTab.prototype.angular = function(module)
{
  module.controller('AdvancedCtrl', ['$scope', '$rootScope', 'rpId', 'rpKeychain',
                                    function ($scope, $rootScope, $id, $keychain)
  {
    $scope.options = Options;
    $scope.optionsBackup = $.extend(true, {}, Options);
    $scope.passwordProtection = !$scope.userBlob.data.persistUnlock;
    $scope.editBridge = false;
    $scope.editBlob = false;
    $scope.editAcctOptions = false;

    $scope.saveBlob = function () {
      // Save in local storage
      if (!store.disabled) {
        store.set('ripple_settings', JSON.stringify($scope.options));
      }

      $scope.editBlob = false;

      // Reload
      location.reload();
    };

    $scope.saveBridge = function () {
      // Save in local storage
      if (!store.disabled) {
        store.set('ripple_settings', JSON.stringify($scope.options));
      }

      $scope.editBridge = false;

      // Reload
      location.reload();
    };

    $scope.saveAcctOptions = function () {
      Options.advanced_feature_switch = !Options.advanced_feature_switch;

      $scope.editAcctOptions = false;
    };

    $scope.deleteBlob = function () {
      $scope.options.blobvault = "";
      // Save in local storage
      if (!store.disabled) {
        store.set('ripple_settings', JSON.stringify($scope.options));
      }
    }

    $scope.deleteBridge = function () {
      $scope.options.bridge.out.bitcoin = "";
      // Save in local storage
      if (!store.disabled) {
        store.set('ripple_settings', JSON.stringify($scope.options));
      }
    }

    $scope.cancelEditBlob = function () {
      $scope.editBlob = false;
      $scope.options.blobvault = $scope.optionsBackup.blobvault;
    }

    $scope.cancelEditBridge = function () {
      $scope.editBridge = false;
      $scope.options.bridge.out.bitcoin = $scope.optionsBackup.bridge.out.bitcoin;
    }

    $scope.cancelEditAcctOptions = function () {
      $scope.editAcctOptions = false;

    }

    $scope.$on('$blobUpdate', function(){
      $scope.passwordProtection = !$scope.userBlob.data.persistUnlock;
    });

    $scope.setPasswordProtection = function () {
      $keychain.setPasswordProtection(!$scope.passwordProtection, function(err, resp){
        if (err) {
          $scope.passwordProtection = !$scope.PasswordProtection;
          //TODO: report errors to user
        }
      });
    };

    // Add a new server
    $scope.addServer = function () {
      // Create a new server line
      $scope.options.server.servers.push({isEmptyServer: true, secure: false});

      // Set editing to true
      $scope.editing = true;
    }

  }]);

  module.controller('ServerRowCtrl', ['$scope',
    function ($scope) {
      $scope.editing = $scope.server.isEmptyServer;

        // Delete the server
      $scope.remove = function () {
        $scope.options.server.servers.splice($scope.index,1);

        // Save in local storage
        if (!store.disabled) {
          store.set('ripple_settings', JSON.stringify($scope.options));
        }
      }

      $scope.hasRemove = function () {
        return !$scope.server.isEmptyServer && $scope.options.server.servers.length !== 1;
      }

      $scope.cancel = function () {
        if ($scope.server.isEmptyServer) {
          $scope.remove();
          return;
        }

        $scope.editing = false;
        $scope.server = $.extend({}, $scope.optionsBackup.server.servers[$scope.index]);

      }

      $scope.save = function () {
        $scope.server.isEmptyServer = false;
        $scope.editing = false;

        // Save in local storage
        if (!store.disabled) {
          store.set('ripple_settings', JSON.stringify($scope.options));
        }

        // Reload
      location.reload();
      };
    }
  ]);
};

module.exports = AdvancedTab;
<commit_msg>[TASK] Advanced feature settings: Remembers setting RT-2089<commit_after>var util = require('util');
var webutil = require('../util/web');
var Tab = require('../client/tab').Tab;

var AdvancedTab = function ()
{
  Tab.call(this);
};

util.inherits(AdvancedTab, Tab);

AdvancedTab.prototype.tabName = 'advanced';
AdvancedTab.prototype.mainMenu = 'advanced';

AdvancedTab.prototype.generateHtml = function ()
{
  return require('../../jade/tabs/advanced.jade')();
};

AdvancedTab.prototype.angular = function(module)
{
  module.controller('AdvancedCtrl', ['$scope', '$rootScope', 'rpId', 'rpKeychain',
                                    function ($scope, $rootScope, $id, $keychain)
  {
    $scope.options = Options;
    $scope.optionsBackup = $.extend(true, {}, Options);
    $scope.passwordProtection = !$scope.userBlob.data.persistUnlock;
    $scope.editBridge = false;
    $scope.editBlob = false;
    $scope.editAcctOptions = false;

    $scope.advanced_feature_switch = Options.advanced_feature_switch;

    $scope.saveBlob = function () {
      // Save in local storage
      if (!store.disabled) {
        store.set('ripple_settings', JSON.stringify($scope.options));
      }

      $scope.editBlob = false;

      // Reload
      location.reload();
    };

    $scope.saveBridge = function () {
      // Save in local storage
      if (!store.disabled) {
        store.set('ripple_settings', JSON.stringify($scope.options));
      }

      $scope.editBridge = false;

      // Reload
      location.reload();
    };

    $scope.saveAcctOptions = function () {
      Options.advanced_feature_switch = !Options.advanced_feature_switch;

      $scope.editAcctOptions = false;
    };

    $scope.deleteBlob = function () {
      $scope.options.blobvault = "";
      // Save in local storage
      if (!store.disabled) {
        store.set('ripple_settings', JSON.stringify($scope.options));
      }
    }

    $scope.deleteBridge = function () {
      $scope.options.bridge.out.bitcoin = "";
      // Save in local storage
      if (!store.disabled) {
        store.set('ripple_settings', JSON.stringify($scope.options));
      }
    }

    $scope.cancelEditBlob = function () {
      $scope.editBlob = false;
      $scope.options.blobvault = $scope.optionsBackup.blobvault;
    }

    $scope.cancelEditBridge = function () {
      $scope.editBridge = false;
      $scope.options.bridge.out.bitcoin = $scope.optionsBackup.bridge.out.bitcoin;
    }

    $scope.cancelEditAcctOptions = function () {
      $scope.editAcctOptions = false;

    }

    $scope.$on('$blobUpdate', function(){
      $scope.passwordProtection = !$scope.userBlob.data.persistUnlock;
    });

    $scope.setPasswordProtection = function () {
      $keychain.setPasswordProtection(!$scope.passwordProtection, function(err, resp){
        if (err) {
          $scope.passwordProtection = !$scope.PasswordProtection;
          //TODO: report errors to user
        }
      });
    };

    // Add a new server
    $scope.addServer = function () {
      // Create a new server line
      $scope.options.server.servers.push({isEmptyServer: true, secure: false});

      // Set editing to true
      $scope.editing = true;
    }

  }]);

  module.controller('ServerRowCtrl', ['$scope',
    function ($scope) {
      $scope.editing = $scope.server.isEmptyServer;

        // Delete the server
      $scope.remove = function () {
        $scope.options.server.servers.splice($scope.index,1);

        // Save in local storage
        if (!store.disabled) {
          store.set('ripple_settings', JSON.stringify($scope.options));
        }
      }

      $scope.hasRemove = function () {
        return !$scope.server.isEmptyServer && $scope.options.server.servers.length !== 1;
      }

      $scope.cancel = function () {
        if ($scope.server.isEmptyServer) {
          $scope.remove();
          return;
        }

        $scope.editing = false;
        $scope.server = $.extend({}, $scope.optionsBackup.server.servers[$scope.index]);

      }

      $scope.save = function () {
        $scope.server.isEmptyServer = false;
        $scope.editing = false;

        // Save in local storage
        if (!store.disabled) {
          store.set('ripple_settings', JSON.stringify($scope.options));
        }

        // Reload
      location.reload();
      };
    }
  ]);
};

module.exports = AdvancedTab;
<|endoftext|>


<commit_before>var util = require('util'),
    webutil = require('../util/web'),
    Tab = require('../client/tab').Tab,
    Amount = ripple.Amount,
    Base = ripple.Base,
    rewriter = require('../util/jsonrewriter'),
    Currency = ripple.Currency;

var ExchangeTab = function ()
{
  Tab.call(this);
};

util.inherits(ExchangeTab, Tab);

ExchangeTab.prototype.tabName = 'exchange';
ExchangeTab.prototype.mainMenu = 'exchange';

ExchangeTab.prototype.generateHtml = function ()
{
  return require('../../jade/tabs/exchange.jade')();
};

ExchangeTab.prototype.angular = function (module)
{
  module.controller('ExchangeCtrl', ['$scope', '$timeout', '$routeParams',
    'rpId', 'rpNetwork', 'rpTracker', 'rpKeychain', '$rootScope', '$location',
    function ($scope, $timeout, $routeParams, id, network, rpTracker, keychain, $rootScope, $location)
    {
      var pathUpdateTimeout;

      var timer, pf;

      // Remember user preference on Convert vs. Trade
      $rootScope.ripple_exchange_selection_trade = false;

      var xrpCurrency = Currency.from_json("XRP");

      $scope.xrp = {
        name: xrpCurrency.to_human({full_name:$scope.currencies_all_keyed.XRP.name}),
        code: xrpCurrency.get_iso(),
        currency: xrpCurrency
      };

      $scope.$watch('exchange.amount', function () {
        $scope.update_exchange();
      }, true);

      $scope.$watch('exchange.currency_name', function () {
        var exchange = $scope.exchange;
        var currency = Currency.from_human($scope.exchange.currency_name ? $scope.exchange.currency_name : "XRP");
        exchange.currency_obj = currency;
        exchange.currency_code = currency.get_iso();
        exchange.currency_name = currency.to_human({
          full_name: $scope.currencies_all_keyed[currency.get_iso()] ? $scope.currencies_all_keyed[currency.get_iso()].name : null
        });
        $scope.update_exchange();
      }, true);

      $scope.gotoFund = function() {
        $location.path('/xrp');
      };

      $scope.reset_paths = function () {
        var exchange = $scope.exchange;

        exchange.alternatives = [];
      };

      $scope.update_exchange = function () {
        var exchange = $scope.exchange;
        var currency = ripple.Currency.from_human(exchange.currency_name);

        $scope.reset_paths();

        // if formatted or money to exchange is 0 then don't calculate paths or offer to exchange
        if (parseFloat(exchange.amount) === 0 || !exchange.currency_name)
        {
          $scope.error_type = 'required';
          return false;
        }

        else {
          $scope.error_type = '';
        }

        var matchedCurrency = currency.has_interest() ? currency.to_hex() : currency.get_iso();
        var match = /^([a-zA-Z0-9]{3}|[A-Fa-f0-9]{40})\b/.exec(matchedCurrency);

        if (!match) {
          // Currency code not recognized, should have been caught by
          // form validator.
          return;
        }

        // Demurrage: Get a reference date five minutes in the future
        //
        // Normally, when using demurrage currencies, we would immediately round
        // down (e.g. 0.99999 instead of 1) as demurrage occurs continuously. Not
        // a good user experience.
        //
        // By choosing a date in the future, this gives us a time window before
        // this rounding down occurs. Note that for positive interest currencies
        // this actually *causes* the same odd rounding problem, so in the future
        // we'll want a better solution, but for right now this does what we need.
        var refDate = new Date(new Date().getTime() + 5 * 60000);

        exchange.amount_feedback = Amount.from_human('' + exchange.amount + ' ' + matchedCurrency, { reference_date: refDate });
        exchange.amount_feedback.set_issuer(id.account);

        if (exchange.amount_feedback.is_valid() && exchange.amount_feedback.is_positive()) {
          exchange.path_status = 'pending';
          exchange.alt = null;

          if (pathUpdateTimeout) clearTimeout(pathUpdateTimeout);
          pathUpdateTimeout = $timeout($scope.update_paths, 500);
        } else {
          exchange.path_status = 'waiting';
        }
      };

      $scope.update_paths = function () {
        $scope.$apply(function () {
          $scope.exchange.path_status = 'pending';
          var amount = $scope.exchange.amount_feedback;

          if (amount.is_zero()) return;

          // Start path find
          pf = network.remote.path_find(id.account,
              id.account,
              amount);
              // $scope.generate_src_currencies());
              // XXX: Roll back pathfinding changes temporarily
          var isIssuer = $scope.generate_issuer_currencies();

          var lastUpdate;

          pf.on('update', function (upd) {
            $scope.$apply(function () {
              lastUpdate = new Date();

              clearInterval(timer);
              timer = setInterval(function(){
                $scope.$apply(function(){
                  var seconds = Math.round((new Date() - lastUpdate) / 1000);
                  $scope.lastUpdate = seconds || 0;
                });
              }, 1000);

              if (!upd.alternatives || !upd.alternatives.length) {
                $scope.exchange.path_status  = "no-path";
                $scope.exchange.alternatives = [];
              } else {
                var currencies = {};
                $scope.exchange.path_status  = "done";
                $scope.exchange.alternatives = _.filter(_.map(upd.alternatives, function (raw) {
                  var alt = {};
                  alt.amount   = Amount.from_json(raw.source_amount);
                  alt.rate     = alt.amount.ratio_human(amount);
                  alt.send_max = alt.amount.product_human(Amount.from_json('1.01'));
                  alt.paths    = raw.paths_computed
                      ? raw.paths_computed
                      : raw.paths_canonical;

                  if (alt.amount.issuer().to_json() != $scope.address && !isIssuer[alt.amount.currency().to_hex()]) {
                    currencies[alt.amount.currency().to_hex()] = true;
                  }

                  return alt;
                }), function(alt) {
                  // XXX: Roll back pathfinding changes temporarily
                  /* if (currencies[alt.amount.currency().to_hex()]) {
                    return alt.amount.issuer().to_json() != $scope.address;
                  } */
                  //return false;
                  return !(alt.amount.is_native() && $scope.account.max_spend
                    && $scope.account.max_spend.to_number() > 1
                    && $scope.account.max_spend.compareTo(alt.amount) < 0);
                });
                if (!$scope.exchange.alternatives.length) {
                  $scope.exchange.path_status  = "no-path";
                  $scope.exchange.alternatives = [];
                }
              }
            });
          });
        });
      };

      var updateCurrencyOptions = function(){
        // create a list of currency codes from the trust line objects
        var currencies = _.uniq(_.map($scope.lines, function (line) {
          return line.currency;
        }));

        // add XRP
        currencies.unshift('XRP');

        // create a currency object for each of the currency codes
        for (var i=0; i < currencies.length; i++) {
          currencies[i] = ripple.Currency.from_json(currencies[i]);
        }

        // create the display version of the currencies
        currencies = _.map(currencies, function (currency) {
          if ($scope.currencies_all_keyed[currency.get_iso()]) {
            return currency.to_human({full_name:$scope.currencies_all_keyed[currency.get_iso()].name});
          }

          return currency.get_iso();
        });

        $scope.currency_choices = currencies;
      };

      $scope.$on('$balancesUpdate', updateCurrencyOptions);

      $scope.reset = function () {
        $scope.mode = "form";

        // XXX Most of these variables should be properties of $scope.exchange.
        //     The Angular devs recommend that models be objects due to the way
        //     scope inheritance works.
        $scope.exchange = {
          amount: '',
          currency_name: $scope.xrp.name,
          currency_code: $scope.xrp.code,
          currency_obj: $scope.xrp.currency,
          path_status: 'waiting',
          fund_status: 'none'
        };
        $scope.nickname = '';
        $scope.error_type = '';
        if ($scope.exchangeForm) $scope.exchangeForm.$setPristine(true);
      };

      $scope.cancelConfirm = function () {
        $scope.mode = "form";
        $scope.exchange.alt = null;
      };

      $scope.reset_goto = function (tabName) {
        $scope.reset();

        // TODO do something clever instead of document.location
        // because goToTab does $scope.$digest() which we don't need
        document.location = '#' + tabName;
      };

      /**
       * N3. Confirmation page
       */
      $scope.exchange_prepared = function () {
        $scope.confirm_wait = true;
        $timeout(function () {
          $scope.confirm_wait = false;
        }, 1000, true);

        $scope.mode = "confirm";
      };

      /**
       * N4. Waiting for transaction result page
       */
      $scope.exchange_confirmed = function () {

        // parse the currency name and extract the iso
        var currency = Currency.from_human($scope.exchange.currency_name);
        currency = currency.has_interest() ? currency.to_hex() : currency.get_iso();
        var amount = Amount.from_human("" + $scope.exchange.amount + " " + currency);

        amount.set_issuer(id.account);

        var tx = network.remote.transaction();

        // Add memo to tx
        tx.addMemo('client', 'rt' + $rootScope.version);

        // Destination tag
        tx.destination_tag(webutil.getDestTagFromAddress(id.account));
        tx.payment(id.account, id.account, amount.to_json());
        tx.send_max($scope.exchange.alt.send_max);
        tx.paths($scope.exchange.alt.paths);

        if ($scope.exchange.secret) {
          tx.secret($scope.exchange.secret);
        } else {
          // Get secret asynchronously
          keychain.requestSecret(id.account, id.username,
            function (err, secret) {
              if (err) {
                console.log("client: exchange tab: error while " +
                  "unlocking wallet: ", err);
                $scope.mode = "error";
                $scope.error_type = "unlockFailed";

                return;
              }

              $scope.exchange.secret = secret;
              $scope.exchange_confirmed();
            });
          return;
        }

        tx.on('proposed', function (res) {
          $scope.$apply(function () {
            setEngineStatus(res, false);
            $scope.exchanged(tx.hash);

            // Remember currency and increase order
            var found;

            for (var i = 0; i < $scope.currencies_all.length; i++) {
              if ($scope.currencies_all[i].value.toLowerCase() === $scope.exchange.amount_feedback.currency().get_iso().toLowerCase()) {
                $scope.currencies_all[i].order++;
                found = true;
                break;
              }
            }

            // // Removed feature until a permanent fix
            // if (!found) {
            //   $scope.currencies_all.push({
            //     "name": $scope.exchange.amount_feedback.currency().to_human().toUpperCase(),
            //     "value": $scope.exchange.amount_feedback.currency().to_human().toUpperCase(),
            //     "order": 1
            //   });
            // }
          });
        });
        tx.on('success', function(res) {
          setEngineStatus(res, true);
          try {
            var tx = rewriter.processTxn(res.tx_json, res.metadata, id.account);
            var ccPairs = _.map(tx.affectedCurrencies, function(currencyCode) {
              if (currencyCode != 'XRP') {
                var balanceChangeEffect = _.filter(tx.effects, function(effect) {
                  return effect.type = "trust_change_balance" && effect.currency == currencyCode;
                })[0];
                var res =  currencyCode + '.' + id.resolveNameSync(balanceChangeEffect.amount.issuer().to_json(), { tilde: true });
                return res;
              }
              return currencyCode;
            });
            var ccyPair = ccPairs.join('/');
            var eventProp = {
              Status: 'success',
              'Currency pair': ccyPair,
              Address: $scope.userBlob.data.account_id,
              'Transaction ID': res.tx_json.hash,
              Amount: amount.to_number(false)
            };
            rpTracker.track('Convert order result', eventProp);
          } catch (err) {
            console.warn(err);
          }
        });
        tx.on('error', function(res) {
          setImmediate(function () {
            $scope.$apply(function () {
              $scope.mode = "rippleerror";
              setEngineStatus(res, false);

              if (res.error === 'remoteError' &&
                  res.remote.error === 'noPath') {
                $scope.mode = "status";
                $scope.tx_result = "noPath";
              }
            });
          });
          try {
            var curAltCode = $scope.exchange.alt.amount.currency().get_iso();
            var ccyPair = $scope.exchange.currency_code + '/' + curAltCode;
            var eventProp = {
              Status: 'error',
              Message: res.engine_result,
              'Currency pair': ccyPair,
              Address: $scope.userBlob.data.account_id
            };
            rpTracker.track('Convert order result', eventProp);
          } catch (err) {
            console.warn(err);
          }
        });
        tx.submit();

        $scope.mode = "sending";
      };

      /**
       * N6. exchanged page
       */
      $scope.exchanged = function (hash) {
        $scope.mode = "status";
        network.remote.on('transaction', handleAccountEvent);

        function handleAccountEvent(e) {
          $scope.$apply(function () {
            if (e.transaction.hash === hash) {
              setEngineStatus(e, true);
              network.remote.removeListener('transaction', handleAccountEvent);
            }
          });
        }
      };

      function setEngineStatus(res, accepted) {
        $scope.engine_result = res.engine_result;
        $scope.engine_result_message = res.engine_result_message;
        $scope.engine_status_accepted = accepted;

        switch (res.engine_result.slice(0, 3)) {
          case 'tes':
            $scope.tx_result = accepted ? "cleared" : "pending";
            break;
          case 'tem':
            $scope.tx_result = "malformed";
            break;
          case 'ter':
            $scope.tx_result = "failed";
            break;
          case 'tep':
            $scope.tx_result = "partial";
            break;
          case 'tec':
            $scope.tx_result = "claim";
            break;
          case 'tef':
            $scope.tx_result = "failure";
            break;
          case 'tel':
            $scope.tx_result = "local";
            break;
          default:
            $scope.tx_result = "unknown";
            console.warn('Unhandled engine status encountered!');
        }
      }

      $scope.reset();

      updateCurrencyOptions();

      // Stop the pathfinding when leaving the page
      $scope.$on('$destroy', function(){
        if (pf && "function" === typeof pf.close) {
          pf.close();
        }
      });
    }]);
};

module.exports = ExchangeTab;
<commit_msg>[FIX] js styles fix (RT-2910)<commit_after>var util = require('util'),
    webutil = require('../util/web'),
    Tab = require('../client/tab').Tab,
    Amount = ripple.Amount,
    Base = ripple.Base,
    rewriter = require('../util/jsonrewriter'),
    Currency = ripple.Currency;

var ExchangeTab = function ()
{
  Tab.call(this);
};

util.inherits(ExchangeTab, Tab);

ExchangeTab.prototype.tabName = 'exchange';
ExchangeTab.prototype.mainMenu = 'exchange';

ExchangeTab.prototype.generateHtml = function ()
{
  return require('../../jade/tabs/exchange.jade')();
};

ExchangeTab.prototype.angular = function (module)
{
  module.controller('ExchangeCtrl', ['$scope', '$timeout', '$routeParams',
    'rpId', 'rpNetwork', 'rpTracker', 'rpKeychain', '$rootScope', '$location',
    function ($scope, $timeout, $routeParams, id, network, rpTracker, keychain, $rootScope, $location)
    {
      var pathUpdateTimeout;

      var timer, pf;

      // Remember user preference on Convert vs. Trade
      $rootScope.ripple_exchange_selection_trade = false;

      var xrpCurrency = Currency.from_json('XRP');

      $scope.xrp = {
        name: xrpCurrency.to_human({full_name:$scope.currencies_all_keyed.XRP.name}),
        code: xrpCurrency.get_iso(),
        currency: xrpCurrency
      };

      $scope.$watch('exchange.amount', function () {
        $scope.update_exchange();
      }, true);

      $scope.$watch('exchange.currency_name', function () {
        var exchange = $scope.exchange;
        var currency = Currency.from_human($scope.exchange.currency_name ? $scope.exchange.currency_name : 'XRP');
        exchange.currency_obj = currency;
        exchange.currency_code = currency.get_iso();
        exchange.currency_name = currency.to_human({
          full_name: $scope.currencies_all_keyed[currency.get_iso()] ? $scope.currencies_all_keyed[currency.get_iso()].name : null
        });
        $scope.update_exchange();
      }, true);

      $scope.gotoFund = function() {
        $location.path('/xrp');
      };

      $scope.reset_paths = function () {
        var exchange = $scope.exchange;

        exchange.alternatives = [];
      };

      $scope.update_exchange = function () {
        var exchange = $scope.exchange;
        var currency = ripple.Currency.from_human(exchange.currency_name);

        $scope.reset_paths();

        // if formatted or money to exchange is 0 then don't calculate paths or offer to exchange
        if (parseFloat(exchange.amount) === 0 || !exchange.currency_name)
        {
          $scope.error_type = 'required';
          return false;
        }

        else {
          $scope.error_type = '';
        }

        var matchedCurrency = currency.has_interest() ? currency.to_hex() : currency.get_iso();
        var match = /^([a-zA-Z0-9]{3}|[A-Fa-f0-9]{40})\b/.exec(matchedCurrency);

        if (!match) {
          // Currency code not recognized, should have been caught by
          // form validator.
          return;
        }

        // Demurrage: Get a reference date five minutes in the future
        //
        // Normally, when using demurrage currencies, we would immediately round
        // down (e.g. 0.99999 instead of 1) as demurrage occurs continuously. Not
        // a good user experience.
        //
        // By choosing a date in the future, this gives us a time window before
        // this rounding down occurs. Note that for positive interest currencies
        // this actually *causes* the same odd rounding problem, so in the future
        // we'll want a better solution, but for right now this does what we need.
        var refDate = new Date(new Date().getTime() + 5 * 60000);

        exchange.amount_feedback = Amount.from_human('' + exchange.amount + ' ' + matchedCurrency, { reference_date: refDate });
        exchange.amount_feedback.set_issuer(id.account);

        if (exchange.amount_feedback.is_valid() && exchange.amount_feedback.is_positive()) {
          exchange.path_status = 'pending';
          exchange.alt = null;

          if (pathUpdateTimeout) clearTimeout(pathUpdateTimeout);
          pathUpdateTimeout = $timeout($scope.update_paths, 500);
        } else {
          exchange.path_status = 'waiting';
        }
      };

      $scope.update_paths = function () {
        $scope.$apply(function () {
          $scope.exchange.path_status = 'pending';
          var amount = $scope.exchange.amount_feedback;

          if (amount.is_zero()) return;

          // Start path find
          pf = network.remote.path_find(id.account,
              id.account,
              amount);
              // $scope.generate_src_currencies());
              // XXX: Roll back pathfinding changes temporarily
          var isIssuer = $scope.generate_issuer_currencies();

          var lastUpdate;

          pf.on('update', function (upd) {
            $scope.$apply(function () {
              lastUpdate = new Date();

              clearInterval(timer);
              timer = setInterval(function(){
                $scope.$apply(function(){
                  var seconds = Math.round((new Date() - lastUpdate) / 1000);
                  $scope.lastUpdate = seconds || 0;
                });
              }, 1000);

              if (!upd.alternatives || !upd.alternatives.length) {
                $scope.exchange.path_status  = 'no-path';
                $scope.exchange.alternatives = [];
              } else {
                var currencies = {};
                $scope.exchange.path_status  = 'done';
                $scope.exchange.alternatives = _.filter(_.map(upd.alternatives, function (raw) {
                  var alt = {};
                  alt.amount   = Amount.from_json(raw.source_amount);
                  alt.rate     = alt.amount.ratio_human(amount);
                  alt.send_max = alt.amount.product_human(Amount.from_json('1.01'));
                  alt.paths    = raw.paths_computed
                      ? raw.paths_computed
                      : raw.paths_canonical;

                  if (alt.amount.issuer().to_json() != $scope.address && !isIssuer[alt.amount.currency().to_hex()]) {
                    currencies[alt.amount.currency().to_hex()] = true;
                  }

                  return alt;
                }), function(alt) {
                  // XXX: Roll back pathfinding changes temporarily
                  /* if (currencies[alt.amount.currency().to_hex()]) {
                    return alt.amount.issuer().to_json() != $scope.address;
                  } */
                  // return false;
                  return !(alt.amount.is_native() && $scope.account.max_spend
                    && $scope.account.max_spend.to_number() > 1
                    && $scope.account.max_spend.compareTo(alt.amount) < 0);
                });
                if (!$scope.exchange.alternatives.length) {
                  $scope.exchange.path_status  = 'no-path';
                  $scope.exchange.alternatives = [];
                }
              }
            });
          });
        });
      };

      var updateCurrencyOptions = function(){
        // create a list of currency codes from the trust line objects
        var currencies = _.uniq(_.map($scope.lines, function (line) {
          return line.currency;
        }));

        // add XRP
        currencies.unshift('XRP');

        // create a currency object for each of the currency codes
        for (var i = 0; i < currencies.length; i++) {
          currencies[i] = ripple.Currency.from_json(currencies[i]);
        }

        // create the display version of the currencies
        currencies = _.map(currencies, function (currency) {
          if ($scope.currencies_all_keyed[currency.get_iso()]) {
            return currency.to_human({full_name:$scope.currencies_all_keyed[currency.get_iso()].name});
          }

          return currency.get_iso();
        });

        $scope.currency_choices = currencies;
      };

      $scope.$on('$balancesUpdate', updateCurrencyOptions);

      $scope.reset = function () {
        $scope.mode = 'form';

        // XXX Most of these variables should be properties of $scope.exchange.
        //     The Angular devs recommend that models be objects due to the way
        //     scope inheritance works.
        $scope.exchange = {
          amount: '',
          currency_name: $scope.xrp.name,
          currency_code: $scope.xrp.code,
          currency_obj: $scope.xrp.currency,
          path_status: 'waiting',
          fund_status: 'none'
        };
        $scope.nickname = '';
        $scope.error_type = '';
        if ($scope.exchangeForm) $scope.exchangeForm.$setPristine(true);
      };

      $scope.cancelConfirm = function () {
        $scope.mode = 'form';
        $scope.exchange.alt = null;
      };

      $scope.reset_goto = function (tabName) {
        $scope.reset();

        // TODO do something clever instead of document.location
        // because goToTab does $scope.$digest() which we don't need
        document.location = '#' + tabName;
      };

      /**
       * N3. Confirmation page
       */
      $scope.exchange_prepared = function () {
        $scope.confirm_wait = true;
        $timeout(function () {
          $scope.confirm_wait = false;
        }, 1000, true);

        $scope.mode = 'confirm';
      };

      /**
       * N4. Waiting for transaction result page
       */
      $scope.exchange_confirmed = function () {

        // parse the currency name and extract the iso
        var currency = Currency.from_human($scope.exchange.currency_name);
        currency = currency.has_interest() ? currency.to_hex() : currency.get_iso();
        var amount = Amount.from_human('' + $scope.exchange.amount + ' ' + currency);

        amount.set_issuer(id.account);

        var tx = network.remote.transaction();

        // Add memo to tx
        tx.addMemo('client', 'rt' + $rootScope.version);

        // Destination tag
        tx.destination_tag(webutil.getDestTagFromAddress(id.account));
        tx.payment(id.account, id.account, amount.to_json());
        tx.send_max($scope.exchange.alt.send_max);
        tx.paths($scope.exchange.alt.paths);

        if ($scope.exchange.secret) {
          tx.secret($scope.exchange.secret);
        } else {
          // Get secret asynchronously
          keychain.requestSecret(id.account, id.username,
            function (err, secret) {
              if (err) {
                console.log('client: exchange tab: error while ' +
                  'unlocking wallet: ', err);
                $scope.mode = 'error';
                $scope.error_type = 'unlockFailed';

                return;
              }

              $scope.exchange.secret = secret;
              $scope.exchange_confirmed();
            });
          return;
        }

        tx.on('proposed', function (res) {
          $scope.$apply(function () {
            setEngineStatus(res, false);
            $scope.exchanged(tx.hash);

            // Remember currency and increase order
            var found;

            for (var i = 0; i < $scope.currencies_all.length; i++) {
              if ($scope.currencies_all[i].value.toLowerCase() === $scope.exchange.amount_feedback.currency().get_iso().toLowerCase()) {
                $scope.currencies_all[i].order++;
                found = true;
                break;
              }
            }

            // // Removed feature until a permanent fix
            // if (!found) {
            //   $scope.currencies_all.push({
            //     name' $scope.exchange.amount_feedback.currency().to_human().toUpperCase(),
            //     value: $scope.exchange.amount_feedback.currency().to_human().toUpperCase(),
            //     order: 1
            //   });
            // }
          });
        });
        tx.on('success', function(res) {
          setEngineStatus(res, true);
          try {
            var tx = rewriter.processTxn(res.tx_json, res.metadata, id.account);
            var ccPairs = _.map(tx.affectedCurrencies, function(currencyCode) {
              if (currencyCode != 'XRP') {
                var balanceChangeEffect = _.filter(tx.effects, function(effect) {
                  return effect.type = 'trust_change_balance' && effect.currency == currencyCode;
                })[0];
                var res =  currencyCode + '.' + id.resolveNameSync(balanceChangeEffect.amount.issuer().to_json(), { tilde: true });
                return res;
              }
              return currencyCode;
            });
            var ccyPair = ccPairs.join('/');
            var eventProp = {
              Status: 'success',
              'Currency pair': ccyPair,
              Address: $scope.userBlob.data.account_id,
              'Transaction ID': res.tx_json.hash,
              Amount: amount.to_number(false)
            };
            rpTracker.track('Convert order result', eventProp);
          } catch (err) {
            console.warn(err);
          }
        });
        tx.on('error', function(res) {
          setImmediate(function () {
            $scope.$apply(function () {
              $scope.mode = 'rippleerror';
              setEngineStatus(res, false);

              if (res.error === 'remoteError' &&
                  res.remote.error === 'noPath') {
                $scope.mode = 'status';
                $scope.tx_result = 'noPath';
              }
            });
          });
          try {
            var curAltCode = $scope.exchange.alt.amount.currency().get_iso();
            var ccyPair = $scope.exchange.currency_code + '/' + curAltCode;
            var eventProp = {
              Status: 'error',
              Message: res.engine_result,
              'Currency pair': ccyPair,
              Address: $scope.userBlob.data.account_id
            };
            rpTracker.track('Convert order result', eventProp);
          } catch (err) {
            console.warn(err);
          }
        });
        tx.submit();

        $scope.mode = 'sending';
      };

      /**
       * N6. exchanged page
       */
      $scope.exchanged = function (hash) {
        $scope.mode = 'status';
        network.remote.on('transaction', handleAccountEvent);

        function handleAccountEvent(e) {
          $scope.$apply(function () {
            if (e.transaction.hash === hash) {
              setEngineStatus(e, true);
              network.remote.removeListener('transaction', handleAccountEvent);
            }
          });
        }
      };

      function setEngineStatus(res, accepted) {
        $scope.engine_result = res.engine_result;
        $scope.engine_result_message = res.engine_result_message;
        $scope.engine_status_accepted = accepted;

        switch (res.engine_result.slice(0, 3)) {
          case 'tes':
            $scope.tx_result = accepted ? 'cleared' : 'pending';
            break;
          case 'tem':
            $scope.tx_result = 'malformed';
            break;
          case 'ter':
            $scope.tx_result = 'failed';
            break;
          case 'tep':
            $scope.tx_result = 'partial';
            break;
          case 'tec':
            $scope.tx_result = 'claim';
            break;
          case 'tef':
            $scope.tx_result = 'failure';
            break;
          case 'tel':
            $scope.tx_result = 'local';
            break;
          default:
            $scope.tx_result = 'unknown';
            console.warn('Unhandled engine status encountered!');
        }
      }

      $scope.reset();

      updateCurrencyOptions();

      // Stop the pathfinding when leaving the page
      $scope.$on('$destroy', function(){
        if (pf && 'function' === typeof pf.close) {
          pf.close();
        }
      });
    }]);
};

module.exports = ExchangeTab;
<|endoftext|>


<commit_before>(function(Factlink, $, _, easyXDM, undefined) {

Factlink.Balloon = function() {
  var id;
  var el;
  var hasFocus = false;
  var factObj;
  var timeout;

  function initialize(factId, fact) {
    id = factId;
    factObj = fact;

    Factlink.getTemplate("indicator", function() {
      initializeTemplate.apply(this);

      bindCheck();
    });
  }

  this.show = function(top, left, fast) {
    window.clearTimeout(timeout);
    if (fast === true) {
      hideAll();
      el.show();
    }else{
      timeout = window.setTimeout(function() {
        hideAll();
        el.fadeIn('fast');
      }, 200);
    }
    Factlink.set_position(top,left,window,el);

    getChannels();
  };

  this.hide = function() {
    window.clearTimeout(timeout);

    el.fadeOut('fast');
    resetState();
  };

  this.isVisible = function() {
    return el.is(':visible');
  };

  this.destroy = function() {
    el.remove();
  };

  function resetState() {
    el.removeClass("fl-channel-active");
  }

  function initializeTemplate(tmpl) {
    el = $(tmpl(factObj.getObject())).appendTo(Factlink.el);

    el.bind('mouseenter', function() {
      factObj.focus();
    }).bind('mouseleave', function() {
      factObj.blur();
    });

    el.find('div.fl-share').hoverIntent({
      over: function(e) {
        el.addClass('fl-channel-active');
      },
      out: function(e) {
        el.removeClass('fl-channel-active');
      },
      timeout: 500
    });

    el.find('div.fl-label').bind('click', function() {
      factObj.click();
    });
  }

  function getChannels() {
    var ul = el.find('ul.fl-channels');

    ul.find('li.fl-loading').show().siblings().remove();

    Factlink.get('/facts/' + id + '/channels.json',{
      dataType: "jsonp",
      jsonp: "callback",
      success: function(data) {
        ul.find('li.fl-loading').hide();
        if(_.isEmpty(data)) {
          ul.append("
You have no channels yet
");
        }
        else {
          _.each(data, function(channel) {
            Factlink.getTemplate('channel_li', function(tmpl) {
              var $li = $(tmpl(channel));

              ul.append($li);
            });
          });
        }
      },
      error: function(e) {
        ul.find('li.fl-loading').hide();
        if (e.message.status === 401) {
          ul.append('
Please login
 to see your channels
');
        } else {
          ul.append('
There was an error retrieving your channels.
');
        }
      }
    });
  }

  function hideAll() {
    el.closest('#fl').find('.fl-popup').hide();
  }

  function bindCheck() {
    el.delegate('ul.fl-channels :checkbox', 'change', function(e) {
      Factlink.post("/" + $(this).data('username') + "/channels/" + $(this).data('channel-id') + "/toggle/fact/" + id, {
        dataType: "script"
      });
    });
  }

  initialize.apply(this, arguments);
};

})(window.Factlink, Factlink.$, Factlink._, Factlink.easyXDM);
<commit_msg>Completely fucked it up, time for weekend :D<commit_after>(function(Factlink, $, _, easyXDM, undefined) {

Factlink.Balloon = function() {
  var id;
  var el;
  var hasFocus = false;
  var factObj;
  var timeout;

  function initialize(factId, fact) {
    id = factId;
    factObj = fact;

    Factlink.getTemplate("indicator", function(tmpl) {
      initializeTemplate(tmpl);

      bindCheck();
    });
  }

  this.show = function(top, left, fast) {
    window.clearTimeout(timeout);
    if (fast === true) {
      hideAll();
      el.show();
    }else{
      timeout = window.setTimeout(function() {
        hideAll();
        el.fadeIn('fast');
      }, 200);
    }
    Factlink.set_position(top,left,window,el);

    getChannels();
  };

  this.hide = function() {
    window.clearTimeout(timeout);

    el.fadeOut('fast');
    resetState();
  };

  this.isVisible = function() {
    return el.is(':visible');
  };

  this.destroy = function() {
    el.remove();
  };

  function resetState() {
    el.removeClass("fl-channel-active");
  }

  function initializeTemplate(tmpl) {
    el = $(tmpl(factObj.getObject())).appendTo(Factlink.el);

    el.bind('mouseenter', function() {
      factObj.focus();
    }).bind('mouseleave', function() {
      factObj.blur();
    });

    el.find('div.fl-share').hoverIntent({
      over: function(e) {
        el.addClass('fl-channel-active');
      },
      out: function(e) {
        el.removeClass('fl-channel-active');
      },
      timeout: 500
    });

    el.find('div.fl-label').bind('click', function() {
      factObj.click();
    });
  }

  function getChannels() {
    var ul = el.find('ul.fl-channels');

    ul.find('li.fl-loading').show().siblings().remove();

    Factlink.get('/facts/' + id + '/channels.json',{
      dataType: "jsonp",
      jsonp: "callback",
      success: function(data) {
        ul.find('li.fl-loading').hide();
        if(_.isEmpty(data)) {
          ul.append("
You have no channels yet
");
        }
        else {
          _.each(data, function(channel) {
            Factlink.getTemplate('channel_li', function(tmpl) {
              var $li = $(tmpl(channel));

              ul.append($li);
            });
          });
        }
      },
      error: function(e) {
        ul.find('li.fl-loading').hide();
        if (e.message.status === 401) {
          ul.append('
Please login
 to see your channels
');
        } else {
          ul.append('
There was an error retrieving your channels.
');
        }
      }
    });
  }

  function hideAll() {
    el.closest('#fl').find('.fl-popup').hide();
  }

  function bindCheck() {
    el.delegate('ul.fl-channels :checkbox', 'change', function(e) {
      Factlink.post("/" + $(this).data('username') + "/channels/" + $(this).data('channel-id') + "/toggle/fact/" + id, {
        dataType: "script"
      });
    });
  }

  initialize.apply(this, arguments);
};

})(window.Factlink, Factlink.$, Factlink._, Factlink.easyXDM);
<|endoftext|>


<commit_before>;(function ( $, window, document, undefined ) {

	var plugin = {
		name: 'delayImages',
		ns: {
			css: 'kist-DelayImages',
			event: '.kist.delayImages'
		},
		error: function ( message ) {
			throw new Error(plugin.name + ': ' + message);
		}
	};
	plugin.classes = {
		image: plugin.ns.css + '-image',
		isLoading: 'is-loading',
		isLoaded: 'is-loaded'
	};
	plugin.publicMethods = ['destroy'];

	var dom = {
		setup: function () {
			this.dom    = this.dom || {};
			this.dom.el = $(this.element);

			// Get collection of elements as array of DOM nodes
			this._element = this.dom.el.toArray();

			this.dom.el.addClass(plugin.classes.image);
		},
		destroy: function () {
			$.each(plugin.classes, $.proxy(function ( prop, val ) {
				this.dom.el.removeClass(val);
			}, this));
		}
	};

	var instance = {
		id: 0,
		setup: function () {
			this.instance     = this.instance || {};
			this.instance.id  = instance.id++;
			this.instance.ens = plugin.ns.event + '.' + this.instance.id;
		},
		destroy: function () {
			this.dom.el.each(function ( index, element ) {
				delete $.data(element)[plugin.name];
			});
		}
	};

	var aux = {
		setup: function () {
			this.aux = this.aux || {};
			this.aux.loadImage = $.kist.loader.loadImage;
			// $.fn.inView
		}
	};

	/**
	 * @param  {Mixed} options
	 *
	 * @return {Object}
	 */
	function constructOptions ( options ) {
		return typeof(options) === 'object' ? options : {};
	}

	/**
	 * @param  {String} options
	 *
	 * @return {Function}
	 */
	function constructMethod ( options ) {
		return options.method === 'postpone' ? Postpone : Lazyload;
	}

	/**
	 * @class
	 *
	 * @param {jQuery} element
	 * @param {Object} options
	 */
	function Lazyload ( element, options ) {

		this.element = element;
		this.options = $.extend({}, this.defaults, options);

		instance.setup.call(this);
		aux.setup.call(this);
		dom.setup.call(this);

		this.parse(this.dom.el);

	}

	$.extend(Lazyload.prototype, {

		/**
		 * @param  {jQuery} images
		 *
		 * @return {Promise}
		 */
		parse: function ( images ) {

			/**
			 * Why not load every image with one call to loadImage?
			 *
			 * Since some images can be unreachable, single image which is like
			 * that will result in rejected promise from loadImage which will trigger
			 * every image’s success callback earlier.
			 *
			 * Using it this way we set loadImage for every image and resolve only
			 * one promise for global success, while every image gets to call
			 * it’s success independetly of other images.
			 */

			var arr = [];
			var dfd = $.Deferred();

			images.addClass(plugin.classes.isLoading);

			images.each($.proxy( function ( index, element ) {

				element = $(element);
				arr.push(this.aux.loadImage(element.data('src')));
				arr[arr.length-1].always($.proxy(this.success, this, element));

			}, this));

			$.when.apply(window, arr).always(dfd.resolve);

			return dfd.promise();

		},

		/**
		 * @param  {jQuery} image
		 *
		 * @return {}
		 */
		success: function ( image ) {

			image
				.attr({
					src: image.data('src'),
					alt: image.data('alt')
				})
				.removeClass(plugin.classes.isLoading)
				.addClass(plugin.classes.isLoaded);

		},

		destroy: function () {
			dom.destroy.call(this);
			instance.destroy.call(this);
		}

	});

	/**
	 * @class
	 */
	function Postpone () {
		Postpone._super.constructor.apply(this, arguments);

	}
	function PostponeTemp () {}
	PostponeTemp.prototype = Lazyload.prototype;
	Postpone.prototype = new PostponeTemp();
	Postpone.prototype.constructor = Postpone;
	Postpone._super = Lazyload.prototype;

	$.extend(Postpone.prototype, {

		parse: function ( images ) {

			images.inView({
				threshold: this.options.threshold,
				debounce: this.options.debounce,
				once: $.proxy(function ( result ) {

					if ( this.options.start ) {
						this.options.start.call(this._element, result);
					}

					Postpone._super.parse.call(this, result)
						.done($.proxy(function () {

							if ( this.options.success ) {
								this.options.success.call(this._element, result);
							}

						}, this));

				}, this)
			});

		},

		destroy: function () {
			Postpone._super.destroy.call(this);
			this.dom.el.inView('destroy');
		},

		defaults: {
			threshold: 0,
			debounce: 300,
			success: null,
			start: null
		}

	});

	$.kist = $.kist || {};

	$.kist[plugin.name] = {
		postpone: {
			defaults: Postpone.prototype.defaults
		}
	};

	$.fn[plugin.name] = function ( options ) {

		if ( typeof(options) === 'string' && $.inArray(options, plugin.publicMethods) !== -1 ) {
			return this.each(function () {
				var pluginInstance = $.data(this, plugin.name);
				if ( pluginInstance ) {
					pluginInstance[options]();
				}
			});
		}

		options = constructOptions(options);
		var Method = constructMethod(options);

		/**
		 * If there are multiple elements, first filter those which don’t
		 * have any instance of plugin instantiated. Then create only one
		 * instance for current collection which will enable us to have
		 * only one scroll/resize event.
		 */
		var collection = this.filter(function () {
			return !$.data(this, plugin.name) && $(this).is('img');
		});
		if ( collection.length ) {
			collection.data(plugin.name, new Method(collection, options));
		}

		return this;

	};

})( jQuery, window, document );
<commit_msg>Always use lazyload method if we it's proxybrowser<commit_after>;(function ( $, window, document, undefined ) {

	var plugin = {
		name: 'delayImages',
		ns: {
			css: 'kist-DelayImages',
			event: '.kist.delayImages'
		},
		error: function ( message ) {
			throw new Error(plugin.name + ': ' + message);
		}
	};
	plugin.classes = {
		image: plugin.ns.css + '-image',
		isLoading: 'is-loading',
		isLoaded: 'is-loaded'
	};
	plugin.publicMethods = ['destroy'];

	var dom = {
		setup: function () {
			this.dom    = this.dom || {};
			this.dom.el = $(this.element);

			// Get collection of elements as array of DOM nodes
			this._element = this.dom.el.toArray();

			this.dom.el.addClass(plugin.classes.image);
		},
		destroy: function () {
			$.each(plugin.classes, $.proxy(function ( prop, val ) {
				this.dom.el.removeClass(val);
			}, this));
		}
	};

	var instance = {
		id: 0,
		setup: function () {
			this.instance     = this.instance || {};
			this.instance.id  = instance.id++;
			this.instance.ens = plugin.ns.event + '.' + this.instance.id;
		},
		destroy: function () {
			this.dom.el.each(function ( index, element ) {
				delete $.data(element)[plugin.name];
			});
		}
	};

	var aux = {
		setup: function () {
			this.aux = this.aux || {};
			this.aux.loadImage = $.kist.loader.loadImage;
			// $.fn.inView
		}
	};

	// @hattip https://github.com/niksy/modernizr-detects/blob/master/feature-detects/proxybrowser.js
	var isProxyBrowser = (('Modernizr' in window) && Modernizr.proxybrowser) || (/Opera Mini|Silk/i.test(navigator.userAgent));

	/**
	 * @param  {Mixed} options
	 *
	 * @return {Object}
	 */
	function constructOptions ( options ) {
		return typeof(options) === 'object' ? options : {};
	}

	/**
	 * @param  {String} options
	 *
	 * @return {Function}
	 */
	function constructMethod ( options ) {
		if ( options.method === 'postpone' ) {
			return isProxyBrowser ? Lazyload : Postpone;
		}
		return Lazyload;
	}

	/**
	 * @class
	 *
	 * @param {jQuery} element
	 * @param {Object} options
	 */
	function Lazyload ( element, options ) {

		this.element = element;
		this.options = $.extend({}, this.defaults, options);

		instance.setup.call(this);
		aux.setup.call(this);
		dom.setup.call(this);

		this.parse(this.dom.el);

	}

	$.extend(Lazyload.prototype, {

		/**
		 * @param  {jQuery} images
		 *
		 * @return {Promise}
		 */
		parse: function ( images ) {

			/**
			 * Why not load every image with one call to loadImage?
			 *
			 * Since some images can be unreachable, single image which is like
			 * that will result in rejected promise from loadImage which will trigger
			 * every image’s success callback earlier.
			 *
			 * Using it this way we set loadImage for every image and resolve only
			 * one promise for global success, while every image gets to call
			 * it’s success independetly of other images.
			 */

			var arr = [];
			var dfd = $.Deferred();

			images.addClass(plugin.classes.isLoading);

			images.each($.proxy( function ( index, element ) {

				element = $(element);
				arr.push(this.aux.loadImage(element.data('src')));
				arr[arr.length-1].always($.proxy(this.success, this, element));

			}, this));

			$.when.apply(window, arr).always(dfd.resolve);

			return dfd.promise();

		},

		/**
		 * @param  {jQuery} image
		 *
		 * @return {}
		 */
		success: function ( image ) {

			image
				.attr({
					src: image.data('src'),
					alt: image.data('alt')
				})
				.removeClass(plugin.classes.isLoading)
				.addClass(plugin.classes.isLoaded);

		},

		destroy: function () {
			dom.destroy.call(this);
			instance.destroy.call(this);
		}

	});

	/**
	 * @class
	 */
	function Postpone () {
		Postpone._super.constructor.apply(this, arguments);

	}
	function PostponeTemp () {}
	PostponeTemp.prototype = Lazyload.prototype;
	Postpone.prototype = new PostponeTemp();
	Postpone.prototype.constructor = Postpone;
	Postpone._super = Lazyload.prototype;

	$.extend(Postpone.prototype, {

		parse: function ( images ) {

			images.inView({
				threshold: this.options.threshold,
				debounce: this.options.debounce,
				once: $.proxy(function ( result ) {

					if ( this.options.start ) {
						this.options.start.call(this._element, result);
					}

					Postpone._super.parse.call(this, result)
						.done($.proxy(function () {

							if ( this.options.success ) {
								this.options.success.call(this._element, result);
							}

						}, this));

				}, this)
			});

		},

		destroy: function () {
			Postpone._super.destroy.call(this);
			this.dom.el.inView('destroy');
		},

		defaults: {
			threshold: 0,
			debounce: 300,
			success: null,
			start: null
		}

	});

	$.kist = $.kist || {};

	$.kist[plugin.name] = {
		postpone: {
			defaults: Postpone.prototype.defaults
		}
	};

	$.fn[plugin.name] = function ( options ) {

		if ( typeof(options) === 'string' && $.inArray(options, plugin.publicMethods) !== -1 ) {
			return this.each(function () {
				var pluginInstance = $.data(this, plugin.name);
				if ( pluginInstance ) {
					pluginInstance[options]();
				}
			});
		}

		options = constructOptions(options);
		var Method = constructMethod(options);

		/**
		 * If there are multiple elements, first filter those which don’t
		 * have any instance of plugin instantiated. Then create only one
		 * instance for current collection which will enable us to have
		 * only one scroll/resize event.
		 */
		var collection = this.filter(function () {
			return !$.data(this, plugin.name) && $(this).is('img');
		});
		if ( collection.length ) {
			collection.data(plugin.name, new Method(collection, options));
		}

		return this;

	};

})( jQuery, window, document );
<|endoftext|>


<commit_before>import {Layer} from './Layer';
import {FeatureGroup} from './FeatureGroup';
import * as Util from '../core/Util';
import {toLatLng} from '../geo/LatLng';
import {toPoint} from '../geometry/Point';
import * as DomUtil from '../dom/DomUtil';

/*
 * @class DivOverlay
 * @inherits Layer
 * @aka L.DivOverlay
 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
 */

// @namespace DivOverlay
export var DivOverlay = Layer.extend({

	// @section
	// @aka DivOverlay options
	options: {
		// @option offset: Point = Point(0, 7)
		// The offset of the popup position. Useful to control the anchor
		// of the popup when opening it on some overlays.
		offset: [0, 7],

		// @option className: String = ''
		// A custom CSS class name to assign to the popup.
		className: '',

		// @option pane: String = 'popupPane'
		// `Map pane` where the popup will be added.
		pane: 'popupPane'
	},

	initialize: function (options, source) {
		Util.setOptions(this, options);

		this._source = source;
	},

	onAdd: function (map) {
		this._zoomAnimated = map._zoomAnimated;

		if (!this._container) {
			this._initLayout();
		}

		if (map._fadeAnimated) {
			DomUtil.setOpacity(this._container, 0);
		}

		clearTimeout(this._removeTimeout);
		this.getPane().appendChild(this._container);
		this.update();

		if (map._fadeAnimated) {
			DomUtil.setOpacity(this._container, 1);
		}

		this.bringToFront();
	},

	onRemove: function (map) {
		if (map._fadeAnimated) {
			DomUtil.setOpacity(this._container, 0);
			this._removeTimeout = setTimeout(Util.bind(DomUtil.remove, undefined, this._container), 200);
		} else {
			DomUtil.remove(this._container);
		}
	},

	// @namespace Popup
	// @method getLatLng: LatLng
	// Returns the geographical point of popup.
	getLatLng: function () {
		return this._latlng;
	},

	// @method setLatLng(latlng: LatLng): this
	// Sets the geographical point where the popup will open.
	setLatLng: function (latlng) {
		this._latlng = toLatLng(latlng);
		if (this._map) {
			this._updatePosition();
			this._adjustPan();
		}
		return this;
	},

	// @method getContent: String|HTMLElement
	// Returns the content of the popup.
	getContent: function () {
		return this._content;
	},

	// @method setContent(htmlContent: String|HTMLElement|Function): this
	// Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
	setContent: function (content) {
		this._content = content;
		this.update();
		return this;
	},

	// @method getElement: String|HTMLElement
	// Alias for [getContent()](#popup-getcontent)
	getElement: function () {
		return this._container;
	},

	// @method update: null
	// Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
	update: function () {
		if (!this._map) { return; }

		this._container.style.visibility = 'hidden';

		this._updateContent();
		this._updateLayout();
		this._updatePosition();

		this._container.style.visibility = '';

		this._adjustPan();
	},

	getEvents: function () {
		var events = {
			zoom: this._updatePosition,
			viewreset: this._updatePosition
		};

		if (this._zoomAnimated) {
			events.zoomanim = this._animateZoom;
		}
		return events;
	},

	// @method isOpen: Boolean
	// Returns `true` when the popup is visible on the map.
	isOpen: function () {
		return !!this._map && this._map.hasLayer(this);
	},

	// @method bringToFront: this
	// Brings this popup in front of other popups (in the same map pane).
	bringToFront: function () {
		if (this._map) {
			DomUtil.toFront(this._container);
		}
		return this;
	},

	// @method bringToBack: this
	// Brings this popup to the back of other popups (in the same map pane).
	bringToBack: function () {
		if (this._map) {
			DomUtil.toBack(this._container);
		}
		return this;
	},

	_prepareOpen: function (parent, layer, latlng) {
		if (!(layer instanceof Layer)) {
			latlng = layer;
			layer = parent;
		}

		if (layer instanceof FeatureGroup) {
			for (var id in parent._layers) {
				layer = parent._layers[id];
				break;
			}
		}

		if (!latlng) {
			if (layer.getCenter) {
				latlng = layer.getCenter();
			} else if (layer.getLatLng) {
				latlng = layer.getLatLng();
			} else {
				throw new Error('Unable to get source layer LatLng.');
			}
		}

		// set overlay source to this layer
		this._source = layer;

		// update the overlay (content, layout, ect...)
		this.update();

		return latlng;
	},

	_updateContent: function () {
		if (!this._content) { return; }

		var node = this._contentNode;
		var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;

		if (typeof content === 'string') {
			node.innerHTML = content;
		} else {
			while (node.hasChildNodes()) {
				node.removeChild(node.firstChild);
			}
			node.appendChild(content);
		}
		this.fire('contentupdate');
	},

	_updatePosition: function () {
		if (!this._map) { return; }

		var pos = this._map.latLngToLayerPoint(this._latlng),
		    offset = toPoint(this.options.offset),
		    anchor = this._getAnchor();

		if (this._zoomAnimated) {
			DomUtil.setPosition(this._container, pos.add(anchor));
		} else {
			offset = offset.add(pos).add(anchor);
		}

		var bottom = this._containerBottom = -offset.y,
		    left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;

		// bottom position the popup in case the height of the popup changes (images loading etc)
		this._container.style.bottom = bottom + 'px';
		this._container.style.left = left + 'px';
	},

	_getAnchor: function () {
		return [0, 0];
	}

});
<commit_msg>Fix DivOverlay.getElement function documentation (#7111)<commit_after>import {Layer} from './Layer';
import {FeatureGroup} from './FeatureGroup';
import * as Util from '../core/Util';
import {toLatLng} from '../geo/LatLng';
import {toPoint} from '../geometry/Point';
import * as DomUtil from '../dom/DomUtil';

/*
 * @class DivOverlay
 * @inherits Layer
 * @aka L.DivOverlay
 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
 */

// @namespace DivOverlay
export var DivOverlay = Layer.extend({

	// @section
	// @aka DivOverlay options
	options: {
		// @option offset: Point = Point(0, 7)
		// The offset of the popup position. Useful to control the anchor
		// of the popup when opening it on some overlays.
		offset: [0, 7],

		// @option className: String = ''
		// A custom CSS class name to assign to the popup.
		className: '',

		// @option pane: String = 'popupPane'
		// `Map pane` where the popup will be added.
		pane: 'popupPane'
	},

	initialize: function (options, source) {
		Util.setOptions(this, options);

		this._source = source;
	},

	onAdd: function (map) {
		this._zoomAnimated = map._zoomAnimated;

		if (!this._container) {
			this._initLayout();
		}

		if (map._fadeAnimated) {
			DomUtil.setOpacity(this._container, 0);
		}

		clearTimeout(this._removeTimeout);
		this.getPane().appendChild(this._container);
		this.update();

		if (map._fadeAnimated) {
			DomUtil.setOpacity(this._container, 1);
		}

		this.bringToFront();
	},

	onRemove: function (map) {
		if (map._fadeAnimated) {
			DomUtil.setOpacity(this._container, 0);
			this._removeTimeout = setTimeout(Util.bind(DomUtil.remove, undefined, this._container), 200);
		} else {
			DomUtil.remove(this._container);
		}
	},

	// @namespace Popup
	// @method getLatLng: LatLng
	// Returns the geographical point of popup.
	getLatLng: function () {
		return this._latlng;
	},

	// @method setLatLng(latlng: LatLng): this
	// Sets the geographical point where the popup will open.
	setLatLng: function (latlng) {
		this._latlng = toLatLng(latlng);
		if (this._map) {
			this._updatePosition();
			this._adjustPan();
		}
		return this;
	},

	// @method getContent: String|HTMLElement
	// Returns the content of the popup.
	getContent: function () {
		return this._content;
	},

	// @method setContent(htmlContent: String|HTMLElement|Function): this
	// Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
	setContent: function (content) {
		this._content = content;
		this.update();
		return this;
	},

	// @method getElement: String|HTMLElement
	// Returns the HTML container of the popup.
	getElement: function () {
		return this._container;
	},

	// @method update: null
	// Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
	update: function () {
		if (!this._map) { return; }

		this._container.style.visibility = 'hidden';

		this._updateContent();
		this._updateLayout();
		this._updatePosition();

		this._container.style.visibility = '';

		this._adjustPan();
	},

	getEvents: function () {
		var events = {
			zoom: this._updatePosition,
			viewreset: this._updatePosition
		};

		if (this._zoomAnimated) {
			events.zoomanim = this._animateZoom;
		}
		return events;
	},

	// @method isOpen: Boolean
	// Returns `true` when the popup is visible on the map.
	isOpen: function () {
		return !!this._map && this._map.hasLayer(this);
	},

	// @method bringToFront: this
	// Brings this popup in front of other popups (in the same map pane).
	bringToFront: function () {
		if (this._map) {
			DomUtil.toFront(this._container);
		}
		return this;
	},

	// @method bringToBack: this
	// Brings this popup to the back of other popups (in the same map pane).
	bringToBack: function () {
		if (this._map) {
			DomUtil.toBack(this._container);
		}
		return this;
	},

	_prepareOpen: function (parent, layer, latlng) {
		if (!(layer instanceof Layer)) {
			latlng = layer;
			layer = parent;
		}

		if (layer instanceof FeatureGroup) {
			for (var id in parent._layers) {
				layer = parent._layers[id];
				break;
			}
		}

		if (!latlng) {
			if (layer.getCenter) {
				latlng = layer.getCenter();
			} else if (layer.getLatLng) {
				latlng = layer.getLatLng();
			} else {
				throw new Error('Unable to get source layer LatLng.');
			}
		}

		// set overlay source to this layer
		this._source = layer;

		// update the overlay (content, layout, ect...)
		this.update();

		return latlng;
	},

	_updateContent: function () {
		if (!this._content) { return; }

		var node = this._contentNode;
		var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;

		if (typeof content === 'string') {
			node.innerHTML = content;
		} else {
			while (node.hasChildNodes()) {
				node.removeChild(node.firstChild);
			}
			node.appendChild(content);
		}
		this.fire('contentupdate');
	},

	_updatePosition: function () {
		if (!this._map) { return; }

		var pos = this._map.latLngToLayerPoint(this._latlng),
		    offset = toPoint(this.options.offset),
		    anchor = this._getAnchor();

		if (this._zoomAnimated) {
			DomUtil.setPosition(this._container, pos.add(anchor));
		} else {
			offset = offset.add(pos).add(anchor);
		}

		var bottom = this._containerBottom = -offset.y,
		    left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;

		// bottom position the popup in case the height of the popup changes (images loading etc)
		this._container.style.bottom = bottom + 'px';
		this._container.style.left = left + 'px';
	},

	_getAnchor: function () {
		return [0, 0];
	}

});
<|endoftext|>