Home > Software engineering >  Add and Remove Buttons JavaScript
Add and Remove Buttons JavaScript

Time:08-29

I've tried this code that was posted but I can't get it to work the way I need. I can get it to create both fields but I can't make them look the same as the format of the first fields. How can I Add the entire section and make it look the same? and control the maximum number of activities inserted.

TIA

This is what I'm trying to acomplish

const addActivity = document.getElementById("add");
var i = 0;
const activityDiv = document.getElementById("Activity");

addActivity.addEventListener("click", function() {
  i  ;
  const newspan = document.createElement('div');
  newspan.className = "activityGroup";
  const removeButton = document.createElement('button');
  removeButton.addEventListener('click', function(e) {
    e.target.closest(".activityGroup").remove();
  });
  removeButton.className  = "delbtn";
  removeButton.innerHTML = "X";
//
  const txtfield = document.createElement('input');
  const txtarea = document.createElement('textarea');
  //
  txtfield.id = 'activity_'   i;
  txtfield.placeholder = "Activity "   i;
  newspan.appendChild(txtfield);
  //
  newspan.appendChild(txtarea);
  txtarea.id = 'activity_description_'   i;
  txtarea.placeholder = "Activity Description "   i;

  
  newspan.appendChild(removeButton);
  activityDiv.appendChild(newspan);
});
.delbtn{color:red;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="Activity">
  <div >
  <input placeHolder="Type your activity" />
  <br>
  <br>
  <textarea rows="5" cols="50" placeHolder="Type your descvription"></textarea>
  <button >X</button>
  </div>
 <!-- Remove button -->
</div>
<button id="add">Add Activity</button>

CodePudding user response:

Do you mean "If I have Activity 1, 2, 3, 4, 5 and Delete 3 I want to be able to get Activities 1, 2, 4, 5?"

From what I've gathered, you want a button that adds and deletes activities but you only want it to work when previous activity isn't empty.

In the javascript, when you handle the buttons click event check previous activity, if it's empty, return, if it isn't empty, execute the add/delete code.

CodePudding user response:

First, the problems:

I can get it to create both fields but I can't make them look the same as the format of the first fields...

This is likely because in your original .activityGroup there are a number of <br> elements between the <input> and the <textarea> elements – incorrectly – used for spacing which you haven't inserted into the copy.

Also, two of the attributes of the original <textarea> (rows="5", and cols="50") are omitted from the one you create within the function, not to mention the line-breaks and other white-space.

This can be rectified by simply creating, and appending, those other elements:

// first, some utility functions to simplify life:
const D = document,
  get = (sel, ctx = D) => ctx.querySelector(sel),
  getAll = (sel, ctx = D) => [...ctx.querySelectorAll(sel)],
  create = (tag, props) => Object.assign(D.createElement(tag), props),
  // renamed variables for consistency between JavaScript
  // and the HTML upon which it acts:
  addBtn = get("#addBtn"),
  activity = get("#activity"),
  // creating a generator function to use as a counter;
  // fundamentally this is little different from the
  // original i = 0 and then incrementing in the function
  // body, but the generator can't be affected by an
  // accidental decrement:
  iterator = function*() {
    // initial value:
    let count = 1;
    // a deliberate infinite loop, but the
    // generator function exits/pauses at
    // every yield so while this is an 'infinite
    // function' it doesn't cause any blocking:
    while (true) {
        // yields value and then increments it:
      yield count  
    }
  },
  // getting a reference to the generator function:
  counter = iterator(),
  remove = (e) => e.currentTarget.closest('div').remove();

addBtn.addEventListener("click", function() {

  // getting the current count of all elements matching
  // the selector within the document:
  const currentCount = getAll('.activityGroup').length,
            // retrieving the next value from the
        // generator function:
            i = counter.next().value;
  
  // if the currentCount is greater than 4 (again, using
  // a Yoda condition):
  if (4 < currentCount) {
    // exit the function:
    return false;
  }
  
  // creating a new <div>:
  const activityGroup = create('div', {
      // assigning the class-name of 'activityGroup':
      className: 'activityGroup'
    }),
    // creating a new <button>:
    removeButton = create('button', {
      // assigning the class-name of 'delBtn',
      className: 'delBtn',
      // and the text-content of 'X':
      textContent: 'X'
    }),
    input = create('input', {
        type: 'text',
      id: `activity_${i}`,
      placeholder: `Activity ${i}`
    }),
    textarea = create('textarea', {
        id: `activity_description_${i}`,
        placeholder: `Activity description ${i}`
    });

  // binding the remove() function as the'click' event-handler
  // on the removeButton:
  removeButton.addEventListener('click', remove);
  
  // using the HTMLElement.dataset API to set the
  // custom data-count attribute to the current
  // value of the i variable (for use in the CSS):
  activityGroup.dataset.count = i;
  // using Element.append() to append multiple elements
  // in one call:
  activityGroup.append(input, textarea, removeButton);
  activity.appendChild(activityGroup);
});
/* caching common values (useful for theming and
   maintenance): */
:root {
  --bg-primary: hsl(0 0% 0% / 0.3);
  --columns: 2;
  --fs: 16px;
  --spacing: 0.5em;
}

/* a simple CSS reset to remove default
   margins and padding, and to have all
   elements sized in a way that includes
   their padding and border-widths in the
   assigned size: */
*,::before,::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-size: var(--fs);
}

#activity {
  /* using CSS Grid for layout: */
  display: grid;
  /* setting a space - held in the custom
     property - between adjacent elements: */
  gap: var(--spacing);
  /* creating a number of columns (equal to the value held
     in the --columns property) of equal width, each of one
     fraction of the remaining space) */
  grid-template-columns: repeat(var(--columns), 1fr);
  padding: var(--spacing);
}

.activityGroup {
  /* aligning an element in the grid to the start of that
     grid (alignment is on the cross-axis of the inline-
     axis, the inline-axis being the writing direction
     of the language of the user's browser): */
  align-self: start;
  border: 0.2em solid var(--bg-primary);
  display: grid;
  gap: inherit;
  padding: var(--spacing);
}

.activityGroup::before {
  background-color: var(--bg-primary);
  /* setting the content of the ::before pseudo element to the
     string of 'Activity ' concatenated with the attribute-value
     of the data-count attribute: */
  content: "Activity " attr(data-count);
  display: block;
  /* spanning all three grid-columns: */
  grid-column: span 3;
  /* applying padding to the start of the inline axis: */
  padding-inline-start: var(--spacing);
  /* moving the element outwards with negative margins,
     to place the content against the outer edges of the
     element, despite the spacing on that element: */
  margin-block-start: calc(-1 * var(--spacing));
  margin-inline: calc(-1 * var(--spacing));
}

input,
textarea {
  grid-column: span 3;
  padding: 0.5em;
}

textarea {
  min-block-size: 5em;
  resize: vertical;
}

.delBtn {
  color: red;
  grid-column: 3;
}

/* using a simple media query to modify the values of the CSS Custom
   properties as required to improve the experience on different
   devices: */
@media screen and (max-width: 700px) {
  :root {
    --columns: 1;
    --fs: 18px;
  }
}
<!-- we don't appear to be using jQuery, so I removed that <script> element -->
<!-- switching id and classes to use camelCase rather than
     having arbitrary mixed-case format; choose a style you
     prefer, but remember that consistency matters more than
     the choice you make: -->
<div id="activity">
  <div >
    <input placeHolder="Type your activity" />
    <!-- removed the <br> elements, as well as the "rows" and "cols"
         attributes from the <textarea>: -->
    <textarea placeHolder="Type your description"></textarea>
    <button >X</button>
  </div>
  <!-- Remove button -->
</div>
<!-- changed the id from "add" to "addBtn," purely for consistency
     but, again, make your own choice on that: -->
<button id="addBtn">Add Activity</button>

As to your question:

How can I [add an] entire section and...control the maximum number of activities inserted[?]

Controlling the number of total activities/sections on the page is relatively easy and simply requires you to check the number of existing sections before adding a new one:

const addActivity = document.getElementById("add");
var i = 0;
const activityDiv = document.getElementById("Activity");

addActivity.addEventListener("click", function() {

  // using document.querySelectorAll() to retrieve a NodeList of all
  // elements matching the supplied CSS selector, and then retrieving
  // the length of that NodeList:
  let currentCount = document.querySelectorAll('.activityGroup').length;

  // using a "Yoda condition" to see if the currentCount is greater than
  // 4 (this approach guards against the most likely error of an assessment,
  // that of accidentally assigning a value instead of comparing); if it is:
  if (4 < currentCount) {
    // we exit the function here:
    return false;
  }

  i  ;

  const newspan = document.createElement('div');
  newspan.className = "activityGroup";
  // creating a <br> element:
  const br = document.createElement('br');
  const removeButton = document.createElement('button');
  removeButton.addEventListener('click', function(e) {
    e.target.closest(".activityGroup").remove();
  });
  removeButton.className = "delbtn";
  removeButton.innerHTML = "X";
  //
  const txtfield = document.createElement('input');
  const txtarea = document.createElement('textarea');
  //
  txtfield.id = 'activity_'   i;
  txtfield.placeholder = "Activity "   i;
  newspan.appendChild(txtfield);
  // appending a clone of the <br> element:
  newspan.appendChild(br.cloneNode());
  // appending the <br> element:
  newspan.appendChild(br);
  //
  newspan.appendChild(txtarea);
  txtarea.id = 'activity_description_'   i;
  txtarea.placeholder = "Activity Description "   i;
  // setting the rows and cols attributes:
  txtarea.setAttribute('rows', 5);
  txtarea.setAttribute('cols', 50);
  // appending white-space (approximating what's in the HTML):
  newspan.append(document.createTextNode('\n '));

  newspan.appendChild(removeButton);
  activityDiv.appendChild(newspan);
});
.delbtn {
  color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="Activity">
  <div >
    <input placeHolder="Type your activity" />
    <br>
    <br>
    <textarea rows="5" cols="50" placeHolder="Type your descvription"></textarea>
    <button >X</button>
  </div>
  <!-- Remove button -->
</div>
<button id="add">Add Activity</button>

Now, I'd like to improve the above functions as follows with explanatory comments in the code itself:

const addActivity = document.getElementById("add");
var i = 0;
const activityDiv = document.getElementById("Activity");

addActivity.addEventListener("click", function() {

  // using document.querySelectorAll() to retrieve a NodeList of all
  // elements matching the supplied CSS selector, and then retrieving
  // the length of that NodeList:
  let currentCount = document.querySelectorAll('.activityGroup').length;

  // using a "Yoda condition" to see if the currentCount is greater than
  // 4 (this approach guards against the most likely error of an assessment,
  // that of accidentally assigning a value instead of comparing); if it is:
  if (4 < currentCount) {
    // we exit the function here:
    return false;
  }

  i  ;

  const newspan = document.createElement('div');
  newspan.className = "activityGroup";
  // creating a <br> element:
  const br = document.createElement('br');
  const removeButton = document.createElement('button');
  removeButton.addEventListener('click', function(e) {
    e.target.closest(".activityGroup").remove();
  });
  removeButton.className = "delbtn";
  removeButton.innerHTML = "X";
  //
  const txtfield = document.createElement('input');
  const txtarea = document.createElement('textarea');
  //
  txtfield.id = 'activity_'   i;
  txtfield.placeholder = "Activity "   i;
  newspan.appendChild(txtfield);
  // appending a clone of the <br> element:
  newspan.appendChild(br.cloneNode());
  // appending the <br> element:
  newspan.appendChild(br);
  //
  newspan.appendChild(txtarea);
  txtarea.id = 'activity_description_'   i;
  txtarea.placeholder = "Activity Description "   i;
  // setting the rows and cols attributes:
  txtarea.setAttribute('rows', 5);
  txtarea.setAttribute('cols', 50);
  // appending white-space (approximating what's in the HTML):
  newspan.append(document.createTextNode('\n '));

  newspan.appendChild(removeButton);
  activityDiv.appendChild(newspan);
});
.delbtn {
  color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="Activity">
  <div >
    <input placeHolder="Type your activity" />
    <br>
    <br>
    <textarea rows="5" cols="50" placeHolder="Type your descvription"></textarea>
    <button >X</button>
  </div>
  <!-- Remove button -->
</div>
<button id="add">Add Activity</button>

JS Fiddle demo.

References:

  • Related