Reputation: 12129
I am writing a very simple jquery imitation library, to allow for some simple DOM manipulation.
I am writing methods to allow me change color of text etc. When I wish to change a class element color I have to use a loop in each method. Ideally I would like to have a function that does that loop for me that I could then use in each method. Unfortunately, my attempt at this is not working.
Please see my code below:
function _(elem) {
this.classOrId(elem);
}
_.prototype = {
add: function(text) {
if (this.e.length >= 1) {
for (var i = 0; i < this.e.length; i++) {
this.e[i].innerHTML = this.e[i].innerHTML + text;
}
} else {
this.e.innerHTML = this.e.innerHTML + text;
}
return this;
},
replace: function(text) {
if (this.e.length >= 1) {
for (var i = 0; i < this.e.length; i++) {
this.e[i].textContent = text;
}
} else {
this.e.textContent = text;
document.body.appendChild(elem);
}
return this;
}
}
_.prototype.classOrId = function(elem) {
var classOrId = elem.charAt(0);
if (classOrId === "#") {
elem = this.sliceElement(elem);
this.e = document.getElementById(elem);
return this;
} else if (classOrId === ".") {
elem = this.sliceElement(elem)
this.e = document.getElementsByClassName(elem);
return this;
}
};
_.prototype.sliceElement = function(elem) {
var elem = elem.slice(1);
return elem;
};
As you can see there is an awful lot of repetition in this code. I tried writing the following to cut down on the repition but it didn't work. Any suggestions here would be greatly appreciated.
_.prototype.loopOverElements = function(effect) {
for (var i = 0; i < this.e.length; i++) {
return this.e[i][effect];
}
}
With the code below it does not recognise the javascript DOM methods such as innerHTML
, style
when they are passed into the function.
With the code above if I pass the effect into the loopOverElements function it shows that console.log(this.e[i][effect])
is undefined when passed into the method.
Upvotes: 0
Views: 103
Reputation: 31779
As I mentioned earlier in the chat session, you basically want to apply transformations on each DOM element in a array. Using a function that defines what needs to be done and mapping each element through this function is the ideal approach.
_.prototype.loopOverElements = function(effect) {
for (var i = 0; i < this.e.length; i++) {
effect(this.e[i]);
}
};
And I can use it as
var test = new _('.test');
test.loopOverElements(function(elem) {
elem.innerHTML += " Modified";
});
Here is a working demo for this implementation.
But your requirement is that you want a loop method that has 2 parameters -propertyName
and value
. There are many problems associated with this like suppose the property is style.color
. You cannot access that property using this.e[i]['style.color']
. Its not valid js. It should be this.e[i]['style']['color']
. So basically this.e[i][effect]
will not work in this case. Library's like lodash have a _.set
method where you can specify a property path and the value will be correctly set. You can see the implementation here.
I've created a naive implementation of the set method.
_.prototype.set = function set(obj, path, value) {
var codeString = 'obj';
codeString += path.split('.').reduce(function(prev, curr) {
return prev += '.' + curr;
}, '');
codeString += ' = value';
eval(codeString);
};
Now your loop code looks like
_.prototype.loopOverElements = function(property, value){
for(var i = 0; i < this.e.length; i++){
this.set(this.e[i], property, value);
}
};
And it can be used like
document.addEventListener("DOMContentLoaded", function(event) {
var test = new _('.testClass');
test.loopOverElements('style.color', 'red');
});
Here is a working demo for this implementation.
Upvotes: 1
Reputation: 13682
You should probably post this on codereview but I'll give it a shot. The first thing bothering me is all the if (this.e.length >= 1) { } else { }
. Make e
an array even when there is only one element, only a variable's value should "variate" not its type.
About classOrId()
, it can be reduced to one line thanks to document.querySelectorAll (returning the needed array). So the code becomes:
function _(elem) {
var about = {
Name: "pQuery",
Version: 0.1,
Author: "Paul Fitzgerald"
}
if (elem) {
if (window === this) {
return new _(elem);
}
this.e = document.querySelectorAll(elem); // no need for classOrId() anymore
} else {
return about;
}
}
_.prototype = {
add: function (text) {
// no if else anymore
for (var i = 0; i < this.e.length; i++) {
this.e[i].innerHTML = this.e[i].innerHTML + text;
}
return this;
}
}
About the repetition, separate the functions into similar usages, for example you could write a function for all the operations related to style:
_.prototype = {
_eachStyle: function (prop, value) {
for (var i = 0; i < this.e.length; i++) {
this.e[i].style[prop] = value;
}
return this;
},
hide: function () {
return _eachStyle('display', 'none');
},
color: function (color) {
return _eachStyle('color', color);
}
}
For the base properties:
_.prototype = {
_each: function (prop, value, append) {
append = append || false; // by default, replace the value
for (var i = 0; i < this.e.length; i++) {
this.e[i][prop] = append ? element[prop] + value : value;
}
return this;
},
add: function (text) {
return _each('innerHTML', text, true);
},
replace: function (text) {
return _each('textContent', text);
}
}
Similarely, for a function to be called on all elements:
_.prototype = {
_eachFn: function (fn, args) {
for (var i = 0; i < this.e.length; i++) {
this.e[i][fn](args);
}
return this;
},
remove: function () {
return _eachFn('remove');
},
}
Upvotes: 1
Reputation: 1340
Another approach to solve your problem is using a callback function:
_.prototype.applyOnElements = function(callback) {
for(var i = 0; i < this.e.length; i++){
callback(this.e[i]);
}
}
And you use it this way:
this.applyOnElements(function(element) {
element.style.color = myColor;
});
Then there is place to improvement by replacing the loop by Array.forEach and including your if (this.e is array) then ... else ... endif
inside this function to avoid more code duplication.
Upvotes: 0
Reputation: 92521
Use brackets notation, i.e. [effect]
instead of .effect
:
_.prototype.loopOverElements = function(effect){
for(var i = 0; i < this.e.length; i++){
return this.e[i][effect];
}
}
See also JavaScript property access: dot notation vs. brackets?.
Upvotes: 0