2
\$\begingroup\$

I'm developing a web app that will be used privately by a company (no need for SEO and old browsers support).

I made some interactive components (calendar, file manager, etc) that I am not sure if I implemented them properly. I use the simplest of them (contact picker) as an example here.

I'm wondering if I should make a js object for each component (with getters, setters, etc), or if there is any other design flaw.
Also is it ok to write plain html code inside js like I did in createContactPicker, considering that it would be a much longer code if I used a bunch of createElement and setAttribute?

The contact picker:

The contact picker is a text box that shows suggestions for contacts (based on server's response to ajax requests) while the user types the name of the contact. The hidden input with name="contact_id" is the value that will actually be used in html forms, the rest is there for the user's convenience.

contactPicker.js

function createContactPicker(container, contactType){
   const picker = document.createElement('div');
   picker.classList.add('contact_picker');
   let html = '<input type="hidden" name="contact_id">';
   if(contactType != null){
      picker.setAttribute('data-type', contactType);
   }
   html += '<input type="text" class="contact_name" oninput="suggestContacts(this)" onfocus="toggleSuggestions(this, true)" onblur="toggleSuggestions(this, false)"><table class="dropdown"></table>';
   picker.innerHTML = html;
   container.appendChild(picker);
}

// onload replace elements of class 'onload_create_contact_picker' with contact pickers
window.addEventListener('DOMContentLoaded', function(){
   let elements = document.getElementsByClassName('onload_create_contact_picker');
   for(let i = elements.length - 1; i >=0; i--){
      let elem = elements.item(i);
      createContactPicker(elem, elem.getAttribute('data-type'));
      let parent = elem.parentElement;
      parent.insertBefore(elem.firstChild, elem);
      parent.removeChild(elem);
   }
});

function suggestContacts(sender){
   const picker = sender.closest('.contact_picker');
   unpickContant(picker);

   const contactName = picker.getElementsByClassName('contact_name')[0].value;
   if(contactName == ''){
      const container = picker.getElementsByClassName('dropdown')[0];
      container.innerHTML = '';
      return;
   }
   const contactType = picker.getAttribute('data-type');
   const ajaxData = {name: contactName, type: contactType};
   if(contactType == null){
      delete ajaxData.type;
   }

   $.ajax({
      type: 'GET',
      url: '../ajax/suggestContacts.php',
      data: ajaxData,
      success: function(data){
         updateSuggestions(data, picker);
      }
   });
}

function updateSuggestions(data, picker){
   const container = picker.getElementsByClassName('dropdown')[0];
   data = JSON.parse(data);

   if(data.length == 0){
      container.innerHTML = '<span class="note">No matches</span>';
   } else {
      container.innerHTML = '';

      for(contact of data){
         const tr = document.createElement('tr');
         let td = document.createElement('td');
         td.textContent = contact.type;
         tr.appendChild(td);
         td = document.createElement('td');
         td.textContent = contact.name;
         let span = document.createElement('span');
         span.textContent = contact.title;
         span.classList.add('note');
         td.appendChild(document.createElement('br'));
         td.appendChild(span);
         tr.appendChild(td);
         tr.setAttribute('data-contact-id', contact.id);
         tr.setAttribute('data-contact-name', contact.name);
         tr.setAttribute('onmousedown', 'pickContact(this)');
         container.appendChild(tr);
      }
   }
   positionSuggestions(picker, container);
}

function positionSuggestions(picker, container){
   const rectP = picker.getBoundingClientRect();
   const rectC = container.getBoundingClientRect();

   if(rectP.bottom + rectC.height > (window.innerHeight || document.documentElement.clientHeight)){
      container.style.top = 'auto';
      container.style.bottom = 'calc(100% - 3px)';
   } else {
      container.style.bottom = 'auto';
      container.style.top = 'calc(100% - 3px)';
   }
}

function clearSuggestions(sender){
   const picker = sender.closest('.contact_picker');
   const container = picker.getElementsByClassName('dropdown')[0];
   container.innerHTML = '';
}

function toggleSuggestions(sender, visible){
   const picker = sender.closest('.contact_picker');
   const container = picker.getElementsByClassName('dropdown')[0];
   container.style.display = visible ? '' : 'none';
}

function pickContact(selected){
   const picker = selected.closest('.contact_picker');
   const idElem = picker.querySelector('input[name="contact_id"]');
   const nameElem = picker.getElementsByClassName('contact_name')[0];
   nameElem.value = selected.getAttribute('data-contact-name');
   idElem.value = selected.getAttribute('data-contact-id');
   if(!picker.classList.contains('picked')){
      picker.classList.add('picked');
   }
}

function unpickContant(picker){
   picker.querySelector('input[name="contact_id"]').value = null;
   picker.classList.remove('picked');
}

css (only the suggestions positioning)

.contact_picker{
   position: relative;
}
.contact_picker .dropdown{
   position: absolute;
   z-index: 1;
   left: 2px;
}

Usage example:

<html>
   <head>
      <script src="javascript/jquery-3.4.0.min.js"></script>
      <script src="components/contactPicker.js"></script>
   </head>
   <body>
      <form name="choose_client_example">
         Choose a client:
         <div class="onload_create_contact_picker" data-type="client"></div>
         <button>Submit</button>
      </form>
   </body>
</html>
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.