<commit_before>function addIngredient(e) {
  var ingredientTable = $('#ingredientTable tbody'),
      newRow   = ingredientTable.find('tr:first').clone(true),
      newRowId = ingredientTable.find('tr').length,
      newId   = `recipe_recipe_ingredients_attributes_${newRowId}_`,
      newName = `recipe[recipe_ingredients_attributes][${newRowId}]`;

  // These are the attributes that need to be edited when cloning a table row.
  // The index values need updating to a new, unique, value -
  //   "recipe[recipe_ingredients_attributes][0][ingredient_id]"
  //   "recipe_recipe_ingredients_attributes_0_ingredient_id"
  // and existing values need to be cleared out -
  //   data-recipe-ingredient-id="16"
  //   selected="selected"
  //   value='4'

  newRow.attr('id', 'recipe-ingredient-id-');

  var selectElems = newRow.find('select');
  selectElems.eq(0).attr('name', newName + '[ingredient_id]');
  selectElems.eq(0).attr('id',   newId   + 'ingredient_id'  );

  var labelElems = newRow.find('label'),
      inputElems = newRow.find('input');
  labelElems.eq(0).attr('for',   newId   + 'quantity'  );
  inputElems.eq(0).attr('name',  newName + '[quantity]');
  inputElems.eq(0).attr('id',    newId   + 'quantity'  );
  inputElems.eq(0).attr('value', '' );

  labelElems.eq(1).attr ('for',  newId   + 'unit_id'  );
  selectElems.eq(1).attr('name', newName + '[unit_id]');
  selectElems.eq(1).attr('id', newId + 'unit_id' );

  newRow.find('a.js-deleteIngredient').eq(0).attr('data-recipe-ingredient-id', '');
  newRow.find('a.js-deleteIngredient').eq(0).click(deleteIngredient);

  selectElems.find('option').each(function(i, e) {
    e.removeAttribute('selected');
  });

  ingredientTable.prepend(newRow);
  e.preventDefault();
}

function addStep(e) {

  var stepTable  = $('#stepTable tbody'),
      clickedRow = $(e.target).parent().parent().parent(),
      newRow     = stepTable.find('tr:first').clone(true),
      newRowId   = stepTable.find('tr').length,
      newId      = `recipe_recipe_steps_attributes_${newRowId}_`,
      newName    = `recipe[recipe_steps_attributes][${newRowId}]`,
      stepNumber = parseInt(clickedRow.find('input[name*="step_number"]').val());

  // These are the attributes that need to be edited when cloning a table row.
  // The index and name values need updating:
  //

  //   "recipe[recipe_steps_attributes][0][step_id]"
  //   "recipe_recipe_steps_attributes_0_step_id"
  // and existing values need to be cleared out:
  //   data-recipe-step-id="16"
  //   $('textarea#...').text()
  //   value='4'

  newRow.removeAttr('id');

  var inputElems = newRow.find('input'),
      labelElems = newRow.find('label');
  labelElems.eq(0).attr('for',   newId   + 'step_number'  );
  inputElems.eq(0).attr('name',  newName + '[step_number]');
  inputElems.eq(0).attr('id',    newId   + 'step_number'  );
  inputElems.eq(0).attr('value', stepNumber);

  var textareaElems = newRow.find('textarea');
  labelElems.eq(1).attr   ('for',  newId   + 'step_number'  );
  textareaElems.eq(0).attr('name', newName + '[description]');
  textareaElems.eq(0).attr('id',   newId   + 'description'  );
  textareaElems.text('');

  newRow.find('a.js-deleteStep').eq(0).attr('data-recipe-step-id', '');
  newRow.find('a.js-deleteStep').eq(0).click(deleteStep);

  newRow.insertAfter(clickedRow);

  // Renumber only visible rows.
  clickedRow.nextAll('tr').each( function() {
    if ($(this).is(':visible')) {
      var stepNumberTd   = $(this).find('td:first'),
          stepNumberElem = stepNumberTd.find('input');
      stepNumber += 1;
      stepNumberElem.val(stepNumber);
      stepNumberTd.find('label').text('Step ' + stepNumber);
    }
  });
  e.preventDefault();
}

//  Delete a recipe ingredient from the recipe.
function deleteIngredient(e) {
  if (confirm('Are you sure?') == true) {
    $(e.target).parent().parent().parent().hide();  // Hide the entire recipe step
.
    var recipeIngredientId = $(e.target).parent().data('recipeIngredientId');
    $("tr#recipe-ingredient-id-" + recipeIngredientId + " input:checkbox.js-destroyIngredient").prop('checked', true);
  }
  e.preventDefault();
}

//  Delete an entire recipe.
function deleteRecipe(e) {
  if (confirm('Are you sure?') == true) {
    var recipeId = e.target.parentElement.getAttribute('data-recipe-id');
    $.ajax({
      type: "DELETE",
      url: `/recipes/${recipeId}`,
    })
    .fail(function(jqXHR, textStatus, error) {
      console.log('ERROR: ' + error);
    });
  }
  e.preventDefault();
}

//  Delete a recipe step from the recipe.
function deleteStep(e) {
  if (confirm('Are you sure?') == true) {
    var clickedRow = $(e.target).parent().parent().parent(),
        stepNumber = parseInt(clickedRow.find('input[name*="step_number"]').val());

    // Renumber the subsequent visible rows.
    clickedRow.nextAll('tr').each( function() {
      if ($(this).is(':visible')) {
        var stepNumberTd   = $(this).find('td:first'),
            stepNumberElem = stepNumberTd.find('input');
        stepNumberElem.val(stepNumber);
        stepNumberTd.find('label').text('Step ' + stepNumber);
        stepNumber += 1;
      }
    });

    // Eliminate the deleted row.
    if (clickedRow.attr('id')) {
      var recipeStepId = $(e.target).parent().data('recipeStepId');
      clickedRow.hide();  // Hide the recipe step row and
      $("tr#recipe-step-id-" + recipeStepId + " input:checkbox.js-destroyStep").prop('checked', true);  // mark for destruction.
    } else {
      clickedRow.remove(); // A new row doesn't have an id. Just delete it.
    }

  }
  e.preventDefault();
}

// Navigate to another recipe ('next', 'prev', 'current').
function navigateRecipe(e, direction) {
  requestRecipe(direction);
  e.preventDefault();
}

var recipeTemplate = nil;

// Get a recipe from the server.
function requestRecipe(direction) {
  $.get('/recipes/0.json', {direction: direction}, function(data) {

    var authorName = data.data.relationships.author.data['email'],
        favorite   = '',
        photoPath  = data.data.attributes['photo-path'];

    var recipe = {};
    recipe['recipe_id']   = data.data['id'];
    recipe['author_id']   = data.data.relationships.author.data['id'];
    recipe['name']        = data.data.attributes['name'];
    recipe['description'] = data.data.attributes['description'];
    recipe['total_time']  = data.data.attributes['total-time'] + ' minutes';

    var thisUserId = $('body').data('userid');
    var userRecipeFavorites = data.data.relationships['user-recipe-favorites']['data'];
    for (var i = 0; i < userRecipeFavorites.length; ++i) {
      if (userRecipeFavorites[i]['user-id'] == thisUserId) {
        favorite = 'checked';
        break;
      }
    }

    recipe['image'] = `<img src='/assets/${photoPath}' height='200' width='360' title="${recipe['name']}} photo">`;
    recipe['favorite'] = `<input type='checkbox' class='favorite' ${favorite}>Favorite`;
    recipe['submitter'] = `Submitted by <a href='/users/${recipe['author_id']}/recipes'>${authorName}}</a>`;
    recipe['ingredientsHeader'] = `<a class="js-ingredients" data-recipe-id=${recipe['recipe_id']} data-show-detail="1" href="#">Ingredients</a>`;
    recipe['stepsHeader'] = `<a class="js-steps" data-recipe-id=${recipe['recipe_id']} data-show-detail="1" href="#">Steps</a>`;
    recipe['reviewsHeader'] = `<a class="js-reviews" data-recipe-id=${recipe['recipe_id']} data-show-detail="1" href="#">Reviews</a>`;

    // Display data via Handlebars template.
    if (!recipeTemplate) {
      recipeTemplate = Handlebars.compile(document.getElementById("recipe-template").innerHTML);
    }
    $('.recipe-detail').html(recipeTemplate(recipe));

    if (history.pushState) {
      if (window.location.pathname != `/recipes/${recipe['recipe_id']}`) {
        var newurl = window.location.protocol + "//" + window.location.host + `/recipes/${recipe['recipe_id']}`;
        window.history.pushState({path:newurl}, '', newurl);
      }
    }

    $('.js-deleteRecipe').eq(0).click(function(e) { deleteRecipe(e); });
    $('.js-ingredients').eq(0).click(function(e)  { showIngredients(e); });
    $('.js-steps').eq(0).click(function(e) { showSteps(e); });
    $('.js-reviews').eq(0).click(function(e) { showReviews(e); });
  })
  .fail(function(jqXHR, textStatus, error) {
    console.log('ERROR: ' + error);
  });
}

// Display recipe's ingredient list.
function showIngredients(e) {
  var showDetail = e.target.getAttribute('data-show-detail');
  if (showDetail == 0) {   // hide detail.
    $('#ingredients').html('');
  } else {                  // show detail.
    var recipeId = e.target.getAttribute('data-recipe-id');
    $.get(`/recipes/${recipeId}/recipe_ingredients.json`, function(data) {
      // Gather up reference data.
      var ingredients = {}, units = {};
      if (data.included) {
        data.included.forEach(function(relation) {
          if (relation.type == 'ingredients') {
            ingredients[relation['id']] = relation.attributes['name'];
          } else if (relation.type == 'units') {
            units[relation['id']] = relation.attributes['name'];
          }
        });
      }

      // Create data array for display.
      var recipeIngredients = [];
      data.data.forEach(function(recipeIngredient) {
        var ingredientName = ingredients[recipeIngredient.relationships.ingredient.data['id']],
            quantity       = recipeIngredient.attributes['quantity'],
            unitsName      = recipeIngredient.attributes['unit-id'] == null ? '' : units[recipeIngredient.attributes['unit-id']];
        recipeIngredients.push({ingredient: ingredientName, quantity: quantity, unit: unitsName});
      });

      // Show a message if there are no ingredients.
      if (recipeIngredients.length == 0) {
        recipeIngredients.push({ingredient: 'No ingredients yet.', quantity: '', unit: ''});
      }

      // Display data via Handlebars template.
      var template = Handlebars.compile(document.getElementById("ingredients-template").innerHTML);
      $('#ingredients').html(template(recipeIngredients));
    })
    .fail(function(jqXHR, textStatus, error) {
      console.log('ERROR: ' + error);
    });
  }
  e.target.setAttribute('data-show-detail', showDetail == 0 ? 1 : 0);  // flip the showDetail flag.
  e.preventDefault();
}

// Display a recipe.
function showRecipe(e) {
  var recipeId = e.target.parentElement.getAttribute('data-recipe-id');
  var recipe    = requestRecipe(recipeId, '');
}

// Display recipe's review list.
function showReviews(e) {
  var showDetail = e.target.getAttribute('data-show-detail');
  if (showDetail == 0) {   // hide detail.
    $('#reviews').html('');
  } else {                  // show detail.
    var recipeId = e.target.getAttribute('data-recipe-id');
    $.get(`/recipes/${recipeId}/recipe_reviews.json`, function(data) {
      // Create data array for display.
      var recipeReviews = [];
      data.data.forEach(function(recipeReview) {

        var comments = recipeReview['comments'],
            numStars = parseInt(recipeReview['stars']),
            recipeId = recipeReview['recipe_id'],
            reviewId = recipeReview['id'],
            title    = recipeReview['title'];

        var stars = '';
        for (var i = 0; i < numStars; ++i) {
          stars += '';
        }

        var titleHeader = `<a href="/recipes/${recipeId}/recipe_reviews/${reviewId}">${title}</a>`;

        if (comments.length > 60) {
          comments = comments.substring(0, 57) + '...';
        }

        recipeReviews.push({stars: stars, titleHeader: titleHeader, comments: comments});
      });

      // Show a message if there are no reviews.
      if (recipeReviews.length == 0) {
        recipeReviews.push({stars: 'No reviews yet.', titleHeader: '', comments: ''});
      }

      // Display data via Handlebars template.
      var template = Handlebars.compile(document.getElementById("reviews-template").innerHTML);
      $('#reviews').html(template(recipeReviews));
    })
    .fail(function(jqXHR, textStatus, error) {
      console.log('ERROR: ' + error);
    });
  }
  e.target.setAttribute('data-show-detail', showDetail == 0 ? 1 : 0);  // flip the showDetail flag.
  e.preventDefault();
}

// Display recipe's step list.
function showSteps(e) {
  var showDetail = e.target.getAttribute('data-show-detail');
  if (showDetail == 0) {   // hide detail.
    $('#steps').html('');
  } else {                  // show detail.
    var recipeId = e.target.getAttribute('data-recipe-id');
    $.get(`/recipes/${recipeId}/recipe_steps.json`, function(data) {
      // Create data array for display.
      var recipeSteps = [];
      data.data.forEach(function(recipeStep) {
        var stepNumber = recipeStep.attributes['step-number'],
            description = recipeStep.attributes['description'];
        recipeSteps.push({step_number: stepNumber, description: description});
      });

      // Show a message if there are no steps.
      if (recipeSteps.length == 0) {
        recipeSteps.push({step_number: 'No steps yet.', description: ''});
      }

      // Display data via Handlebars template.
      var template = Handlebars.compile(document.getElementById("steps-template").innerHTML);
      $('#steps').html(template(recipeSteps));
    })
    .fail(function(jqXHR, textStatus, error) {
      console.log('ERROR: ' + error);
    });
  }
  e.target.setAttribute('data-show-detail', showDetail == 0 ? 1 : 0);  // flip the showDetail flag.
  e.preventDefault();
}

function toggleFavorite(elem) {
  var recipeId = elem.dataset.recipeId,
      userId   = elem.dataset.userId;
  $.ajax({
    type: "PATCH",
    url: `/users/${userId}/user_recipe_favorites/${recipeId}`,
    data: {'favorite': elem.checked},
    dataType: 'json'
  });
}
<commit_msg>Don't clone a hidden row. Don't _destroy a new row, just delete it from the html.<commit_after>function addIngredient(e) {
  var ingredientTable = $('#ingredientTable tbody'),
      newRow   = ingredientTable.find('tr:visible').first().clone(true),
      newRowId = ingredientTable.find('tr').length,
      newId   = `recipe_recipe_ingredients_attributes_${newRowId}_`,
      newName = `recipe[recipe_ingredients_attributes][${newRowId}]`;

  // These are the attributes that need to be edited when cloning a table row.
  // The index values need updating to a new, unique, value -
  //   "recipe[recipe_ingredients_attributes][0][ingredient_id]"
  //   "recipe_recipe_ingredients_attributes_0_ingredient_id"
  // and existing values need to be cleared out -
  //   data-recipe-ingredient-id="16"
  //   selected="selected"
  //   value='4'

  newRow.attr('id', 'recipe-ingredient-id-');

  var selectElems = newRow.find('select');
  selectElems.eq(0).attr('name', newName + '[ingredient_id]');
  selectElems.eq(0).attr('id',   newId   + 'ingredient_id'  );

  var labelElems = newRow.find('label'),
      inputElems = newRow.find('input');
  labelElems.eq(0).attr('for',   newId   + 'quantity'  );
  inputElems.eq(0).attr('name',  newName + '[quantity]');
  inputElems.eq(0).attr('id',    newId   + 'quantity'  );
  inputElems.eq(0).attr('value', '' );

  labelElems.eq(1).attr ('for',  newId   + 'unit_id'  );
  selectElems.eq(1).attr('name', newName + '[unit_id]');
  selectElems.eq(1).attr('id', newId + 'unit_id' );

  newRow.find('a.js-deleteIngredient').eq(0).attr('data-recipe-ingredient-id', '');
  newRow.find('a.js-deleteIngredient').eq(0).click(deleteIngredient);

  selectElems.find('option').each(function(i, e) {
    e.removeAttribute('selected');
  });

  ingredientTable.prepend(newRow);
  e.preventDefault();
}

function addStep(e) {
  var stepTable  = $('#stepTable tbody'),
      clickedRow = $(e.target).parent().parent().parent(),
      newRow     = clickedRow.clone(true),
      newRowId   = stepTable.find('tr').length,
      newId      = `recipe_recipe_steps_attributes_${newRowId}_`,
      newName    = `recipe[recipe_steps_attributes][${newRowId}]`,
      stepNumber = parseInt(clickedRow.find('input[name*="step_number"]').val());

  // These are the attributes that need to be edited when cloning a table row.
  // The index and name values need updating:
  //

  //   "recipe[recipe_steps_attributes][0][step_id]"
  //   "recipe_recipe_steps_attributes_0_step_id"
  // and existing values need to be cleared out:
  //   data-recipe-step-id="16"
  //   $('textarea#...').text()
  //   value='4'

  newRow.removeAttr('id');

  var inputElems = newRow.find('input'),
      labelElems = newRow.find('label');
  labelElems.eq(0).attr('for',   newId   + 'step_number'  );
  inputElems.eq(0).attr('name',  newName + '[step_number]');
  inputElems.eq(0).attr('id',    newId   + 'step_number'  );
  inputElems.eq(0).attr('value', stepNumber);

  var textareaElems = newRow.find('textarea');
  labelElems.eq(1).attr   ('for',  newId   + 'step_number'  );
  textareaElems.eq(0).attr('name', newName + '[description]');
  textareaElems.eq(0).attr('id',   newId   + 'description'  );
  textareaElems.text('');

  newRow.find('a.js-deleteStep').eq(0).attr('data-recipe-step-id', '');
  newRow.find('a.js-deleteStep').eq(0).click(deleteStep);

  newRow.insertAfter(clickedRow);

  // Renumber only visible rows.
  clickedRow.nextAll('tr').each( function() {
    if ($(this).is(':visible')) {
      var stepNumberTd   = $(this).find('td:first'),
          stepNumberElem = stepNumberTd.find('input');
      stepNumber += 1;
      stepNumberElem.val(stepNumber);
      stepNumberTd.find('label').text('Step ' + stepNumber);
    }
  });
  e.preventDefault();
}

//  Delete a recipe ingredient from the recipe.
function deleteIngredient(e) {
  if (confirm('Are you sure?') == true) {
    var clickedRow = $(e.target).parent().parent().parent();

    // Eliminate the deleted row.
    if (clickedRow.attr('id')) {
      var recipeIngredientId = $(e.target).parent().data('recipeIngredientId');
      clickedRow.hide();  // Hide the row and
      $("tr#recipe-ingredient-id-" + recipeIngredientId + " input:checkbox.js-destroyIngredient").prop('checked', true);  // mark for it destruction.
    } else {
      clickedRow.remove(); // A new row doesn't have an id. Just delete it.
    }
  }
  e.preventDefault();
}

//  Delete an entire recipe.
function deleteRecipe(e) {
  if (confirm('Are you sure?') == true) {
    var recipeId = e.target.parentElement.getAttribute('data-recipe-id');
    $.ajax({
      type: "DELETE",
      url: `/recipes/${recipeId}`,
    })
    .fail(function(jqXHR, textStatus, error) {
      console.log('ERROR: ' + error);
    });
  }
  e.preventDefault();
}

//  Delete a recipe step from the recipe.
function deleteStep(e) {
  if (confirm('Are you sure?') == true) {
    var clickedRow = $(e.target).parent().parent().parent(),
        stepNumber = parseInt(clickedRow.find('input[name*="step_number"]').val());

    // Renumber the subsequent visible rows.
    clickedRow.nextAll('tr').each( function() {
      if ($(this).is(':visible')) {
        var stepNumberTd   = $(this).find('td:first'),
            stepNumberElem = stepNumberTd.find('input');
        stepNumberElem.val(stepNumber);
        stepNumberTd.find('label').text('Step ' + stepNumber);
        stepNumber += 1;
      }
    });

    // Eliminate the deleted row.
    if (clickedRow.attr('id')) {
      var recipeStepId = $(e.target).parent().data('recipeStepId');
      clickedRow.hide();  // Hide the row and
      $("tr#recipe-step-id-" + recipeStepId + " input:checkbox.js-destroyStep").prop('checked', true);  // mark it for destruction.
    } else {
      clickedRow.remove(); // A new row doesn't have an id. Just delete it.
    }
  }
  e.preventDefault();
}

// Navigate to another recipe ('next', 'prev', 'current').
function navigateRecipe(e, direction) {
  requestRecipe(direction);
  e.preventDefault();
}

var recipeTemplate = nil;

// Get a recipe from the server.
function requestRecipe(direction) {
  $.get('/recipes/0.json', {direction: direction}, function(data) {

    var authorName = data.data.relationships.author.data['email'],
        favorite   = '',
        photoPath  = data.data.attributes['photo-path'];

    var recipe = {};
    recipe['recipe_id']   = data.data['id'];
    recipe['author_id']   = data.data.relationships.author.data['id'];
    recipe['name']        = data.data.attributes['name'];
    recipe['description'] = data.data.attributes['description'];
    recipe['total_time']  = data.data.attributes['total-time'] + ' minutes';

    var thisUserId = $('body').data('userid');
    var userRecipeFavorites = data.data.relationships['user-recipe-favorites']['data'];
    for (var i = 0; i < userRecipeFavorites.length; ++i) {
      if (userRecipeFavorites[i]['user-id'] == thisUserId) {
        favorite = 'checked';
        break;
      }
    }

    recipe['image'] = `<img src='/assets/${photoPath}' height='200' width='360' title="${recipe['name']}} photo">`;
    recipe['favorite'] = `<input type='checkbox' class='favorite' ${favorite}>Favorite`;
    recipe['submitter'] = `Submitted by <a href='/users/${recipe['author_id']}/recipes'>${authorName}}</a>`;
    recipe['ingredientsHeader'] = `<a class="js-ingredients" data-recipe-id=${recipe['recipe_id']} data-show-detail="1" href="#">Ingredients</a>`;
    recipe['stepsHeader'] = `<a class="js-steps" data-recipe-id=${recipe['recipe_id']} data-show-detail="1" href="#">Steps</a>`;
    recipe['reviewsHeader'] = `<a class="js-reviews" data-recipe-id=${recipe['recipe_id']} data-show-detail="1" href="#">Reviews</a>`;

    // Display data via Handlebars template.
    if (!recipeTemplate) {
      recipeTemplate = Handlebars.compile(document.getElementById("recipe-template").innerHTML);
    }
    $('.recipe-detail').html(recipeTemplate(recipe));

    if (history.pushState) {
      if (window.location.pathname != `/recipes/${recipe['recipe_id']}`) {
        var newurl = window.location.protocol + "//" + window.location.host + `/recipes/${recipe['recipe_id']}`;
        window.history.pushState({path:newurl}, '', newurl);
      }
    }

    $('.js-deleteRecipe').eq(0).click(function(e) { deleteRecipe(e); });
    $('.js-ingredients').eq(0).click(function(e)  { showIngredients(e); });
    $('.js-steps').eq(0).click(function(e) { showSteps(e); });
    $('.js-reviews').eq(0).click(function(e) { showReviews(e); });
  })
  .fail(function(jqXHR, textStatus, error) {
    console.log('ERROR: ' + error);
  });
}

// Display recipe's ingredient list.
function showIngredients(e) {
  var showDetail = e.target.getAttribute('data-show-detail');
  if (showDetail == 0) {   // hide detail.
    $('#ingredients').html('');
  } else {                  // show detail.
    var recipeId = e.target.getAttribute('data-recipe-id');
    $.get(`/recipes/${recipeId}/recipe_ingredients.json`, function(data) {
      // Gather up reference data.
      var ingredients = {}, units = {};
      if (data.included) {
        data.included.forEach(function(relation) {
          if (relation.type == 'ingredients') {
            ingredients[relation['id']] = relation.attributes['name'];
          } else if (relation.type == 'units') {
            units[relation['id']] = relation.attributes['name'];
          }
        });
      }

      // Create data array for display.
      var recipeIngredients = [];
      data.data.forEach(function(recipeIngredient) {
        var ingredientName = ingredients[recipeIngredient.relationships.ingredient.data['id']],
            quantity       = recipeIngredient.attributes['quantity'],
            unitsName      = recipeIngredient.attributes['unit-id'] == null ? '' : units[recipeIngredient.attributes['unit-id']];
        recipeIngredients.push({ingredient: ingredientName, quantity: quantity, unit: unitsName});
      });

      // Show a message if there are no ingredients.
      if (recipeIngredients.length == 0) {
        recipeIngredients.push({ingredient: 'No ingredients yet.', quantity: '', unit: ''});
      }

      // Display data via Handlebars template.
      var template = Handlebars.compile(document.getElementById("ingredients-template").innerHTML);
      $('#ingredients').html(template(recipeIngredients));
    })
    .fail(function(jqXHR, textStatus, error) {
      console.log('ERROR: ' + error);
    });
  }
  e.target.setAttribute('data-show-detail', showDetail == 0 ? 1 : 0);  // flip the showDetail flag.
  e.preventDefault();
}

// Display a recipe.
function showRecipe(e) {
  var recipeId = e.target.parentElement.getAttribute('data-recipe-id');
  var recipe    = requestRecipe(recipeId, '');
}

// Display recipe's review list.
function showReviews(e) {
  var showDetail = e.target.getAttribute('data-show-detail');
  if (showDetail == 0) {   // hide detail.
    $('#reviews').html('');
  } else {                  // show detail.
    var recipeId = e.target.getAttribute('data-recipe-id');
    $.get(`/recipes/${recipeId}/recipe_reviews.json`, function(data) {
      // Create data array for display.
      var recipeReviews = [];
      data.data.forEach(function(recipeReview) {

        var comments = recipeReview['comments'],
            numStars = parseInt(recipeReview['stars']),
            recipeId = recipeReview['recipe_id'],
            reviewId = recipeReview['id'],
            title    = recipeReview['title'];

        var stars = '';
        for (var i = 0; i < numStars; ++i) {
          stars += '';
        }

        var titleHeader = `<a href="/recipes/${recipeId}/recipe_reviews/${reviewId}">${title}</a>`;

        if (comments.length > 60) {
          comments = comments.substring(0, 57) + '...';
        }

        recipeReviews.push({stars: stars, titleHeader: titleHeader, comments: comments});
      });

      // Show a message if there are no reviews.
      if (recipeReviews.length == 0) {
        recipeReviews.push({stars: 'No reviews yet.', titleHeader: '', comments: ''});
      }

      // Display data via Handlebars template.
      var template = Handlebars.compile(document.getElementById("reviews-template").innerHTML);
      $('#reviews').html(template(recipeReviews));
    })
    .fail(function(jqXHR, textStatus, error) {
      console.log('ERROR: ' + error);
    });
  }
  e.target.setAttribute('data-show-detail', showDetail == 0 ? 1 : 0);  // flip the showDetail flag.
  e.preventDefault();
}

// Display recipe's step list.
function showSteps(e) {
  var showDetail = e.target.getAttribute('data-show-detail');
  if (showDetail == 0) {   // hide detail.
    $('#steps').html('');
  } else {                  // show detail.
    var recipeId = e.target.getAttribute('data-recipe-id');
    $.get(`/recipes/${recipeId}/recipe_steps.json`, function(data) {
      // Create data array for display.
      var recipeSteps = [];
      data.data.forEach(function(recipeStep) {
        var stepNumber = recipeStep.attributes['step-number'],
            description = recipeStep.attributes['description'];
        recipeSteps.push({step_number: stepNumber, description: description});
      });

      // Show a message if there are no steps.
      if (recipeSteps.length == 0) {
        recipeSteps.push({step_number: 'No steps yet.', description: ''});
      }

      // Display data via Handlebars template.
      var template = Handlebars.compile(document.getElementById("steps-template").innerHTML);
      $('#steps').html(template(recipeSteps));
    })
    .fail(function(jqXHR, textStatus, error) {
      console.log('ERROR: ' + error);
    });
  }
  e.target.setAttribute('data-show-detail', showDetail == 0 ? 1 : 0);  // flip the showDetail flag.
  e.preventDefault();
}

function toggleFavorite(elem) {
  var recipeId = elem.dataset.recipeId,
      userId   = elem.dataset.userId;
  $.ajax({
    type: "PATCH",
    url: `/users/${userId}/user_recipe_favorites/${recipeId}`,
    data: {'favorite': elem.checked},
    dataType: 'json'
  });
}
<|endoftext|>


<commit_before>// Copyright 2010 Tim Caswell <tim@creationix.com>
//
// MIT licensed

var sys = require('sys'),
  fs = require('fs'),
  Buffer = require('buffer').Buffer;

// This size only affects performance, it's not a constraint on data sizes
var CHUNK_SIZE = 1024;
// This is the max size of a single serialized document
var MAX_SIZE = 1024 * 1024;

function nStore(filename) {
  var fd; // FD for reading and writing to the file
  var index = {}; // Index of file positions of all documents by key
  var writeQueue = []; // Queue of new docs to write to the hd
  var queueSize = 0;
  var maxKey = 0; // The higest number key (used for automatic keys)
  var dbLength = 0; // The size of the current db file in bytes

  // Open a single handle for reading and writing
  fd = fs.openSync(filename, "a+");

  // Loads the database from disk using blocking I/O
  // TODO: see if non-blocking is faster, this takes a long time
  function loadDatabase() {

    // Create a buffer for reading chunks from the disk
    var chunk = new Buffer(CHUNK_SIZE);

    // Create an empty stream buffer
    var input = new Buffer(MAX_SIZE);
    input.length = 0;

    // These are positions in the database file
    var offset = 0;
    var base = 0;

    // This is a position within the input stream
    var pos = 0;
    var mid = 0;

    // Read a chunk from the file into `chunk`
    while ((chunk.length = fs.readSync(fd, chunk, 0, CHUNK_SIZE, offset)) > 0) {

      // Move the offset so the outer loop stays in sync
      offset += chunk.length;

      // Copy the chunk onto the input stream
      chunk.copy(input, input.length, 0, chunk.length);
      input.length += chunk.length;

      // See if there is input to consume
      for (var i = pos, l = input.length; i < l; i++) {
        if (input[i] === 9) {
          mid = i + 1;
        }
        if (mid && input[i] === 10) {
          // var doc = input.slice(pos, mid - 1).toString();
          var meta = JSON.parse(input.slice(mid, i).toString());
          var info = {
            meta: meta,
            position: pos,
            length: mid - pos - 1
          };
          index[meta.key] = info;
          mid = 0;
          pos = i + 1;
        }
      }

      // Shift the input back down
      if (pos > 0) {
        input.copy(input, 0, pos, input.length);
        input.length -= pos;
        base += pos;
        pos = 0;
      }
    }

    dbLength = offset;

    // Preset maxKey
    Object.keys(index).forEach(function (key) {
      if (/^[1-9][0-9]*$/.test(key)) {
        var val = parseInt(key, 10);
        if (val > maxKey) {
          maxKey = val;
        }
      }
    });
  }
  loadDatabase();

  // Load a single record from the disk
  function getByKey(key, callback) {
    try {
      var info = index[key];
      if (!info) {
        var err = new Error("Document does not exist for " + key);
        err.errno = process.ENOENT;
        callback(err);
        return;
      }

      var buffer = new Buffer(info.length);

      var offset = 0;
      function getNext() {
        fs.read(fd, buffer, offset, info.length - offset, info.position + offset, function (err, bytes) {
          if (err) {
            callback(err);
            return;
          }
          try {
            if (bytes < info.length - offset) {
              offset += bytes;
              getNext();
              return;
            }
            var data = JSON.parse(buffer.toString());
            callback(null, data, info.meta);
          } catch (err) {
            callback(err);
          }
        });
      }
      getNext();
    } catch (err) {
      callback(err);
    }
  }

  var lock = 0;
  function checkQueue() {
    lock++;
    if (lock > 1 || writeQueue.length === 0) { return; }

    // Merge the line buffers into a single large buffer
    var buffer = new Buffer(queueSize);
    var offset = 0;
    var position;
    var length = writeQueue.length;
    var callbacks = [];
    var newIndex = {};
    for (var i = 0; i < length; i++) {
      // Unpack the queue item
      var item = writeQueue[i],
        line = item.line;
      position = position || item.position;
      callbacks[i] = item.callback;
      newIndex[item.key] = {
        position: item.position,
        length: item.length
      };

      // Copy the line into the larger buffer
      line.copy(buffer, offset);
      offset += line.length;
    }

    // Empty the queue
    writeQueue.length = 0;
    queueSize = 0;

    fs.write(fd, buffer, null, buffer.length, position, function (err, written) {
      // Mix in the updated indexes
      Object.keys(newIndex).forEach(function (key) {
        index[key] = newIndex[key];
      });

      // Call all the individual callbacks for the write
      for (var i = 0; i < length; i++) {
        var fn = callbacks[i];
        if (fn) {
          fn(err);
        } else {
          if (err) {
            throw err;
          }
        }
      }

      // Unlock and try the loop again
      lock = 0;
      checkQueue();
    });

  }

  return {
    get length() {
      return Object.keys(index).length;
    },

    // Saves a document with optional key. The effect if immediate to the
    // running program, but not persistent till after the callback.
    // Pass null as the key to get a generated key.
    save: function (key, doc, callback) {
      if (!key) {
        key = maxKey = maxKey + 1;
      }
      var data = JSON.stringify(doc);
      var line = new Buffer(data + "\t" + JSON.stringify({key: key}) + "\n");
      writeQueue[writeQueue.length] = {
        key: key,
        position: dbLength,
        length: Buffer.byteLength(data),
        line: line,
        callback: callback
      };
      dbLength += line.length;
      queueSize += line.length;
      checkQueue();
    },

    // Removes a document from the collection by key
    // The effect is immediate to the running program, but not permanent
    // till the callback returns.
    remove: function (key, callback) {
      if (key in index) {
        delete index[key];
        var line = new Buffer("\t" + JSON.stringify({key: key}) + "\n");

        writeQueue[writeQueue.length] = {
          key: key,
          position: dbLength,
          length: 0,
          line: line,
          callback: callback
        };
        dbLength += line.length;
        queueSize += line.length;
        checkQueue();
      }
    },

    // Returns a readable stream of the whole collection.
    // Supports pause and resume so that you can delay events for layer.
    // This queues "data" and "end" events in memory./
    // Also you can provide a filter to pre-filter results before they
    // go to the queue
    stream: function (filter) {
      var counter = 0;
      var stream = new process.EventEmitter();
      var buffer = [];
      var paused = false;

      // Checks to see if we should emit the "end" event yet.
      function checkDone() {
        if (!paused && counter === 0) {
          counter--;
          stream.emit("end");
        }
      }

      // Tries to push events through
      function flush() {
        if (paused) { return; }
        for (var i = 0, l = buffer.length; i < l; i++) {
          stream.emit("data", buffer[i]);
          counter--;
        }
        buffer.length = 0;
        process.nextTick(checkDone);
      }

      stream.pause = function () {
        paused = true;
      };

      // Resumes emitting of events
      stream.resume = function () {
        paused = false;
        process.nextTick(function () {
          flush();
          checkDone();
        });
      };

      Object.keys(index).forEach(function (key) {
        counter++;
        getByKey(key, function (err, doc) {
          if (err) {
            stream.emit("error", err);
            return;
          }
          if (!filter || filter(doc)) {
            buffer.push(doc);
            flush();
          } else {
            counter--;
            process.nextTick(checkDone);
          }
        });
      });

      process.nextTick(checkDone);

      return stream;
    },

    // Loads a single document by id, accepts key and callback
    // the callback will be called with (err, doc, meta)
    get: getByKey
  };
}

module.exports = nStore;

<commit_msg>Fixed deleting to get getTest and removeTest passing.<commit_after>// Copyright 2010 Tim Caswell <tim@creationix.com>
//
// MIT licensed

var sys = require('sys'),
  fs = require('fs'),
  Buffer = require('buffer').Buffer;

// This size only affects performance, it's not a constraint on data sizes
var CHUNK_SIZE = 1024;
// This is the max size of a single serialized document
var MAX_SIZE = 1024 * 1024;

function nStore(filename) {
  var fd; // FD for reading and writing to the file
  var index = {}; // Index of file positions of all documents by key
  var writeQueue = []; // Queue of new docs to write to the hd
  var queueSize = 0;
  var maxKey = 0; // The higest number key (used for automatic keys)
  var dbLength = 0; // The size of the current db file in bytes

  // Open a single handle for reading and writing
  fd = fs.openSync(filename, "a+");

  // Loads the database from disk using blocking I/O
  // TODO: see if non-blocking is faster, this takes a long time
  function loadDatabase() {

    // Create a buffer for reading chunks from the disk
    var chunk = new Buffer(CHUNK_SIZE);

    // Create an empty stream buffer
    var input = new Buffer(MAX_SIZE);
    input.length = 0;

    // These are positions in the database file
    var offset = 0;
    var base = 0;

    // This is a position within the input stream
    var pos = 0;
    var mid = 0;

    // Read a chunk from the file into `chunk`
    while ((chunk.length = fs.readSync(fd, chunk, 0, CHUNK_SIZE, offset)) > 0) {

      // Move the offset so the outer loop stays in sync
      offset += chunk.length;

      // Copy the chunk onto the input stream
      chunk.copy(input, input.length, 0, chunk.length);
      input.length += chunk.length;

      // See if there is input to consume
      for (var i = pos, l = input.length; i < l; i++) {
        if (input[i] === 9) {
          mid = i + 1;
        }
        if (mid && input[i] === 10) {
          // var doc = input.slice(pos, mid - 1).toString();
          var meta = JSON.parse(input.slice(mid, i).toString());
          var info = {
            meta: meta,
            position: pos,
            length: mid - pos - 1
          };
          if (info.length > 0) {
            index[meta.key] = info;
          } else {
            delete index[meta.key];
          }
          mid = 0;
          pos = i + 1;
        }
      }

      // Shift the input back down
      if (pos > 0) {
        input.copy(input, 0, pos, input.length);
        input.length -= pos;
        base += pos;
        pos = 0;
      }
    }

    dbLength = offset;

    // Preset maxKey
    Object.keys(index).forEach(function (key) {
      if (/^[1-9][0-9]*$/.test(key)) {
        var val = parseInt(key, 10);
        if (val > maxKey) {
          maxKey = val;
        }
      }
    });
  }
  loadDatabase();

  // Load a single record from the disk
  function getByKey(key, callback) {
    try {
      var info = index[key];
      if (!info) {
        var err = new Error("Document does not exist for " + key);
        err.errno = process.ENOENT;
        callback(err);
        return;
      }

      var buffer = new Buffer(info.length);

      var offset = 0;
      function getNext() {
        fs.read(fd, buffer, offset, info.length - offset, info.position + offset, function (err, bytes) {
          if (err) {
            callback(err);
            return;
          }
          try {
            if (bytes < info.length - offset) {
              offset += bytes;
              getNext();
              return;
            }
            var data = JSON.parse(buffer.toString());
            callback(null, data, info.meta);
          } catch (err) {
            callback(err);
          }
        });
      }
      getNext();
    } catch (err) {
      callback(err);
    }
  }

  var lock = 0;
  function checkQueue() {
    lock++;
    if (lock > 1 || writeQueue.length === 0) { return; }

    // Merge the line buffers into a single large buffer
    var buffer = new Buffer(queueSize);
    var offset = 0;
    var position;
    var length = writeQueue.length;
    var callbacks = [];
    var newIndex = {};
    for (var i = 0; i < length; i++) {
      // Unpack the queue item
      var item = writeQueue[i],
        line = item.line;
      position = position || item.position;
      callbacks[i] = item.callback;
      newIndex[item.key] = {
        position: item.position,
        length: item.length
      };

      // Copy the line into the larger buffer
      line.copy(buffer, offset);
      offset += line.length;
    }

    // Empty the queue
    writeQueue.length = 0;
    queueSize = 0;

    fs.write(fd, buffer, null, buffer.length, position, function (err, written) {
      // Mix in the updated indexes
      Object.keys(newIndex).forEach(function (key) {
        if (newIndex[key].length === 0) {
          delete index[key];
        } else {
          index[key] = newIndex[key];
        }
      });

      // Call all the individual callbacks for the write
      for (var i = 0; i < length; i++) {
        var fn = callbacks[i];
        if (fn) {
          fn(err);
        } else {
          if (err) {
            throw err;
          }
        }
      }

      // Unlock and try the loop again
      lock = 0;
      checkQueue();
    });

  }

  return {
    get length() {
      return Object.keys(index).length;
    },

    // Saves a document with optional key. The effect if immediate to the
    // running program, but not persistent till after the callback.
    // Pass null as the key to get a generated key.
    save: function (key, doc, callback) {
      if (!key) {
        key = maxKey = maxKey + 1;
      }
      var data = JSON.stringify(doc);
      var line = new Buffer(data + "\t" + JSON.stringify({key: key}) + "\n");
      writeQueue[writeQueue.length] = {
        key: key,
        position: dbLength,
        length: Buffer.byteLength(data),
        line: line,
        callback: callback
      };
      dbLength += line.length;
      queueSize += line.length;
      checkQueue();
    },

    // Removes a document from the collection by key
    // The effect is immediate to the running program, but not permanent
    // till the callback returns.
    remove: function (key, callback) {
      if (key in index) {
        delete index[key];
        var line = new Buffer("\t" + JSON.stringify({key: key}) + "\n");

        writeQueue[writeQueue.length] = {
          key: key,
          position: dbLength,
          length: 0,
          line: line,
          callback: callback
        };
        dbLength += line.length;
        queueSize += line.length;
        checkQueue();
      }
    },

    // Returns a readable stream of the whole collection.
    // Supports pause and resume so that you can delay events for layer.
    // This queues "data" and "end" events in memory./
    // Also you can provide a filter to pre-filter results before they
    // go to the queue
    stream: function (filter) {
      var counter = 0;
      var stream = new process.EventEmitter();
      var buffer = [];
      var paused = false;

      // Checks to see if we should emit the "end" event yet.
      function checkDone() {
        if (!paused && counter === 0) {
          counter--;
          stream.emit("end");
        }
      }

      // Tries to push events through
      function flush() {
        if (paused) { return; }
        for (var i = 0, l = buffer.length; i < l; i++) {
          stream.emit("data", buffer[i]);
          counter--;
        }
        buffer.length = 0;
        process.nextTick(checkDone);
      }

      stream.pause = function () {
        paused = true;
      };

      // Resumes emitting of events
      stream.resume = function () {
        paused = false;
        process.nextTick(function () {
          flush();
          checkDone();
        });
      };

      Object.keys(index).forEach(function (key) {
        counter++;
        getByKey(key, function (err, doc) {
          if (err) {
            stream.emit("error", err);
            return;
          }
          if (!filter || filter(doc)) {
            buffer.push(doc);
            flush();
          } else {
            counter--;
            process.nextTick(checkDone);
          }
        });
      });

      process.nextTick(checkDone);

      return stream;
    },

    // Loads a single document by id, accepts key and callback
    // the callback will be called with (err, doc, meta)
    get: getByKey
  };
}

module.exports = nStore;

<|endoftext|>


<commit_before>// # Place all the behaviors and hooks related to the matching controller here.
// # All this logic will automatically be available in application.js.
// # You can use CoffeeScript in this file: http://coffeescript.org/

$(function() {
  ['weekly_report', 'monthly_report', 'yearly_report'].forEach(function(reportName) {
    var chart = c3.generate({
      bindto: '#chart-' + reportName,
      data: {
          json: [
            gon[reportName]['DC'],
            gon[reportName]['MD'],
            gon[reportName]['VA']
          ],
          type: 'bar',
          keys: {
            x: 'name',
            value: ['patients_contacted', 'new_patients_contacted', 'pledges_sent'],
          }
      },
      bar: {
          width: {
              ratio: 0.5
          }
      },
      axis: {
        x: {
          type: 'category'
        }
      }
    });
  });
});
<commit_msg>remove useless comment<commit_after>$(function() {
  ['weekly_report', 'monthly_report', 'yearly_report'].forEach(function(reportName) {
    var chart = c3.generate({
      bindto: '#chart-' + reportName,
      data: {
          json: [
            gon[reportName]['DC'],
            gon[reportName]['MD'],
            gon[reportName]['VA']
          ],
          type: 'bar',
          keys: {
            x: 'name',
            value: ['patients_contacted', 'new_patients_contacted', 'pledges_sent'],
          }
      },
      bar: {
          width: {
              ratio: 0.5
          }
      },
      axis: {
        x: {
          type: 'category'
        }
      }
    });
  });
});
<|endoftext|>


<commit_before>Object.defineProperties(Object.prototype, {

    /**
     * Returns a clone of an object. Not a perfect solution, but will be
     * able to clone deep objects, dates, arrays and functions.
     *
     * @return clone            the clone of the object
     */
    clone:{
        enumerable:false,
        value:function(){

            // Handle Date
            if(this.instance() == 'Date'){
                var d = new this.constructor();
                return (d.setTime(this.getTime()), d);
            }

            // Handle Array
            if(this.instance() == 'Array'){
                var arr = [];
                for(var i = 0, len = this.length; i < len; i++){
                    if(typeof this[i] == "object" && this[i] !== null){
                        arr[i] = this[i].clone();
                    } else {
                        arr[i] = this[i];
                    }
                }
                return arr;
            }

            // Handle String and Number
            if(this.instance() == 'String' || this.instance() == 'Number'){
                return this;
            }

            // Handle Object
            if(this instanceof Object){
                var obj = new this.constructor;
                for(var key in this){
                    if(this.hasOwnProperty(key)){
                        if(typeof this[key] == "object" && this[key] !== null) {
                            obj[key] = this[key].clone();
                        } else {
                            obj[key] = this[key];
                        }
                    }
                }
                return obj;
            }

        }
    },

    /**
     * Simple implementation of Object.equals() to calculate whether two
     * objects are the same based on keys. Be aware that this doesn't
     * account for key ordering.
     *
     * @param obj               the comparison object to compare against
     * @param eq                whether to use == for comparison
     * @return true             if the objects are the same
     */
    equals:{
        enumerable:false,
        value:function(obj, eq){
            // if this instance is a Number, just compare the values
            if (this.instance() === "Number") {
                return eq ? this.valueOf() == obj.valueOf() : this.valueOf() === obj;
            }

            // if this instance is a string, compare the string values
            if (this.instance() === "String") {
                return eq ? this.toString() == obj.toString() : this.toString() === obj;
            }

            // if both x and y are null or undefined and exactly the same
            if ((!eq && this === obj) || (eq && this == obj)) return true;

            // if they are not strictly equal, they both need to be Objects
            if (!( typeof this === "object" ) || !( typeof obj === "object" )) return false;

            // they must have the exact same prototype chain, the closest we can do is
            // test there constructor.
            if (this.constructor !== obj.constructor) return false;

            for (var p in this) {
                // other properties were tested using x.constructor === y.constructor
                if (!this.hasOwnProperty(p)) continue;

                // allows to compare x[ p ] and y[ p ] when set to undefined
                if (!obj.hasOwnProperty(p)) return false;

                // if they have the same strict value or identity then they are equal
                if ((!eq && this[ p ] === obj[ p ]) || (eq && this[ p ] == obj[ p ])) continue;

                // Numbers, Strings, Functions, Booleans must be strictly equal
                if (typeof( this[ p ] ) !== "object") return false;

                // Objects and Arrays must be tested recursively
                if (!this[ p ].equals(obj[ p ], eq)) return false;
            }

            for (p in obj) {
                // allows x[ p ] to be set to undefined
                if (obj.hasOwnProperty(p) && !this.hasOwnProperty(p)) return false;
            }
            return true;
        }
    },

    /**
     * Returns the 'instanceof' of the Object via its constructor. This
     * replicates Function.name for browsers with no support.
     *
     * @returns {*}
     */
    instance:{
        enumerable:false,
        value:function(){
            var ctor = this.constructor.toString();
            var match = ctor.match(/function (\w+)\(/);
            return match.length ? match[1] : '';
        }
    },

    /**
     * Loops the keys of an object, top-level. Passes the key and value to a
     * handling function. Allows the user to return a value from within
     * the closure.
     *
     * @param handler           the processor
     */
    loop:{
        enumerable:false,
        value:function(handler){
            if(!handler){
                return;
            }
            for(var k in this){
                if(this.hasOwnProperty(k)){
                    var res = handler(k, this[k]);
                    if(res || typeof res == "object"){
                        return res;
                    }
                }
            }
        }
    },

    /**
     * Loops an object recursively and processes using a passed in handler.
     * Can take a key parameter, if the user has called .loopr() on a nested
     * object.
     *
     * @param handler           the processor
     * @param key               an optional starting key
     */
    loopr:{
        enumerable:false,
        value:function(handler, key){
            if(!handler){
                return;
            }
            for(var k in this){
                if(this.hasOwnProperty(k)){
                    var newK = ~k.indexOf('.') ? '[\'' + k + '\']' : key ? '.' + k : k,
                        pass = (key || "") + newK;
                    if(this[k] && this[k].instance() == "Object"){
                        this[k].loopr(handler, pass);
                    } else {
                        var res = handler(k, this[k], pass);
                        if(res || typeof res == "object"){
                            return res;
                        }
                    }
                }
            }
        }
    },

    /**
     * Returns a prettied representation of an object via JSON.stringify().
     *
     * @returns {*}
     */
    pretty:{
        enumerable:false,
        value:function(indent){
            return JSON.stringify(this, null, indent == (undefined || null) ? 4 : indent);
        }
    },

    /**
     * Sorts an object by key alphabetically. Returns a sorted
     * copy of the object in question.
     *
     * @param topLevel          only sort the top level keys
     * @returns {{}}            the sorted object
     */
    sort:{
        enumerable:false,
        value:function(topLevel){
            var sortedObj = {},
                keys = Object.keys(this);

            keys.sort(function(key1, key2){
                key1 = key1.toLowerCase(), key2 = key2.toLowerCase();
                return key1 < key2 ? -1 : 1;
            });

            for(var index in keys){
                if(keys.hasOwnProperty(index) && this.hasOwnProperty(keys[index])){
                    var key = keys[index];
                    if(this[key] && this[key].instance() == "Object" && !topLevel){
                        sortedObj[key] = this[key].sort();
                    } else {
                        sortedObj[key] = this[key];
                    }
                }
            }

            return sortedObj;
        }
    },

    /**
     * Validates types of object values against a given schema.
     * Used for simple and quick object validation.
     *
     * @param validation        the validation object
     * @returns true            if the object could be validated
     */
    validate:{
        enumerable:false,
        value:function(validation){
            if(!validation){
                return true;
            }
            for(var key in this){
                if(!this.hasOwnProperty(key) || !validation[key]){
                    continue;
                }

                var vInstance = validation[key];
                if(!this[key]){
                    if(typeof this[key] == "object" && vInstance.toLowerCase() != "null"){
                        return false;
                    } else if(typeof this[key] == "undefined" && vInstance.toLowerCase() != "undefined") {
                        return false;
                    }
                    continue;
                }

                var tInstance = this[key].instance().toLowerCase();
                if(tInstance == "object"){
                    if(vInstance.instance() != "Object"){
                        if(vInstance.toLowerCase() != tInstance){
                            return false;
                        }
                    } else {
                        return this[key].validate(vInstance);
                    }
                } else if(tInstance != vInstance.toLowerCase()) {
                    return false;
                }

            }
            return true;
        }
    },

    /**
     * Small helper to enable adding of dynamic keys on object
     * creation. Returns itself for chaining, and the ability
     * to include in assignments. Allows dot noted keys.
     *
     * @param k                 a key, or a dot-noted key path
     * @param v                 the final value of the path
     * @return this             the modified object
     */
    with:{
        enumerable:false,
        value:function(k, v){
            if(k == undefined || k.instance() != "String"){
                return this;
            }
            var keys = k.asKeys();
            for (var i = 0, tmp = this; i < keys.length - 1; i++) {
                if(tmp[keys[i]]){
                    tmp = tmp[keys[i]];
                } else {
                    tmp = tmp[keys[i]] = {};
                }
            }
            return tmp[keys[i]] = v, this;
        }
    },

});<commit_msg>Missing check inside Object#instance<commit_after>Object.defineProperties(Object.prototype, {

    /**
     * Returns a clone of an object. Not a perfect solution, but will be
     * able to clone deep objects, dates, arrays and functions.
     *
     * @return clone            the clone of the object
     */
    clone:{
        enumerable:false,
        value:function(){

            // Handle Date
            if(this.instance() == 'Date'){
                var d = new this.constructor();
                return (d.setTime(this.getTime()), d);
            }

            // Handle Array
            if(this.instance() == 'Array'){
                var arr = [];
                for(var i = 0, len = this.length; i < len; i++){
                    if(typeof this[i] == "object" && this[i] !== null){
                        arr[i] = this[i].clone();
                    } else {
                        arr[i] = this[i];
                    }
                }
                return arr;
            }

            // Handle String and Number
            if(this.instance() == 'String' || this.instance() == 'Number'){
                return this;
            }

            // Handle Object
            if(this instanceof Object){
                var obj = new this.constructor;
                for(var key in this){
                    if(this.hasOwnProperty(key)){
                        if(typeof this[key] == "object" && this[key] !== null) {
                            obj[key] = this[key].clone();
                        } else {
                            obj[key] = this[key];
                        }
                    }
                }
                return obj;
            }

        }
    },

    /**
     * Simple implementation of Object.equals() to calculate whether two
     * objects are the same based on keys. Be aware that this doesn't
     * account for key ordering.
     *
     * @param obj               the comparison object to compare against
     * @param eq                whether to use == for comparison
     * @return true             if the objects are the same
     */
    equals:{
        enumerable:false,
        value:function(obj, eq){
            // if this instance is a Number, just compare the values
            if (this.instance() === "Number") {
                return eq ? this.valueOf() == obj.valueOf() : this.valueOf() === obj;
            }

            // if this instance is a string, compare the string values
            if (this.instance() === "String") {
                return eq ? this.toString() == obj.toString() : this.toString() === obj;
            }

            // if both x and y are null or undefined and exactly the same
            if ((!eq && this === obj) || (eq && this == obj)) return true;

            // if they are not strictly equal, they both need to be Objects
            if (!( typeof this === "object" ) || !( typeof obj === "object" )) return false;

            // they must have the exact same prototype chain, the closest we can do is
            // test there constructor.
            if (this.constructor !== obj.constructor) return false;

            for (var p in this) {
                // other properties were tested using x.constructor === y.constructor
                if (!this.hasOwnProperty(p)) continue;

                // allows to compare x[ p ] and y[ p ] when set to undefined
                if (!obj.hasOwnProperty(p)) return false;

                // if they have the same strict value or identity then they are equal
                if ((!eq && this[ p ] === obj[ p ]) || (eq && this[ p ] == obj[ p ])) continue;

                // Numbers, Strings, Functions, Booleans must be strictly equal
                if (typeof( this[ p ] ) !== "object") return false;

                // Objects and Arrays must be tested recursively
                if (!this[ p ].equals(obj[ p ], eq)) return false;
            }

            for (p in obj) {
                // allows x[ p ] to be set to undefined
                if (obj.hasOwnProperty(p) && !this.hasOwnProperty(p)) return false;
            }
            return true;
        }
    },

    /**
     * Returns the 'instanceof' of the Object via its constructor. This
     * replicates Function.name for browsers with no support.
     *
     * @returns {*}
     */
    instance:{
        enumerable:false,
        value:function(){
            var ctor = this.constructor.toString();
            var match = ctor.match(/function (\w+)\(/);
            return match && match.length ? match[1] : '';
        }
    },

    /**
     * Loops the keys of an object, top-level. Passes the key and value to a
     * handling function. Allows the user to return a value from within
     * the closure.
     *
     * @param handler           the processor
     */
    loop:{
        enumerable:false,
        value:function(handler){
            if(!handler){
                return;
            }
            for(var k in this){
                if(this.hasOwnProperty(k)){
                    var res = handler(k, this[k]);
                    if(res || typeof res == "object"){
                        return res;
                    }
                }
            }
        }
    },

    /**
     * Loops an object recursively and processes using a passed in handler.
     * Can take a key parameter, if the user has called .loopr() on a nested
     * object.
     *
     * @param handler           the processor
     * @param key               an optional starting key
     */
    loopr:{
        enumerable:false,
        value:function(handler, key){
            if(!handler){
                return;
            }
            for(var k in this){
                if(this.hasOwnProperty(k)){
                    var newK = ~k.indexOf('.') ? '[\'' + k + '\']' : key ? '.' + k : k,
                        pass = (key || "") + newK;
                    if(this[k] && this[k].instance() == "Object"){
                        this[k].loopr(handler, pass);
                    } else {
                        var res = handler(k, this[k], pass);
                        if(res || typeof res == "object"){
                            return res;
                        }
                    }
                }
            }
        }
    },

    /**
     * Returns a prettied representation of an object via JSON.stringify().
     *
     * @returns {*}
     */
    pretty:{
        enumerable:false,
        value:function(indent){
            return JSON.stringify(this, null, indent == (undefined || null) ? 4 : indent);
        }
    },

    /**
     * Sorts an object by key alphabetically. Returns a sorted
     * copy of the object in question.
     *
     * @param topLevel          only sort the top level keys
     * @returns {{}}            the sorted object
     */
    sort:{
        enumerable:false,
        value:function(topLevel){
            var sortedObj = {},
                keys = Object.keys(this);

            keys.sort(function(key1, key2){
                key1 = key1.toLowerCase(), key2 = key2.toLowerCase();
                return key1 < key2 ? -1 : 1;
            });

            for(var index in keys){
                if(keys.hasOwnProperty(index) && this.hasOwnProperty(keys[index])){
                    var key = keys[index];
                    if(this[key] && this[key].instance() == "Object" && !topLevel){
                        sortedObj[key] = this[key].sort();
                    } else {
                        sortedObj[key] = this[key];
                    }
                }
            }

            return sortedObj;
        }
    },

    /**
     * Validates types of object values against a given schema.
     * Used for simple and quick object validation.
     *
     * @param validation        the validation object
     * @returns true            if the object could be validated
     */
    validate:{
        enumerable:false,
        value:function(validation){
            if(!validation){
                return true;
            }
            for(var key in this){
                if(!this.hasOwnProperty(key) || !validation[key]){
                    continue;
                }

                var vInstance = validation[key];
                if(!this[key]){
                    if(typeof this[key] == "object" && vInstance.toLowerCase() != "null"){
                        return false;
                    } else if(typeof this[key] == "undefined" && vInstance.toLowerCase() != "undefined") {
                        return false;
                    }
                    continue;
                }

                var tInstance = this[key].instance().toLowerCase();
                if(tInstance == "object"){
                    if(vInstance.instance() != "Object"){
                        if(vInstance.toLowerCase() != tInstance){
                            return false;
                        }
                    } else {
                        return this[key].validate(vInstance);
                    }
                } else if(tInstance != vInstance.toLowerCase()) {
                    return false;
                }

            }
            return true;
        }
    },

    /**
     * Small helper to enable adding of dynamic keys on object
     * creation. Returns itself for chaining, and the ability
     * to include in assignments. Allows dot noted keys.
     *
     * @param k                 a key, or a dot-noted key path
     * @param v                 the final value of the path
     * @return this             the modified object
     */
    with:{
        enumerable:false,
        value:function(k, v){
            if(k == undefined || k.instance() != "String"){
                return this;
            }
            var keys = k.asKeys();
            for (var i = 0, tmp = this; i < keys.length - 1; i++) {
                if(tmp[keys[i]]){
                    tmp = tmp[keys[i]];
                } else {
                    tmp = tmp[keys[i]] = {};
                }
            }
            return tmp[keys[i]] = v, this;
        }
    },

});
<|endoftext|>


<commit_before>'use strict';
var https = require( 'https' );
var crypto = require('crypto');
var activityUtils = require('./activityUtils');

/*
 * POST Handler for / route of Activity (this is the edit route).
 */
exports.edit = function( req, res ) {
    // Data from the req and put it in an array accessible to the main app.
    //console.log( req.body );
    activityUtils.logData( req );
    res.send( 200, 'Edit' );
};

/*
 * POST Handler for /save/ route of Activity.
 * This gets called when the journey is activated in the JB UI (and before /validate/ and /publish/)
 */
exports.save = function( req, res ) {
    // Data from the req and put it in an array accessible to the main app.
    //console.log( req.body );

    activityUtils.logData( req );
    res.send( 200, 'Save' );
};

/*
 * POST Handler for /publish/ route of Activity.
 * Run any code here that you need to run to prepare the activity for execution.
 * This gets called when the journey is activated in JB (and after /save/ and /validate/)
 */
exports.publish = function( req, res ) {
    // Data from the req and put it in an array accessible to the main app.
    //console.log( req.body );

    activityUtils.logData( req );
    res.send( 200, 'Publish' );
};

/*
 * POST Handler for /validate/ route of Activity.
 * This method should validate the configuration inputs.
 * This gets called when the journey is activated in the JB UI (and after /save/ but before /publish/)
 * Respond with 200 for valid, 500 for invalid
 */
exports.validate = function( req, res ) {
    // Data from the req and put it in an array accessible to the main app.
    //console.log( req.body );
    activityUtils.logData( req );
    res.send( 200, 'Validate' );

    /*
    If validation passes then call
    res.send( 200, 'Validate' );

    If validation fails then call
    res.send( 500, 'Validate' );
    */
};

/*
 * POST Handler for /execute/ route of Activity.
 * Runs each time the activity executes.  This is the main workhorse of the custom activity
 * Caller expects a 200 response back.  Can return any custom data also.
 */
exports.execute = function( req, res ) {
    // Data from the req and put it in an array accessible to the main app.
    activityUtils.logData( req );

	//merge the array of objects for easy access in code.
	var aArgs = req.body.inArguments;
	console.log( aArgs );
	var oArgs = {};
	for (var i=0; i<aArgs.length; i++) {
		for (var key in aArgs[i]) {
			oArgs[key] = aArgs[i][key];
		}
	}

	var contactKey = req.body.keyValue;

	// these values come from the config.json
	var EmailAddress = oArgs.EmailAddress;
	var Name = oArgs.Name;
	var muid = oArgs.muid;
	var SubscriberKey = oArgs.SubscriberKey;

	// these values come from the custom activity form inputs
	var pushMessage = oArgs.pushMessage;

	// Template Engine for personalisation
	var SFMCTemplateEngine = function(tpl, data) {
    var re = /<%([^%>]+)?%>/g, match;
    while(match = re.exec(tpl)) {
        tpl = tpl.replace(match[0], data[match[1]])
    }
    return tpl;
	}
	var template = pushMessage;

	var pushMessageText = SFMCTemplateEngine(template, {Name: Name});
	console.log('pushMessageText=' + pushMessageText);

	// Prepare post data for remote API
	var pushInfo = JSON.stringify({
    "pushInfo": [{"muid": muid, msg: pushMessageText}]
	});

	//var endpoint = "https://jsonplaceholder.typicode.com/posts";
	var endpoint = "http://push-api-dev.hkmpcl.com.hk:8001/api/sf_push";
	var remoteHost = "jsonplaceholder.typicode.com";
	var remotePort = "443";
	var remotePath = "/posts";
	var secret = process.env.API_Secret;
	var signature = endpoint + pushInfo + secret;
	var hash_signature = crypto.createHash('md5').update(signature).digest('hex');

	var post_data = JSON.stringify({
    	"pushInfo": [{"muid": muid, msg: pushMessageText}],
    	"s": hash_signature
	});

	console.log('post_data=' + post_data);

	var options = {
		'hostname': remoteHost,
		'port': remotePort,
		'path': remotePath,
		'method': 'POST',
		'headers': {
			'Accept': 'application/json',
			'Content-Type': 'application/json, charset=\"utf-16\"',
			//'Authorization':'Basic '+activityUtils.endpOintcreds.token,
			'Content-Length': Buffer.byteLength(post_data)
		},
	};

	console.log('options=' + options);

	var httpsCall = https.request(options, function(response) {
		var data = '';
		var error = '';

		response.on( 'data' , function( chunk ) {
			data += chunk;
		});

		response.on( 'end' , function() {
			console.log("data:",data);
			console.log("response code:", response.statusCode);

			if (response.statusCode == 201) {
				data = JSON.parse(data);
				console.log('onEND PushResponse:', response.statusCode, data);
				res.send(200, {"pushId": 200});
			} else {
				console.log('onEND fail:', response.statusCode);
				res.send(response.statusCode, {"pushId": response.statusCode});
			}
		});
	});

	httpsCall.on( 'error', function( e ) {
		console.error(e);
		res.send(500, 'createCase', {}, { error: e });
	});

	httpsCall.write(post_data);
	httpsCall.end();

};<commit_msg>update env var<commit_after>'use strict';
var https = require( 'https' );
var crypto = require('crypto');
var activityUtils = require('./activityUtils');

/*
 * POST Handler for / route of Activity (this is the edit route).
 */
exports.edit = function( req, res ) {
    // Data from the req and put it in an array accessible to the main app.
    //console.log( req.body );
    activityUtils.logData( req );
    res.send( 200, 'Edit' );
};

/*
 * POST Handler for /save/ route of Activity.
 * This gets called when the journey is activated in the JB UI (and before /validate/ and /publish/)
 */
exports.save = function( req, res ) {
    // Data from the req and put it in an array accessible to the main app.
    //console.log( req.body );

    activityUtils.logData( req );
    res.send( 200, 'Save' );
};

/*
 * POST Handler for /publish/ route of Activity.
 * Run any code here that you need to run to prepare the activity for execution.
 * This gets called when the journey is activated in JB (and after /save/ and /validate/)
 */
exports.publish = function( req, res ) {
    // Data from the req and put it in an array accessible to the main app.
    //console.log( req.body );

    activityUtils.logData( req );
    res.send( 200, 'Publish' );
};

/*
 * POST Handler for /validate/ route of Activity.
 * This method should validate the configuration inputs.
 * This gets called when the journey is activated in the JB UI (and after /save/ but before /publish/)
 * Respond with 200 for valid, 500 for invalid
 */
exports.validate = function( req, res ) {
    // Data from the req and put it in an array accessible to the main app.
    //console.log( req.body );
    activityUtils.logData( req );
    res.send( 200, 'Validate' );

    /*
    If validation passes then call
    res.send( 200, 'Validate' );

    If validation fails then call
    res.send( 500, 'Validate' );
    */
};

/*
 * POST Handler for /execute/ route of Activity.
 * Runs each time the activity executes.  This is the main workhorse of the custom activity
 * Caller expects a 200 response back.  Can return any custom data also.
 */
exports.execute = function( req, res ) {
    // Data from the req and put it in an array accessible to the main app.
    activityUtils.logData( req );

	//merge the array of objects for easy access in code.
	var aArgs = req.body.inArguments;
	console.log( aArgs );
	var oArgs = {};
	for (var i=0; i<aArgs.length; i++) {
		for (var key in aArgs[i]) {
			oArgs[key] = aArgs[i][key];
		}
	}

	var contactKey = req.body.keyValue;

	// these values come from the config.json
	var EmailAddress = oArgs.EmailAddress;
	var Name = oArgs.Name;
	var muid = oArgs.muid;
	var SubscriberKey = oArgs.SubscriberKey;

	// these values come from the custom activity form inputs
	var pushMessage = oArgs.pushMessage;

	// Template Engine for personalisation
	var SFMCTemplateEngine = function(tpl, data) {
    var re = /<%([^%>]+)?%>/g, match;
    while(match = re.exec(tpl)) {
        tpl = tpl.replace(match[0], data[match[1]])
    }
    return tpl;
	}
	var template = pushMessage;

	var pushMessageText = SFMCTemplateEngine(template, {Name: Name});
	console.log('pushMessageText=' + pushMessageText);

	// Prepare post data for remote API
	var pushInfo = JSON.stringify({
    "pushInfo": [{"muid": muid, msg: pushMessageText}]
	});

	//var endpoint = "https://jsonplaceholder.typicode.com/posts";
	var endpoint = process.env.Remote_URL;
	var remoteHost = process.env.Remote_Host;
	var remotePort = process.env.Remote_Port;
	var remotePath = process.env.Remote_Path;
	var secret = process.env.API_Secret;
	var signature = endpoint + pushInfo + secret;
	var hash_signature = crypto.createHash('md5').update(signature).digest('hex');

	var post_data = JSON.stringify({
    	"pushInfo": [{"muid": muid, msg: pushMessageText}],
    	"s": hash_signature
	});

	console.log('post_data=' + post_data);

	var options = {
		'hostname': remoteHost,
		'port': remotePort,
		'path': remotePath,
		'method': 'POST',
		'headers': {
			'Accept': 'application/json',
			'Content-Type': 'application/json, charset=\"utf-16\"',
			//'Authorization':'Basic '+activityUtils.endpOintcreds.token,
			'Content-Length': Buffer.byteLength(post_data)
		},
	};

	console.log('options=' + options);

	var httpsCall = https.request(options, function(response) {
		var data = '';
		var error = '';

		response.on( 'data' , function( chunk ) {
			data += chunk;
		});

		response.on( 'end' , function() {
			console.log("data:",data);
			console.log("response code:", response.statusCode);

			if (response.statusCode == 201) {
				data = JSON.parse(data);
				console.log('onEND PushResponse:', response.statusCode, data);
				res.send(200, {"pushId": 200});
			} else {
				console.log('onEND fail:', response.statusCode);
				res.send(response.statusCode, {"pushId": response.statusCode});
			}
		});
	});

	httpsCall.on( 'error', function( e ) {
		console.error(e);
		res.send(500, 'createCase', {}, { error: e });
	});

	httpsCall.write(post_data);
	httpsCall.end();

};<|endoftext|>


<commit_before>'use strict';

const Transform = require('stream').Transform;
const util = require('util');
const _ = require('lodash');
const maxRecord = '[MAX]';
const codeRecord = "Code_";

function mapObject(options, mapSpecification, executeOnRead) {
    if (!(this instanceof mapObject)) {
        var ofInstance = new mapObject(options, mapSpecification, executeOnRead);
        ofInstance.mapSpecification = mapSpecification;
        if (!!executeOnRead)
            ofInstance.on('readable', executeOnRead);
        return ofInstance;
    }
    Transform.call(this, options);
}
util.inherits(mapObject, Transform);

mapObject.prototype._transform = function (chunk, encoding, done) {
    var data = JSON.parse(chunk.toString());
    var output = this.mapSpecification[this.mapSpecification.TargetElement] ||{};
    if (Array.isArray(data.Record)||false) {
        var mappedTarget = [];
        data.Record.forEach(function(record) {
            mappedTarget = this.mapSpecification.recordMaps.filter(function (mapItem) {
                return record.RecordType === mapItem.RecordType;
            })[0];
            if (!!mappedTarget) {
                mappedTarget.TargetHierarchy.forEach(function (mappedItem) {
                    if (!!mappedTarget.TargetIncrement) {
                        var TargetIncementProperty = mappedTarget.TargetIncementProperty;
                        if (TargetIncementProperty.indexOf(maxRecord) > -1) {
                            mappedTarget.ParentIncementedValue = ((_.get(output, mappedTarget.ParentRecord) || []).length -1) || 0;
                            TargetIncementProperty = TargetIncementProperty.replace (maxRecord, '[' + mappedTarget.ParentIncementedValue + ']');
                        }
                        mappedTarget.TargetIncementedValue = (_.get(output, TargetIncementProperty) || []).length || 0;
                    }
                    Object.keys(mappedItem).forEach(function(key) {
                        var targetProperty = mappedItem[key];
                        if (key.indexOf('*') > -1) {
                            _.set(output, targetProperty,  record[key.split('*')[0]].trim());
                        } else if (key.indexOf('$') > -1) {
                            var targetValue = key.split('$');
                            var sourceProperty = targetValue[0];
                            if (!!mappedTarget.TargetIncrement) {
                                targetProperty = targetProperty.replace (mappedTarget.TargetIncementProperty+'[0]', mappedTarget.TargetIncementProperty+'['+ mappedTarget.TargetIncementedValue +']');
                            }

                            if (targetValue[1] ===  record[sourceProperty]) {
                                //if (sourceProperty === 'PersonInvolvedType')
                                //    console.log('' + targetProperty + ' Key: ' + key + ' ' + targetValue[2]);
                                _.set(output, targetProperty, targetValue[2].trim());
                            } else if ((targetValue[3] || '') === '!') {
                                _.set(output, targetProperty, '');
                            }
                        } else if (((!!mappedItem[key]) && (!!record[key])) || (key.indexOf(codeRecord) > -1)) {

                            if (!!mappedTarget.TargetIncrement) {
                                targetProperty = targetProperty.replace (mappedTarget.TargetIncementProperty+'[0]', mappedTarget.TargetIncementProperty+'['+ mappedTarget.TargetIncementedValue +']');

                                if (targetProperty.indexOf(maxRecord) > -1) {
                                    targetProperty = targetProperty.replace (maxRecord, '[' + mappedTarget.ParentIncementedValue + ']');
                                    //console.log(targetProperty + ' ' + record[key]  + ' ' + mappedTarget.ParentIncementedValue);
                                }

                            }
                            if (key.indexOf(codeRecord) === -1) {
                                _.set(output, targetProperty, record[key].trim());
                            } else {
                                //console.log ('targetProprty: ' + targetProperty + ' V: ' + key.substr(5));
                                _.set(output, targetProperty, key.substr(5).trim());
                            }
                        }
                    });
                });
            }
            mappedTarget = [];
        }, this);
        this.push(JSON.stringify(output));
    } else {
        this.push(chunk);
    }
    done();
};

mapObject.prototype._flush = function (done) {
    done();
};

module.exports = mapObject;<commit_msg>update<commit_after>'use strict';

const Transform = require('stream').Transform;
const util = require('util');
const _ = require('lodash');
const maxRecord = '[MAX]';
const codeRecord = "Code_";

function mapObject(options, mapSpecification, executeOnRead) {
    if (!(this instanceof mapObject)) {
        var ofInstance = new mapObject(options, mapSpecification, executeOnRead);
        ofInstance.mapSpecification = mapSpecification;
        if (!!executeOnRead)
            ofInstance.on('readable', executeOnRead);
        return ofInstance;
    }
    Transform.call(this, options);
}
util.inherits(mapObject, Transform);

mapObject.prototype._transform = function (chunk, encoding, done) {
    var data = JSON.parse(chunk.toString());
    var output = this.mapSpecification[this.mapSpecification.TargetElement] ||{};
    if (Array.isArray(data.Record)||false) {
        var mappedTarget = [];
        data.Record.forEach(function(record) {
            mappedTarget = this.mapSpecification.recordMaps.filter(function (mapItem) {
                return record.RecordType === mapItem.RecordType;
            })[0];
            if (!!mappedTarget) {
                mappedTarget.TargetHierarchy.forEach(function (mappedItem) {
                    if (!!mappedTarget.TargetIncrement) {
                        var TargetIncementProperty = mappedTarget.TargetIncementProperty;
                        if (TargetIncementProperty.indexOf(maxRecord) > -1) {
                            mappedTarget.ParentIncementedValue = ((_.get(output, mappedTarget.ParentRecord) || []).length -1) || 0;
                            TargetIncementProperty = TargetIncementProperty.replace (maxRecord, '[' + mappedTarget.ParentIncementedValue + ']');
                        }
                        mappedTarget.TargetIncementedValue = (_.get(output, TargetIncementProperty) || []).length || 0;
                    }
                    Object.keys(mappedItem).forEach(function(key) {
                        var targetProperty = mappedItem[key];
                        if (key.indexOf('*') > -1) {
                            _.set(output, targetProperty,  record[key.split('*')[0]].trim());
                        } else if (key.indexOf('$') > -1) {
                            var targetValue = key.split('$');
                            var sourceProperty = targetValue[0];
                            if (!!mappedTarget.TargetIncrement) {
                                targetProperty = targetProperty.replace (mappedTarget.TargetIncementProperty+'[0]', mappedTarget.TargetIncementProperty+'['+ mappedTarget.TargetIncementedValue +']');
                            }

                            if (targetValue[1] ===  record[sourceProperty]) {
                                _.set(output, targetProperty, targetValue[2].trim());
                            } else if ((targetValue[3] || '') === '!') {
                                _.set(output, targetProperty, '');
                            }
                        } else if (((!!mappedItem[key]) && (!!record[key])) || (key.indexOf(codeRecord) > -1)) {

                            if (!!mappedTarget.TargetIncrement) {
                                targetProperty = targetProperty.replace (mappedTarget.TargetIncementProperty+'[0]', mappedTarget.TargetIncementProperty+'['+ mappedTarget.TargetIncementedValue +']');

                                if (targetProperty.indexOf(maxRecord) > -1) {
                                    targetProperty = targetProperty.replace (maxRecord, '[' + mappedTarget.ParentIncementedValue + ']');
                                    //console.log(targetProperty + ' ' + record[key]  + ' ' + mappedTarget.ParentIncementedValue);
                                }

                            }
                            if (key.indexOf(codeRecord) === -1) {
                                _.set(output, targetProperty, record[key].trim());
                            } else {
                                //console.log ('targetProprty: ' + targetProperty + ' V: ' + key.substr(5));
                                _.set(output, targetProperty, key.substr(5).trim());
                            }
                        }
                    });
                });
            }
            mappedTarget = [];
        }, this);
        this.push(JSON.stringify(output));
    } else {
        this.push(chunk);
    }
    done();
};

mapObject.prototype._flush = function (done) {
    done();
};

module.exports = mapObject;<|endoftext|>


<commit_before>$(function() {
  function getUrlVars() {
    var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0, len = hashes.length; i < len; i++) {
      hash = hashes[i].split('=');
      vars.push(hash[0]);
      vars[hash[0]] = hash[1];
    }
    return vars;
  }

  function getUrlVar(name){
    return getUrlVars()[name];
  }

  function onDesignPrinciples() {
    var pathName = window.location.pathname,
        isDP = !! pathName.match(/^\/designprinciples/),
        isPF;

    if (isDP) {
      // performance framework is in beta so return false to force that popup
      isPF = !! pathName.match(/^\/designprinciples\/performanceframework/);
      if (isPF) {
        return false;
      }

      return true;
    }

    return false;
  }

  function activeCookieName() {
    if (onDesignPrinciples()){
      return 'dp-tour';
    } else {
      return 'govuk-tour';
    }
  }

  $(".welcome-tour .close").live("click", function(){
    setCookie(activeCookieName(), "dismiss", 7);
    return;
  });

  $(".thanks-dismiss").live("click", function(){
    setCookie(activeCookieName(), "dismiss", 7);
    closePopup();
    return false;
  });

  closePopup = function(){
    $("#popup").fadeOut(400, function(){
      $("#mask").slideUp('fast');
      $("#mask").remove();
      $("#popup").remove();
    });

    $(".header-global h1 a").focus();
  };

});

function setCookie(name, value, days){
  var cookieString = name + "=" + value + "; path=/";
  if (days) {
    var date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    cookieString = cookieString + "; expires=" + date.toGMTString();
  }
  if (document.location.protocol == 'https:'){
    cookieString = cookieString + "; Secure";
  }
  document.cookie = cookieString;
}

function getCookie(name){
  var nameEQ = name + "=";
  var ca = document.cookie.split(';');
  for(var i = 0, len = ca.length; i < len; i++) {
    var c = ca[i];
    while (c.charAt(0) == ' ') {
      c = c.substring(1, c.length);
    }
    if (c.indexOf(nameEQ) === 0) {
      return c.substring(nameEQ.length, c.length);
    }
  }
  return null;
}

$(function() {
  var addStyle,
      $message = $('#global-cookie-message'),
      $relatedColumn = $('#wrapper .related-positioning');

  if ($message.length && getCookie('seen_cookie_message') === null) {
    if ($relatedColumn.length) {
      // correct the related module top position to consider the cookie bar
      $relatedColumn.addClass('related-with-cookie');
      // related content box needs to know the top position of the footer
      // this changes when content is split into tabs
      if (typeof GOVUK.stopScrollingAtFooter !== 'undefined') {
              GOVUK.stopScrollingAtFooter.updateFooterTop();
      }
    }
    $message.show();
    setCookie('seen_cookie_message', 'yes', 28);
  }
});
<commit_msg>Remove design principles pop up javascript<commit_after>function setCookie(name, value, days){
  var cookieString = name + "=" + value + "; path=/";
  if (days) {
    var date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    cookieString = cookieString + "; expires=" + date.toGMTString();
  }
  if (document.location.protocol == 'https:'){
    cookieString = cookieString + "; Secure";
  }
  document.cookie = cookieString;
}

function getCookie(name){
  var nameEQ = name + "=";
  var ca = document.cookie.split(';');
  for(var i = 0, len = ca.length; i < len; i++) {
    var c = ca[i];
    while (c.charAt(0) == ' ') {
      c = c.substring(1, c.length);
    }
    if (c.indexOf(nameEQ) === 0) {
      return c.substring(nameEQ.length, c.length);
    }
  }
  return null;
}

$(function() {
  var addStyle,
  $message = $('#global-cookie-message'),
  $relatedColumn = $('#wrapper .related-positioning');

  if ($message.length && getCookie('seen_cookie_message') === null) {
    if ($relatedColumn.length) {
      // correct the related module top position to consider the cookie bar
      $relatedColumn.addClass('related-with-cookie');
      // related content box needs to know the top position of the footer
      // this changes when content is split into tabs
      if (typeof GOVUK.stopScrollingAtFooter !== 'undefined') {
        GOVUK.stopScrollingAtFooter.updateFooterTop();
      }
    }
    $message.show();
    setCookie('seen_cookie_message', 'yes', 28);
  }
});
<|endoftext|>