Lately I've been working on a practice project using JS ES6 to understand it better, therefore I've read a lot about JS design patterns and JS coding strategy. My question is about splitting the functionality of the project so I have to briefly explain to what I'm trying to do in this project.
I have an input that gets a command with a URL of a page. The commands are summarize
and crawl
. summarize
actually summarizes the content of the given URL and crawl extracts the words in the URL.
class App {
constructor(options) {
this.email = options.email;
this.inputElement = options.inputElement;
this.progressElement = options.progressElement;
this.infoElement = options.infoElement;
this.outputElement = options.outputElement;
this.init();
}
init() {
this.socket = io();
this.socket.emit('join', {email: this.email});
this.progress = {
action: document.querySelector('.progress-action'),
percentage: document.querySelector('.progress-percentage'),
bar: document.querySelector('.progress-bar')
};
this.bindEvents();
}
bindEvents() {
let that = this;
this.inputElement.addEventListener('keyup', function (e) {
if (e.keyCode === 13) {
let compiledInput = that.tools().compileInput(this.value);
if (!compiledInput.target) {
that.tools().showInfo(that.resources().messages.noURL);
}
else if (!compiledInput.command) {
that.tools().showInfo(that.resources().messages.noCommand);
}
else {
that.tools().showInfo(that.resources().messages.syntax);
that.progressElement.classList.add('show');
that.action()[compiledInput.command](compiledInput);
}
}
else if (this.value.trim() !== '') {
that.tools().showInfo(that.resources().messages.pressEnter);
}
else {
that.tools().showInfo(that.resources().messages.syntax);
}
});
that.socket.on('crawlProcess', function (resp) {
that.progress.action.innerHTML = resp.action;
that.progress.percentage.innerHTML = resp.index + ' Files Crawled - ' + resp.percentage + '%';
that.progress.bar.style.width = resp.percentage + '%';
that.outputElement.innerHTML = '';
for (let i = 0; i < resp.response.length; i++) {
let fieldKey = Object.keys(resp.response[i])[0];
let fields = '<span class="word">' + resp.response[i][fieldKey] + '</span>';
that.outputElement.insertAdjacentHTML('beforeend', fields);
}
if (parseInt(resp.percentage) === 100) {
setTimeout(()=> {
that.progressElement.classList.remove('show');
if (that.outputElement.innerHTML.trim() === '') {
that.tools().showInfo(that.resources().messages.alreadyCrawled);
}
else {
that.outputElement.classList.add('show');
}
}, 500);
}
});
that.socket.on('summarize', function (resp) {
that.progress.action.innerHTML = resp.action;
that.progress.percentage.innerHTML = resp.percentage + '%';
that.progress.bar.style.width = resp.percentage + '%';
if (parseInt(resp.percentage) === 100) {
setTimeout(()=> {
if (!resp.error) {
that.outputElement.innerHTML = resp.response;
that.outputElement.classList.add('show');
}
else {
that.outputElement.innerHTML = '';
that.outputElement.classList.remove('show');
that.tools().showInfo(resp.response.message);
}
that.progressElement.classList.remove('show');
}, 500);
}
});
}
action() {
let that = this;
return {
crawl(options){
that.socket.emit(options.command, {
url: options.target,
maxCrawlCount: options.option || 10,
includeAbsolute: true
});
},
summarize(options){
that.socket.emit(options.command, {
url: options.target,
paragraphs: options.option || 1
});
}
}
}
resources() {
return {
messages: {
pressEnter: 'Press enter to go!',
noURL: 'Please provide a valid target URL to act on!',
noCommand: 'Command not found!',
syntax: 'What can I do for you?',
alreadyCrawled: 'This page is already crawled!'
},
commands: ['summarize', 'crawl']
}
}
tools() {
let that = this;
return {
levenshteinDistance(a, b) {
if (a.length == 0) return b.length;
if (b.length == 0) return a.length;
let matrix = [];
// increment along the first column of each row
let i;
for (i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
// increment each column in the first row
let j;
for (j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
// Fill in the rest of the matrix
for (i = 1; i <= b.length; i++) {
for (j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) == a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
Math.min(matrix[i][j - 1] + 1, // insertion
matrix[i - 1][j] + 1)); // deletion
}
}
}
return matrix[b.length][a.length];
},
levenshteinPercentage(a, b) {
return 1 - (1 / (Math.max(a.length, b.length)) * that.tools().levenshteinDistance(a, b));
},
compileInput (input) {
let url = that.tools().extractURL(input),
command = that.tools().extractCommand(input.replace(url || '', '')),
option = input.match(/ \d+/);
option = (option) ? parseInt(option[0]) : null;
return {
target: url,
command: command,
option: option
};
},
extractURL (input) {
let matchedURLs = input.match(/(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/ig);
return (matchedURLs) ? matchedURLs[0] : null;
},
extractCommand (input) {
let matchedCommands = [];
input = input.split(' ');
for (let j = 0; j < that.resources().commands.length; j++) {
for (let i = 0; i < input.length; i++) {
if (input[i].trim() !== '') {
let distance = that.tools().levenshteinPercentage(input[i].toLowerCase(), that.resources().commands[j].toLowerCase());
if (distance > .5) {
matchedCommands.push({
command: that.resources().commands[j],
distance: distance
});
}
}
}
}
return that.tools().sortByKeyVal(matchedCommands, 'distance')[0].command;
},
sortByKeyVal (arr, key) {
return arr.sort(function (a, b) {
if (a[key] < b[key])
return 1;
if (a[key] > b[key])
return -1;
return 0;
});
},
showInfo(message){
that.infoElement.innerText = message;
}
}
}
}
new App({
email: '[email protected]',
inputElement: document.querySelector('.input'),
progressElement: document.querySelector('.progress'),
infoElement: document.querySelector('.info'),
outputElement: document.querySelector('.output')
});
Is this a valid coding strategy to have only one class and wrapping and grouping the functions into methods? Will this make things complicated in larger projects? If so, what are the suggested or best practices coding strategy for projects like my current projects?