Executing polymorphism in Javascript without the use of OOP classes

In JavaScript or other object-oriented programming languages, polymorphism is achieved by creating different types.

For instance:

class Field {...}

class DropdownField extends Field {
  getValue() { 
     //implementation ....
  }
}

Imagine a library forms.js with the following methods:

class Forms {
    getFieldsValues() {
      let values = [];
      for (let f of this.fields) {
          values.push(f.getValue());
      }
      return values;
    }
}

This function retrieves all field values, regardless of the field type.

This allows developer A to create the library while developer B can introduce new fields, such as AutocompleterField, without having to modify the library code (Forms.js).

If using functional programming in JavaScript, how could one achieve equivalent functionality?

In case an object doesn't have predefined methods, one may resort to using conditional statements like so:

if (field.type == 'DropdownField')...
else if (field.type == 'Autocompleter')..

However, adding a new type would require modifying the library code.

Is there a more elegant solution in JavaScript that does not rely on object-oriented programming?

While JavaScript is not strictly OOP or FP, are there alternative approaches to tackle this challenge?

Thank you.

Answer №1

If you want to tackle the problem in JavaScript using functional programming, the solution is quite straightforward - utilize functions! Instead of overcomplicating things, you can achieve the same outcome with just a few lines of code:

// getValue :: DOMNode -> String
const getValue = field => field.value;

// readForm :: Array DOMNode -> Array String
const readForm = formFields => formFields.map(getValue);

readForm(Array.from(document.querySelectorAll('input, textarea, select')));
// -> ['Value1', 'Value2', ... 'ValueN']

The key consideration lies in how the Field::getValue() function is implemented and what it actually returns. Specifically, does DropdownField::getValue() differ significantly from AutocompleteField::getValue() or NumberField::getValue()? Are they solely responsible for returning values, or do they serve different purposes?

When defining your Field classes and their subtypes, do their distinctions arise from the workings of their getValue() methods or from other functionalities they possess? For instance, the autocomplete feature of a textfield should ideally operate independently of how its value is extracted.

If you do require distinct methods for retrieving values, you can create a function that accepts a map/object containing {fieldtype: readerFunction} pairs:

/* Library code */

// getTextInputValue :: DOMNode -> String
const getTextInputValue = field => field.value;

// getDropdownValue :: DOMNode -> String
const getDropdownValue = field => field.options[field.selectedIndex].value;

// getTextareaValue :: DOMNode -> String
const getTextareaValue = field => field.textContent;

// readFieldsBy :: {String :: (a -> String)} -> DOMNode -> Array String
readFieldsBy = kv => form => Object.keys(kv).reduce((acc, k) => {
  return acc.concat(Array.from(form.querySelectorAll(k)).map(kv[k]));
}, []);



/* Code the library consumer writes */

const readMyForm = readFieldsBy({
  'input[type="text"]': getTextInputValue,
  'select': getDropdownValue,
  'textarea': getTextareaValue
});

readMyForm(document.querySelector('#myform'));
// -> ['Value1', 'Value2', ... 'ValueN']

Please note: I purposely omitted complex concepts like the IO monad to keep things simple, but delving into this area may prove beneficial.

Answer №2

In JavaScript or Object-Oriented Programming language, polymorphism is achieved by incorporating various types.

Absolutely. Alternatively, it can be achieved by implementing the identical type interface in multiple objects.

Is there a way to utilize Javascript polymorphism without OOP classes?

There appears to be some confusion regarding the distinction between classes and types. It is not necessary to use JS class syntax in order to create objects.

You can simply have

const autocompleteField = {
    getValue() {
        …
    }
};
const dropdownField = {
    getValue() {
        …
    }
};

and then utilize both within your Forms instance.

Answer №3

When discussing "polymorphism," the concept can vary depending on context. You may be referring to ad-hoc polymorphism, which is commonly facilitated by type classes in programming languages like Haskell, Scala, or PureScript. In this approach, dispatching is typically achieved by including witness objects as additional function arguments. These witness objects then dictate how to execute the polymorphic behavior.

For instance, consider the following snippet of PureScript code (sourced from the documentation), which defines a show function for various data types:

class Show a where
  show :: a -> String

instance showString :: Show String where
  show s = s

instance showBoolean :: Show Boolean where
  show true = "true"
  show false = "false"

instance showArray :: (Show a) => Show (Array a) where
  show xs = "[" <> joinWith ", " (map show xs) <> "]"

example = show [true, false]

This PureScript code is transpiled into JavaScript, resulting in the following shortened version:

var Show = function (show) {
    this.show = show;
};

var show = function (dict) {
    return dict.show;
};

var showString = new Show(function (s) {
    return s;
});

var showBoolean = new Show(function (v) {
    if (v) {
        return "true";
    };
    if (!v) {
        return "false";
    };
    throw new Error("Failed pattern match at Main line 12, column 1 - line 12, column 37: " + [ v.constructor.name ]);
});

var showArray = function (dictShow) {
    return new Show(function (xs) {
        return "[" + (Data_String.joinWith(", ")(Data_Functor.map(Data_Functor.functorArray)(show(dictShow))(xs)) + "]");
    });
};

var example = show(showArray(showBoolean))([ true, false ]);

This implementation relies on passing additional arguments without any hidden complexities. When dealing with specific concrete types, you must provide the corresponding witness objects accordingly.

In your particular scenario, it would involve circulating a HasValue witness tailored for different structures.

Answer №4

One way to adhere to the open-closed principle is by implementing the factory pattern. According to this principle, software entities should be open for extension but closed for modification.

class FieldValueProviderFactory {
    getFieldValue(field) {
        return this.providers.find(p => p.type === field.type).provider(field);
    }
    registerProvider(type, provider) {
        if(!this.providers) {
            this.providers = [];
        }

        this.providers.push({type:type, provider:provider});
    }
}

var provider = new FieldValueProviderFactory();
provider.registerProvider('DropdownField', (field) => [ 1, 2, 3 ]);
provider.registerProvider('Autocompleter', (field) => [ 3, 2, 1 ]);

class FieldCollection {
    getFieldsValues() {
        this.fields = [ { type:'DropdownField',value:'1' }, { type:'Autocompleter',value:'2' } ];

        let values = [];
        for (let field of this.fields) {
            values.push(provider.getFieldValue(field));
        }
        return values;
    }
}

By registering providers for new field types in the factory, you can add functionality without modifying existing code.

new Field().getFieldsValues();

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

React.js: Why does the array index change after dropping an element?

When I create a table with items in a checkbox list, the issue arises; after selecting and submitting some items, the index of the remaining items changes. Consequently, re-submitting the remaining items becomes impossible. Below is my code snippet: expo ...

Using React Native to Store Items in Flatlist via AsyncStorage

My challenge involves displaying/storing a list of items in a flatlist. The issue arises when I save an item and then load it on another screen; there seems to be a repetitive pattern (refer to the screenshot). Additionally, adding a new item results in re ...

What is the best way to handle a specific submit button using jQuery/Ajax?

I created a web page with 4 submit buttons that all call the same PHP page process.php using jQuery/Ajax. I trigger this PHP page using the onClick event as shown below: <div class="col-md-12"> <input type="hidden" name="_token" value="<?p ...

How to use Sencha Touch to automatically target a textfield on iOS programmatically?

My goal is to implement a pin login feature similar to the ones found on iOS and Android platforms, where users are required to enter a 4-digit pin to proceed. The issue I'm facing is that I want the input field to be automatically selected and the nu ...

Techniques for eliminating single quotes from string arrays and then creating a new array by separating them with commas

I have an array of elements containing IP addresses with single quotes. I need to remove the single quotes and separate each IP address into new values, storing them as strings in another array using JavaScript. let myArray = [ '10.202.10.800,10.202 ...

Image loading failure detected in ReactJS

In my current project using Reactjs (Nextjs framework), I encountered an issue where I am unable to display an image on a page without specifying the "height" and "width" attributes in the Image tag. I attempted the following code snippet but the image is ...

Angular 6 and the intricacies of nested ternary conditions

I need help with a ternary condition in an HTML template file: <div *ngFor="let $m of $layer.child; let $childIndex=index" [Latitude]="$m.latitude" [Longitude]="$m.longitude" [IconInfo]="$childIndex== 0 ? _iconInfo1:$c ...

Failure to register Express Route

I am currently using express and facing some challenges with creating routes using express.Router. Below is my index.js file (npm main file): require('dotenv').config() const express = require('express') const loaders = require('. ...

TRPC fails to respond to the passed configuration or variables (e.g., when enabled is set to false)

Recently started using trpc and I'm trying to grasp how to utilize useQuery (which I've previously worked with in react-query): const IndexPage = () => { const { isLoading, data, isIdle } = trpc.useQuery([ "subscriber.add", { email: ...

Obtaining a subset of data from firebase

I am currently working on retrieving a sub-collection from the Firestore database using Angular. In my database, I have a collection called 'Company' which contains fields for 'Name' and 'Id', as well as a sub-collection named ...

Strategies for Synchronizing Multiple Asynchronous Calls in NodeJS

I have a function getAliasesByRoleDetailed(role) that is responsible for retrieving user data based on a given role. This particular function utilizes axios to fetch the necessary data. The result obtained from executing this function appears in the follo ...

Retrieving text from a draggable div using jQuery

I have a draggable div that I can move over another element with the class .outerDiv which contains text content. Is there a way for me to retrieve the text from .outerDiv that overlaps with the draggable div? $(".outerDiv .isStore").draggable({ contain ...

Using Javascript to iterate through and increase HTML code with values all the way up to 55

I am looking for a way to automatically generate a list of links in an HTML page using JavaScript. I have tried a few scripts, but none have worked for me. This is the current structure of my HTML... <a href="1.html"><img src="images/1.jpg" widt ...

Stop node.js from automatically converting a nested object containing numeric keys into an array

Every time I send an object with a nested object containing keys that are numbers to my node.js server, the nested object gets converted into an array. Is there a way to prevent this from happening? Client: $.ajax({ url : `/cctool/report`, method ...

Discover past stock prices on Yahoo Finance

I'm stuck on tweaking a functioning jfiddle example that I have. Can anyone help me with this two-part question regarding the jfiddle: http://jsfiddle.net/maxmillien/qPVSy/ Part 1) Is there a way to clear the search each time a new search is performe ...

What is the procedure to obtain a session object on the server side in next.js version 14?

I am currently utilizing version 14 of next.js with its app routing feature and NextAuth. My goal is to secure the API, however, I encounter a null object when using the getServerSession( authOptions ) method while attempting to access a protected endpoin ...

The perplexing phenomena of Ajax jQuery errors

Hey there! I'm having a bit of trouble with ajax jquery and could use some guidance. $.ajax({ type:"get", url:"www.google.com", success: function(html) { alert("success"); }, error : function(request,status,error) { alert(st ...

Two distinct iterations of the identical jquery version sourced from external sources

NOTE: This situation involves having two copies of jQuery with the same version number but different libraries loaded by external sources. This is distinct from the issue of using multiple versions of jQuery on a single page, as discussed here: Can I use m ...

Using a single function to generate multiple instances of a table in JavaScript

My journey to learning javascript led me to create a simple game called circle of crosses. Below is the code I used: (JS) // JavaScript code here (HTML) <!DOCTYPE html> <html> // HTML code here </html> I came across an issue whil ...

Update the browser URL dynamically without redirecting the page, by utilizing JavaScript after an AJAX call receives a

I am currently endeavoring to find a solution to replace the URL on my page without triggering a reload. My approach involves utilizing an infinite scroll JavaScript plugin. Upon receiving a response from an AJAX call, I intend to update the URL with the n ...