I'm trying to create a function using vanilla JS that will:
Create a new DOM element
Assign it a Class Name
Place it in the DOM either appending to an existing div or inserting it specifically into the DOM if required using "insertBefore()"
I have come up with the somewhat inelegant solution below:
function createDomElem(elem, className, parent, refElement, type) {
var a = document.createElement(elem);
if (type == "append") {
document.querySelector(parent).appendChild(a);
} else if (type == "insert") {
document.querySelector(parent).parentNode.insertBefore(a, refElement)
}
a.className = className;
};
My problems with this solution are
Too many arguments to be passed
If not passing "insert" then you don't require refElement and to avoid "type" being mistaken for "refElement" you'd have to pass "refElement" as "null" and then define type as "append"
So my question is where can I streamline this function to become more useful within my program?
I'm also dreaming of the ability to be able to push child divs into the newly created div right within this function, defining how many child divs you would want and then using a for loop to append or insert these. Would this be better placed in a new function though?
I would split the code into two parts, as they have to separate concerns. I use something similar to the following for creating DOM elements:
var DomFactory = (function (document) {
var api = {
element: function (name, attributes) {
var el = document.createElement(name);
if (attributes) {
for (var key in attributes) {
if (attributes.hasOwnProperty(key)) {
el.setAttribute(key, attributes[key]);
}
}
}
return el;
},
div: function (attributes) {
return api.element('div', attributes);
}
};
return api;
}(window.document));
Usage:
var div = DomFactory.div({ 'class': 'hero' });
var table = DomFactory.element('table', { 'class': 'table table-bordered' });
Then for positioning, you could have a generalised position function:
function attach(source, target, position) {
switch (position) {
case 'before': {
target.parentNode.insertBefore(source, target);
break;
}
case 'after': {
if (target.nextSibling) {
target.parentNode.insertBefore(source, target.nextSibling);
} else {
target.parentNode.appendChild(source);
}
}
}
}
Usage:
attach(table, div, 'before');
Related
I need to get the style object for a row in ag-grid without using
document.querySelector(`[row-index="${rowIndex}"]`).style
Is there a way to get it from the RowNode or anything else?
Other than the code in your question. No you can't. If you look at the source code. They don't save any styles objects internally.
RowComp.prototype.postProcessStylesFromGridOptions = function () {
var rowStyles = this.processStylesFromGridOptions();
this.eAllRowContainers.forEach(function (row) {
return addStylesToElement(row, rowStyles);
});
};
And here is the definition of addStylesToElement(). As you can see the styles is applied directly to the DOM element.
export function addStylesToElement(eElement, styles) {
if (!styles) {
return;
}
Object.keys(styles).forEach(function (key) {
var keyCamelCase = hyphenToCamelCase(key);
if (keyCamelCase) {
eElement.style[keyCamelCase] = styles[key];
}
});
}
So I made 2 classes,first is the create element one:
class CElement {
constructor(value,elementType,elementStyle){
this.element = document.createElement(elementType);
this.element.innerHTML = value;
this.element.style = elementStyle;
}
display(displayTo) {
document.getElementsByClassName(displayTo).appendChild(element);
}
}
The second is the create mob and diplay to:
class Mob {
constructor(level) {
this.mobLvl = level;
}
display(elementClass) {
ele = new CElement(this.mobLvl + "<br>",'p',"color:red;");
ele.display(elementId);
}
}
I checked my code online for syntax errors , and I don't have any?????
So why doesn't it work when I call:
var mob = new Mob(1,"div","color:red;");
mob.display("someClassName");
You have a couple of issues in your code:
You forgot this in the display() function.
getElementsByClassName() returns an array, so you can't use appendChild() directly, you have to loop over your array or use another selector, for example querySelector().
ele is not defined in your display() function.
You use elementClass as argument of the display() function but then use elementId inside your function.
Finally, a working version could be something like this, that you can of course adapt to your need:
class CElement {
constructor(value, elementType, elementStyle) {
this.element = document.createElement(elementType);
this.element.innerHTML = value;
this.element.style = elementStyle;
}
display(displayTo) {
document.querySelector(displayTo).appendChild(this.element);
}
}
class Mob {
constructor(level) {
this.mobLvl = level;
}
display(elementClass) {
var ele = new CElement(this.mobLvl + "<br>", 'p', "color:red;");
ele.display(elementClass);
}
}
var mob = new Mob(1, "div", "color:red;");
mob.display(".someClassName");
<div class="someClassName"></div>
Looks like you forgot the this. in your CElement.display function. This makes it so that the element variable is undefined, and thus it doesn't append anything to the document.
I am creating a mini-library, sort of trying to reconstruct, at least partly, the way jQuery works for learning purposes and to understand better how object-oriented programming works.
I have recreated the jQuery methods click and addClass, but when I call them like:
$(".class1").click(function() {
$(".class1").addClass("class2"); // Works, but it adds class2 to all elements
this.addClass("class2"); // Doesn't work
});
I get an Uncaught Error saying this.addClass is not a function, which is normal, since I shouldn't be able to access another object's methods.
How is $(this) made in jQuery to mean the DOM element that triggered an event, so that in my case I can use it to add class2 only to the element clicked and not all elements that have the class class1?
P.S: I tried reading the jQuery file, but I feel like these waters are currently too deep for me.
Edit:
I always appreciate all the answers and the help I get on Stack Overflow, but telling me to use $(this) instead of this doesn't solve my issue, because $(this) doesn't exist in my code. I'm trying to learn how to create something like jQuery's $(this) & what's the logic behind it.
The click method is defined as follows:
$.prototype.click = function(callback) {
for (var i = 0; i < this.length; i++) {
this[i].onclick = function(event) {
callback.call(this, event);
}
}
};
With an extra 1.5 years of experience, this question becomes rather easy.
Alter $, so that, except string selectors, it can accept HTML elements.
Create a new instance of the object containing the HTML element given.
Call addClass with that as the context.
Code:
;(function() {
/* The object constructor. */
function ElementList(arg) {
/* Cache the context. */
var that = this;
/* Save the length of the object. */
this.length = 0;
/* Check whether the argument is a string. */
if (typeof arg == "string") {
/* Fetch the elements matching the selector and inject them in 'this'. */
[].forEach.call(document.querySelectorAll(arg), function(element, index) {
that[index] = element;
that.length++;
});
}
/* Check whether the argument is an HTML element and inject it into 'this'. */
else if (arg instanceof Element) {
this[0] = arg;
this.length = 1;
}
}
/* The 'click' method of the prototype. */
ElementList.prototype.click = function(callback) {
/* Iterate over every element and set the 'click' event. */
[].forEach.call(this, function(element) {
element.addEventListener("click", function(event) {
callback.call(this, event);
});
});
}
/* The 'addClass' method of the prototype. */
ElementList.prototype.addClass = function(className) {
/* Iterate over every element. */
[].forEach.call(this, function(element) {
/* Cache the classList of the element. */
var list = element.classList;
/* Add the specified className, if it doesn't already exist. */
if (!list.contains(className)) list.add(className);
});
}
/* The global callable. */
window.$ = function(arg) {
return new ElementList(arg);
}
})();
/* Example */
$("#b1").click(function() {
$(this).addClass("clicked");
console.log(this);
});
<button id="b1">Click</button>
You need to use call, apply, bind or some combination of those to set the callback's context to the DOM Node. Here is a contrived example of jquery's each method that sets the context of the callback using call:
var $ = {
each: function(selector, callback) {
var collection = Array.from(document.querySelectorAll(selector));
collection.forEach(function(element, index) {
// the magic...
callback.call(element, index, element);
});
}
}
$.each('.foo', function(idx, el) {
console.log(this.textContent);
});
this is the native JavaScript element and only exposes the native API. You need to pass it to the jQuery constructor in order to access jQuery's API
$(this).addClass("class2"); // This will work
One possible way (only selectors are accepted):
$ = function(selector) {
this.elements = '';//Select the element(s) based on your selector
this.addClass = function(klass) {
//apply your klass to you element(s)
return this;
};
this.click= function(handler) {
//Attach click event to your element(s)
return this;
};
return this;
};
Please keep in mind it's just an example.
Edit 1:
In your click method you are calling the handler in the wrong scope (the anonymous function scope). You need to use the outer scope:
$.prototype = {
click: function(callback) {
console.log(this.length);
var _self = this;
for (var i = 0; i < this.length; i++) {
this[i].onclick = function(event) {
//this here presents the anonymous function scope
//You need to call the handler in the outer scope
callback.call(_self, event);
//If you want to call the handler in the Element scope:
//callback.call(_self[i], event);
}
}
}
}
Note: In your example, this.addClass("class2"); doesn't work because jQuery calls the click handler in the Element scope not jQuery scope. Therefore, this presents the Element which dosen't have the addClass method;
Ok, I understand now your question. Let me try to help you again.
jQuery doesn't knows what DOM element do you use when you give it to selector. It doesn't parsing it or something else. Just save it to the internal property.
Very simplified code to understand:
$ = function(e) {
// finding object. For example "this" is object
if (typeof e !== 'object' || typeof e.className === 'undefined') {
if (typeof e == 'string') {
if (e[0] == '#') {
e = document.getElementById(e.substring(1));
} else if (e[0] == '.') {
e = document.getElementsByClassName(e.substring(1))[0];
} else {
// ... etc
}
}
// ... etc
}
var manager = {
elem: e,
addClass: function(newClass) {
manager.elem.className = manager.elem.className + ' ' + newClass;
return manager;
},
click: function(callback) {
// here is just simple way without queues
manager.elem.onclick = function(event) {
callback.call(manager, event);
}
}
}
return manager;
}
I'm looking for the shortest syntax which could provide me the same result as this dojo line:
var divblock5 = dojo.create("div", {className: "barlittle", id: "block5"});
but I want to use plain JavaScript instead of dojo framework. I have a lot of dynamic element creation and I want to make my code short as possible.
var create = function(element, properties) {
var elmt = document.createElement(element);
for (var prop in properties) {
elmt[prop] = properties[prop];
}
return elmt;
}
create("div", {className: "barlittle", id: "block5"});
Or, my personal favorite that simply takes HTML and converts it to a DOM node :
var elmtify(html) {
var wrapper = document.createElement('div');
wrapper.innerHTML = html;
return wrapper.firstChild;
}
elmtify('<div class="barlittle" id="block5"></div>');
You should check put-selector: https://github.com/kriszyp/put-selector.
Since this seems to be a still open question I'm adding my method, which is almost the same as Tom said but my approach takes into account style information:
function createElement(tag, attrs) {
if(!tag) throw new SyntaxError("'tag' not defined"); // In case you forget
var ele = document.createElement(tag), attrName, styleName;
if(attrs) for(attrName in attrs) {
if(attrName === "style")
for(styleName in attrs.style) {ele.style[styleName] = attrs.style[styleName];}
else
ele[attrName] = attrs[attrName];
}
return ele;
}
So if you normally write, without any library:
var divBlock5 = document.createElement("div");
divBlock5.className = "barlittle";
divBlock5.id = "block5";
With the snippet Tom provided, you would just write:
var divBlock5 = createElement("div", {className:"barlittle", id:"block5"});
But say you want to add an independent style to your element. Then, with my addition, you write:
var divBlock5 = createElement("div", {className:"barlittle", id:"block5", style:{color:"#08A", fontWeight:"bold"}});
Hope this helps. Cheers!
function createElement(tag, attrs, html) {
if (!tag) throw new SyntaxError("'tag' not defined"); // In case you forget
var ele = document.createElement(tag),
attrName, styleName;
if (attrs)
for (attrName in attrs) {
if (attrName === "style")
for (styleName in attrs.style) { ele.style[styleName] = attrs.style[styleName]; }
else if (attrs[attrName])
ele.setAttribute(attrName, attrs[attrName]);
}
if (html)
ele.innerHTML = html;
return ele;
}
This solution builds on what Tom and Xch3l proposed, but it adds support for passing custom attributes to be applied to the DOM element. Which attributes exist as properties for any given DOM element depends on the DOM element's type, so using the element's setAttribute method is a more robust solution.
Note that you will need to pass a "class" attribute instead of "className," and that this solution also adds support for an optional HTML string that can be passed as the third parameter.
Here is an example of how to use the function:
var divBlock5 = createElement("div", {class:"barlittle", "data-my-custom-attribute":"lorem ipsum", id:"block5", style:{color:"#08A", fontWeight:"bold"}},`<h5>An Optional String of HTML to be Inserted Inside the Newly Created Element</h5><p>Preserve Line Spacing by Using Backticks Instead of Quotes</p>`);
createElement = function(type, className, id) {
var element = document.createElement(type);
element.className = className;
element.id = id;
return element;
}
I am relatively new to OOP so I am not sure about the terminology.
I have created a DOM element as a field of an object (eg myObject.myElement) and I appended the element to the document. The object has a .mousemove() event attached (using jQuery). I want to be able to select the object (myObject) for whom the selected element (myElement) is a field so that I can access other fields of the object. There is more than one object and the event handler is the same for elements of different objects. Is it possible to select the parent object of the element? Do I need to give the element the same fields so that I can access the same data?
I want to be able to do something like this but maybe it is not that straightforward:
$('.bubble').on({mousemove: function () {
parentObject = this.parentObject();
alert(parentObject.otherDataField);
});
The element was created like this:
function bubbleObject(value)
{
this.value = value;
this.element = document.createElement('div');
$(this.element).appendTo('.bubbles');
}
myFirstBubble = new bubbleObject(10);
mySecondBubble = new bubbleObject(100);
and I need to be able to access the value field for the object (I am making it simpler as the code is pretty long and mostly irrelevant to my issue).
Do I need to do this:
function bubbleObject(value)
{
this.value = value;
this.element = document.createElement('div');
$(this.element).appendTo('.bubbles');
this.element.value = value; // add same value to element
}
or is there a better way?
A back reference to the parent could be better than copying values:
function bubbleObject(value)
{
this.value = value;
this.element = document.createElement('div');
$(this.element).appendTo('.bubbles');
this.element.parent = this;
}
However, using this way you have to use element.parent.value instead.
Another approach (as I mentioned before) is to scan all element-containing objects if they contain a specific element. If you don't have all these objects in an array you have to scan all window components (as I said, this is expensive):
function findElement(element) {
for (obj in window) {
if (typeof obj === 'object' && obj.myElement == element) {
return obj;
}
}
return null;
}
This is just an untested draft. And it would be so much better if you have a list of parent objects which you can use instead of window.
function findElement(element) {
for (int i = 0, l = objList.length; i < l; i++) {
if (typeof objList[i] === 'object' && objList[i].myElement == element) {
return objList[i];
}
}
return null;
}