3
\$\begingroup\$

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?

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Nov 28, 2016 at 18:17
\$\endgroup\$

1 Answer 1

2
+100
\$\begingroup\$

tools(), resources() and actions() should all be statically implemented as Objects instead of being a function that returns an Object. Otherwise they will get re-created in memory every time they are being used.

Example:

class App { ... }
App.Tools = { tools code... };
App.Resources = { resources code... };
App.Actions = { actions code };

Taking it a bit further; tools often are called Utilities and may be defined in a separate utilities file/module, that can be referenced and re-used thoughout the application. Especially if the utilities contains commonly used functions.

answered Dec 1, 2016 at 14:55
\$\endgroup\$
7
  • \$\begingroup\$ thanks for your suggestion, let me know if I get the general idea right! public functions and object that we may use throughout the project (in this case Tools and Resources) must be different modules or classes, and the rest of functionalities must be defined as objects bound to their respective class, am I right? \$\endgroup\$ Commented Dec 1, 2016 at 15:18
  • \$\begingroup\$ Yes, if you happend to have code that will get re-used anywhere else in the app, you want to extract it into a separate module/class. Typically you have a Utils object that holds all comon methods, variables, constants etc. \$\endgroup\$ Commented Dec 1, 2016 at 15:20
  • \$\begingroup\$ thanks again, and one final question if I may, are there any other approaches? in general I mean, just for expanding my knowledge not for this case specifically \$\endgroup\$ Commented Dec 1, 2016 at 15:22
  • \$\begingroup\$ I just tested your suggestion, if I do App.Resources={ resources code... }, after defining the App class, then create a new instance of App lets say var myApp = new App(); then myApp.Resources is undefined, am I missing something? \$\endgroup\$ Commented Dec 1, 2016 at 15:38
  • \$\begingroup\$ There are probably many approaches, but using utility modules is definitely used by 99.99999% of all code. +/- 0.1%. You did define App.Resources as the object you are returning from your current implementation, right? eg. App.Resources = {myConstant:123, myFunction() { return 10; } } and so on \$\endgroup\$ Commented Dec 1, 2016 at 15:41

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.