Code to dynamically add a div to an HTML template using JavaScript logic

A simple web application was developed by me to retrieve recipe data from an API. This data is then displayed by inserting it into an HTML template specified in the JavaScript file. The layout is managed using a float grid in CSS.

Here is the code snippet responsible for rendering the result and inserting it into the template:

function displayRecipeSearchData(data) {
  var results = ' ';
  if (data.hits.length) {
    data.hits.forEach(function(item) {
      results += template.item(item);
    });
  }
  else {
    results += '<p> No results </p>';
  }
  $('#js-search-results').html(results);
}

The HTML template used to display responses:

const template = {
  item: function(item) {
    return '<div class ="col-4">' +
             '<div class ="result">' +
               '<div class="recipelabel">' +
                 '<div class="reclist">' + item.recipe.ingredientLines + '</div><!-- end reclist -->' +
                  '<p class="label">' + item.recipe.label + '</p>' +
                  '<div class="thumbnail">' + 
                    '<a href="'+ httpsTransform(item.recipe.url) + '" target="_blank">' +
                      '<img src="' + item.recipe.image + '"alt="' + item.recipe.label + '">' +
                    '</a>' +
                    '<div class="recipesource">' +
                      '<p class="source">' + item.recipe.source + '</p>' +
                    '</div><!-- end recipesource -->' +
                  '</div><!-- end thumbnail -->' +
                '</div><!-- end recipelabel -->' +
              '</div><!-- end result -->' + 
            '</div><!-- end col-4 -->';
  }
};

I am attempting to modify the logic in the displayRecipeSearchData function so that a <div></div> encompasses every group of three results. This will ensure that the rows/columns function correctly in the flex grid. I have experimented with various approaches, but have not achieved the correct syntax/logic yet. Would nesting an if statement within the existing statement be an effective solution?

if(i % 3 === 0 ){ results. += '<div class="row">''</div>'}

Any advice or suggestions on this matter would be greatly appreciated.

Answer №1

Consider utilizing an additional variable to store a single row of HTML:

function displayRecipeSearchData(data) {
  var results = ' ', row = '';
  if (data.hits.length) {
    data.hits.forEach(function(item, i) {
      row += template.item(item);
      if (i % 3 == 2) { // wrap row and add to result
        results += '<div class="row">' + row + '</div>';
        row = '';
      }
    });
    if (row.length) { // flush remainder into a row
      results += '<div class="row">' + row + '</div>';
    }
  }
  else {
    results += '<p> No results </p>';
  }
  $('#js-search-results').html(results);
}

Answer №2

In my opinion, you're making things more difficult than they need to be.

Instead of manually writing the template as a string and trying to inject it into the correct place (which could potentially create invalid HTML), consider using JavaScript's built-in element creation. It will be more modular to create children in their own functions. Using a function instead of an object to hold your object creator will also make it much easier. Although my version may involve more code, it will be easier to modify in the long run.

const Itemizer = function(){
  this.items = [];
  const createEl = function(elType, classes, attributes, text, html){
    let el = document.createElement(elType)
    for(let i = 0; i < classes.length; i++){
      el.classList.add(classes[i]
    }
    for(let attr in attributes){
      el.setAttribute(attr, attributes[attr])
    }
    if(text){
      el.innerText = text
    }
    if(html){
      el.innerHTML = html
    }
    return el
  };

  const createThumbnail = function(url, image, alt, source){
    let thumbnail = createEl("DIV", ["thumbnail"]),
        link = createEl("A", [], {href: httpsTransform(url)}),
        img = createEl("IMG", [], {src: image, alt: label});
        rSource = createRecipeSource(source)
    link.appendChild(img);
    thumbnail.appendChild(link);
    thumbnail.appendChild(rSource)
    return thumbnail
  };

  const createRecipeSource = function(source){
    let wrapper = createEl("DIV", ["recipe-source"]);
    wrapper.appendChild(createEl("P", ["source"], {}, source))
    return wrapper
  }

  const createRecipeLabel = function({
    recipe: {
      ingredientLines,
      label,
      url,
      source
    }
  }){
    let labelWrapper = createEl("DIV", ["recipe-label"]),
        ingredients = createEl("DIV", ["rec-list"], {}, false, ingredientLines),
        recipeLabel = createEl("P", ["label"], {}, label),
        thumbnail = createThumbnail(url, image, label, source)
    labelWrapper.appendChild(ingredients)
    labelWrapper.appendChild(recipeLabel)
    labelWrapper.appendChild(thumbnail)
    return labelWrapper
  }

  const createNewItem = function(data){
    let columnWrapper = createEl("DIV", ["col-4"]),
        result = createEl("DIV", ["result"]),
        label = createRecipeLabel(data)

    columnWrapper.appendChild(result)
    result.appendChild(label)
    this.items.push(columnWrapper)
    return columnWrapper
  }.bind(this)

  const getItems = function(){
   return this.items
  }.bind(this)

  const getRows = function(){
    const rows = []
    let row;
    for(let i = 0; i < this.items.length; i++){
      const item = this.items[i]
      if(i % 3 === 0){
        row = createEl("DIV", ["row"])
        rows.push(row)
      }
      row.appendChild(item)
    }
    return rows;
  }.bind(this)

  return {
    add: createNewItem,
    get: getItems,
    rows: getRows
  }
}

You can then implement the function in the following way:

const template = new Itemizer()
function displayRecipeSearchData(data) {
  let rows
  if (data.hits.length) {
    for(let i = 0; i < data.hits.length; i++){
      template.add(data.hits[i])
    }
    rows = template.rows()
  } else {
    const p = document.createElement("P")
    p.innerText = "No Results")
    rows = [p]
  }
  const resultsWrapper = document.getElementById("js-search-results");
  for(let i = 0; i < rows.length; i++){
    resultsWrapper.appendChild(rows[i])
  }
}

It's also recommended to separate CSS classes with hyphens, so I have adjusted some of your class names to reflect that.

Additionally, note that you don't necessarily need more than 1 row. If you wrap all your items in one row section, columns will automatically overflow to the next row when they reach the grid limit.

Lastly, avoid using target blank as it goes against proper UX and introduces security vulnerabilities. If users need to open a link in a new tab, they can simply hold ctrl or right-click and choose "open in new tab."

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue encountered with JavaScript function within TypeScript file connected to HTML code

I am currently working on a simple SharePoint web part and encountering an issue with using a function from another module file in my main file. Snippet from the JSFunctions.module.js file (where I define my function): function getApi(){ [my code]... }; ...

Apply jQuery addClass to the first child option in a multi-select dropdown menu

I'm looking to dynamically add the class userFirstChoice to the first option a user selects from a dropdown list. The CSS for .userFirstChoice is designed to visually highlight the first choice in the list. Is there a way to achieve this using jQuery ...

Error message indicating that Element <> cannot be scrolled into view persisting despite attempting common troubleshooting methods

Currently, I am utilizing Selenium to create a web scraper for downloading multiple podcast episodes from Spreaker. # https://www.spreaker.com/show/alabamas-morning-news-with-jt for i in range(3): print("Click number: {}".format(str(i))) see_mor ...

Is it possible to trigger a JavaScript function by manipulating the URL of an HTML page?

Imagine this scenario: You have an HTML page located at example.com/test.html that contains several pre-defined JavaScript functions, including one named play(). How can I add JavaScript to the URL in order to automatically trigger the play() function wh ...

When Components in Vue are called in a Single File, certain elements may not be displaying as expected

I have just finished creating my components using Vue 2, Vuetify, and Vue cli - 4.5.15. I attempted to combine them into a Single Vue file but encountered issues with the components not displaying <v-icons>, <v-textfield>, and some other elemen ...

Unable to activate focus() on a specific text field

It's quite peculiar. I'm working with a Sammy.js application, and my goal is to set the focus on a text field as soon as the HTML loads. Here's the CoffeeScript code snippet I've written: this.partial('templates/my-template.jqt&ap ...

Utilizing Props in React to Slice and Dice Data Within a Separate Component

Currently, I am in the process of creating an about text for a profile that will include an option to expand or collapse based on its length. To achieve this, I am utilizing a function from the main home component: <AboutText text={aboutData}/> Abo ...

Experiencing the Pause: A Fresh Take on

I'm currently using this slideshow for a project, and I need to understand if it's possible to resume its interval. The current issue is that when you hover over the slideshow, the progress bar stops. But once you remove the mouse, it continues f ...

Instructions for appending an id to the URL of events in fullcalendar using Rails

Looking for a way to attach an ID to the URL of a fullcalendar event in a Rails application? I am using a json.jbuilder file: json.array!(@estudiante.clases) do |clase| json.extract! clase, :id json.id clase.id json.title clase.name json.start cl ...

Exclude specific fields when updating a document in Firebase using the update()

Currently, I am storing a class in Firebase by using the update() function. Is there a way to stop specific fields (identified by name) of the object from being saved to the Firebase database? It's similar to how we use the transient keyword in Java ...

Prevent pinch zoom in webkit (or electron)

Is there a way to prevent pinch zoom in an electron app? I've tried different methods, such as using event.preventDefault() on touchmove/mousemove events in JavaScript, adding meta viewport tags in HTML, adjusting -webkit-text-size-adjust in CSS, and ...

Set a value for the hidden field on the page using client-side scripting

When working with an ASP.net application, I often use Page.ClientScript.RegisterHiddenField("hf_Name", value). However, I am curious about how to override or set a new value for the same Hidden Field 'hf_Name' in the code behind. Can you provide ...

Issue with wrapper not aligning correctly at the top of the screen

I keep noticing a gap between my wrapper and the top of the page. Despite trying multiple solutions, none seem to work for me. The background image covers the entire background and is aligned at the top perfectly, but the wrapper with its own background a ...

Once an AJAX request is sent on mouseover and a response is received, it should not be sent again if I hover over the HTML element a second time

During a mouse over event, I am sending an ajax request and successfully receiving the desired response. However, if I hover over the same element again, the request is sent once more. Instead of resending the request, I would like the page to use the pre ...

A guide to customizing node names using vue-slider-component

I am facing an issue with the vue-slider-component. Below is the link to my current test module: template:` <div> <vue-slider v-model="value" :order="false" :tooltip="'always'" :process="false" ...

I am encountering an issue where the nested loop in Angular TypeScript is failing to return

I am facing an issue with my nested loop inside a function. The problem is that it is only returning the default value of false, instead of the value calculated within the loop. Can someone please point out what I might be doing incorrectly? Provided belo ...

JavaScript - Unexpected fluctuations in variable values

After studying Japanese language, I decided to try my hand at experimenting with JavaScript by creating a simple FlashCard game for a project. The game generates an array of random numbers, fills the divs with 6 possible choices using jQuery, randomly sele ...

Having trouble getting the jQuery autocomplete feature to work on textboxes that are generated dynamically

I've encountered an issue with implementing autocomplete on dynamically generated textboxes. Despite searching online, I haven't been able to find a suitable solution yet. Below is what I have tried so far: JQUERY: function bindAutoComplete(cla ...

Updating JQuery dropdown menu fills in additional form fields based on selection

I have a dropdown menu (select) that is dynamically generated using JSON data from a PHP script and JQuery. Please refer to the image under the Components label. The select menu displays the Component name and Component ID as values. I would like to use a ...

Fade-In and Fade-Out CSS Effect with Background Images Displaying a Blank Last Image

I've been experimenting with applying a CSS-only background image transition to a div, but I encountered an issue where after cycling through three specified images, the background reverts back to the original black color. Even stranger, when I adjust ...