3
\$\begingroup\$

In the process of learning JavaScript, I built a Google Chrome extension. It works but as I am adding more features the code is quickly getting duplicated, so I'm looking for ways to improve it.

This is the code (other parts of the extension are omitted for brevity):

let createProject = document.getElementById('cproject');
var errorMessage = document.getElementById('error_message');

const cprojectRipple = new mdc.ripple.MDCRipple(document.getElementById('cproject'));
const pLanguageElement = new mdc.select.MDCSelect(document.getElementById('planguage'));
const buildtoolElement = new mdc.select.MDCSelect(document.getElementById('buildtool'));
const frameworkElement = new mdc.select.MDCSelect(document.getElementById('framework'));
const buildtooljsElement = new mdc.select.MDCSelect(document.getElementById('buildtool-js'));
const frameworkjsElement = new mdc.select.MDCSelect(document.getElementById('framework-js'));

const isPrivateSwitch = new mdc.switchControl.MDCSwitch(document.querySelector('.mdc-switch'));
const pnameElement = new mdc.textField.MDCTextField(document.querySelector('.mdc-text-field'));


frameworkElement.listen('MDCSelect:change', () => {
  // Set build tool to Maven if a framework is selected
  if (frameworkElement.value !== 'none' && buildtoolElement.value === 'none') {
    buildtoolElement.selectedIndex = 1;
  }
});

buildtoolElement.listen('MDCSelect:change', () => {
  // Reset framework if build tool is none
  if (buildtoolElement.value === 'none' && frameworkElement.value !== 'none') {
    frameworkElement.selectedIndex = 0;
  }
});

frameworkjsElement.listen('MDCSelect:change', () => {
  if (frameworkjsElement.selectedIndex !== buildtooljsElement.selectedIndex) {
    buildtooljsElement.selectedIndex = frameworkjsElement.selectedIndex;
  }
});

buildtooljsElement.listen('MDCSelect:change', () => {
  if (frameworkjsElement.selectedIndex !== buildtooljsElement.selectedIndex) {
    frameworkjsElement.selectedIndex = buildtooljsElement.selectedIndex;
  }
});

pLanguageElement.listen('MDCSelect:change', () => {
  console.log(`Selected language at index ${pLanguageElement.selectedIndex} with value "${pLanguageElement.value}"`);
  const javaConfig = document.getElementById('java-configurator');
  const pyConfig = document.getElementById('python-configurator');
  const jsConfig = document.getElementById('javascript-configurator');
  if (pLanguageElement.value === 'java') {
    javaConfig.style.display = 'inline';
    pyConfig.style.display = 'none';
    jsConfig.style.display = 'none';
  } else if (pLanguageElement.value === 'python') {
    javaConfig.style.display = 'none';
    jsConfig.style.display = 'none';
    pyConfig.style.display = 'inline';
  } else if(pLanguageElement.value === 'javascript'){
    javaConfig.style.display = 'none';
    jsConfig.style.display = 'inline';
    pyConfig.style.display = 'node';
  }
  buildtoolElement.selectedIndex=0;
  frameworkElement.selectedIndex=0;
  buildtooljsElement.selectedIndex=0;
  frameworkjsElement.selectedIndex=0;
});


createProject.onclick = function () {
  errorMessage.innerHTML = '';
  // Validate project name
  if (!pnameElement.valid) {
    errorMessage.innerHTML = 'Please provide the project name';
    return false;
  }
  var projectName = pnameElement.value;
  console.log('Project name: ', projectName);

  // Get language
  const language = pLanguageElement.value;
  console.log('Language selected: ', language);
  // Get Build Tool
  const buildtool = buildtoolElement.value;
  console.log('Build tool selected: ', buildtool);
  
  const buildtooljs = buildtooljsElement.value;
  const frameworkjs = frameworkjsElement.value;
  
  //Get Framework
  const framework = frameworkElement.value;
  console.log('Framework selected: ', framework);
  

  // build project type
  var ptype = language;
  if (buildtool !== 'none') {
    ptype = ptype + '-' + buildtool;
  }
  if (framework !== 'none') {
    ptype = ptype + '-' + framework;
  }
  if(buildtooljs !== 'none'){
    ptype = ptype + '-' + buildtooljs;
  }
  if(frameworkjs !== 'none'){
    ptype = ptype + '-' + frameworkjs;
  }
  console.log('Project type: ', ptype);
  
  var isPrivate = isPrivateSwitch.checked;
  
  alert(JSON.stringify({project_type:ptype,project_name:projectName,is_private:isPrivate}));
  return true;
};
<link href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" rel="stylesheet">

<script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>

<div id="pconfig">

  <!-- PROJECT LANGUAGE -->
  <div id="planguage" class="mdc-select mdc-select--filled">
    <div class="mdc-select__anchor">
      <span class="mdc-select__ripple"></span>
      <span class="mdc-select__selected-text"></span>
      <span class="mdc-select__dropdown-icon">
        <svg class="mdc-select__dropdown-icon-graphic" viewBox="7 10 10 5">
          <polygon class="mdc-select__dropdown-icon-inactive" stroke="none" fill-rule="evenodd" points="7 10 12 15 17 10">
          </polygon>
          <polygon class="mdc-select__dropdown-icon-active" stroke="none" fill-rule="evenodd" points="7 15 12 10 17 15">
          </polygon>
        </svg>
      </span>
      <span class="mdc-floating-label">Language</span>
      <span class="mdc-line-ripple"></span>
    </div>
    <div class="mdc-select__menu mdc-menu mdc-menu-surface mdc-menu-surface--fullwidth">
      <ul class="mdc-list">
        <li class="mdc-list-item mdc-list-item--selected" data-value="java" aria-selected="true">
          <span class="mdc-list-item__ripple"></span>
          <span class="mdc-list-item__text">Java</span>
        </li>
        <li class="mdc-list-item" data-value="python">
          <span class="mdc-list-item__ripple"></span>
          <span class="mdc-list-item__text">Python</span>
        </li>
        <li class="mdc-list-item" data-value="javascript">
          <span class="mdc-list-item__ripple"></span>
          <span class="mdc-list-item__text">Javascript</span>
        </li>
      </ul>
    </div>
  </div>
  <br><br>

  <!-- JAVA CONFIGURATOR -->
  <div id="java-configurator">
    <!-- BUILD TOOLS -->
    <div id="buildtool" class="mdc-select mdc-select--filled demo-width-class">
      <div class="mdc-select__anchor">
        <span class="mdc-select__ripple"></span>
        <span class="mdc-select__selected-text"></span>
        <span class="mdc-select__dropdown-icon">
          <svg class="mdc-select__dropdown-icon-graphic" viewBox="7 10 10 5">
            <polygon class="mdc-select__dropdown-icon-inactive" stroke="none" fill-rule="evenodd" points="7 10 12 15 17 10">
            </polygon>
            <polygon class="mdc-select__dropdown-icon-active" stroke="none" fill-rule="evenodd" points="7 15 12 10 17 15">
            </polygon>
          </svg>
        </span>
        <span class="mdc-floating-label">Build tool</span>
        <span class="mdc-line-ripple"></span>
      </div>
      <div class="mdc-select__menu mdc-menu mdc-menu-surface mdc-menu-surface--fullwidth">
        <ul class="mdc-list">
          <li class="mdc-list-item mdc-list-item--selected" data-value="none" aria-selected="true">
            <span class="mdc-list-item__ripple"></span>
            <span class="mdc-list-item__text">None</span>
          </li>
          <li class="mdc-list-item" data-value="maven">
            <span class="mdc-list-item__ripple"></span>
            <span class="mdc-list-item__text">Maven</span>
          </li>
          <li class="mdc-list-item" data-value="gradle">
            <span class="mdc-list-item__ripple"></span>
            <span class="mdc-list-item__text">Gradle</span>
          </li>
        </ul>
      </div>
    </div>
    <br><br>

    <!-- FRAMEWORK -->
    <div id="framework" class="mdc-select mdc-select--filled demo-width-class">
      <div class="mdc-select__anchor">
        <span class="mdc-select__ripple"></span>
        <span class="mdc-select__selected-text"></span>
        <span class="mdc-select__dropdown-icon">
          <svg class="mdc-select__dropdown-icon-graphic" viewBox="7 10 10 5">
            <polygon class="mdc-select__dropdown-icon-inactive" stroke="none" fill-rule="evenodd" points="7 10 12 15 17 10">
            </polygon>
            <polygon class="mdc-select__dropdown-icon-active" stroke="none" fill-rule="evenodd" points="7 15 12 10 17 15">
            </polygon>
          </svg>
        </span>
        <span class="mdc-floating-label">Framework</span>
        <span class="mdc-line-ripple"></span>
      </div>
      <div class="mdc-select__menu mdc-menu mdc-menu-surface mdc-menu-surface--fullwidth">
        <ul class="mdc-list">
          <li class="mdc-list-item mdc-list-item--selected" data-value="none" aria-selected="true">
            <span class="mdc-list-item__ripple"></span>
            <span class="mdc-list-item__text">None</span>
          </li>
          <li class="mdc-list-item" data-value="springboot">
            <span class="mdc-list-item__ripple"></span>
            <span class="mdc-list-item__text">Spring Boot</span>
          </li>
        </ul>
      </div>
    </div>
    <br><br>
  </div>

  <!-- PYTHON CONFIGURATOR -->
  <div id="python-configurator" style="display:none">
  </div>

  <!-- JAVASCRIPT CONFIGURATOR -->
  <div id="javascript-configurator" style="display:none">
    <!-- BUILD TOOLS -->
    <div id="buildtool-js" class="mdc-select mdc-select--filled demo-width-class">
      <div class="mdc-select__anchor">
        <span class="mdc-select__ripple"></span>
        <span class="mdc-select__selected-text"></span>
        <span class="mdc-select__dropdown-icon">
          <svg class="mdc-select__dropdown-icon-graphic" viewBox="7 10 10 5">
            <polygon class="mdc-select__dropdown-icon-inactive" stroke="none" fill-rule="evenodd" points="7 10 12 15 17 10">
            </polygon>
            <polygon class="mdc-select__dropdown-icon-active" stroke="none" fill-rule="evenodd" points="7 15 12 10 17 15">
            </polygon>
          </svg>
        </span>
        <span class="mdc-floating-label">Build tool</span>
        <span class="mdc-line-ripple"></span>
      </div>
      <div class="mdc-select__menu mdc-menu mdc-menu-surface mdc-menu-surface--fullwidth">
        <ul class="mdc-list">
          <li class="mdc-list-item mdc-list-item--selected" data-value="none" aria-selected="true">
            <span class="mdc-list-item__ripple"></span>
            <span class="mdc-list-item__text">None</span>
          </li>
          <li class="mdc-list-item" data-value="npm">
            <span class="mdc-list-item__ripple"></span>
            <span class="mdc-list-item__text">npm</span>
          </li>
        </ul>
      </div>
    </div>
    <br><br>

    <!-- FRAMEWORK -->
    <div id="framework-js" class="mdc-select mdc-select--filled demo-width-class">
      <div class="mdc-select__anchor">
        <span class="mdc-select__ripple"></span>
        <span class="mdc-select__selected-text"></span>
        <span class="mdc-select__dropdown-icon">
          <svg class="mdc-select__dropdown-icon-graphic" viewBox="7 10 10 5">
            <polygon class="mdc-select__dropdown-icon-inactive" stroke="none" fill-rule="evenodd" points="7 10 12 15 17 10">
            </polygon>
            <polygon class="mdc-select__dropdown-icon-active" stroke="none" fill-rule="evenodd" points="7 15 12 10 17 15">
            </polygon>
          </svg>
        </span>
        <span class="mdc-floating-label">Framework</span>
        <span class="mdc-line-ripple"></span>
      </div>
      <div class="mdc-select__menu mdc-menu mdc-menu-surface mdc-menu-surface--fullwidth">
        <ul class="mdc-list">
          <li class="mdc-list-item mdc-list-item--selected" data-value="none" aria-selected="true">
            <span class="mdc-list-item__ripple"></span>
            <span class="mdc-list-item__text">None</span>
          </li>
          <li class="mdc-list-item" data-value="nodejs">
            <span class="mdc-list-item__ripple"></span>
            <span class="mdc-list-item__text">Node.js</span>
          </li>
        </ul>
      </div>
    </div>
    <br><br>
  </div>

  <!-- PROJECT NAME -->
  <label class="mdc-text-field mdc-text-field--filled mdc-text-field--label-floating">
    <span class="mdc-text-field__ripple"></span>
    <input class="mdc-text-field__input" type="text" aria-labelledby="my-label-id" value="my-new-project" required>
    <span class="mdc-floating-label mdc-floating-label--float-above" id="my-label-id">Project name</span>
    <span class="mdc-line-ripple"></span>
  </label>
  <br><br>

  <!-- IS PRIVATE -->
  <label for="basic-switch">Private project</label>
  <div class="mdc-switch mdc-switch--checked">
    <div class="mdc-switch__track"></div>
    <div class="mdc-switch__thumb-underlay">
      <div class="mdc-switch__thumb"></div>
      <input type="checkbox" id="basic-switch" class="mdc-switch__native-control" role="switch" aria-checked="true" checked>
    </div>
  </div>
  <br><br>

  <!-- CREATE BUTTON-->
  <button id="cproject" class="mdc-button mdc-button--outlined">
    <div class="mdc-button__ripple"></div>
    <span class="mdc-button__label">Create</span>
  </button>
  <p id="error_message" style="color:red;"></p>
</div>

If you run the code snippet and click CREATE, an alert shows the JSON that will be sent to the backend.

The JSON represents a project containing: a name, a type, and a boolean "isPrivate". The project_type is a lowercase string with this format: language-[build tool]-[framework]. For example: java-gradle-springboot or simply java. Depending on the language, a project can have additional fields.

  • How can I reduce the duplicated code and have a more flexible solution?

My plan is to add more languages and configurations. Any comments are welcome.

The complete extension is available here. It creates a project in your GitHub repo and opens it in Gitpod.

\$\endgroup\$
3
+100
\$\begingroup\$

Javascript feedback

Redundant code

There is some redundant code - take the first three lines:

let createProject = document.getElementById('cproject');
var errorMessage = document.getElementById('error_message');

const cprojectRipple = new mdc.ripple.MDCRipple(document.getElementById('cproject'));

There are two places where document.getElementById('cproject') appears. While users often have a lot more resources these days and browsers have come along way in the past couple decades, bear in mind that DOM access is not cheap.

createProject could be passed on the third line:

const cprojectRipple = new mdc.ripple.MDCRipple(createProject);

prefer using const

The variable createProject is never re-assigned so it can be declared using const instead of let. Getting in the habit of this can help avoid accidental re-assignment and other bugs. Many users also believe var should not be used with ES6 code - refer to the ES lint rule no-var.

click handler registration

The click handler for createProject is assigned to the onclick property. This isn't wrong and it may never be required but if there was a need to have multiple click handlers then a different registration mechanism would be needed - e.g. using addEventListener() - similar to the .listen() method on the mdc elements.

using alert()

The end of that click handler for the createProject button calls alert(). While the functionality may just be test code for demonstration, know that using alert() can be an issue because some users may have disabled alerts in a browser setting. It is better to use HTML5 <dialog> element - it allows more control over the style and doesn't block the browser. Bear in mind that it isn't supported by IE and Safari (And seemingly Chrome on iOS) but there is a polyfill

Question

How can I reduce the duplicated code and have a more flexible solution?

I'm honestly not very familiar with the material design framework but there may be components that lend themselves to conditional display. Also, there maybe some mechanism similar to using FormData that can be used to simplify the serialization code in the click handler function.

One approach to simplifying the code is to add a single event listener for all elements - e.g. observe all changes on a parent element and use event delegation:

const pconfig = document.getElementById('pconfig');
pconfig.addEventListener('MDCSelect:change', { target } => {
    //delegate functionality based on target.id to other functions
})

And perhaps a way to reduce redundancy is to only have one list for framework and one list for build tool, with options that are only displayed based on the selected language - e.g. using data attributes. If no options are applicable - e.g. for python- then hide the list.

\$\endgroup\$
1
3
\$\begingroup\$

You can rewrite the HTML in JavaScript so that you may add an array of all supported languages that you plan on adding and then just loop through each element in the array to generate a new "language" block. You can also nest that code in. something like this

(function () {

})();

So that it will run when everything boots up.

As for the JavaScript side, it is already really condensed and clean. You do not need to do many edits for that file. Although you can look into JS Array Prototypes to try making a list of *Elements then you iterate through each one and call the same code on it.

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.