Angel Politis
Angel Politis

Reputation: 11313

Use object directly

I'm making an object that receives a selector as an argument, which has one method named stylise, which is used to make alterations to the CSS of the HTML element(s) selected.

Code:

/* ----- JavaScript ----- */
function get(selector) {
    var
        self = {},
        elements = Array.from(document.querySelectorAll(selector));
    
    this.stylise = function(object) {
        Object.keys(object).forEach(function(propertyName) {
            for (var index = 0; index < elements.length; index++) {
                elements[index].style[propertyName] = object[propertyName];
            };
        });
    };
    
    return Object.setPrototypeOf(self, this.prototype);
};
<!----- HTML -----> 
<input id = "text" type = "text" style = "padding: .5em .7em;"/>

If the line return Object.setPrototypeOf(self, this.prototype); is skipped the above code will work as expected by calling it like:

var a = new get("#text");
a.stylise({
    backgroundColor: "yellow",
    borderRadius: "5px"
});

However, I want the above to work by calling it like:

get("#text").stylise({
    backgroundColor: "yellow",
    borderRadius: "5px"
});

I tried to achieve that by using Object.setPrototypeOf(self, this.prototype), but to no avail as I got the following error in the console: Object doesn't support property or method 'stylise'.

What part of my code is causing this and how can I resolve this?

Upvotes: 1

Views: 66

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074018

You don't need setPrototypeOf for that (there are very, very few situations where it's what you would reach for), just have get return the object you've created and assigned to self and assign to self.stylise rather than this.stylise:

/* ----- JavaScript ----- */
function get(selector) {
    var
        self = {},
        elements = Array.from(document.querySelectorAll(selector));
    
    self.stylise = function(object) {
        Object.keys(object).forEach(function(propertyName) {
            for (var index = 0; index < elements.length; index++) {
                elements[index].style[propertyName] = object[propertyName];
            };
        });
    };
    
    return self;
}
get("#text").stylise({
    backgroundColor: "yellow",
    borderRadius: "5px"
});
<!----- HTML -----> 
<input id = "text" type = "text" style = "padding: .5em .7em;"/>

In fact, you can create stylise within the object initializer. You can also use forEach on elements:

/* ----- JavaScript ----- */
function get(selector) {
  var elements = Array.from(document.querySelectorAll(selector));

  return {
    stylise: function(object) {
      Object.keys(object).forEach(function(propertyName) {
        elements.forEach(function(element) {
          element.style[propertyName] = object[propertyName];
        });
      });
    }
  };
}
get("#text").stylise({
  backgroundColor: "yellow",
  borderRadius: "5px"
});
<!----- HTML ----->
<input id="text" type="text" style="padding: .5em .7em;" />


You've said elsewhere that you want to define stylise outside the constructor (and that putting elements on the object was fine). If so, you do that by using Object.create to create an object backed by a specific prototype object (in our case, get.prototype); this does what new does when it creates objects before calling the constructor, but you've said you don't want to use new.

Note that this example first creates two objects via get, then calls stylise on them; the various attempts you posted trying to do this would fail in that scenario:

/* ----- JavaScript ----- */

// The builder function
function get(selector) {
  // Create a new object we'll return, backed by `get.prototype`
  var obj = Object.create(get.prototype);
  
  // Put the elements matching the selector on it
  obj.elements = Array.from(document.querySelectorAll(selector));
  
  // Return it
  return obj;
}

// Add `stylise` to the prototype `get` assigns objects
get.prototype.stylise = function(object) {
  // Get our elements, so we don't have to worry about `this` in the callbacks below
  var elements = this.elements;
  
  // Style them
  Object.keys(object).forEach(function(propertyName) {
    elements.forEach(function(element) {
      element.style[propertyName] = object[propertyName];
    });
  });
};

// Using it
var t1 = get("#text1");
var t2 = get("#text2");
t1.stylise({
  backgroundColor: "yellow",
  borderRadius: "5px"
});
t2.stylise({
  backgroundColor: "green",
  borderRadius: "10px"
});
<!----- HTML ----->
<input id="text1" type="text" style="padding: .5em .7em;" />
<input id="text2" type="text" style="padding: .5em .7em;" />


Side note: Function declarations don't need ; after them (although putting one there is harmless). ; is for terminating statements. Declarations aren't statements.

Upvotes: 5

Related Questions