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

	angular.module('xpsui:directives')
	.directive('xpsuiObjectlink2Edit', [
		'xpsui:logging',
		'$parse',
		'xpsui:DropdownFactory',
		'xpsui:Objectlink2Factory',
		'xpsui:SelectDataFactory',
		'xpsui:SchemaUtil',
		'xpsui:RemoveButtonFactory',
	function(log, $parse, dropdownFactory, objectlink2Factory, dataFactory, schemaUtil, removeButtonFactory) {
		return {
			restrict: 'A',
			require: ['ngModel', '?^xpsuiFormControl', 'xpsuiObjectlink2Edit'],
			controller: function($scope, $element, $attrs) {
				this.setup = function(){
					this.$input = angular.element('

');
					this.$input.addClass('x-input');
				};

				this.getInput = function(){
					return this.$input;
				};

				this.$input = null;
				this.setup();
			},
			link: function(scope, elm, attrs, ctrls) {
				log.group('ObjectLink2 edit Link');

				var ngModel = ctrls[0],
					formControl = ctrls[1] || {},
					selfControl = ctrls[2],
					input = selfControl.getInput(),
					parseSchemaFragment = $parse(attrs.xpsuiSchema),
					schemaFragment = parseSchemaFragment(scope)
				;

				var removeButton = removeButtonFactory.create(elm,{
					enabled: !!!schemaFragment.required,
					input: input,
					onClear: function(){
						input.empty();
						scope.$apply(function() {
							ngModel.$setModelValue(
								{}
							);
						});
					}
				});

				elm.addClass('x-control');
				elm.addClass('x-select-edit x-objectlink2-edit');

				ngModel.$render = function() {
					if(!angular.equals({},ngModel.$viewValue)) {
						// get data from schema or model and render it
						render(dataFactory.getObjectLinkData(
							schemaFragment.objectLink2, ngModel.$modelValue
						));
					} else {
						input.empty();
					}
				};

				function render(data) {
					input.empty();

					if (data) {
						removeButton.show();
						// get fields schema fragments
						schemaUtil.getFieldsSchemaFragment(
							schemaFragment.objectLink2.schema,
							schemaFragment.objectLink2.fields,
							function(fields) {
								objectlink2Factory.renderElement(
									input,
									fields,
									data
								);
							}
						);
					}
				}

				elm.append(input);

				elm.bind('focus', function(evt) {
					input[0].focus();
				});

				// dropdown
				var dropdown = dropdownFactory.create(elm, {
					titleTransCode: schemaFragment.transCode
				});

				dropdown.setInput(selfControl.getInput())
					.render()
				;

				// selectobx
				var selectbox = objectlink2Factory.create(elm, {
					onSelected: function(value){

						scope.$apply(function() {
							ngModel.$setViewValue(
								value
							);
						});

						render(value);
						console.log('onSelected');
						console.log(arguments);
					}
				});
				selectbox.setInput(selfControl.getInput());
				selectbox.setDropdown(dropdown);

				// store
				var dataset = dataFactory.createObjectDataset(schemaFragment);
				selectbox.setDataset(dataset);

				log.groupEnd();
			}
		};
	}]);

}(window.angular));

<commit_msg>FIX exception on objectlink remove button<commit_after>(function(angular) {
	'use strict';

	angular.module('xpsui:directives')
	.directive('xpsuiObjectlink2Edit', [
		'xpsui:logging',
		'$parse',
		'xpsui:DropdownFactory',
		'xpsui:Objectlink2Factory',
		'xpsui:SelectDataFactory',
		'xpsui:SchemaUtil',
		'xpsui:RemoveButtonFactory',
	function(log, $parse, dropdownFactory, objectlink2Factory, dataFactory, schemaUtil, removeButtonFactory) {
		return {
			restrict: 'A',
			require: ['ngModel', '?^xpsuiFormControl', 'xpsuiObjectlink2Edit'],
			controller: function($scope, $element, $attrs) {
				this.setup = function(){
					this.$input = angular.element('

');
					this.$input.addClass('x-input');
				};

				this.getInput = function(){
					return this.$input;
				};

				this.$input = null;
				this.setup();
			},
			link: function(scope, elm, attrs, ctrls) {
				log.group('ObjectLink2 edit Link');

				var ngModel = ctrls[0],
					formControl = ctrls[1] || {},
					selfControl = ctrls[2],
					input = selfControl.getInput(),
					parseSchemaFragment = $parse(attrs.xpsuiSchema),
					schemaFragment = parseSchemaFragment(scope)
				;

				var removeButton = removeButtonFactory.create(elm,{
					enabled: !!!schemaFragment.required,
					input: input,
					onClear: function(){
						input.empty();
						scope.$apply(function() {
							ngModel.$modelValue = {};
						});
					}
				});

				elm.addClass('x-control');
				elm.addClass('x-select-edit x-objectlink2-edit');

				ngModel.$render = function() {
					if(!angular.equals({},ngModel.$viewValue)) {
						// get data from schema or model and render it
						render(dataFactory.getObjectLinkData(
							schemaFragment.objectLink2, ngModel.$modelValue
						));
					} else {
						input.empty();
					}
				};

				function render(data) {
					input.empty();

					if (data) {
						removeButton.show();
						// get fields schema fragments
						schemaUtil.getFieldsSchemaFragment(
							schemaFragment.objectLink2.schema,
							schemaFragment.objectLink2.fields,
							function(fields) {
								objectlink2Factory.renderElement(
									input,
									fields,
									data
								);
							}
						);
					}
				}

				elm.append(input);

				elm.bind('focus', function(evt) {
					input[0].focus();
				});

				// dropdown
				var dropdown = dropdownFactory.create(elm, {
					titleTransCode: schemaFragment.transCode
				});

				dropdown.setInput(selfControl.getInput())
					.render()
				;

				// selectobx
				var selectbox = objectlink2Factory.create(elm, {
					onSelected: function(value){

						scope.$apply(function() {
							ngModel.$setViewValue(
								value
							);
						});

						render(value);
						console.log('onSelected');
						console.log(arguments);
					}
				});
				selectbox.setInput(selfControl.getInput());
				selectbox.setDropdown(dropdown);

				// store
				var dataset = dataFactory.createObjectDataset(schemaFragment);
				selectbox.setDataset(dataset);

				log.groupEnd();
			}
		};
	}]);

}(window.angular));

<|endoftext|>


<commit_before>// Set listeners for rendering based on user status.
// Listener that triggers when a new or different user signs in.
var userChanged = setIcons;

// Listener that triggers when sign-in status changes (but not when user changes).
var signInChanged = function(signedIn) {
  if (!signedIn) {
    const recipeCards = document.getElementsByClassName("recipe-card");
    for (let i = 0; i < recipeCards.length; i++) {
      let recipeCard = recipeCards[i];
      const addToCookbookButton = recipeCard.getElementsByClassName("add-to-cookbook-btn")[0];
      const addToPlannerButton = recipeCard.getElementsByClassName("add-to-planner-btn")[0];
      // removes the checkmark (nothing happens if the class does not exist)
      addToCookbookButton.classList.remove("fa-check");
      addToPlannerButton.classList.remove("fa-check");
      // adds the plus sign (nothing is added if it already exists)
      addToCookbookButton.classList.add("fa-plus");
      addToPlannerButton.classList.add("fa-plus");
    }
  }
}

/**
 * Takes the recipe info of a recipe as a JS object and the div that the recipe
 * card will be added to. Creates only one recipe card. For the method to work,
 * recipeInfo is expected to have the following data saved as properties,
 * regardless of the explicit property name:
 * a recipe name, a recipe description, and a recipe
 */
createRecipeCard = (divID, recipeInfo) => {
  let docDiv = document.getElementById(divID);

  let recipeDiv = createElement("div", "", {"class": "recipe-card"});
  let imageDiv = createElement("div", "", {"class": "image-div"});
  let textDiv = createElement("div", "", {"class": "text-div"});

  const id = recipeInfo["id"];
  const name = recipeInfo["name"];

  const addToPlannerButton = createElement("button", " Planner", {"class": "card-button action-button bottom more-left add-to-planner-btn fas fa-plus"});
  addToPlannerButton.addEventListener('click', () => manageList("add", id, name, "planner"));

  const addToCookbookButton = createElement("button", " Cookbook", {"class": "card-button action-button bottom more-right add-to-cookbook-btn fas fa-plus"});
  addToCookbookButton.addEventListener('click', () => manageList("add", id, name, "cookbook"));

  const removeFromPlannerButton = createElement("button", " Planner ", {"class": "card-button action-button bottom more-left fa fa-remove remove-from-planner-btn"});
  removeFromPlannerButton.addEventListener('click', () => manageList("remove", id, name, "planner"));
  removeFromPlannerButton.style.display = "none";

  const removeFromToCookbookButton = createElement("button", " Cookbook ", {"class": "card-button action-button bottom more-right fa fa-remove remove-from-cookbook-btn"});
  removeFromToCookbookButton.addEventListener('click', () => manageList("remove", id, name, "cookbook"));
  removeFromToCookbookButton.style.display = "none";

  let elementsToAddToImageDiv = [
    createImage(recipeInfo["name"], recipeInfo["imageBlobKey"]),
    addToPlannerButton, addToCookbookButton, removeFromPlannerButton, removeFromToCookbookButton,
  ];
  elementsToAddToImageDiv.forEach(elem => imageDiv.appendChild(elem));
  let elementsToAddToTextDiv = [
    createElement("button", recipeInfo["name"],
      {"class": "recipe-card-name",
      "onClick": "redirectRecipePage(" + recipeInfo["id"].toString() + ")"}),
    createElement("p", recipeInfo["id"], {"class": "hidden recipe-id"}),
    createElement("p", recipeInfo["description"], {"class": "recipe-card-description"}),
  ];
  elementsToAddToTextDiv.forEach(elem => textDiv.appendChild(elem));

  let allElementsToAdd = [imageDiv, textDiv];
  allElementsToAdd.forEach(elem => recipeDiv.appendChild(elem));
  docDiv.appendChild(recipeDiv);
}

/**
 * Takes the id for a recipe in Datastore, and redirects to the recipe page template
 * with the recipe ID as a search param.
 */
function redirectRecipePage(id) {
  window.location.assign("/pages/RecipePageTemplate.jsp?id=" + id.toString());
}

/**
 * Function for loading the recipe template page based on the id in the URL
 * search params.
 */
async function fillRecipeTemplate() {
  const recipeId = (new URL(document.location)).searchParams.get("id");

  // Gets the servlet data for the recipe
  // TODO: put this code in an if statement to handle recipe not found scenarios
  let response = await fetch("/search-id?recipeId=" + recipeId.toString());
  let responseText = await response.text();
  let recipeInfo = JSON.parse(responseText);

  let detailsSection = document.getElementById("details");
  let imageSection = document.getElementById("image-container");
  let descriptionSection = document.getElementById("description");
  let ingredientSection = document.getElementById("ingredients");
  let stepsSection = document.getElementById("steps");

  addRecipeInfo(recipeInfo, detailsSection);
  imageSection.appendChild(createImage(recipeInfo["name"], recipeInfo["imageBlobKey"]));
  descriptionSection.appendChild(createElement(
      "p", recipeInfo["description"], {"id": "description-text"}));
  addAsList(recipeInfo["ingredients"], ingredientSection, "Ingredients");
  addAsList(recipeInfo["steps"], stepsSection, "Steps");
}

/**
 * Creates the HTML for the information about the recipe and adds it to the div.
 * Current implementation adds the recipe name and the author.
 * Takes the recipe object and div (not div name) being used.
 */
addRecipeInfo = (recipeObj, infoDiv) => {
  let elementsForDiv = [
    createElement("h1", recipeObj["name"], {"id": "recipe-name"})/*,
    createElement("h3", recipeObj["author"], {"id": "recipe-author"})*/
    // Uncomment once the backend can handle sending author info too
  ];

  // Adds all the recipe info to the passed div
  elementsForDiv.forEach(elem => infoDiv.appendChild(elem));
}

/**
 * Creates the HTML for some part of the recipe which needs to shown as a list
 * (ingredients, steps, etc.) and adds it to the given div.
 * Takes a list and div (not div name) being used.
 * You may specify ordered or unordered, default is ordered.
 */
addAsList = (listObj, listDiv, listName, ordered) => {
  listType = "ol";
  if (ordered != null && ordered == false) {
    listType = "ul";
  }

  // Constructs the base of how all item created in the function will be
  // named for CSS
  let cssRef = listName.toLowerCase() + "-list";

  // Creates the title for the list, and the list element
  let listForDiv = createElement(listType, "", {"id": cssRef})
  let listTitle = createElement("h2", listName, {"id": cssRef + "-title"});

  // Goes through all the objects in listObj and adds them as items to listForDiv
  listObj.forEach(item => listForDiv.appendChild(
    createElement("li", item, {"class": cssRef + "-item"})));

  listDiv.appendChild(listTitle);
  listDiv.appendChild(listForDiv);
}

/**
 * Takes an object which can be represented as a string or a string, an html
 * tag, and the html tag options stored as an object. Returns the reference to the
 * constructed html element.
 * Default values are set for object and tag options for instances where these aren't
 * needed, such as when making a new container div.
 */
createElement = (htmlTag, object = "", tagOptions = {}) => {
  // Create an HTML element with the given tag
  let htmlElement = document.createElement(htmlTag.toLowerCase());

  // If the object parameter is not null or an empty string, it
  // creates a textnode and adds it to the HTML element
  if (object !== null && object !== "") {
    let htmlText = document.createTextNode(object);
    htmlElement.appendChild(htmlText);
  }

  // If the tagOptions object is not null or empty, it goes through each
  // option and adds the attribute to the new HTML element.
  if (tagOptions !== null && Object.keys(tagOptions).length > 0) {
    for (opt in tagOptions) {
      htmlElement.setAttribute(opt, tagOptions[opt]);
    }
  }
  return htmlElement;
}

/**
 * Takes two objects which represent the name and the blobkey
 * Returns the reference to the constructed image element.
 */
createImage = (name, blobkey) => {
  let imageElement = document.createElement('img');
  imageElement.alt = name + " image";
  imageElement.className = "recipe-card-image";
  imageElement.src = "/serve?blobkey="+blobkey;
  return imageElement;
}

function setIcons() {
  const recipeCards = document.getElementsByClassName("recipe-card");
  for (let i = 0; i < recipeCards.length; i++) {
    const recipeCard = recipeCards[i];
    const recipeID = recipeCard.getElementsByClassName("recipe-id")[0].innerText;
    const addToPlannerButton = recipeCard.getElementsByClassName("add-to-planner-btn")[0];
    const addToCookbookButton = recipeCard.getElementsByClassName("add-to-cookbook-btn")[0];

    setIcon(addToPlannerButton, recipeID, "planner");
    setIcon(addToCookbookButton, recipeID, "cookbook");
  }
}

/**
 * Sets the icon of the button to a checkmark if already added to list
 * and a plus otherwise
 * Takes in button you wish to change, the id of the recipe its part of, and which
 * list the button is adding to
 */
function setIcon(button, id, type) {
  if (auth2 != null && auth2.isSignedIn.get()) {
    const idToken = getIdToken();
    // checks if the current recipe is in the user's planner
    fetch("/manage-list?id=" + id + "&idToken=" + idToken + "&type=" + type)
        .then(response => response.text()).then((contains) => {
      if ((/true/i).test(contains)) {
        button.classList.remove("fa-plus");
        button.classList.add("fa-check");
      }
    });
  }
}

// lets the user know if they have already added a recipe to a particular list
// otherwise adds the recipe
function manageList(action, id, name, type) {
  let idToken;
  confirmUser().then((googleUser) => {
    idToken = getIdToken();
    fetch("/manage-list?id=" + id + "&idToken=" + idToken + "&type=" + type)
        .then(response => response.text()).then((contains) => {
      if ((/true/i).test(contains)) {
        if (action == "add") {
          // if the user is trying to add a recipe and the list already contains it
          // alerts the user that the recipe is already in the list
          alert("You have already added the " + name + " recipe to your " + type);
        } else {
          // if the user is trying to remove a precipe and the list contains it
          // asks the user to confirm that they wish to remove the recipe
          const confirmedRemove = confirm("Are you sure you want to remove the " + name + " recipe from your " + type + "?");
          // tells the server to remove the recipe's key from the user's cookbook/planner
          if (confirmedRemove) {
            const params = new URLSearchParams();
            params.append("action", action);
            params.append("id", id);
            params.append("idToken", getIdToken());
            params.append("type", type);
            fetch("/manage-list", {method: "POST", body: params});
            if (document.URL.includes("UserPage")) {
              location.reload();
            }
          }
        }
      } else {
        if (action == "add") {
          // if the user is trying to add a recipe not already in the list
          const params = new URLSearchParams();
          params.append("action", action);
          params.append("id", id);
          params.append("idToken", getIdToken());
          params.append("type", type);
          fetch("/manage-list", {method: "POST", body: params});
          // finds the button that was clicked using the recipe id
          let recipeCards = document.getElementsByClassName("recipe-card");
          for (let i = 0; i < recipeCards.length; i++) {
            let recipeID = recipeCards[i].getElementsByClassName("recipe-id")[0].innerText;
            let button = recipeCards[i].getElementsByClassName("add-to-" + type + "-btn")[0];
            if (recipeID == id) {
              // makes the icon a checkmark
              button.classList.remove("fa-plus");
              button.classList.add("fa-check");
              break;
            }
          }
          if (document.URL.includes("UserPage")) {
            location.reload();
          }
        }
      }
    });
  });
}
<commit_msg>update addAsList to incorporate functionality in Mathew's branch<commit_after>// Set listeners for rendering based on user status.
// Listener that triggers when a new or different user signs in.
var userChanged = setIcons;

// Listener that triggers when sign-in status changes (but not when user changes).
var signInChanged = function(signedIn) {
  if (!signedIn) {
    const recipeCards = document.getElementsByClassName("recipe-card");
    for (let i = 0; i < recipeCards.length; i++) {
      let recipeCard = recipeCards[i];
      const addToCookbookButton = recipeCard.getElementsByClassName("add-to-cookbook-btn")[0];
      const addToPlannerButton = recipeCard.getElementsByClassName("add-to-planner-btn")[0];
      // removes the checkmark (nothing happens if the class does not exist)
      addToCookbookButton.classList.remove("fa-check");
      addToPlannerButton.classList.remove("fa-check");
      // adds the plus sign (nothing is added if it already exists)
      addToCookbookButton.classList.add("fa-plus");
      addToPlannerButton.classList.add("fa-plus");
    }
  }
}

/**
 * Takes the recipe info of a recipe as a JS object and the div that the recipe
 * card will be added to. Creates only one recipe card. For the method to work,
 * recipeInfo is expected to have the following data saved as properties,
 * regardless of the explicit property name:
 * a recipe name, a recipe description, and a recipe
 */
createRecipeCard = (divID, recipeInfo) => {
  let docDiv = document.getElementById(divID);

  let recipeDiv = createElement("div", "", {"class": "recipe-card"});
  let imageDiv = createElement("div", "", {"class": "image-div"});
  let textDiv = createElement("div", "", {"class": "text-div"});

  const id = recipeInfo["id"];
  const name = recipeInfo["name"];

  const addToPlannerButton = createElement("button", " Planner", {"class": "card-button action-button bottom more-left add-to-planner-btn fas fa-plus"});
  addToPlannerButton.addEventListener('click', () => manageList("add", id, name, "planner"));

  const addToCookbookButton = createElement("button", " Cookbook", {"class": "card-button action-button bottom more-right add-to-cookbook-btn fas fa-plus"});
  addToCookbookButton.addEventListener('click', () => manageList("add", id, name, "cookbook"));

  const removeFromPlannerButton = createElement("button", " Planner ", {"class": "card-button action-button bottom more-left fa fa-remove remove-from-planner-btn"});
  removeFromPlannerButton.addEventListener('click', () => manageList("remove", id, name, "planner"));
  removeFromPlannerButton.style.display = "none";

  const removeFromToCookbookButton = createElement("button", " Cookbook ", {"class": "card-button action-button bottom more-right fa fa-remove remove-from-cookbook-btn"});
  removeFromToCookbookButton.addEventListener('click', () => manageList("remove", id, name, "cookbook"));
  removeFromToCookbookButton.style.display = "none";

  let elementsToAddToImageDiv = [
    createImage(recipeInfo["name"], recipeInfo["imageBlobKey"]),
    addToPlannerButton, addToCookbookButton, removeFromPlannerButton, removeFromToCookbookButton,
  ];
  elementsToAddToImageDiv.forEach(elem => imageDiv.appendChild(elem));
  let elementsToAddToTextDiv = [
    createElement("button", recipeInfo["name"],
      {"class": "recipe-card-name",
      "onClick": "redirectRecipePage(" + recipeInfo["id"].toString() + ")"}),
    createElement("p", recipeInfo["id"], {"class": "hidden recipe-id"}),
    createElement("p", recipeInfo["description"], {"class": "recipe-card-description"}),
  ];
  elementsToAddToTextDiv.forEach(elem => textDiv.appendChild(elem));

  let allElementsToAdd = [imageDiv, textDiv];
  allElementsToAdd.forEach(elem => recipeDiv.appendChild(elem));
  docDiv.appendChild(recipeDiv);
}

/**
 * Takes the id for a recipe in Datastore, and redirects to the recipe page template
 * with the recipe ID as a search param.
 */
function redirectRecipePage(id) {
  window.location.assign("/pages/RecipePageTemplate.jsp?id=" + id.toString());
}

/**
 * Function for loading the recipe template page based on the id in the URL
 * search params.
 */
async function fillRecipeTemplate() {
  const recipeId = (new URL(document.location)).searchParams.get("id");

  // Gets the servlet data for the recipe
  // TODO: put this code in an if statement to handle recipe not found scenarios
  let response = await fetch("/search-id?recipeId=" + recipeId.toString());
  let responseText = await response.text();
  let recipeInfo = JSON.parse(responseText);

  let detailsSection = document.getElementById("details");
  let imageSection = document.getElementById("image-container");
  let descriptionSection = document.getElementById("description");
  let ingredientSection = document.getElementById("ingredients");
  let stepsSection = document.getElementById("steps");

  addRecipeInfo(recipeInfo, detailsSection);
  imageSection.appendChild(createImage(recipeInfo["name"], recipeInfo["imageBlobKey"]));
  descriptionSection.appendChild(createElement(
      "p", recipeInfo["description"], {"id": "description-text"}));
  addAsList(recipeInfo["ingredients"], ingredientSection, "Ingredients");
  addAsList(recipeInfo["steps"], stepsSection, "Steps");
}

/**
 * Creates the HTML for the information about the recipe and adds it to the div.
 * Current implementation adds the recipe name and the author.
 * Takes the recipe object and div (not div name) being used.
 */
addRecipeInfo = (recipeObj, infoDiv) => {
  let elementsForDiv = [
    createElement("h1", recipeObj["name"], {"id": "recipe-name"})/*,
    createElement("h3", recipeObj["author"], {"id": "recipe-author"})*/
    // Uncomment once the backend can handle sending author info too
  ];

  // Adds all the recipe info to the passed div
  elementsForDiv.forEach(elem => infoDiv.appendChild(elem));
}

/**
 * Creates the HTML for some part of the recipe which needs to shown as a list
 * (ingredients, steps, etc.) and adds it to the given div.
 * Takes a list and div (not div name) being used.
 * You may specify ordered or unordered, default is ordered.
 */
addAsList = (listObj, listDiv, listName, ordered) => {
  listType = "ol";
  if (ordered != null && ordered == false) {
    listType = "ul";
  }

  // Constructs the base of how all item created in the function will be
  // named for CSS
  let cssRef = listName.toLowerCase() + "-list";

  // Creates the title for the list, and the list element
  let listForDiv = createElement(listType, "", {"id": cssRef})
  let listTitle = createElement("h2", listName, {"id": cssRef + "-title", "class": "list-header"});

  // Goes through all the objects in listObj and adds them as items to listForDiv
  listObj.forEach(item => listForDiv.appendChild(
    createElement("li", item, {"class": cssRef + "-item"})));

  listDiv.appendChild(listTitle);
  listDiv.appendChild(listForDiv);
}

/**
 * Takes an object which can be represented as a string or a string, an html
 * tag, and the html tag options stored as an object. Returns the reference to the
 * constructed html element.
 * Default values are set for object and tag options for instances where these aren't
 * needed, such as when making a new container div.
 */
createElement = (htmlTag, object = "", tagOptions = {}) => {
  // Create an HTML element with the given tag
  let htmlElement = document.createElement(htmlTag.toLowerCase());

  // If the object parameter is not null or an empty string, it
  // creates a textnode and adds it to the HTML element
  if (object !== null && object !== "") {
    let htmlText = document.createTextNode(object);
    htmlElement.appendChild(htmlText);
  }

  // If the tagOptions object is not null or empty, it goes through each
  // option and adds the attribute to the new HTML element.
  if (tagOptions !== null && Object.keys(tagOptions).length > 0) {
    for (opt in tagOptions) {
      htmlElement.setAttribute(opt, tagOptions[opt]);
    }
  }
  return htmlElement;
}

/**
 * Takes two objects which represent the name and the blobkey
 * Returns the reference to the constructed image element.
 */
createImage = (name, blobkey) => {
  let imageElement = document.createElement('img');
  imageElement.alt = name + " image";
  imageElement.className = "recipe-card-image";
  imageElement.src = "/serve?blobkey="+blobkey;
  return imageElement;
}

function setIcons() {
  const recipeCards = document.getElementsByClassName("recipe-card");
  for (let i = 0; i < recipeCards.length; i++) {
    const recipeCard = recipeCards[i];
    const recipeID = recipeCard.getElementsByClassName("recipe-id")[0].innerText;
    const addToPlannerButton = recipeCard.getElementsByClassName("add-to-planner-btn")[0];
    const addToCookbookButton = recipeCard.getElementsByClassName("add-to-cookbook-btn")[0];

    setIcon(addToPlannerButton, recipeID, "planner");
    setIcon(addToCookbookButton, recipeID, "cookbook");
  }
}

/**
 * Sets the icon of the button to a checkmark if already added to list
 * and a plus otherwise
 * Takes in button you wish to change, the id of the recipe its part of, and which
 * list the button is adding to
 */
function setIcon(button, id, type) {
  if (auth2 != null && auth2.isSignedIn.get()) {
    const idToken = getIdToken();
    // checks if the current recipe is in the user's planner
    fetch("/manage-list?id=" + id + "&idToken=" + idToken + "&type=" + type)
        .then(response => response.text()).then((contains) => {
      if ((/true/i).test(contains)) {
        button.classList.remove("fa-plus");
        button.classList.add("fa-check");
      }
    });
  }
}

// lets the user know if they have already added a recipe to a particular list
// otherwise adds the recipe
function manageList(action, id, name, type) {
  let idToken;
  confirmUser().then((googleUser) => {
    idToken = getIdToken();
    fetch("/manage-list?id=" + id + "&idToken=" + idToken + "&type=" + type)
        .then(response => response.text()).then((contains) => {
      if ((/true/i).test(contains)) {
        if (action == "add") {
          // if the user is trying to add a recipe and the list already contains it
          // alerts the user that the recipe is already in the list
          alert("You have already added the " + name + " recipe to your " + type);
        } else {
          // if the user is trying to remove a precipe and the list contains it
          // asks the user to confirm that they wish to remove the recipe
          const confirmedRemove = confirm("Are you sure you want to remove the " + name + " recipe from your " + type + "?");
          // tells the server to remove the recipe's key from the user's cookbook/planner
          if (confirmedRemove) {
            const params = new URLSearchParams();
            params.append("action", action);
            params.append("id", id);
            params.append("idToken", getIdToken());
            params.append("type", type);
            fetch("/manage-list", {method: "POST", body: params});
            if (document.URL.includes("UserPage")) {
              location.reload();
            }
          }
        }
      } else {
        if (action == "add") {
          // if the user is trying to add a recipe not already in the list
          const params = new URLSearchParams();
          params.append("action", action);
          params.append("id", id);
          params.append("idToken", getIdToken());
          params.append("type", type);
          fetch("/manage-list", {method: "POST", body: params});
          // finds the button that was clicked using the recipe id
          let recipeCards = document.getElementsByClassName("recipe-card");
          for (let i = 0; i < recipeCards.length; i++) {
            let recipeID = recipeCards[i].getElementsByClassName("recipe-id")[0].innerText;
            let button = recipeCards[i].getElementsByClassName("add-to-" + type + "-btn")[0];
            if (recipeID == id) {
              // makes the icon a checkmark
              button.classList.remove("fa-plus");
              button.classList.add("fa-check");
              break;
            }
          }
          if (document.URL.includes("UserPage")) {
            location.reload();
          }
        }
      }
    });
  });
}
<|endoftext|>


<commit_before>describe("me.Vector2d", function () {

    var x = 1, y = 2;

    it("should be initialized to a (0, 0) 2d vector", function () {
        var vec = new me.Vector2d();
        var result = "x:0,y:0";
        expect(vec.toString() === result).toEqual(true);
    });

    it("a(1, 2, 3) should be copied into b", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d().copy(a);
        expect(b.equals(a)).toEqual(true);
    });

    it("set (1, 2, 3) into a defined vector", function () {
        var vec = new me.Vector2d().set(x, y);
        var result = "x:"+x+",y:"+y;
        expect(vec.toString() === result).toEqual(true);
    });

    it("add (1, 2, 3) to (-1, -2, -3)", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);
        var result = "x:0,y:0";
        expect(a.add(b).toString() === result).toEqual(true);
    });

    it("sub (1, 2, 3) to (-1, -2)", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);
        var result = "x:"+(x-(-x))+",y:"+(y-(-y));
        expect(a.sub(b).toString() === result).toEqual(true);
    });

    it("scale (1, 2, 3) by (-1, -2)", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);
        var result = "x:"+x*(-x)+",y:"+y*(-y);
        expect(a.scaleV(b).toString() === result).toEqual(true);
    });

    it("negate (1, 2, 3)", function () {
        var a = new me.Vector2d(x, y);
        var result = "x:"+-x+",y:"+-y;
        expect(a.negateSelf().toString() === result).toEqual(true);
    });

    it("dotProduct (1, 2, 3) and (-1, -2)", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);
        // calculate the dot product
        expect(a.dotProduct(b) === (-x*x-y*y)).toEqual(true);
    });

    it("length/lengthSqrt functions", function () {
        var a = new me.Vector2d( x, 0);
        var b = new me.Vector2d( 0, -y);
        var c = new me.Vector2d();

        expect( a.length() === x).toEqual(true);
        expect( a.length2() === x*x).toEqual(true);
        expect( b.length() === y).toEqual(true);
        expect( b.length2() === y*y).toEqual(true);
        expect( c.length() === 0).toEqual(true);
        expect( c.length2() === 0).toEqual(true);

        a.set( x, y);
        expect( a.length() === Math.sqrt( x*x + y*y )).toEqual(true);
        expect( a.length2() === ( x*x + y*y )).toEqual(true);
    });

    it("normalize function", function () {
        var a = new me.Vector2d( x, 0 );
        var b = new me.Vector2d( 0, -y );

        a.normalize();
        expect( a.length() === 1).toEqual(true);
        expect( a.x === 1).toEqual(true);

        b.normalize();
        expect( b.length() === 1).toEqual(true);
        expect( b.y === -1).toEqual(true);

    });

    it("distance function", function () {
        var a = new me.Vector2d( x, 0);
        var b = new me.Vector2d( 0, -y);
        var c = new me.Vector2d( 0, 0);

        expect( a.distance( c ) === x).toEqual(true);
        expect( b.distance( c ) === y).toEqual(true);
    });

    it( "min/max/clamp", function() {
        var a = new me.Vector2d( x, y );
        var b = new me.Vector2d( -x, -y );
        var c = new me.Vector2d();

        c.copy( a ).minV( b );
        expect( c.x === -x).toEqual(true);
        expect( c.y === -y).toEqual(true);

        c.copy( a ).maxV( b );
        expect( c.x === x).toEqual(true);
        expect( c.y === y).toEqual(true);

        c.set( -2*x, 2*x );
        c.clampSelf( -x, x );
        expect( c.x === -x).toEqual(true);
        expect( c.y === x).toEqual(true);
    });

    it( "ceil/floor", function() {
        expect( new me.Vector2d( -0.1, 0.1 ).floorSelf().equals(new me.Vector2d( -1, 0 ))).toEqual(true);
        expect( new me.Vector2d( -0.5, 0.5 ).floorSelf().equals(new me.Vector2d( -1, 0 ))).toEqual(true);
        expect( new me.Vector2d( -0.9, 0.9 ).floorSelf().equals(new me.Vector2d( -1, 0 ))).toEqual(true);

        expect( new me.Vector2d( -0.1, 0.1 ).ceilSelf().equals(new me.Vector2d( 0, 1 ))).toEqual(true);
        expect( new me.Vector2d( -0.5, 0.5 ).ceilSelf().equals(new me.Vector2d( 0, 1 ))).toEqual(true);
        expect( new me.Vector2d( -0.9, 0.9 ).ceilSelf().equals(new me.Vector2d( 0, 1 ))).toEqual(true);
    });

    it("project a on b", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);

        // the following only works with (-)1, (-)2style of values
        expect(a.project(b).equals(b)).toEqual(true);
    });

    it("angle between a and b", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);

        // why is this not perfectly 180 degrees ?
        expect(Math.round(a.angle(b).radToDeg()) === 180).toEqual(true);

        b.set(4*x, -y);
        expect(a.angle(b) === Math.PI / 2).toEqual(true);
    });

});
<commit_msg>test name fixes<commit_after>describe("me.Vector2d", function () {

    var x = 1, y = 2;

    it("should be initialized to a (0, 0) 2d vector", function () {
        var vec = new me.Vector2d();
        var result = "x:0,y:0";
        expect(vec.toString() === result).toEqual(true);
    });

    it("a(1, 2) should be copied into b", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d().copy(a);
        expect(b.equals(a)).toEqual(true);
    });

    it("set (1, 2) into a defined vector", function () {
        var vec = new me.Vector2d().set(x, y);
        var result = "x:"+x+",y:"+y;
        expect(vec.toString() === result).toEqual(true);
    });

    it("add (1, 2) to (-1, -2)", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);
        var result = "x:0,y:0";
        expect(a.add(b).toString() === result).toEqual(true);
    });

    it("sub (1, 2) to (-1, -2)", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);
        var result = "x:"+(x-(-x))+",y:"+(y-(-y));
        expect(a.sub(b).toString() === result).toEqual(true);
    });

    it("scale (1, 2) by (-1, -2)", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);
        var result = "x:"+x*(-x)+",y:"+y*(-y);
        expect(a.scaleV(b).toString() === result).toEqual(true);
    });

    it("negate (1, 2)", function () {
        var a = new me.Vector2d(x, y);
        var result = "x:"+-x+",y:"+-y;
        expect(a.negateSelf().toString() === result).toEqual(true);
    });

    it("dotProduct (1, 2) and (-1, -2)", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);
        // calculate the dot product
        expect(a.dotProduct(b) === (-x*x-y*y)).toEqual(true);
    });

    it("length/lengthSqrt functions", function () {
        var a = new me.Vector2d( x, 0);
        var b = new me.Vector2d( 0, -y);
        var c = new me.Vector2d();

        expect( a.length() === x).toEqual(true);
        expect( a.length2() === x*x).toEqual(true);
        expect( b.length() === y).toEqual(true);
        expect( b.length2() === y*y).toEqual(true);
        expect( c.length() === 0).toEqual(true);
        expect( c.length2() === 0).toEqual(true);

        a.set( x, y);
        expect( a.length() === Math.sqrt( x*x + y*y )).toEqual(true);
        expect( a.length2() === ( x*x + y*y )).toEqual(true);
    });

    it("normalize function", function () {
        var a = new me.Vector2d( x, 0 );
        var b = new me.Vector2d( 0, -y );

        a.normalize();
        expect( a.length() === 1).toEqual(true);
        expect( a.x === 1).toEqual(true);

        b.normalize();
        expect( b.length() === 1).toEqual(true);
        expect( b.y === -1).toEqual(true);

    });

    it("distance function", function () {
        var a = new me.Vector2d( x, 0);
        var b = new me.Vector2d( 0, -y);
        var c = new me.Vector2d( 0, 0);

        expect( a.distance( c ) === x).toEqual(true);
        expect( b.distance( c ) === y).toEqual(true);
    });

    it( "min/max/clamp", function() {
        var a = new me.Vector2d( x, y );
        var b = new me.Vector2d( -x, -y );
        var c = new me.Vector2d();

        c.copy( a ).minV( b );
        expect( c.x === -x).toEqual(true);
        expect( c.y === -y).toEqual(true);

        c.copy( a ).maxV( b );
        expect( c.x === x).toEqual(true);
        expect( c.y === y).toEqual(true);

        c.set( -2*x, 2*x );
        c.clampSelf( -x, x );
        expect( c.x === -x).toEqual(true);
        expect( c.y === x).toEqual(true);
    });

    it( "ceil/floor", function() {
        expect( new me.Vector2d( -0.1, 0.1 ).floorSelf().equals(new me.Vector2d( -1, 0 ))).toEqual(true);
        expect( new me.Vector2d( -0.5, 0.5 ).floorSelf().equals(new me.Vector2d( -1, 0 ))).toEqual(true);
        expect( new me.Vector2d( -0.9, 0.9 ).floorSelf().equals(new me.Vector2d( -1, 0 ))).toEqual(true);

        expect( new me.Vector2d( -0.1, 0.1 ).ceilSelf().equals(new me.Vector2d( 0, 1 ))).toEqual(true);
        expect( new me.Vector2d( -0.5, 0.5 ).ceilSelf().equals(new me.Vector2d( 0, 1 ))).toEqual(true);
        expect( new me.Vector2d( -0.9, 0.9 ).ceilSelf().equals(new me.Vector2d( 0, 1 ))).toEqual(true);
    });

    it("project a on b", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);

        // the following only works with (-)1, (-)2style of values
        expect(a.project(b).equals(b)).toEqual(true);
    });

    it("angle between a and b", function () {
        var a = new me.Vector2d(x, y);
        var b = new me.Vector2d(-x, -y);

        // why is this not perfectly 180 degrees ?
        expect(Math.round(a.angle(b).radToDeg()) === 180).toEqual(true);

        b.set(4*x, -y);
        expect(a.angle(b) === Math.PI / 2).toEqual(true);
    });

});
<|endoftext|>


<commit_before>var point = require('@turf/helpers').point;

/**
 * Takes a {@link LineString|linestring}, {@link MultiLineString|multi-linestring}, {@link MultiPolygon|multi-polygon}, or {@link Polygon|polygon} and returns {@link Point|points} at all self-intersections.
 *
 * @name kinks
 * @param {Feature<LineString|MultiLineString|MultiPolygon|Polygon>} featureIn input feature
 * @returns {FeatureCollection<Point>} self-intersections
 * @example
 * var poly = turf.polygon([[
 *   [-12.034835, 8.901183],
 *   [-12.060413, 8.899826],
 *   [-12.03638, 8.873199],
 *   [-12.059383, 8.871418],
 *   [-12.034835, 8.901183]
 * ]]);
 *
 * var kinks = turf.kinks(poly);
 *
 * //addToMap
 * var addToMap = [poly, kinks]
 */
module.exports = function (featureIn) {
    var coordinates;
    var feature;
    var results = {
        type: 'FeatureCollection',
        features: []
    };
    if (featureIn.type === 'Feature') {
        feature = featureIn.geometry;
    } else {
        feature = featureIn;
    }
    if (feature.type === 'LineString') {
        coordinates = [feature.coordinates];
    } else if (feature.type === 'MultiLineString') {
        coordinates = feature.coordinates;
    } else if (feature.type === 'MultiPolygon') {
        coordinates = [].concat.apply([], feature.coordinates);
    } else if (feature.type === 'Polygon') {
        coordinates = feature.coordinates;
    } else {
        throw new Error('Input must be a LineString, MultiLineString, ' +
            'Polygon, or MultiPolygon Feature or Geometry');
    }
    coordinates.forEach(function (line1) {
        coordinates.forEach(function (line2) {
            for (var i = 0; i < line1.length - 1; i++) {
                // start iteration at i, intersections for k < i have already been checked in previous outer loop iterations
                for (var k = i; k < line2.length - 1; k++) {
                    if (line1 === line2) {
                        // segments are adjacent and always share a vertex, not a kink
                        if (Math.abs(i - k) === 1) {
                            continue
                        }
                        // first and last segment in a closed lineString or ring always share a vertex, not a kink
                        if (
                            // segments are first and last segment of lineString
                            i === 0 &&
                            k === line1.length - 2 &&
                            // lineString is closed
                            line1[i][0] === line1[line1.length - 1][0] &&
                            line1[i][1] === line1[line1.length - 1][1]
                        ) {
                            continue
                        }
                    }

                    var intersection = lineIntersects(line1[i][0], line1[i][1], line1[i + 1][0], line1[i + 1][1],
                        line2[k][0], line2[k][1], line2[k + 1][0], line2[k + 1][1]);
                    if (intersection) {
                        results.features.push(point([intersection[0], intersection[1]]));
                    }
                }
            }
        });
    });
    return results;
};

// modified from http://jsfiddle.net/justin_c_rounds/Gd2S2/light/
function lineIntersects(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
    // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
    var denominator, a, b, numerator1, numerator2,
        result = {
            x: null,
            y: null,
            onLine1: false,
            onLine2: false
        };
    denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
    if (denominator === 0) {
        if (result.x !== null && result.y !== null) {
            return result;
        } else {
            return false;
        }
    }
    a = line1StartY - line2StartY;
    b = line1StartX - line2StartX;
    numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
    numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
    a = numerator1 / denominator;
    b = numerator2 / denominator;

    // if we cast these lines infinitely in both directions, they intersect here:
    result.x = line1StartX + (a * (line1EndX - line1StartX));
    result.y = line1StartY + (a * (line1EndY - line1StartY));

    // if line1 is a segment and line2 is infinite, they intersect if:
    if (a >= 0 && a <= 1) {
        result.onLine1 = true;
    }
    // if line2 is a segment and line1 is infinite, they intersect if:
    if (b >= 0 && b <= 1) {
        result.onLine2 = true;
    }
    // if line1 and line2 are segments, they intersect if both of the above are true
    if (result.onLine1 && result.onLine2) {
        return [result.x, result.y];
    } else {
        return false;
    }
}
<commit_msg>fixed linter errors<commit_after>var point = require('@turf/helpers').point;

/**
 * Takes a {@link LineString|linestring}, {@link MultiLineString|multi-linestring}, {@link MultiPolygon|multi-polygon}, or {@link Polygon|polygon} and returns {@link Point|points} at all self-intersections.
 *
 * @name kinks
 * @param {Feature<LineString|MultiLineString|MultiPolygon|Polygon>} featureIn input feature
 * @returns {FeatureCollection<Point>} self-intersections
 * @example
 * var poly = turf.polygon([[
 *   [-12.034835, 8.901183],
 *   [-12.060413, 8.899826],
 *   [-12.03638, 8.873199],
 *   [-12.059383, 8.871418],
 *   [-12.034835, 8.901183]
 * ]]);
 *
 * var kinks = turf.kinks(poly);
 *
 * //addToMap
 * var addToMap = [poly, kinks]
 */
module.exports = function (featureIn) {
    var coordinates;
    var feature;
    var results = {
        type: 'FeatureCollection',
        features: []
    };
    if (featureIn.type === 'Feature') {
        feature = featureIn.geometry;
    } else {
        feature = featureIn;
    }
    if (feature.type === 'LineString') {
        coordinates = [feature.coordinates];
    } else if (feature.type === 'MultiLineString') {
        coordinates = feature.coordinates;
    } else if (feature.type === 'MultiPolygon') {
        coordinates = [].concat.apply([], feature.coordinates);
    } else if (feature.type === 'Polygon') {
        coordinates = feature.coordinates;
    } else {
        throw new Error('Input must be a LineString, MultiLineString, ' +
            'Polygon, or MultiPolygon Feature or Geometry');
    }
    coordinates.forEach(function (line1) {
        coordinates.forEach(function (line2) {
            for (var i = 0; i < line1.length - 1; i++) {
                // start iteration at i, intersections for k < i have already been checked in previous outer loop iterations
                for (var k = i; k < line2.length - 1; k++) {
                    if (line1 === line2) {
                        // segments are adjacent and always share a vertex, not a kink
                        if (Math.abs(i - k) === 1) {
                            continue;
                        }
                        // first and last segment in a closed lineString or ring always share a vertex, not a kink
                        if (
                            // segments are first and last segment of lineString
                            i === 0 &&
                            k === line1.length - 2 &&
                            // lineString is closed
                            line1[i][0] === line1[line1.length - 1][0] &&
                            line1[i][1] === line1[line1.length - 1][1]
                        ) {
                            continue;
                        }
                    }

                    var intersection = lineIntersects(line1[i][0], line1[i][1], line1[i + 1][0], line1[i + 1][1],
                        line2[k][0], line2[k][1], line2[k + 1][0], line2[k + 1][1]);
                    if (intersection) {
                        results.features.push(point([intersection[0], intersection[1]]));
                    }
                }
            }
        });
    });
    return results;
};

// modified from http://jsfiddle.net/justin_c_rounds/Gd2S2/light/
function lineIntersects(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
    // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
    var denominator, a, b, numerator1, numerator2,
        result = {
            x: null,
            y: null,
            onLine1: false,
            onLine2: false
        };
    denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
    if (denominator === 0) {
        if (result.x !== null && result.y !== null) {
            return result;
        } else {
            return false;
        }
    }
    a = line1StartY - line2StartY;
    b = line1StartX - line2StartX;
    numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
    numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
    a = numerator1 / denominator;
    b = numerator2 / denominator;

    // if we cast these lines infinitely in both directions, they intersect here:
    result.x = line1StartX + (a * (line1EndX - line1StartX));
    result.y = line1StartY + (a * (line1EndY - line1StartY));

    // if line1 is a segment and line2 is infinite, they intersect if:
    if (a >= 0 && a <= 1) {
        result.onLine1 = true;
    }
    // if line2 is a segment and line1 is infinite, they intersect if:
    if (b >= 0 && b <= 1) {
        result.onLine2 = true;
    }
    // if line1 and line2 are segments, they intersect if both of the above are true
    if (result.onLine1 && result.onLine2) {
        return [result.x, result.y];
    } else {
        return false;
    }
}
<|endoftext|>


<commit_before>[['idb-1', 'http-1'],
 ['http-1', 'http-2'],
 ['http-1', 'idb-1']].map(function(adapters) {

  module('replication: ' + adapters[0] + ':' + adapters[1], {
    setup : function () {
      this.name = generateAdapterUrl(adapters[0]);
      this.remote = generateAdapterUrl(adapters[1]);
    }
  });

  var docs = [
    {_id: "0", integer: 0, string: '0'},
    {_id: "1", integer: 1, string: '1'},
    {_id: "2", integer: 2, string: '2'}
  ];

  asyncTest("Test basic pull replication", function() {
    var self = this;
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs}, {}, function(err, results) {
        db.replicate.from(self.remote, function(err, result) {
          ok(result.ok, 'replication was ok');
          ok(result.docs_written = docs.length, 'correct # docs written');
          start();
        });
      });
    });
  });

  asyncTest("Local DB contains documents", function() {
    var self = this;
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs}, {}, function(err, _) {
        db.bulkDocs({docs: docs}, {}, function(err, _) {
          db.replicate.from(self.remote, function(err, _) {
            db.allDocs(function(err, result) {
              ok(result.rows.length === docs.length, 'correct # docs exist');
              start();
            });
          });
        });
      });
    });
  });

  asyncTest("Test basic push replication", function() {
    var self = this;
    initDBPair(this.name, this.remote, function(db, remote) {
      db.bulkDocs({docs: docs}, {}, function(err, results) {
        db.replicate.to(self.remote, function(err, result) {
          ok(result.ok, 'replication was ok');
          ok(result.docs_written === docs.length, 'correct # docs written');
          start();
        });
      });
    });
  });

  asyncTest("Test basic push replication take 2", function() {
    var self = this;
    initDBPair(this.name, this.remote, function(db, remote) {
      db.bulkDocs({docs: docs}, {}, function(err, _) {
        db.replicate.to(self.remote, function(err, _) {
          remote.allDocs(function(err, result) {
            ok(result.rows.length === docs.length, 'correct # docs written');
            start();
          });
        });
      });
    });
  });

  asyncTest("Test checkpoint", function() {
    var self = this;
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs}, {}, function(err, results) {
        db.replicate.from(self.remote, function(err, result) {
          ok(result.ok, 'replication was ok');
          ok(result.docs_written === docs.length, 'correct # docs written');
          db.replicate.from(self.remote, function(err, result) {
            ok(result.ok, 'replication was ok');
            ok(result.docs_written === 0, 'correct # docs written');
            ok(result.docs_read === 0, 'no docs read');
            start();
          });
        });
      });
    });
  });

  asyncTest("Test checkpoint 2", function() {
    var self = this;
    var doc = {_id: "3", count: 0};
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.put(doc, {}, function(err, results) {
        db.replicate.from(self.remote, function(err, result) {
          ok(result.ok, 'replication was ok');
          doc._rev = results.rev;
          doc.count++;
          remote.put(doc, {}, function(err, results) {
            doc._rev = results.rev;
            doc.count++;
            remote.put(doc, {}, function(err, results) {
              db.replicate.from(self.remote, function(err, result) {
                ok(result.ok, 'replication was ok');
                ok(result.docs_written === 1, 'correct # docs written');
                start();
              });
            });
          });
        });
      });
    });
  });

  asyncTest("Test checkpoint 3 :)", function() {
    var self = this;
    var doc = {_id: "3", count: 0};
    initDBPair(this.name, this.remote, function(db, remote) {
      db.put(doc, {}, function(err, results) {
        Pouch.replicate(db, remote, {}, function(err, result) {
          ok(result.ok, 'replication was ok');
          doc._rev = results.rev;
          doc.count++;
          db.put(doc, {}, function(err, results) {
            doc._rev = results.rev;
            doc.count++;
            db.put(doc, {}, function(err, results) {
              Pouch.replicate(db, remote, {}, function(err, result) {
                ok(result.ok, 'replication was ok');
                ok(result.docs_written === 1, 'correct # docs written');
                start();
              });
            });
          });
        });
      });
    });
  });

  // CouchDB will not generate a conflict here, it uses a deteministic
  // method to generate the revision number, however we cannot copy its
  // method as it depends on erlangs internal data representation
  asyncTest("Test basic conflict", function() {
    var self = this;
    var doc1 = {_id: 'adoc', foo:'bar'};
    var doc2 = {_id: 'adoc', bar:'baz'};
    initDBPair(this.name, this.remote, function(db, remote) {
      db.put(doc1, function(err, localres) {
        remote.put(doc2, function(err, remoteres) {
          db.replicate.to(self.remote, function(err, _) {
            remote.get('adoc', {conflicts: true}, function(err, result) {
              ok(result._conflicts, 'result has a conflict');
              start();
            });
          });
        });
      });
    });
  });

  asyncTest("Test basic continous pull replication", function() {
    var self = this;
    var doc1 = {_id: 'adoc', foo:'bar'};
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs}, {}, function(err, results) {
        var count = 0;
        var rep = db.replicate.from(self.remote, {continuous: true});
        var change = db.changes({
          onChange: function(change) {
            ++count;
            if (count === 4) {
              ok(true, 'Got all the changes');
              rep.cancel();
              start();
            }
          },
          continuous: true,
        });
        setTimeout(function() {
          remote.put(doc1);
        }, 500);
      });
    });
  });

  asyncTest("Test basic continous push replication", function() {
    var self = this;
    var doc1 = {_id: 'adoc', foo:'bar'};
    initDBPair(this.name, this.remote, function(db, remote) {
      db.bulkDocs({docs: docs}, {}, function(err, results) {
        var count = 0;
        var rep = remote.replicate.from(db, {continuous: true});
        var change = remote.changes({
          onChange: function(change) {
            ++count;
            if (count === 4) {
              ok(true, 'Got all the changes');
              rep.cancel();
              start();
            }
          },
          continuous: true,
        });
        setTimeout(function() {
          db.put(doc1, {});
        }, 50);
      });
    });
  });

  asyncTest("Test cancel pull replication", function() {
    var self = this;
    var doc1 = {_id: 'adoc', foo:'bar'};
    var doc2 = {_id: 'anotherdoc', foo:'baz'};
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs}, {}, function(err, results) {
        var count = 0;
        var replicate = db.replicate.from(self.remote, {continuous: true});
        var change = db.changes({
          continuous: true,
          onChange: function(change) {
            ++count;
            if (count === 4) {
              replicate.cancel();
              remote.put(doc2);
              setTimeout(function() {
                ok(count === 4, 'got no more docs');
                start();
              }, 500);
            }
          },
        });
        setTimeout(function() {
          remote.put(doc1);
        }, 50);
      });
    });
  });

  asyncTest("Replication filter", function() {
    var docs1 = [
      {_id: "0", integer: 0},
      {_id: "1", integer: 1},
      {_id: "2", integer: 2},
      {_id: "3", integer: 3}
    ];

    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs1}, function(err, info) {
        var replicate = db.replicate.from(remote, {
          continuous: true,
          filter: function(doc) { return doc.integer % 2 === 0; }
        });
        setTimeout(function() {
          db.allDocs(function(err, docs) {
            equal(docs.rows.length, 2);
            replicate.cancel();
            start();
          });
        }, 200);
      });
    });
  });

  asyncTest("Attachments replicate", function() {

    var binAttDoc = {
      _id: "bin_doc",
      _attachments:{
        "foo.txt": {
          content_type:"text/plain",
          data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
        }
      }
    };

    var docs1 = [
      binAttDoc,
      {_id: "0", integer: 0},
      {_id: "1", integer: 1},
      {_id: "2", integer: 2},
      {_id: "3", integer: 3}
    ];

    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs1}, function(err, info) {
        var replicate = db.replicate.from(remote, function() {
          db.get('bin_doc', {attachments: true}, function(err, doc) {
            equal(binAttDoc._attachments['foo.txt'].data,
                  doc._attachments['foo.txt'].data);
            start();
          });
        });
      });
    });
  });

});<commit_msg>test all permutations of replication<commit_after>[['idb-1', 'http-1'],
 ['http-1', 'http-2'],
 ['http-1', 'idb-1'],
 ['idb-1', 'idb-2']].map(function(adapters) {

  module('replication: ' + adapters[0] + ':' + adapters[1], {
    setup : function () {
      this.name = generateAdapterUrl(adapters[0]);
      this.remote = generateAdapterUrl(adapters[1]);
    }
  });

  var docs = [
    {_id: "0", integer: 0, string: '0'},
    {_id: "1", integer: 1, string: '1'},
    {_id: "2", integer: 2, string: '2'}
  ];

  asyncTest("Test basic pull replication", function() {
    var self = this;
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs}, {}, function(err, results) {
        db.replicate.from(self.remote, function(err, result) {
          ok(result.ok, 'replication was ok');
          ok(result.docs_written = docs.length, 'correct # docs written');
          start();
        });
      });
    });
  });

  asyncTest("Local DB contains documents", function() {
    var self = this;
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs}, {}, function(err, _) {
        db.bulkDocs({docs: docs}, {}, function(err, _) {
          db.replicate.from(self.remote, function(err, _) {
            db.allDocs(function(err, result) {
              ok(result.rows.length === docs.length, 'correct # docs exist');
              start();
            });
          });
        });
      });
    });
  });

  asyncTest("Test basic push replication", function() {
    var self = this;
    initDBPair(this.name, this.remote, function(db, remote) {
      db.bulkDocs({docs: docs}, {}, function(err, results) {
        db.replicate.to(self.remote, function(err, result) {
          ok(result.ok, 'replication was ok');
          ok(result.docs_written === docs.length, 'correct # docs written');
          start();
        });
      });
    });
  });

  asyncTest("Test basic push replication take 2", function() {
    var self = this;
    initDBPair(this.name, this.remote, function(db, remote) {
      db.bulkDocs({docs: docs}, {}, function(err, _) {
        db.replicate.to(self.remote, function(err, _) {
          remote.allDocs(function(err, result) {
            ok(result.rows.length === docs.length, 'correct # docs written');
            start();
          });
        });
      });
    });
  });

  asyncTest("Test checkpoint", function() {
    var self = this;
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs}, {}, function(err, results) {
        db.replicate.from(self.remote, function(err, result) {
          ok(result.ok, 'replication was ok');
          ok(result.docs_written === docs.length, 'correct # docs written');
          db.replicate.from(self.remote, function(err, result) {
            ok(result.ok, 'replication was ok');
            ok(result.docs_written === 0, 'correct # docs written');
            ok(result.docs_read === 0, 'no docs read');
            start();
          });
        });
      });
    });
  });

  asyncTest("Test checkpoint 2", function() {
    var self = this;
    var doc = {_id: "3", count: 0};
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.put(doc, {}, function(err, results) {
        db.replicate.from(self.remote, function(err, result) {
          ok(result.ok, 'replication was ok');
          doc._rev = results.rev;
          doc.count++;
          remote.put(doc, {}, function(err, results) {
            doc._rev = results.rev;
            doc.count++;
            remote.put(doc, {}, function(err, results) {
              db.replicate.from(self.remote, function(err, result) {
                ok(result.ok, 'replication was ok');
                ok(result.docs_written === 1, 'correct # docs written');
                start();
              });
            });
          });
        });
      });
    });
  });

  asyncTest("Test checkpoint 3 :)", function() {
    var self = this;
    var doc = {_id: "3", count: 0};
    initDBPair(this.name, this.remote, function(db, remote) {
      db.put(doc, {}, function(err, results) {
        Pouch.replicate(db, remote, {}, function(err, result) {
          ok(result.ok, 'replication was ok');
          doc._rev = results.rev;
          doc.count++;
          db.put(doc, {}, function(err, results) {
            doc._rev = results.rev;
            doc.count++;
            db.put(doc, {}, function(err, results) {
              Pouch.replicate(db, remote, {}, function(err, result) {
                ok(result.ok, 'replication was ok');
                ok(result.docs_written === 1, 'correct # docs written');
                start();
              });
            });
          });
        });
      });
    });
  });

  // CouchDB will not generate a conflict here, it uses a deteministic
  // method to generate the revision number, however we cannot copy its
  // method as it depends on erlangs internal data representation
  asyncTest("Test basic conflict", function() {
    var self = this;
    var doc1 = {_id: 'adoc', foo:'bar'};
    var doc2 = {_id: 'adoc', bar:'baz'};
    initDBPair(this.name, this.remote, function(db, remote) {
      db.put(doc1, function(err, localres) {
        remote.put(doc2, function(err, remoteres) {
          db.replicate.to(self.remote, function(err, _) {
            remote.get('adoc', {conflicts: true}, function(err, result) {
              ok(result._conflicts, 'result has a conflict');
              start();
            });
          });
        });
      });
    });
  });

  asyncTest("Test basic continous pull replication", function() {
    var self = this;
    var doc1 = {_id: 'adoc', foo:'bar'};
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs}, {}, function(err, results) {
        var count = 0;
        var rep = db.replicate.from(self.remote, {continuous: true});
        var change = db.changes({
          onChange: function(change) {
            ++count;
            if (count === 4) {
              ok(true, 'Got all the changes');
              rep.cancel();
              start();
            }
          },
          continuous: true,
        });
        setTimeout(function() {
          remote.put(doc1);
        }, 500);
      });
    });
  });

  asyncTest("Test basic continous push replication", function() {
    var self = this;
    var doc1 = {_id: 'adoc', foo:'bar'};
    initDBPair(this.name, this.remote, function(db, remote) {
      db.bulkDocs({docs: docs}, {}, function(err, results) {
        var count = 0;
        var rep = remote.replicate.from(db, {continuous: true});
        var change = remote.changes({
          onChange: function(change) {
            ++count;
            if (count === 4) {
              ok(true, 'Got all the changes');
              rep.cancel();
              start();
            }
          },
          continuous: true,
        });
        setTimeout(function() {
          db.put(doc1, {});
        }, 50);
      });
    });
  });

  asyncTest("Test cancel pull replication", function() {
    var self = this;
    var doc1 = {_id: 'adoc', foo:'bar'};
    var doc2 = {_id: 'anotherdoc', foo:'baz'};
    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs}, {}, function(err, results) {
        var count = 0;
        var replicate = db.replicate.from(self.remote, {continuous: true});
        var change = db.changes({
          continuous: true,
          onChange: function(change) {
            ++count;
            if (count === 4) {
              replicate.cancel();
              remote.put(doc2);
              setTimeout(function() {
                ok(count === 4, 'got no more docs');
                start();
              }, 500);
            }
          },
        });
        setTimeout(function() {
          remote.put(doc1);
        }, 50);
      });
    });
  });

  asyncTest("Replication filter", function() {
    var docs1 = [
      {_id: "0", integer: 0},
      {_id: "1", integer: 1},
      {_id: "2", integer: 2},
      {_id: "3", integer: 3}
    ];

    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs1}, function(err, info) {
        var replicate = db.replicate.from(remote, {
          continuous: true,
          filter: function(doc) { return doc.integer % 2 === 0; }
        });
        setTimeout(function() {
          db.allDocs(function(err, docs) {
            equal(docs.rows.length, 2);
            replicate.cancel();
            start();
          });
        }, 200);
      });
    });
  });

  asyncTest("Attachments replicate", function() {

    var binAttDoc = {
      _id: "bin_doc",
      _attachments:{
        "foo.txt": {
          content_type:"text/plain",
          data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
        }
      }
    };

    var docs1 = [
      binAttDoc,
      {_id: "0", integer: 0},
      {_id: "1", integer: 1},
      {_id: "2", integer: 2},
      {_id: "3", integer: 3}
    ];

    initDBPair(this.name, this.remote, function(db, remote) {
      remote.bulkDocs({docs: docs1}, function(err, info) {
        var replicate = db.replicate.from(remote, function() {
          db.get('bin_doc', {attachments: true}, function(err, doc) {
            equal(binAttDoc._attachments['foo.txt'].data,
                  doc._attachments['foo.txt'].data);
            start();
          });
        });
      });
    });
  });

});<|endoftext|>


<commit_before>angular.module('Directory.dashboard.controllers', ['Directory.loader', 'Directory.user', 'Directory.dashboard.models'])
.controller('DashboardCtrl', [ '$scope', 'Item', 'Loader', 'Me', '$timeout', 'Stats', function ItemsCtrl($scope, Item, Loader, Me, $timeout, Stats) {
  Me.authenticated(function (data) {
  });

  if (window.location.pathname == "/"){
    mixpanel.track(
      "Home Page"
    );
  };

  //tally total minutes in archive for display on homepage
  var minutes = function () {
    Stats.query().then(function (stats) {
      $scope.stats = Math.floor((stats.publicAudio + stats.privateAudio)/60);
      $scope.mytimeout = $timeout(minutes, 3000);
    });
  };
  minutes();

  //cancel minutes timeout on route change
 $scope.$on('$locationChangeStart', function() {
    $timeout.cancel($scope.mytimeout);
 });

  $scope.subscribe = function () {
    window.location = "/users/sign_up?plan_id=community";
    mixpanel.track(
      "Clicked Get Started"
    );
  }
}])<commit_msg>zap timeout/refresh of home page total minutes since server caches it for an hour (cf be39730db11d6e8e977c5783176c768012dd61e8)<commit_after>angular.module('Directory.dashboard.controllers', ['Directory.loader', 'Directory.user', 'Directory.dashboard.models'])
.controller('DashboardCtrl', [ '$scope', 'Item', 'Loader', 'Me', 'Stats', function ItemsCtrl($scope, Item, Loader, Me, Stats) {
  Me.authenticated(function (data) {
  });

  if (window.location.pathname == "/"){
    mixpanel.track(
      "Home Page"
    );
  };

  //tally total minutes in archive for display on homepage
  var minutes = function () {
    Stats.query().then(function (stats) {
      $scope.stats = Math.floor((stats.publicAudio + stats.privateAudio)/60);
    });
  };
  minutes();

  $scope.subscribe = function () {
    window.location = "/users/sign_up?plan_id=community";
    mixpanel.track(
      "Clicked Get Started"
    );
  }
}])
<|endoftext|>


<commit_before>/*global $, jQuery, L*/

var map;
var pois = [];
var markerlayer;
var waylayer;
var myLocation = null;
var OSM_URL = "https://z.overpass-api.de/api/interpreter?data=%5Bout:json%5D;";
var berlin = new L.LatLng(52.5213616409873, 13.4101340342265);
var icon_user;
var mapDragged = false;
var way;
var myLocationMarker = null;
var myLocationCircle = null;
var poiData = null;

function initMap(loc, zoom) {

    markerlayer = L.layerGroup();
    waylayer = L.layerGroup();

	icon_user = L.icon({
	    iconUrl: 'app/images/user.svg',
	    iconRetinaUrl: 'app/images/user.png',
	    iconSize: [40, 40],
	    iconAnchor: [20, 20],
	    popupAnchor: [10, -20],
	 //   shadowUrl: 'my-icon-shadow.png',
	 //   shadowRetinaUrl: 'my-icon-shadow@2x.png',
	    shadowSize: [68, 95],
	    shadowAnchor: [22, 94]
	});

	map = new L.Map('map', {
		center: loc,
		zoom: zoom
	});

	// create a CloudMade tile layer
	L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

	map.addLayer(markerlayer);
  map.addLayer(waylayer);
}

function loadPOIs(manualRefresh) {
  var i;
	console.log("loadPOIs called");
	var tags = getTag();
	if ( tags === '') return;

	var OSM_PARAMS = "";
	var tag = tags.split(";");

	//get map bounds from current window
	var southwest = map.getBounds().getSouthWest();
	var northeast = map.getBounds().getNorthEast();

	//build URL
	//search around only if map not dragged, otherwise search in map window
	//but only with appropriate zoom level

	if(!manualRefresh && !mapDragged)
	{
		//search around user position
		for ( i in tag) {
			OSM_PARAMS += "way["+tag[i]+ "](around:2000," +myLocation.lat+  ","  +myLocation.lng+ ");>;" +
			"node["+tag[i]+ "](around:2000," +myLocation.lat+  ","  +myLocation.lng+ ");";
		}
		OSM_PARAMS = "(" + OSM_PARAMS + ");out;";

	} else {
		//don't search in big areas
		if(map.getZoom() < 13) {
			alert("Please zoom in");
			return;
		}
		//search in current map window
		for ( i in tag) {
			OSM_PARAMS += "way["+tag[i]+ "](" +southwest.lat+  ","  +southwest.lng+  ","  +
			northeast.lat+ "," +northeast.lng +");>;" + "node["+tag[i]+ "](" +southwest.lat+  ","  +southwest.lng+  ","  +northeast.lat+ "," +northeast.lng +");";
		}
		OSM_PARAMS = "(" + OSM_PARAMS + ");out;";

	}

	console.log(OSM_PARAMS);
	var URL = OSM_URL + encodeURIComponent(OSM_PARAMS);

	//remove old markers
	markerlayer.clearLayers();
  waylayer.clearLayers();

	//show loading indicator
  $("#loading").css("visibility", "visible");

	//load POIs from OSM
	var markers = [];
        var ways = [];
	$.getJSON(URL)
	.done( function(data) {

        //remove loading indicator
        $("#loading").css("visibility", "hidden");

		//build markers
		pois = data.elements;

		if(pois.length === 0) {
			//$('#modal_no_pois').modal();

			// show a no POI alert - ToDo: remove alert, show short notice on site
			alert("Sorry, no POI in this area. Zoom out or pan the map.");

			return;
		}
    //ga('send', 'event', 'poisearch', 'got_results', pois.length);
		$.each(pois, function(index, poi) {
                        var popuptext = "";
                        if(poi.type == 'node' && typeof poi.tags != 'undefined'){
                                lat = poi.lat;
                                lon = poi.lon;
                                popuptext = getPopupText(poi);
                                markers.push(new L.Marker([lat, lon]).bindPopup(getTagName() + "
" + popuptext));
                        }
                        if(poi.type == 'way' && typeof poi.tags != 'undefined'){
                                way = new L.polygon({color: 'blue'});
                                $.each(poi.nodes, function(index, poinode) {
                                        $.each(pois, function(index, waynode) {
                                                if (waynode.id == poinode){
                                                        //console.log(waynode);
                                                        way.addLatLng(new L.latLng(waynode.lat, waynode.lon));
                                                        //console.log(way);
                                                }
                                        });
                                });
                                popuptext = getPopupText(poi);

                                //add polygon to map
                                ways.push(way.bindPopup(getTagName() + "
" + popuptext));

                                //add aditional marker at polygons bbox center
                                markers.push(new L.Marker(way.getBounds().getCenter()).bindPopup(getTagName() + "
" + popuptext));
                        }
	  	});

	  	//add markers to map
		newmarkers = L.layerGroup(markers);
		markerlayer.addLayer(newmarkers);

                //add polygons to map
                newways = L.layerGroup(ways);
		waylayer.addLayer(newways);

		//if no geolocation, finish here
		if(myLocation === null){
			return;
		}

		//find nearest marker
		var distance = Number.MAX_VALUE;
		var nearest = null;
    var temp;
		$.each(markers, function(i, marker) {
			temp = marker.getLatLng();
			if(temp.distanceTo(myLocation) < distance) {
				distance = temp.distanceTo(myLocation);
				nearest = temp;
			}
		});

                //find nearest polygon
		$.each(ways, function(i, polygon) {
			temp = polygon.getBounds().getCenter();
			if(temp.distanceTo(myLocation) < distance) {
				distance =temp.distanceTo(myLocation);
				nearest = temp;
			}
		});

		//zoom map
		if(!manualRefresh && !mapDragged) {
			map.fitBounds(new L.LatLngBounds([myLocation, nearest], {padding:[10,10]}));
		}

	})
	.fail( function(jqxhr, textStatus, error ) {
		var err = textStatus + ', ' + error;
		console.log( "Request Failed: " + err);
	});
}

function onLocationFound(e) {
    var radius = e.accuracy / 2;

    //opt: remove old marker
    if(myLocationMarker !== null) {
        map.removeLayer(myLocationMarker);
        map.removeLayer(myLocationCircle);
    }

    //draw stuff
    myLocationMarker = L.marker(e.latlng, {icon: icon_user});
    myLocationMarker.addTo(map).bindPopup("You are somewhere here").openPopup();
    myLocationCircle = L.circle(e.latlng, radius);
    myLocationCircle.addTo(map);

    //save current location
    myLocation = e.latlng;

    //update browser URL
    updateHashURL();

    //ga('send', 'event', 'geolocation', 'found');
}

function onLocationError(e) {
    //ga('send', 'event', 'geolocation', 'LocationError');
    alert(e.message);
}

function onMapDragged(){
	mapDragged = true;
    updateHashURL();
}

function onMapZoomed() {
    updateHashURL();
}

function getTagName(){
	var tagName = "";
  tagName = poiData[$('#mydropdown').val()]["lang-en"];
  return tagName;
}

function getTag() {
	var tag = "";
	var selection = $('#mydropdown').val();

	// OSM-Tag preset for mobile
  //search tag in object
  console.log(poiData);
  var tagdata = poiData[selection].osm;
  if(tagdata)
  tag = tagdata;

  //ga('send', 'event', 'poisearch', 'search_for', tag);
  return tag;
}

function reloadCurrentMapWindow() {
	//get current zoom level and check
    //get current map coordinates

	//get current tag
	//go
}

//init function
$(function() {

    //disable cache for ajax
    $.ajaxSetup({ cache: false });

    //detect if url parameter existing
    var hash = window.location.hash;
    var type = null;
    var url_location = null;

    var startloc = berlin;
    var startzoom = 13;

    if (hash.length > 0) {
        hash = hash.replace('#', '');
        var params = hash.split('&');

        $.each(params, function(i, param) {
            var setting = param.split('=');
            switch(setting[0])
            {
                case "map" :
                  url_location = setting[1];
                  break;
                case "type" :
                  type = setting[1];
                  break;
            }

        });

    }

    if(url_location) {
        //new location and zoom to
        var loc_array = url_location.split('/');
        startloc = new L.LatLng(loc_array[1], loc_array[2]);
        startzoom = loc_array[0];

        // set dragged -> search in current map
        mapDragged = true;
    }

    initMap(startloc, startzoom);

	// setup geolocation
	map.on('locationfound', onLocationFound);
	map.on('locationerror', onLocationError);
	map.on('dragend', onMapDragged);
    map.on('zoomend', onMapZoomed);

    //start location detection if no location preset
    if(!url_location) {
        locateMe();
    }

    if(type) {
        //search for POI
    }

    //load dropdown OSM data
    loadPOIdataFromFile();

	  //setup dropdown listener
		$('#mydropdown').change(function()
			{
			loadPOIs();
		});

	//set onClick for refresh button
	$('#reload-button').click(function(){loadPOIs(true);});
    $('#locateMe-button').click(function(){locateMe();});
    $('#info-button').click(function(){showInfo();});
    $('#editOSM-button').click(function(){editOSM();});

    $('#info_overlay').click(function(){
      hideInfo();
    });

});

function locateMe() {
	map.locate({setView: true, maxZoom: 16});
    mapDragged = false;
}

function showInfo() {
  $("#info_overlay").css("visibility", "visible");
}

function hideInfo() {
  $("#info_overlay").css("visibility", "hidden");
}

function editOSM() {
        var center = map.getCenter();
        var z = map.getZoom();
        window.open('https://www.openstreetmap.org/edit?' + 'zoom=' + z +
            '&editor=id' + '&lat=' + center.lat + '&lon=' + center.lng);
    }

function getPopupText(poi) {
  var popuptext = "";
        if(typeof poi.tags.name != 'undefined')
                popuptext += poi.tags.name + "
";
        if(typeof poi.tags.operator != 'undefined')
                popuptext += poi.tags.operator + "
";
        if(typeof poi.tags.collection_times != 'undefined')
                popuptext += poi.tags.collection_times + "
";
        if(typeof poi.tags.opening_hours != 'undefined')
                popuptext += poi.tags.opening_hours + "
";
        if(typeof poi.tags.phone != 'undefined')
                popuptext += '' + poi.tags.phone + '
';
        if(typeof poi.tags.website != 'undefined')
                popuptext += '' + poi.tags.website + '
';

        if( poi.tags['recycling:clothes'] == 'yes')
                popuptext += "Clothes" + "
";
        if( poi.tags['recycling:paper'] == 'yes')
                popuptext += "Paper" + "
";
        if( poi.tags['recycling:glass'] == 'yes')
                popuptext += "Glass" + "
";
        if( poi.tags['recycling:garden_waste'] == 'yes')
                popuptext += "Garden waste" + "
";

        return popuptext;
}

function updateHashURL() {

    var urlhash_location = "map=" + map.getZoom() + '/' + map.getCenter().lat.toFixed(5) + '/' + map.getCenter().lng.toFixed(5);
    history.replaceState(null, null, window.location.origin + "/#" + urlhash_location);
}

function loadPOIdataFromFile() {

    $.getJSON("content.json", function(data){
            console.log(data);

            //save to global var
            poiData = data;
            fillMobileSelectionBox(poiData);
    });
}

function fillMobileSelectionBox(data) {

            //language detection
            var default_lang = "lang-en";
            var preferred_lang = "lang-" + window.navigator.language.substring(0, 2);

            //fill list
            $.each(data, function(key, poi) {
                console.log(poi["lang-en"]);
                var text = poi.hasOwnProperty(preferred_lang) ? poi[preferred_lang] : poi[default_lang];
                var val = key;

                //add entry to drop down list
                $('#mydropdown').append( new Option(text,val) );
                //    $('#mySelect').append( new Option(text,val,defaultSelected,nowSelected) );

            });

  // Now that the selection box is filled, adjust font-size of the whole
  // inputarea content so that it fits one line
  $('#inputarea').textFontAdjust($('#inputarea-content'));
}

// A small jQuery plugin for scaling down the font-size of an element until the
// parent container isn't higher than allowed
$.fn.textFontAdjust = function(subElem, options) {
  var config = $.extend({
    maxFontPerc : 100,
    minFontPerc : 25,
    additionalWidthToDecrease: 0
  },options);
  var fontSizePerc = config.maxFontPerc,
      textElem = $(subElem),
      maxHeight = '40',
      maxWidth = $(this).width()-config.additionalWidthToDecrease;
  var textHeight, textWidth;
  do {
    textElem.css('font-size', fontSizePerc + '%');
    fontSizePerc = fontSizePerc - 1;
  } while ((textElem.height() > maxHeight || textElem.width() > maxWidth) && fontSizePerc > config.minFontPerc);
    return this;
};
<commit_msg>don't zoom into Berlin, fix #39<commit_after>/*global $, jQuery, L*/

var map;
var pois = [];
var markerlayer;
var waylayer;
var myLocation = null;
var OSM_URL = "https://z.overpass-api.de/api/interpreter?data=%5Bout:json%5D;";
var berlin = new L.LatLng(52.5213616409873, 13.4101340342265);
var icon_user;
var mapDragged = false;
var way;
var myLocationMarker = null;
var myLocationCircle = null;
var poiData = null;

function initMap(loc, zoom) {

    markerlayer = L.layerGroup();
    waylayer = L.layerGroup();

	icon_user = L.icon({
	    iconUrl: 'app/images/user.svg',
	    iconRetinaUrl: 'app/images/user.png',
	    iconSize: [40, 40],
	    iconAnchor: [20, 20],
	    popupAnchor: [10, -20],
	 //   shadowUrl: 'my-icon-shadow.png',
	 //   shadowRetinaUrl: 'my-icon-shadow@2x.png',
	    shadowSize: [68, 95],
	    shadowAnchor: [22, 94]
	});

	map = new L.Map('map', {
		center: loc,
		zoom: zoom
	});

	// create a CloudMade tile layer
	L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

	map.addLayer(markerlayer);
  map.addLayer(waylayer);
}

function loadPOIs(manualRefresh) {
  var i;
	console.log("loadPOIs called");
	var tags = getTag();
	if ( tags === '') return;

	var OSM_PARAMS = "";
	var tag = tags.split(";");

	//get map bounds from current window
	var southwest = map.getBounds().getSouthWest();
	var northeast = map.getBounds().getNorthEast();

	//build URL
	//search around only if map not dragged, otherwise search in map window
	//but only with appropriate zoom level

	if(!manualRefresh && !mapDragged)
	{
		//search around user position
		for ( i in tag) {
			OSM_PARAMS += "way["+tag[i]+ "](around:2000," +myLocation.lat+  ","  +myLocation.lng+ ");>;" +
			"node["+tag[i]+ "](around:2000," +myLocation.lat+  ","  +myLocation.lng+ ");";
		}
		OSM_PARAMS = "(" + OSM_PARAMS + ");out;";

	} else {
		//don't search in big areas
		if(map.getZoom() < 13) {
			alert("Please zoom in");
			return;
		}
		//search in current map window
		for ( i in tag) {
			OSM_PARAMS += "way["+tag[i]+ "](" +southwest.lat+  ","  +southwest.lng+  ","  +
			northeast.lat+ "," +northeast.lng +");>;" + "node["+tag[i]+ "](" +southwest.lat+  ","  +southwest.lng+  ","  +northeast.lat+ "," +northeast.lng +");";
		}
		OSM_PARAMS = "(" + OSM_PARAMS + ");out;";

	}

	console.log(OSM_PARAMS);
	var URL = OSM_URL + encodeURIComponent(OSM_PARAMS);

	//remove old markers
	markerlayer.clearLayers();
  waylayer.clearLayers();

	//show loading indicator
  $("#loading").css("visibility", "visible");

	//load POIs from OSM
	var markers = [];
        var ways = [];
	$.getJSON(URL)
	.done( function(data) {

        //remove loading indicator
        $("#loading").css("visibility", "hidden");

		//build markers
		pois = data.elements;

		if(pois.length === 0) {
			//$('#modal_no_pois').modal();

			// show a no POI alert - ToDo: remove alert, show short notice on site
			alert("Sorry, no POI in this area. Zoom out or pan the map.");

			return;
		}
    //ga('send', 'event', 'poisearch', 'got_results', pois.length);
		$.each(pois, function(index, poi) {
                        var popuptext = "";
                        if(poi.type == 'node' && typeof poi.tags != 'undefined'){
                                lat = poi.lat;
                                lon = poi.lon;
                                popuptext = getPopupText(poi);
                                markers.push(new L.Marker([lat, lon]).bindPopup(getTagName() + "
" + popuptext));
                        }
                        if(poi.type == 'way' && typeof poi.tags != 'undefined'){
                                way = new L.polygon({color: 'blue'});
                                $.each(poi.nodes, function(index, poinode) {
                                        $.each(pois, function(index, waynode) {
                                                if (waynode.id == poinode){
                                                        //console.log(waynode);
                                                        way.addLatLng(new L.latLng(waynode.lat, waynode.lon));
                                                        //console.log(way);
                                                }
                                        });
                                });
                                popuptext = getPopupText(poi);

                                //add polygon to map
                                ways.push(way.bindPopup(getTagName() + "
" + popuptext));

                                //add aditional marker at polygons bbox center
                                markers.push(new L.Marker(way.getBounds().getCenter()).bindPopup(getTagName() + "
" + popuptext));
                        }
	  	});

	  	//add markers to map
		newmarkers = L.layerGroup(markers);
		markerlayer.addLayer(newmarkers);

                //add polygons to map
                newways = L.layerGroup(ways);
		waylayer.addLayer(newways);

		//if no geolocation, finish here
		if(myLocation === null){
			return;
		}

		//find nearest marker
		var distance = Number.MAX_VALUE;
		var nearest = null;
    var temp;
		$.each(markers, function(i, marker) {
			temp = marker.getLatLng();
			if(temp.distanceTo(myLocation) < distance) {
				distance = temp.distanceTo(myLocation);
				nearest = temp;
			}
		});

                //find nearest polygon
		$.each(ways, function(i, polygon) {
			temp = polygon.getBounds().getCenter();
			if(temp.distanceTo(myLocation) < distance) {
				distance =temp.distanceTo(myLocation);
				nearest = temp;
			}
		});

		//zoom map
		if(!manualRefresh && !mapDragged) {
			map.fitBounds(new L.LatLngBounds([myLocation, nearest], {padding:[10,10]}));
		}

	})
	.fail( function(jqxhr, textStatus, error ) {
		var err = textStatus + ', ' + error;
		console.log( "Request Failed: " + err);
	});
}

function onLocationFound(e) {
    var radius = e.accuracy / 2;

    //opt: remove old marker
    if(myLocationMarker !== null) {
        map.removeLayer(myLocationMarker);
        map.removeLayer(myLocationCircle);
    }

    //draw stuff
    myLocationMarker = L.marker(e.latlng, {icon: icon_user});
    myLocationMarker.addTo(map).bindPopup("You are somewhere here").openPopup();
    myLocationCircle = L.circle(e.latlng, radius);
    myLocationCircle.addTo(map);

    //save current location
    myLocation = e.latlng;

    //update browser URL
    updateHashURL();

    //ga('send', 'event', 'geolocation', 'found');
}

function onLocationError(e) {
    //ga('send', 'event', 'geolocation', 'LocationError');
    alert(e.message);
}

function onMapDragged(){
	mapDragged = true;
    updateHashURL();
}

function onMapZoomed() {
    updateHashURL();
}

function getTagName(){
	var tagName = "";
  tagName = poiData[$('#mydropdown').val()]["lang-en"];
  return tagName;
}

function getTag() {
	var tag = "";
	var selection = $('#mydropdown').val();

	// OSM-Tag preset for mobile
  //search tag in object
  console.log(poiData);
  var tagdata = poiData[selection].osm;
  if(tagdata)
  tag = tagdata;

  //ga('send', 'event', 'poisearch', 'search_for', tag);
  return tag;
}

function reloadCurrentMapWindow() {
	//get current zoom level and check
    //get current map coordinates

	//get current tag
	//go
}

//init function
$(function() {

    //disable cache for ajax
    $.ajaxSetup({ cache: false });

    //detect if url parameter existing
    var hash = window.location.hash;
    var type = null;
    var url_location = null;

    var startloc = berlin;
    var startzoom = 3;

    if (hash.length > 0) {
        hash = hash.replace('#', '');
        var params = hash.split('&');

        $.each(params, function(i, param) {
            var setting = param.split('=');
            switch(setting[0])
            {
                case "map" :
                  url_location = setting[1];
                  break;
                case "type" :
                  type = setting[1];
                  break;
            }

        });

    }

    if(url_location) {
        //new location and zoom to
        var loc_array = url_location.split('/');
        startloc = new L.LatLng(loc_array[1], loc_array[2]);
        startzoom = loc_array[0];

        // set dragged -> search in current map
        mapDragged = true;
    }

    initMap(startloc, startzoom);

	// setup geolocation
	map.on('locationfound', onLocationFound);
	map.on('locationerror', onLocationError);
	map.on('dragend', onMapDragged);
    map.on('zoomend', onMapZoomed);

    //start location detection if no location preset
    if(!url_location) {
        locateMe();
    }

    if(type) {
        //search for POI
    }

    //load dropdown OSM data
    loadPOIdataFromFile();

	  //setup dropdown listener
		$('#mydropdown').change(function()
			{
			loadPOIs();
		});

	//set onClick for refresh button
	$('#reload-button').click(function(){loadPOIs(true);});
    $('#locateMe-button').click(function(){locateMe();});
    $('#info-button').click(function(){showInfo();});
    $('#editOSM-button').click(function(){editOSM();});

    $('#info_overlay').click(function(){
      hideInfo();
    });

});

function locateMe() {
	map.locate({setView: true, maxZoom: 16});
    mapDragged = false;
}

function showInfo() {
  $("#info_overlay").css("visibility", "visible");
}

function hideInfo() {
  $("#info_overlay").css("visibility", "hidden");
}

function editOSM() {
        var center = map.getCenter();
        var z = map.getZoom();
        window.open('https://www.openstreetmap.org/edit?' + 'zoom=' + z +
            '&editor=id' + '&lat=' + center.lat + '&lon=' + center.lng);
    }

function getPopupText(poi) {
  var popuptext = "";
        if(typeof poi.tags.name != 'undefined')
                popuptext += poi.tags.name + "
";
        if(typeof poi.tags.operator != 'undefined')
                popuptext += poi.tags.operator + "
";
        if(typeof poi.tags.collection_times != 'undefined')
                popuptext += poi.tags.collection_times + "
";
        if(typeof poi.tags.opening_hours != 'undefined')
                popuptext += poi.tags.opening_hours + "
";
        if(typeof poi.tags.phone != 'undefined')
                popuptext += '' + poi.tags.phone + '
';
        if(typeof poi.tags.website != 'undefined')
                popuptext += '' + poi.tags.website + '
';

        if( poi.tags['recycling:clothes'] == 'yes')
                popuptext += "Clothes" + "
";
        if( poi.tags['recycling:paper'] == 'yes')
                popuptext += "Paper" + "
";
        if( poi.tags['recycling:glass'] == 'yes')
                popuptext += "Glass" + "
";
        if( poi.tags['recycling:garden_waste'] == 'yes')
                popuptext += "Garden waste" + "
";

        return popuptext;
}

function updateHashURL() {

    var urlhash_location = "map=" + map.getZoom() + '/' + map.getCenter().lat.toFixed(5) + '/' + map.getCenter().lng.toFixed(5);
    history.replaceState(null, null, window.location.origin + "/#" + urlhash_location);
}

function loadPOIdataFromFile() {

    $.getJSON("content.json", function(data){
            console.log(data);

            //save to global var
            poiData = data;
            fillMobileSelectionBox(poiData);
    });
}

function fillMobileSelectionBox(data) {

            //language detection
            var default_lang = "lang-en";
            var preferred_lang = "lang-" + window.navigator.language.substring(0, 2);

            //fill list
            $.each(data, function(key, poi) {
                console.log(poi["lang-en"]);
                var text = poi.hasOwnProperty(preferred_lang) ? poi[preferred_lang] : poi[default_lang];
                var val = key;

                //add entry to drop down list
                $('#mydropdown').append( new Option(text,val) );
                //    $('#mySelect').append( new Option(text,val,defaultSelected,nowSelected) );

            });

  // Now that the selection box is filled, adjust font-size of the whole
  // inputarea content so that it fits one line
  $('#inputarea').textFontAdjust($('#inputarea-content'));
}

// A small jQuery plugin for scaling down the font-size of an element until the
// parent container isn't higher than allowed
$.fn.textFontAdjust = function(subElem, options) {
  var config = $.extend({
    maxFontPerc : 100,
    minFontPerc : 25,
    additionalWidthToDecrease: 0
  },options);
  var fontSizePerc = config.maxFontPerc,
      textElem = $(subElem),
      maxHeight = '40',
      maxWidth = $(this).width()-config.additionalWidthToDecrease;
  var textHeight, textWidth;
  do {
    textElem.css('font-size', fontSizePerc + '%');
    fontSizePerc = fontSizePerc - 1;
  } while ((textElem.height() > maxHeight || textElem.width() > maxWidth) && fontSizePerc > config.minFontPerc);
    return this;
};
<|endoftext|>