Install SonarQube and add it as a verification step in Jenkins

Install SonarQube

Instructions

Install SonarQube. This is a reporting tool.

$ brew install sonar

But it also requires Sonar-scanner in order to scan the code.

$ brew install sonar-scanner

 

Install Jest Sonar reporter

Jest sonar reporter is a plug-in that wi
jest-sonar-reporter is a custom results processor for Jest. The processor converts Jest’s output into Sonar’s generic test data format.

In other words we need this in order for SonarQube to to be able to process the result of Jest tests.

$ yarn add jest-sonar-reporter --dev

Let’s configure jest to use sonar-reporter and also to process and exclude of testing certain folders:

./package.json

...
  "jest": {
    "setupFiles": [
      "./src/setupTests.js"
    ],
    "collectCoverageFrom": [
      "**/*.{js,jsx}",
      "!coverage/**",
      "!node_modules/**",
      "!src/index.js",
      "!src/setupTests.js",
      "!public/**",
      "!server-build/**"
    ],
    "moduleNameMapper": {
      "\\.(css|less|scss)$": "<rootDir>/src/__mocks__/styleMock.js"
    },
    "coverageDirectory": "reports/coverage",
    "coverageReporters": [
      "json",
      "lcov",
      "text"
    ],
    "testResultsProcessor": "jest-sonar-reporter"
  },
  "jestSonar": {
    "reportPath": "reports",
    "reportFile": "test-reporter.xml"
  },
...
what we just did:
– lines 6 -14 we described which folders to be added in the coverage (“**/*.{js,jsx}”) and which to be excluded. (All that start with exclamation mark).
– line 18 describes where to create the report file, which sonarqube scanner will use.

Add Sonar-project.properties file

This file is used to configure sonnar-scanenr.

./sonar-project.properties

sonar.projectKey="SparkJS-key"
sonar.projectName="SparkJS"
sonar.sourceEncoding=UTF-8
sonar.sources="./src"
sonar.tests="./src"
sonar.exclusions=**/*.test.js
sonar.test.inclusions=**/*.test.js
sonar.javascript.lcov.reportPaths=reports/coverage/lcov.info
sonar.testExecutionReportPaths=reports/test-reporter.xml

what we just did:
projectKey – is the unique identifier of the project.
projectName – is the name of the project.
sources – describes which folders to scan
tests – where the scanner will look for test files.
exclusions – which files should be excluded from coverage.

Create SonarQube project

Run sonar-scanner inside the root of the project:

$ sonar-scanner

Navigate to SonarQube web UI at the projects section, and you will see the new project created.

Integrating SonarQube quality tests with Jenkins

Before you continue make sure that you have Jenkins installed locally and is configured to run tests and deploy your project. Detailed instructions are found here:

Adding continuous integration with Jenkins pipeline and Github webhooks

After you have Jenkins set up and configured, we could proceed to adding another two `stages` into the pipeline:

– Running SonarQube Scanner
Quality Gate

Adding SonarQube plug-in for Jenkins

Open Jenkins Web UI ->Manage Jenkins->Manage Plugins and add ‘SonarQube Scanner for Jenkins

Configuring Jenkins pipeline to runs Sonar-scanner and do Quality gate.


Open
./jenkins/pr.groovy

 

pipeline {
  agent any
    
  tools {nodejs "SparkJS"}
    
  stages {
        
    stage('Cloning Git Repo') {
      steps {
        git 'https://github.com/ToniNichev/projects-sparkjs.git'
      }
    }
    stage('Install dependencies') {
      steps {
        echo '#################################'              
        echo 'Building...'       
        echo '#################################'                      
        sh '/usr/local/bin/yarn install'
      }
    }   
     
    stage('Running Tests') {
      steps {
        echo '#################################'              
        echo 'Running tests ...'          
        echo '#################################'               
         sh '/usr/local/bin/yarn test'
      }
    }    

    stage('Running ESLint') {
      steps {
        echo '#################################'              
        echo 'Running ESLint ...'          
        echo '#################################'               
         sh '/usr/local/bin/yarn lint'
      }
    }       

    stage('Running SonarQube Scanner') {
       steps {
        script {
          // requires SonarQube Scanner 2.8+
          scannerHome = tool 'SonarScanner'
        }
        withSonarQubeEnv('Tonis SonarQube') { // If you have configured more than one global server connection, you can specify its name
          sh "${scannerHome}/bin/sonar-scanner"
        }
       }
    }
   
    // No need to occupy a node
    stage("Quality Gate"){
     steps {
         script {
            timeout(time: 2, unit: 'MINUTES') { // Just in case something goes wrong, pipeline will be killed after a timeout
              def qg = waitForQualityGate() // Reuse taskId previously collected by withSonarQubeEnv
              if (qg.status != 'OK') {
                error "Pipeline aborted due to quality gate failure: ${qg.status}"
              }
            }
         }
      }
    }

    stage('Deploy and run server') {
      steps { 
        echo 'Starting server ...'
        sh '/usr/local/bin/yarn clean; /usr/local/bin/yarn build-prod; /usr/local/bin/yarn build-prod-ssr;'
        sh '/usr/local/bin/pm2 start ./server-build/server-bundle.js -f'
      }
    }      
  }
}

Now make some code changes like for example ./env.production change the app name title i.e.

APP_NAME=Webpack React Tutorial Production 2

Push your changes and navigate back to Jenkins WEB UI. Now two new steps will be added to the pipeline:”Running SonarQube Scanner”and”Quality Gate”

Working with danger JS and integrating it with GitHub

What is danger JS ?

Danger runs during your CI process, and gives teams the chance to automate common code review chores. It’s an automation checker against any opened pull requests.

Install danger JS

Navigate to the root of your folder and execute:

$ yarn add danger --dev; yarn danger init

yarn danger init
yarn run v1.22.4
$ /Users/toninichev/Cloud/workspace/nodeJS/Examples/Sparkjs/node_modules/.bin/danger init
Welcome to Danger Init - this will take you through setting up Danger for this project.
There are four main steps we need to do:

 - [ ] Create a Dangerfile and add a few simple rules.
 - [ ] Create a GitHub account for Danger to use, for messaging.
 - [ ] Set up an access token for Danger.
 - [ ] Set up Danger to run on your CI.

But before we start, we need one bit of information from you.
Is this is for an Open Source or private project?

[1] Open Source
[2] Private Repo
[0] CANCEL

1

## Step 1: Creating a starter Dangerfile


I've set up an example Dangerfile for you in this folder.

> cat /Users/toninichev/Cloud/workspace/nodeJS/Examples/Sparkjs/dangerfile.js 

  import {danger, warn} from 'danger'
  
    
  // No PR is too small to include a description of why you made a change
  if (danger.github.pr.body.length < 10) {
    warn('Please include a description of your PR changes.');
  }
  
  
  // Request changes to src also include changes to tests.
  const allFiles = danger.git.modified_files.concat(danger.git.created_files)
  const hasAppChanges = allFiles.some(p => includes(p, 'src/'))
  const hasTestChanges = allFiles.some(p => includes(p, '__tests__/'))
  
  if (hasAppChanges && !hasTestChanges) {
    warn('This PR does not include changes to tests, even though it affects app code.');
  }
  
    

There's a collection of small, simple rules in here, but Danger is about being able to easily
iterate. The power comes from you having the ability to codify fixes for some of the problems
that come up in day to day programming. It can be difficult to try and see those from day 1.

If you'd like to investigate the file, and make some changes - I'll wait here,
press return when you're ready to move on...

↵ 

## Step 2: Creating a GitHub account

In order to get the most out of Danger, I'd recommend giving it the ability to post in
the code-review comment section.

https://github.com

IMO, it's best to do this by using the private mode of your browser.
Create an account like: SparkjsBot and don't forget a cool robot avatar too.

Here are great resources for creative-commons images of robots:

 - https://www.flickr.com/search/?text=robot&license=2%2C3%2C4%2C5%2C6%2C9
 - https://www.google.com/search?q=robot&tbs=sur:fmc&tbm=isch&tbo=u&source=univ&sa=X&ved=0ahUKEwjgy8-f95jLAhWI7hoKHV_UD00QsAQIMQ&biw=1265&bih=1359

Sidenote:  Holding cmd ( ⌘ ) and double clicking a link will open it in your browser.

SparkjsBot does not need privileged access to your repo or org. This is because Danger will only
be writing comments, and you do not need special access for that.

Cool, please press return when you have your account ready (and you've verified the email...)

↵ 

## Step 3: Configuring a GitHub Personal Access Token

Here's the link, you should open this in the private session where you just created the new GitHub account

https://github.com/settings/tokens/new

For Open Source projects, I'd recommend giving the token the smallest scope possible.
This means only providing access to public_repo in the token.
This token limits Danger's abilities to just writing comments on OSS projects. We recommend
this because the token can quite easily be extracted from the environment via pull requests.

It is important that you do not store this token in your repository, as GitHub will
automatically revoke your token when pushed.


👍, please press return when you have your token set up...

↵ 

## Add to CI

You need to expose a token called DANGER_GITHUB_API_TOKEN and the value is the GitHub Personal Access Token.
Depending on the CI system, this may need to be done on the machine (in the ~/.bashprofile) or in a web UI somewhere.
We have a guide for all supported CI systems on danger.systems:
http://danger.systems/js/guides/getting_started.html#setting-up-danger-to-run-on-your-ci

## Useful info

- One of the best ways to test out new rules as you build them is via bundle exec danger pr.
- You can have Danger output a lot of info via the --verbose option.
- You can look at the following Dangerfiles to get some more ideas:

  * https://github.com/artsy/emission/blob/master/dangerfile.ts
  * https://github.com/facebook/react-native/blob/master/bots/dangerfile.js
  * https://github.com/apollographql/apollo-client/blob/master/config/dangerfile.ts
  * https://github.com/styleguidist/react-styleguidist/blob/master/dangerfile.js
  * https://github.com/storybooks/storybook/blob/master/dangerfile.js
  * https://github.com/ReactiveX/rxjs/blob/master/dangerfile.js


🎉

And you're good to go. Danger is a collaboration between Orta Therox, Gem 'Danger' Maslen,
and every who has sent PRs.

If you like Danger, let others know. If you want to know more, follow @orta and @DangerSystems on Twitter.
If you don't like something about Danger, help us improve the project - it's all done on volunteer time! xxx
Remember: it's nice to be nice.

✨  Done in 1701.50s.

At the end this will create danger.js file in the root of your project.

Setup Github token

Create new Github token and give it “repo” access. This should be enough to allow the Github bot that will use it to post messages on pull requests.

Make sure that you copied the token and save it safely OUTSIDE OF THE PROJECT’S FOLDER,  because you won’t be able to see the token again.
Also if you save it in the project’s folder, next time when you push the code Github will invalidate it cause this is considered a security breach.

Link to create new token: https://github.com/settings/tokens/new

Override the contents of the danger file

import { fail, warn, message, markdown, danger } from "danger"

fail("Testing failure message");
warn("Testing warning");
message("Normal message");
markdown("*Markdown* is also **supported**");

const { additions = 0, deletions = 0 } = danger.github.pr;
message(`:tada: The PR added ${additions} and removed ${deletions} lines.`);

const modifiedMD = danger.git.modified_files.join("\n");
message(`Changed Files in this PR: \n ${modifiedMD} \n`);

Create pull request and test danger JS locally

Before we could do this, we have to export environment variable DANGER_GITHUB_API_TOKEN with the value equal to the tocen that we created in the previous chapter.

$ export DANGER_GITHUB_API_TOKEN="XXXXXXXXXXXXXXXXXXXXXXXX"

And now, test it locally:

$ yarn danger pr https://github.com/ToniNichev/projects-sparkjs/pull/1 

Integrate it with GitHub

Github provides api that we could use to get information about commits and pull requests.

Example of getting all pull requests:

https://api.github.com/repos/ToniNichev/projects-sparkjs/pulls?state=all

Before we are able to see messages in github we have to txport two more environment variables:

$ export DANGER_FAKE_CI="YEP"
$ export DANGER_TEST_REPO='ToniNichev/projects-sparkjs'

Test danger JS. Execute:

$ DANGER_TEST_PR='4' yarn danger ci 

And if everything is correct you should see the messages in the pull request:

 

Adding continuous integration with Jenkins pipeline and Github webhooks

 

Pre requirements:

  • Windows, Linux or OSX machine running latest Java JDK or JRE.
  • Github account
  • You could fork the example project repo or you could use project of your own if you prefer.
  • if you want to set up a Webhook that will trigger automatic builds  you will need Jenkins to be accessible from outside your network. You will have to set up port forwarding in your rauther.

Example project repo:

branch-name:  
Click To Copy

 

 

Installing Jenkins

https://jenkins.io/download/

Jenkins come in two versions: Long-term Support (LTS) and Weekly releases. If you want stable version choose (LTS)

Jenkins also require Java so make sure that you have the appropriate version installed. By the time I’m writing this it requires

  • On MAC OS
    brew install jenkins-lts

https://jenkins.io/download/lts/macos/

  • On CentOS
    add Jenkins repo
    sudo rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
    then install it.
    yum install jenkins
    Then start the service
    brew services start jenkins-lts

Change default port (if needed, homebrew only)

Jenkins runs by default on port 8080, but I have another app running there so I had to change the default port.

Edit homebrew plist file as follows:
(replace 2.222.1 with the actual installed version)

/usr/local/Cellar/jenkins-lts/2.222.1/homebrew.mxcl.jenkins-lts.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>homebrew.mxcl.jenkins-lts</string>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/libexec/java_home</string>
      <string>-v</string>
      <string>1.8</string>
      <string>--exec</string>
      <string>java</string>
      <string>-Dmail.smtp.starttls.enable=true</string>
      <string>-jar</string>
      <string>/usr/local/opt/jenkins-lts/libexec/jenkins.war</string>
      <string>--httpListenAddress=0.0.0.0</string>
      <string>--httpPort=8082</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
  </dict>
</plist>

Line 18: change the port to 8082.
Line 17: Change  httpListenAddress from 127.0.0.1 to 0.0.0.0. This is necessary if you want to access Jenkins from Internet, outside of the internal network.

Now run the server as service.

brew services start jenkins-lts

Install the necessary plug-ins

Jenkins -> Manage Jenkinst -> Manage Plugins

Create First Pipeline

Create New Item

Pipeline ->Pipeline Script From SCM and put Git repository link.

Create Jenkins file with the pipeline steps

The whole pipeline should be wrapped in

pipeline {
}

A few words about pipeline syntax:

Agent is declared in the very beginning of the pipeline. This instructs Jenkins to allocate an executor (on a node) and workspace for the entire Pipeline.
An agent is typically a machine, or container, which connects to a Jenkins master and executes tasks when directed by the master.

Stage is part of Pipeline, and used for defining a conceptually distinct subset of the entire Pipeline, for example: “Build”, “Test”, and “Deploy”, which is used by many plugins to visualize or present Jenkins Pipeline status/progress.

Step A single task; fundamentally steps tell Jenkins what to do inside of a Pipeline or Project.

Full glossary could be found here

Let’s get started by creating pipeline file in the example project folder:

./jenkins/pr.groovy

pipeline {
  agent any
    
  tools {nodejs "SparkJS"}
    
  stages {
        
    stage('Cloning Git Repo') {
      steps {
        git 'https://github.com/ToniNichev/projects-sparkjs.git'
      }
    }
    stage('Install dependencies') {
      steps {
        echo '######################'              
        echo 'Building...'       
        echo '######################'                      
        sh '/usr/local/bin/yarn install'
      }
    }
     
    stage('Running Tests') {
      steps {
        echo '######################'              
        echo 'Running tests ...'          
        echo '######################'               
         sh '/usr/local/bin/yarn test'
      }
    }      
  }

  post { 
      always { 
          echo 'Starting server ...'
          sh '/usr/local/bin/yarn clean; /usr/local/bin/yarn build-prod; /usr/local/bin/yarn build-prod-ssr;'
          sh '/usr/local/bin/pm2 start ./server-build/server-bundle.js -f'
      }
  }  
}

what we just did:
– line 2, told Jenkins that it could run this pipeline for any agent. Agent basically allows you to specify where the task is to be executed. It could be Docker, Node or any agent.
– line 6, we defined our stages: Cloning Git Repo, Install dependencies, Running Tests
– line 32: finally after all stage script passed we defined the post script to run the server.

I’m using pm2 (a process manager and launcher) for running the app so if you don’t have it installed you should install it using npm or yarn.

npm install pm2@latest -g

or

yarn global add pm2

Running the pipeline task

So now everything is set up, let’s test the pipeline. Navigate to the pipeline and  from the vertical menu on the right select “build now”. If everything is good you should see a pipeline stages with progress bars filling out.

After the execution you could navigate to the log (build history in the right side -> select last job ->Console output)

There you could see a log of all stages executions including the snapshot tests

Test Suites: 2 passed, 2 total

Setting up Jenkins to listen to Github Webhook and trigger automatic builds on every commit

This is probably the best and the most tricky one to make it work. We are going to add Github Webhook that will make a post request to Jenkins every time when we push code change and this will trigger our pipeline and will rebuild the app and redeploy it. We are building so called continuous integration process. CI

Adding API key to the admin user.

Select the current (admin) user from the top right.

then on the left vertical menu choose “configure”.
Navigate to the “API Token” section and click “Add new token”

Important!!! Copy the token and save it somewhere safely because you won’t be able to see it again.

Navigate back to the pipeline that we created and click “configure” from the left vertical menu.
Scroll down to “Build Triggers” and check “Trigger builds remotely (e.g., from scripts)”

Paste the authentication token in the field and copy the example url below the text field where it says: “Use the following URL to trigger build remotely” We will need this to add it into Github webhook.

Click “save” on the bottom.

Setting up Github Webhook

Navigate to the example project in your Github space, select “settings” and “Webhooks”.

Click on “add webhook” and you will see this screen:

In the payload URL put the url that we copied from Jenkins -> Build Triggers above.

Important!!! Make sure that you replace ‘JENKINS_URL’ with the actual IP of the machine where Jenkins is running or the hostname if you set up one, and replace the token with the actual token that we generated for the ‘admin’ user. 

On the dropdown below “Content type” select “application/json”

Leave “Secret” below empty.

Next on “Which events would like to trigger this webhook” is up to you, but for simplicity I just left the default “Just push event”

Make sure that “Active” is checked and click “Add webhook”

At this point if you commit some changes to the example project and push them a webhook should fire and do a POST request to your jenkins instance, notifying it that there are code changes and triggering the pipeline process … but when I did this and looked at the response I saw: “403 No valid crumb was included in the request

This simply means that Jenkins require another token to be sent in the headers, to make sure that only authorised cities (Github in this example) will trigger the pipeline process.

This is the most obscure and unclear part of setting up Webhooks. I google it for quite some time and figured out that there is no way to send custom header parameters (like Jenkins-crumb) from Github so the only option was to disable this security feature … which I think is fine since the pipeline is already protected with API key that we added.

Disabling CSRF Protection in Jenkins

The CSRF protection settings lives in “Manage Jenkins” under “Configure Global Security” but as it looks like the lates Jenkins releases don’t have an option to disable this, so the only alternative was to do it through the groovy script.

Go to Manage Jenkins -> Script Console
and paste the code below in the console.

import jenkins.model.Jenkins
def instance = Jenkins.instance
instance.setCrumbIssuer(null)

Click run. You will see empty result which is expected.

Go to the example project commit some change and push it again.

git commit . -m"Testing push webhook";git push

Navigate to Jenkins and you will observe that the new tack is queued in the “Build executor status” in the bottom left.

Test it

Let’s do it again by making some code changes and commit and push and observe how Jenkins will run the pipeline, test and deploy the project!

Cheers!

Using Nginx as a reverse proxy and add source code replacement

Task:

I have web app (in my case Node app running on port 3000)
I would add Nginx in front of it and will serve it on port 80.

Adding reverse proxy config.

Find Nginx.config file by executing: nginx -V

Edit nginx.conf and edit the server section

...
    server {
        listen       8081;
        server_name  toni-develops.com;
        location / {
            proxy_pass http://localhost:3000/;
        }
...

Line 5 tells enginx to intercept all requests / and proxy them from http://localhost:3000
If the request from example is /home Nginx will make request to http://localhost:3000/home

If for example we want to proxy only specific urls (let’s say only *.php) we could do this:

location ~ \.php {
    proxy_pass http://localhost:3000/
}

Add source code replacement

Nginx has to be built with sub_filter module.
The example below will override all localhost:3000 to toni-develops.com:80

...

server {
    listen       8081;
    server_name  toni-develops.com;


    location / {
        proxy_pass http://localhost:3000/;
        proxy_set_header Accept-Encoding "";
        proxy_buffers 8 1024k;  
        proxy_buffer_size 1024k;
        sub_filter 'localhost:3000'  'toni-develops.com:80';
        sub_filter_once off;            
    }

...

 

Unique-paths

Task

A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).

The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).

How many possible unique paths are there?

Note: m and n will be at most 100.

Example 1:

Input:

 m = 3, n = 2

Output:

 3

Explanation:

From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
1. Right -> Right -> Down
2. Right -> Down -> Right
3. Down -> Right -> Right

Example 2:

Input:

 m = 7, n = 3

Output:

 28

This problem was taken from Leetcode unique paths and Leetcode_unique_paths_part_II

Solution

Since we can move only right or down on every cell in the first row we will have only one place from where we can come and this is the cell before. And same for the first vertical row.

Unique Paths

Then after we figured out that there is only one way to reach each cell in the first row and the first column (which is from the cell before) we could start calculating possible lays to go to the next cells.
Let’s look at the cell in the second row and second column.There are actually two possible ways to go there: from the cell above, and the cell before, so 2 possible ways. (figure below).
The cell in the third column on the second row: same 1 way from the cell above, and from the cell before. But since there are already 2 ways to reach the cell before the total ways to reach this cell is: 1 + 2 = 3.

 

The solution:

/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) {
    
    var memo = [];

    for(var i=0;i < n; i ++) {
        for(var j = 0; j < m; j ++) {
            var index = (i * m) + j; 
            if(i == 0) {
                memo[index] = 1;
            }
            else if(j == 0) {
                memo[index] = 1;
            }
            else {
                var up = index - m;
                var left = index - 1;
                memo[index] = memo[up] + memo[left];
            }
        }
    }
    return memo[memo.length - 1];
}

console.log(uniquePaths(7,3));

 

Unique paths with obstacles.

 

/**
 * @param {number[][]} obstacleGrid
 * @return {number}
 */
var uniquePathsWithObstacles = function(obstacleGrid) {
    var m = obstacleGrid[0].length;
    var n = obstacleGrid.length;
    var row = 0;
    if(obstacleGrid[0][0] == 1)
        return 0;

    var memo = [];

    for(var i=0;i < n; i ++) {
        for(var j = 0; j < m; j ++) {
            var index = (i * m) + j; 
            if(i == 0) {
                if(obstacleGrid[i][j] == 1 || (j > 0 && memo[index -1] == 0))
                    memo[index] = 0;
                else                
                    memo[index] = 1;
            }
            else if(j == 0) {
                if(obstacleGrid[i][j] == 1 || (i > 0 && memo[index - m] == 0))
                    memo[index] = 0;
                else                
                    memo[index] = 1;
            }
            else {
                var up = index - m;
                var left = index - 1;
                if(obstacleGrid[i][j] == 1)
                    memo[index] = 0;
                else
                    memo[index] = memo[up] + memo[left];
            }
            row += memo[index] ? 0 : 1;
        }
        if(row == m)
            return 0;            
        row = 0;
    }
    return memo[memo.length - 1];
};


var grid = [
  [0,0,0],
  [0,1,0],
  [0,0,0]
];

console.log(uniquePathsWithObstacles(grid));

 

Intersection of Two Linked Lists

Task

Write a program to find the node at which the intersection of two singly linked lists begins.

For example, the following two linked lists:

begin to intersect at node c1.

 

Example 1:

This problem was taken from Leetcode

Solution

We are not asked to compare the values inside the linked lists but the list node objects, so we could ignore the values of the list.

Approach 1: Brute Force

For each node ai in list A, traverse the entire list B and check if any node in list B coincides with ai.

Complexity Analysis

  • Time complexity : O(mn).
  • Space complexity : O(1).

Approach 2: Calculating the length of the two linked lists and compare the elements that could potentially intersect.

  • Time complexity : O(m+n).
  • Space complexity : O(m) or O(n).
 function ListNode(val) {
      this.val = val;
      this.next = null;
 }

headA = new ListNode(4);
headA.next = new ListNode(1);


headB = new ListNode(5);
headB.next = new ListNode(0);
headB.next.next = new ListNode(1);

headA.next.next = headB.next.next.next = new ListNode(8);
headB.next.next.next.next = headA.next.next.next = new ListNode(4);
headB.next.next.next.next.next = headA.next.next.next.next = new ListNode(5);


/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */


var getIntersectionNode = function(headA, headB) {
      let node = headA;
      let countA = 0;
      while(node != null ) {            
            node = node.next;           
            countA ++;       
      }    

      node = headB;
      let countB = 0;
      while(node != null ) {            
            node = node.next;           
            countB ++;       
      }    


      let longList, shortList, diff, iteratorLongLength,iteratorShortLength;
      if(countA > countB) 
        longList = headA, shortList = headB,  diff = countA - countB;
      else
        longList = headB, shortList = headA, diff =  countB - countA;


      let i = 0;
      while(shortList != null) {
        if(i < diff ) {
              longList = longList.next; 
        }
        else {
              console.log("long list, short list", longList.val, shortList.val);
              if(longList == shortList) 
                  return longList.val;
              longList = longList.next; 
              shortList = shortList.next;     
        }        

      i ++;      
      }
};


console.log (getIntersectionNode(headA, headB) );

what we just did:
– we calculated the length of the first list to be 5 and the second 6 (first and the second loop)
– the third loop is doing two things:
– first since the difference between the shorter and the longer list is 1 we move the cursor to the second element of the longer list (lines 59-61)
– after we position the longer list cursor at the second element we could start comparing (line 64)

If we execute the function we will see this result:

long list, short list 0 4
long list, short list 1 1
long list, short list 8 8

And the third element is exactly where the intersection is.

Approach 3: Traverse both lists and when reaching the end of each one, move the pointer to the opposite list and traverse again till intersection is found.

  • Time complexity : O(m+n).
  • Space complexity : O(m) or O(n).

This basically is the same concept as in the example above, just written in a bit more elegant way.

 function ListNode(val) {
      this.val = val;
      this.next = null;
 }

headA = new ListNode(4);
headA.next = new ListNode(1);


headB = new ListNode(5);
headB.next = new ListNode(0);
headB.next.next = new ListNode(1);

headA.next.next = headB.next.next.next = new ListNode(8);
headB.next.next.next.next = headA.next.next.next = new ListNode(4);
headB.next.next.next.next.next = headA.next.next.next.next = new ListNode(5);


/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */


var getIntersectionNode = function(headA, headB) {
      let nodeA = headA;
      let nodeB = headB;
      let swapA = false;
      let swapB = false;
      var i = 0;
      while(nodeA != null && nodeB!=null ) {
            // node A            
            if(!swapA && nodeA.next == null) {
                  nodeA = headB;
                  swapA = true;
            }
            else {
                  nodeA = nodeA.next;
            }
            // node B
            if(!swapB && nodeB.next == null) {
                  nodeB = headA;
                  swapB = true;
            }
            else {
                  nodeB = nodeB.next;
            }            


            if(nodeA === nodeB)
                  return nodeA.val;

      }    
};


console.log (getIntersectionNode(headA, headB) );

what we just did:
– traverse listA and listB till we reach the end of each one (lines 47 and 55) .
– once we reach the end of each list we point the cursor to the opposite list (lines 43 and 51)

Approach 4: Hash Table

Traverse list A and store the address / reference to each node in a hash set. Then check every node bi in list B: if bi appears in the hash set, then bi is the intersection node.

Complexity Analysis

  • Time complexity : O(m+n).
  • Space complexity : O(m) or O(n).
 function ListNode(val) {
      this.val = val;
      this.next = null;
 }

// link-list A: [4,1,8,4,5]
// link-list B: [5,0,1,8,4,5]

headA = new ListNode(4);
headA.next = new ListNode(1);


headB = new ListNode(5);
headB.next = new ListNode(0);
headB.next.next = new ListNode(1);

headA.next.next = headB.next.next.next = new ListNode(8);
headB.next.next.next.next = headA.next.next.next = new ListNode(4);
headB.next.next.next.next.next = headA.next.next.next.next = new ListNode(5);


/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */


var getIntersectionNode = function(headA, headB) {
      let hashMap = {}; 
      let node = headA;
      while(node != null ) {
            
            hashMap[node.val] = node;
            node = node.next;                  
      }    

      node = headB;
      while(node != null) {
            let val = node.val;
            if(hashMap[val] == node) {
                  return val;
            }
            node = node.next;
      }
};

console.log ("result: ", getIntersectionNode(headA, headB) );

 

Check if string has all unique characters

Task

Implement an algorithm to determine if a string (of characters from ‘a’ to ‘z’) has all unique characters or not.

Example 1:

var s = "abcde";
returns true;

Example 2:

var s = "abcade";
returns false;

Solution

Solution 1: The brute force solution will be to iterate through all characters and compare with all other characters.

function areCharactersUnique(s) {
    for(let i=0; i < s.length; i++) {
        for(let j=0; j < s.length; j++) {
            if(i == j)
                continue;
            if(s[i] == s[j]) {
                return false;
            }
        }

    }
    return true;
}

var s = "abcade";

console.log(areCharactersUnique(s));

Solution 2: Using an array (or hashMap table) with key equal to the ASCII character code.

function areCharactersUnique(s) {
    var checker = new Array(26);
    for(let i=0; i < s.length; i++) {
        var pos = s[i].charCodeAt(0) - 'a'.charCodeAt(0);
        if(typeof checker[pos] != 'undefined') {
            return false;
        }
        checker[pos] = 1;
    }
    return true;
}

var s = "abcde";

console.log(areCharactersUnique(s));

Let’s make it more challenging and prohibit the use of additional data structures like count array, hash, etc.

Solution 3: Using bitwise operations to store into 32 bit if one of all 26 characters is presented or not.
We have 26 letters (from a to z). Let’s imagine that we could have 26 empty slots that we could set up to true if the character exists, pretty much as if we have a hashTable.
for simplicity I will use only 6 slots (from a to G) instead of all 26 that represent the whole alphabet.
In addition we have to mention that the slots are actually 32 (this is usually the length of an integer in JavaScript but we need only 26)

6 5 4 3 2 1 0
G F E D C B A
false false false false false false false

Given the string: ‘ABCG’ for example we will end up with this matrix.

6 5 4 3 2 1 0
G F E D C B A
true false false false true true true

But this could be stored into 32bit value using bitwise operations. The binary representation of the matrix above will be:  1000111

The solution:

function areCharactersUnique(s) {
    var checker = 0;

    for(let i=0; i < s.length; i++) {
        // Charcode of a is 97 but we want to start with 0
        var val = s[i].charCodeAt(0) - 'a'.charCodeAt(0);
        // & - Sets each bit to 1 if both bits are 1
        // examples: 
        // 1 & 10 = 0
        // 1 & 101 = 1
        if(checker & ( 1 << val)) {
            return false;
        }

        // | - Sets each bit to 1 if one of two bits is 1
        // examples:
        // 1 | 10 = 11
        // 1 | 1 = 1
        checker = checker | ( 1 << val);
    }
    return true;
}

what we just did:
– we started with creating a loop to go through all characters
– we set up an empty value checker to store if the character is used or not (this is the binary representation of the matrix above)
– (line 6) grabbing the value for each letter in the string but removing ‘a’ = 97 so ‘a’ character will be equal to 0 and z = 26
– (line 19) we are setting the position of the character into the checker to true using bitwise shift left (1 << a) and preserving other already set positions using bitwise | ‘or’
– (line 11) using the same technique but with bitwise & ‘and’ we check if the character position is set to true or not.

Here is a step by  step example for ‘ABCG’ character:
– initially checker = 0 // or the binary representation is '00000000...0'
– we are going to insert a using Zero fill left shift. This is basically going to add ‘1’ followed by as many ‘0’ to the right of the checker as the value of ‘a’ is. In this case 0, so schecker = 1 // binary '00000000...1

– next step inserting ‘b’ follows the same procedure: b = 1, (1 << b) = 2 '000000...10' but we also want to preserve whatever was already inserted so we use bitwise ‘|’ ‘or’ which sets each bit to 1 if one of two bits is 1.
so
checker = checker | (1 << b) = 2
or the binary representation will be:
checker = '00000000...1' | (1 << b) = '000000000...11'

and the same for the rest of the characters

var a = 0;
var b = 1;
var c = 2;
var d = 3;
var e = 4;
var f = 5;
var g = 6;

var s = "abcg";
var checker = 0; 

checker = checker | (1 << a);   // 1
checker = checker | (1 << b);   // 11
checker = checker | (1 << c);   // 111
checker = checker | (1 << g);   // 1000111

Let’s modify the problem, and ask to return the index of the first unique character in the string.
For example for string ‘abcac’ the return will be the index of b – ‘1’
This problem is asked in Leetcode

/**
 * @param {string} s
 * @return {number}
 */
var firstUniqChar = function(s) {
    
    let lastSingle = null;
    let hashMap = {};
    for(var i = 0;i < s.length; i ++) {
        var val = s[i];
        hashMap[val] =  hashMap[val] == undefined ? i: 'not-unique';        
    }

    for(let i=0; i < s.length; i++) {
        var key = s[i];
        if(hashMap[key] != 'not-unique') {
            return hashMap[key];
        }
    }
    return -1;

}

 

LRU Cache

Table of Contents

Task

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.

get(key) – Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) – Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

The cache is initialized with a positive capacity.

Follow up:
Could you do both operations in O(1) time complexity?

Example:

LRUCache cache = new LRUCache( 2 /* capacity */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.put(4, 4);    // evicts key 1
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4

This problem was taken from Leetcode

Solution

The brute force solution will be to use array, to push every new element at the top, and on ‘get’ to pop out the element and to put it at the top of the array.

A better solution will be to use hashmap where the element retrieval will be O(1) (constant) but the hashmap is not keeping track of the order of the elements. To solve this problem we are going to link elements in the hashnmap table with double linked list, where each element will point to its previous and next sibling.
On every ‘get’ operation we are going to re-link the element and it’s siblings.

When the cache reaches the capacity we are going to remove the least used node from the bottom.

put(1,1) put(2,2) get(1) put(3,3) get(2) put(4,4) get(1) get(3) get(4)
return 1 -1 -1 3 4
result 1 2,1 1,2 3,1 => (2) 3,1 4,3 => (1) 4,3 3,4 4,3

 

class LRUCache {

  constructor(capacity) {
    
        this.head = null;
        this.tail = null;
        this.capacity = capacity;
        this.count = 0;
    this.hashMap  = new Map();    
  }

 
  get(key) {
    var node = this.hashMap.get(key);
    if(node) {
      if(node == this.head) {
        // node is already at the head, just return the value
        return node.val;
      }			
      if(this.tail == node && this.tail.prev) {
        // if the node is at the tail,
        // set tail to the previous node if it exists.
        this.tail = this.tail.prev;
        this.tail.next = null;
      }
      // link neibouring nodes together
      if(node.prev)
        node.prev.next = node.next;
      if(node.next)
        node.next.prev = node.prev;			
      // add the new head node
      node.prev = null;
      node.next = this.head;
      this.head.prev = node;
      this.head = node;

      return node.val;
    }
    return -1;
  }

  put(key, val) {
    this.count ++;
    var newNode = { key, val, prev: null, next: null };

    if(this.head == null) {
      // this.hashMap is empty creating new node
      this.head =  newNode;
      this.tail = newNode;
    }
    else {
      var oldNode = this.hashMap.get(key);
      if(oldNode) {
        // if node with the same key exists, 
        // clear prev and next pointers before deleting the node.
        if(oldNode.next) {
          if(oldNode.prev)
            oldNode.next.prev = oldNode.prev;
          else
            this.head = oldNode.next;
        }
        if(oldNode.prev) {					
          oldNode.prev.next = oldNode.next;
          if(oldNode == this.tail)
            this.tail = oldNode.prev;
        }
        // removing the node
        this.hashMap.delete(key);
        this.count --;				
      }

      // adding the new node and set up the pointers to it's neibouring nodes			
      var currentHead = this.head;
      currentHead.prev = newNode;				
      newNode.next = currentHead;
      this.head = newNode;

      if(this.tail == null)
        this.tail = currentHead;

      if(this.count == this.capacity + 1) {
        // remove last nove if over capacity
        var lastNode = this.tail;
        this.tail = lastNode.prev;
        if(!this.tail) {
          //debugger;
        }
        this.tail.next = null;
        this.hashMap.delete(lastNode.key);
        this.count --;
      }

    }
    this.hashMap.set(key, newNode);
    return null;
  }
}

 

Trapping Rain Water

Table of Contents

Task

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.


image was borrowed from leetcode

The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!

Example:

Input:

 [0,1,0,2,1,0,1,3,2,1,2,1]

Output:

 6

This problem was taken from Leetcode

Solution

 

The brute force approach: for each element we go to the right and find the maximum height of the bar, then we go to the left and do the same.

For any element the maximum amount of the water that could be trapped will be the minimum of left height and right height, minus the height of the bar.

So for the array [0,1,0,2,1,0,1,3,2,1,2,1] we go all the way to the right and calculate the max right value, starting from first element ‘0’ max right will be 0. ‘1’ – max right is ‘1’ and so on.
We repeat the same from last element ‘1’ to the first one.

Then the trapped water for the first column will be: theArrayElement[n] –  min(maxRight, maxLeft)

the array 0 1 0 2 1 0 1 3 2 1 2 1
max right 0 1 1 2 2 2 2 3 3 3 3 3
max left 3 3 3 3 3 3 3 3 2 2 2 1
collected
water
0 0 1 0 1 2 1 0 0 1 0 0

The complexity will be O(n2)

/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function(height) {
    if(height.length < 2)
        return 0;

    let findMaxLeft = function(idx, height) {
        let max = 0;
        for(let i =idx;i >= 0; i --) {
            max = Math.max(max, height[i]);
        }
        return max;
    }

    let findMaxRight = function(idx, height) {
        let max = 0;
        for(let i = idx;i < height.length; i ++) {
            max = Math.max(max, height[i]);
        }
        return max;
    }  

    let collectedWater = 0;
    for(let i = 0;i < height.length; i ++) {

        const maxLeft = findMaxLeft(i, height);
        const maxRight = findMaxRight(i, height);

        let min = Math.min(maxLeft, maxRight);
        collectedWater += (min - height[i]);
    }

    return collectedWater;
};

The better solution: find all max left and max right with one loop, then do a second loop for each element in the array, and calculate trapped water.

/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function(height) {
    let maxLeftArray = [], maxRightArray = [];
    let maxLeft = 0, maxRight = 0;
    const ln = height.length;
    let trappedWater = 0;

    for(let i = 0;i < height.length; i ++) {
        maxLeftArray[i] = Math.max(height[i], maxLeft);
        maxLeft = maxLeftArray[i];

        maxRightArray[ln - i - 1] = Math.max(height[ln - i - 1], maxRight);
        maxRight = maxRightArray[ln - i - 1];
    }

    for(let i = 0;i < height.length; i ++) {
        trappedWater += Math.min(maxLeftArray[i], maxRightArray[i]) - height[i];
    }
    return trappedWater;

};
what we just did:

– With one loop find the max left and right bar on each side.
– for any element the maximum amount of the water that could be trapped will be the minimum of left height and right height, minus the height of the bar.

Web Components: create text field with autosuggest dropdown and dictionary

What are web components?

Web components are new reusable components that add new functionalities to a standard components (ie: img, input, table, h, p, div, etc.) or create new components using and combining the existing standard components. You could read more here: web components

How to create custom web component ?

The simplest idea of how to create a web component is this: (although this could be highly customised, and done in many different ways, for the purpose of this exercise to be as simple as possible we will explore the simplest approach):

  1. Create custom class that will handle the presentational logic of the component.
  2. Add the custom tag in the document.
  3. Add the styles and the HTML markup to the main DOM. This is usually done by adding the shadow DOM of the new component to the document’s DOM.

Let’s make a real useful web component, while going to the process

Create a Text Field component with auto suggest drop down drawer and dictionary of the suggested words.

Final product will look like this:

Day:
Month:

First We will

Create the HTML and the CSS layout.

Create a new file

./text-box-with-dropdown.js

var template = `
<style>
  .wrapper {
    display: inline-grid;
  }
  #drawer {
    cursor: pointer;
    border: 1px solid silver;
    background: #f4f4f4;
  }
  .selectedRow {
    color: white;
    background: #606062;
  }
  p {
    margin: 1px;
    border-bottom: 1px solid silver;
  }
  p:last-child {
    border-bottom: none;
  }  
</style>

<div class="wrapper">
  <input type="text" id='textfield'>
  <div id='drawer'>
  </div>
</div>
`;

We added the CSS, and the HTML markup which consists of a wrapper div, a text field, and a ‘drawer’ div, which will show the suggested words.

Add web component’s class.

Next, In the same file create a class to handle the events.

class TextboxWithDropdown extends HTMLElement {  

  constructor() {
    // Always call super first in constructor
    super();
    this._shadowRoot = this.attachShadow({ 'mode': 'open' });
  }

  connectedCallback() {  
    this._shadowRoot.innerHTML = template; 
  }
}

And now let’s attach the newly created web component to the document DOM

window.customElements.define('textbox-with-dropdown', TextboxWithDropdown);

Now we are going to create the actual HTML document to view the component.

./index.html

<!DOCTYPE html>
<html lang="en" prefix="og=https://ogp.me/ns#" itemType="https://schema.org/WebPage" data-reactroot=""></html>
  <head>
    <script src="./text-box-with-dropdown1.js"></script>
    <style>
      body {
            padding: 40px;
            padding-bottom: 40px;
            background-color: #f5f5f5;
      }    
    </style>
  </head>

  <body>
    Day: <textbox-with-dropdown id="txt1" dictionary="1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31" value='1'></textbox-with-dropdown>
    Month: <textbox-with-dropdown id="txt2" dictionary="January,February,March,April,May,June,July,August,September,October,November,December"></textbox-with-dropdown>
  </body>
</html>

 

What we just did:
– we created simple HTML document
– Added two instances of our new WEB component textbox-with-dropdown
– Passed a property with a list of all days of the month to the first component and set it’s value=’1′ which will be the default parameter.
– Passed months of the year as a property to the second component.

So far the component will be visible but not quite functional. Let’s add all that we need to make the component functional.

First let’s discuss connectedCallback hook. It is invoked each time the custom element is appended into a document-connected element, while attributeChangedCallback is invoked when one of the custom element’s attributes is added, removed, or changed. How we are going to use this? We are going to move all initialization logic to connectedCallback
Then we are going to use attributeChangedCallback to listen to attribute changes and update the component. For example if you use the dev tools and inspect the component and edit the dictionary attribute, our component will update accordingly.

Next we are going to add a function that will accept a prefix parameter, which will be whatever the user starts typing in the text field and will filter the dictionary and return only matching words (or numbers).

We will also add keyUp events, so the user will be able to use ‘up’ and ‘down’ keys to navigate through the words list, and in addition we will add mousemove events to highlight the words where the mouse is.

Go back to text-box-with-dropdown.js and add these functionalities.

./text-box-with-dropdown.js

var template = `
<style>
  .wrapper {
    display: inline-grid;
  }
  #drawer {
    cursor: pointer;
    border: 1px solid silver;
    background: #f4f4f4;
  }
  .selectedRow {
    color: white;
    background: #606062;
  }
  p {
    margin: 1px;
    border-bottom: 1px solid silver;
  }
  p:last-child {
    border-bottom: none;
  }  
</style>

<div class="wrapper">
  <input type="text" id='textfield'>
  <div id='drawer'>
  </div>
</div>
`;

class TextboxWithDropdown extends HTMLElement {  

  constructor() {
    // Always call super first in constructor
    super();
    // and attach the component
    this._shadowRoot = this.attachShadow({ 'mode': 'open' });
  }

  connectedCallback() {
    // and when the component is mounted, do the rest to make it work
    this.selectedIndex = 0;
    this.filteredWordsCount = 0;
    this.isDrawerOpen = false;

    this._shadowRoot.innerHTML = template;
    this.textfield = this._shadowRoot.getElementById("textfield");
    this.dictionary = this.getAttribute("dictionary").split(',');
    this.textfield.value = this.getAttribute("value");
    this.textfield.addEventListener("keyup", function(e) {
      this.keyUp(e);
    }.bind(this));    
  }

  static get observedAttributes() {
    // on attributes changed by the browser dev tools this will reflect the changes
    return ["dictionary", "value"];
  }  

  get value() {
   // return the value
    return this.textfield.value;
  }

  attributeChangedCallback(name, oldValue, newValue) {
    //Custom square element attributes changed.
    this.dictionary = newValue.split(',');
  }

  selectHighlight(i) {
    this._shadowRoot.getElementById('row-' + this.selectedIndex).classList.remove("selectedRow");
    this._shadowRoot.getElementById('row-' + i).classList.add("selectedRow");
    this.selectedIndex = i;
  }

  keyUp(e) {
    if(e.keyCode == 13) {
      this.rowSelected(e);
      return;
    }
    if(e.keyCode == '38' || e.keyCode == '40') {
      this.arrowUpDown(e);
      return;
    }
    var prefix = this._shadowRoot.getElementById('textfield').value;
    if(prefix == '') {
      this._shadowRoot.getElementById('drawer').innerHTML = '';
      this.selectedIndex = 0;
      this.filteredWordsCount = 0;
      return;
    }
    if(this.isDrawerOpen == true)
      return;
    var words = this.filterWords(prefix);
    this._shadowRoot.getElementById('drawer').innerHTML = words.join('');
    // attach the events
    var c = 0;
    words.map(function(row, i) {
      var row = this._shadowRoot.getElementById('row-' + i);
      row.addEventListener("mousemove", function() {
        this.selectHighlight(i);
      }.bind(this));
      row.addEventListener("click", function(e) {
        this.rowSelected(e);
      }.bind(this));
    }.bind(this));
    // select first row if any
    if(words.length > 0)
      this._shadowRoot.getElementById('row-0').classList.add("selectedRow");
  }  

  arrowUpDown(e) {
    if(this.selectedIndex > -1)
      this._shadowRoot.getElementById('row-' + this.selectedIndex).classList.remove("selectedRow");    
    if(e.keyCode == '38' && this.selectedIndex > 0) {
      // arrow up
      this.selectedIndex --;
    }
    else if(e.keyCode == '40' && this.selectedIndex < this.filteredWordsCount - 1) {
      // arrow down
      this.selectedIndex ++;
    }
    this._shadowRoot.getElementById('row-' + this.selectedIndex).classList.add("selectedRow");
    e.preventDefault();
    return false;
  }

  rowSelected(e) {
    if(this.filteredWordsCount == 0)
      return;
    this._shadowRoot.getElementById('textfield').value = this._shadowRoot.getElementById('row-' + this.selectedIndex).innerText;
    this._shadowRoot.getElementById('drawer').innerHTML = '';
    this.filteredWordsCount = 0;
    this.selectedIndex = 0;
    e.preventDefault();
    return false;    
  }

  //business logic comes here. Bad idea!. Separate this in a diffrent class. But for the simplicity of the example we will keep it here.
  filterWords(prefix) {
    prefix = prefix.toLowerCase();
    var result = [];
    for(var i=0; i < this.dictionary.length;i ++) {
      var wordArray = this.dictionary[i].toLowerCase();
      for(var j=0; j < prefix.length && j < wordArray.length; j ++) {
        if(prefix[j] != wordArray[j]) {
          break;
        }
      }
      if(prefix.length == j) {
        var wordRow = '<p id="row-' + result.length + '">' + this.dictionary[i] + '</p>';
        result.push(wordRow);
      }
    }
    this.filteredWordsCount = result.length;
    return result;
  }  

}



window.customElements.define('textbox-with-dropdown', TextboxWithDropdown);

What we just did:
– in connectedCallback function we set up:
this.selectedIndex which will point to the selected word in the drawer
this.filteredWordsCount storing the number of word matched
this.isDrawerOpen
this._shadowRoot.innerHTML is set up with the HTML layout that we created in the beginning of the file.
this.textfield is set up with the value of value attribute. this._shadowRoot is the way how we are referring to the root of the component.
this.dictionary stores an array of all dictionary words that we pass using the dictionary property.
– added event listener to the text field, which will respond on keyUp and keyDown and will manipulate the highlighted word.
– (line 56) observedAttributes function returns a list of the attributes that we need to observe. Ie on adding or removing words through browser’s dev tools.
– (line 61) this function is called every time when the value of value attribute is called.Ie when  document.getElementById('txt1').value is called.
– (line 66) attributeChangedCallback is called whenever the observed parameters that we set up in observedAttributes function. Once the dictionary parameter is changed we are updating this.dictionary
– (line 71) selectHighlight function is called on mousemove over the words in the drawer.
– (line 77) keyUp(e) function responds to key press and is responsible to call rowSelected() on enter key, or arrowUpDown() on button up or down. If any other key is pressed filterWords(prefix)is called, prefix is the whatever the user started to type in the textfield. Then when the new words list is returned, we attach mousemove event (line 101), and click event (line 104)
arrowUpDown() simply move the highlighter up or down.
rowSelected() is called when a word is selected either with mouse click or when enter key is pressed.

 

 https://github.com/ToniNichev/WebComponents-text-box-with-dropdown.git