Technicians working with DOM: parent, child and adjacent elements. Techniques of work with DOM: Parental, child and adjacent elements Adding and removing subsidiaries

Complex and heavy web applications have become usual today. Crossbox and easy-to-use jQuery type libraries with their wide functionality can strongly help in manipulating DOM on the fly. Therefore, it is not surprising that many developers use such libraries more often than they work with the native DOM API, with which there were a lot of problems. And although the distinctions in browsers still remain a problem, DOM is now in better shape than 5-6 years ago, when jQuery gained popularity.

In this article, I will demonstrate the possibilities of DOM on HTML manipulating, focusing on the relationship of parental, child and neighboring elements. In conclusion, I will give data on supporting these opportunities in browsers, but consider that the JQuery type library still remains a good option due to the availability of bugs and inconsistencies in the implementation of the native functional.

Counting subsidiaries

To demonstrate, I will use the next HTML markup, during the article we will change it several times:

  • Example One.
  • EXAMPLE TWO.
  • Example Three
  • Example Four.
  • Example Five.
  • Example Six.


Var mylist \u003d document.getelementbyid ("MyList"); Console.log (MyList.Children.Length); // 6 Console.log (MyList.ChildElementCount); // 6.

As you can see, the results are the same, although the techniques are used different. In the first case, I use the Children property. This is a read-only property, it returns a collection. hTML elementsinside the requested element; To count their number, I use the Length property of this collection.

In the second example, I use the ChildElementCount method, which seems to me a more accurate and potentially more supported way (we will discuss it later, I don't think that you have problems with understanding what he does).

I could try to use Childnodes.Length (instead of children.length), but look at the result:

Var mylist \u003d document.getelementbyid ("MyList"); console.log (mylist.childnodes.length); // 13

It returns 13, because Childnodes is a collection of all nodes, including spaces - consider this if you are important than the difference between the subsidiaries and the child elements.

Check the existence of subsidiaries

To check the presence of the element of the subsidiary, I can use the HaschildNodes () method. The method returns logical valuereporting on their availability or absence:

Var mylist \u003d document.getelementbyid ("MyList"); console.log (mylist.haschildnodes ()); // True.

I know that there are subsidiaries in my list, but I can change HTML so that they are not; Now the markup looks like this:



And here is the result of the new launch of HaschildNodes ():

Console.log (mylist.haschildnodes ()); // True.

The method still returns TRUE. Although the list does not contain any items, there is a space that is a valid type of node. This method takes into account all nodes, not only nodes-elements. To make HaschildNodes () returned False, we need to change the markup again:



And now the expected result is displayed in the console:

Console.log (mylist.haschildnodes ()); // False

Of course, if I know that I can encounter a space, first I will check the existence of child nodes, then using the NodeType property, I define whether there are elements-elements among them.

Adding and removing subsidiaries

There are techniques that can be used to add and delete elements from DOM. The most famous of them is based on a combination of CreateEelement () methods and appendchild ().

Var myel \u003d document.createelement ("DIV"); document.body.appendchild (MYEL);

In this case, I create

Using the CreateEelement () method and then add it to Body. Very simple and you probably used this technique before.

But instead of inserting a specially created element, I can also use appendchild () and simply move the existing element. Suppose we have the following markup:

  • Example One.
  • EXAMPLE TWO.
  • Example Three
  • Example Four.
  • Example Five.
  • Example Six.

EXAMPLE TEXT.

I can change the location of the list using the following code:

Var mylist \u003d document.getelementByid ("MyList"), Container \u003d Document.getelementByid ("C"); Container.APPendChild (Mylist);

The final DOM will look like this:

EXAMPLE TEXT.

  • Example One.
  • EXAMPLE TWO.
  • Example Three
  • Example Four.
  • Example Five.
  • Example Six.

Please note that the entire list has been removed from your place (above paragraph) and then inserted after it in front of the closing body. And although usually the AppendChild () method is used to add items created using CreateElement (), it can also be used to move existing items.

I can also completely remove the child element from DOM using removechild (). This is how our list is deleted from the previous example:

Var mylist \u003d document.getelementByid ("MyList"), Container \u003d Document.getelementByid ("C"); Container.Removechild (Mylist);

Now the element is removed. The removechild () method returns a remote item and I can save it in case it will need it later.

Var myoldchild \u003d document.body.removechild (MyList); document.body.appendchild (Myoldchild);

This is the method of childnode.remove (), relatively recently added to the specification:

Var mylist \u003d document.getelementbyid ("MyList"); mylist.remove ();

This method does not return remote object And does not work in IE (only in EDGE). And both methods remove text nodes in the same way as nodes-elements.

Replacing subsidiaries

I can replace the existing child element, regardless of whether this new element exists or I created it from scratch. Here is the markup:

EXAMPLE TEXT.

Var mypar \u003d document.getelementByid ("PAR"), mydiv \u003d document.createElement ("DIV"); MyDiv.ClassName \u003d "Example"; MyDiv.CreateTextNode ("New Element Text")); document.body.replacechild (MyDiv, MyPar);

New Element Text

As you can see, the REPLACECHILD () method takes two arguments: a new element and the old element replaced by it.

I can also use this method to move the existing item. Take a look next HTML:

EXAMPLE TEXT 1.

EXAMPLE TEXT 2.

EXAMPLE TEXT 3.

I can replace the third paragraph first paragraph with the following code:

Var mypelementbyid ("PAR1"), mypar3 \u003d document.getelementbyid ("PAR3"); document.body.replacechild (MYPAR1, MYPAR3);

Now the generated Dom looks like this:

EXAMPLE TEXT 2.

EXAMPLE TEXT 1.

Sampling of specific child elements

There are several in different ways Select a specific element. As shown earlier, I can start using the Collection of Children or the properties of the Childnodes. But take a look at other options:

The properties of Firstelmentchild and LastelementChild make exactly what can be expected from them by their name: choose the first and last daughter elements. Let's go back to our markup:

  • Example One.
  • EXAMPLE TWO.
  • Example Three
  • Example Four.
  • Example Five.
  • Example Six.


I can choose the first and last elements with these properties:

Var mylist \u003d document.getelementbyid ("MyList"); Console.log (mylist.firstelementchild.innerhtml); // "Example One" Console.log (mylist.lastelementchild.innerhtml); // "EXAMPLE SIX"

I can also use the previoulementsIbling and NexTelementsibling properties, if I want to choose child elements other than the first or last. This is done by combining the properties of FirstelementChild and LastelementChild:

Var mylist \u003d document.getelementbyid ("MyList"); Console.log (mylist.firstelementchild.nextelementsibling.innerhtml); // "Example Two" Console.log (mylist.lastelementchild.previouslementsibling.innerhtml); // "EXAMPLE FIVE"

Also there are similar properties of Firstchild, Lastchild, Previoussibling, and NextSibling, but they take into account all types of nodes, and not just elements. As a rule, properties that take into account only nodes-elements are useful than those who choose all nodes.

Insert content in DOM

I already considered ways to insert elements in DOM. Let's go to a similar topic and take a look at the new options in the insertion of the content.

First, there is a simple method insertBefore (), it is largely similar to the replacechild (), takes two arguments and at the same time works both with new elements and with existing ones. Here is the markup:

  • Example One.
  • EXAMPLE TWO.
  • Example Three
  • Example Four.
  • Example Five.
  • Example Six.

Example Paragraph

Pay attention to the paragraph, I'm going to first remove it, and then insert in front of the list, everything is fine:

Var mylist \u003d document.getelementByid ("MyList"), Container \u003d Document.GetElementBy ("C"), mypar \u003d document.getelementByid ("PAR"); Container.insertBefore (MYPAR, MYLIST);

In the resulting HTML, the paragraph will be in front of the list and this is another way to transfer the item.

Example Paragraph

  • Example One.
  • EXAMPLE TWO.
  • Example Three
  • Example Four.
  • Example Five.
  • Example Six.

Like Replacechild (), InsertBefore () takes two arguments: the element is added and the element in front of which we want to insert it.

This method is simple. Let's try now a more powerful insert method: insertadjacenthtml () method.

As a rule, when you need to perform any action with DOM, the developers use jQuery. However, almost any DOM manipulation can be done on pure JavaScript using its DOM API.

Consider this API in more detail:

At the end you will write your simple DOM library, which can be used in any project.

DOM requests

DOM requests are carried out using the method.QuerySelector (), which is received as an argument an arbitrary CSS selector.

Const MyElement \u003d Document.QuerySelector ("# Foo\u003e Div.bar")

He will return the first fit element. On the contrary, it is possible to check whether the element of the selector corresponds to:

MyElement.matches ("div.bar") \u003d\u003d\u003d True

If you need to get all the items corresponding to the selector, use the following design:

Const Myelements \u003d Document.QuerySelectorall ("Bar")

If you know how to refer to which parent element, you can simply search among its child elements, instead of searching throughout the code:

Const Mychildelemet \u003d MyElement.QuerySelector ("Input") // instead: // Document.QuerySelector ("# Foo\u003e Div.bar Input")

The question arises: why then use other, less convenient methods like .gelementSbyTagname ()? There is a small problem - the output result .QuerySelector () is not updated, and when we add a new element (see), it will not change.

Const elements1 \u003d document.queryselectorall ("div") const elements2 \u003d document.getelementsbytagname ("div") const newelement \u003d document.createelement ("div" document.bodyeelement ("DIV") document.body.appendchild (NewElement) elements1.length \u003d\u003d\u003d elements2.length // False

Also QuerySelectorall () collects everything in one list, which makes it not very effective.

How to work with lists?

In addition to all u.QuerySelectorall () there are two small nuances. You can not simply call the methods to the results and expect that they will apply to each of them (as you could get used to doing it with jQuery). In any case, it will be necessary to sort out all the elements in the cycle. The second - the returned object is a list of elements, not an array. Consequently, the methods of arrays will not work. Of course, there are methods for lists, something like .Foreach (), but, alas, they are not suitable for all cases. So it is better to convert a list into an array:

// Using Array.From () Array.From (MyElements) .Foreach // or prototype array (up to ES6) Array.prototype.Foreach.call (Myelements, DosomethingWitheachelement) // Easier: .Foreach.call (Myelements , dosomethingwitheachelement)

Each element has some properties that refer to the "family".

MyElement.Children MyElement.Firstelmentchild MyeElement.LastelementChild MyeElement.previoulementsIbling MyeElement.NexTelementsibling

Since the element interface (Element) is inherited from the node interface (Node), the following properties are also present:

MyElement.Childnodes MyeElement.firstchild MyeElement.Lastchild MyElement.PreviousSibling MyElement.NextSibling MyElement.parenNode MyeElement.ParentElement

The first properties refer to the item, and the last (with the exception of .Parenelement) can be lists of elements of any type. Accordingly, you can check the type of item:

MyElement.firstchild.nodeType \u003d\u003d\u003d 3 // This item will be a text node

Adding classes and attributes

Add new Class very simple:

MyElement.classList.add ("Foo") MyeElement.classList.Remove ("Bar") myElement.classList.Toggle ("BAZ")

Adding a property for an item occurs just as for any object:

// Obtaining the value of the attribute Const Value \u003d myElement.Value // Set the attribute as the property of the myElement.value \u003d "element (! Lang: Foo" // Для установки нескольких свойств используйте.Object.assign() Object.assign(myElement, { value: "foo", id: "bar" }) // Удаление атрибута myElement.value = null !}

You can use methods.getAttribute (), .setattribute () i.removeattribute (). They immediately change the HTML attributes of the element (in contrast to the DOM properties), which will cause browser redrawing (you can see all changes by studying the element using the developer tools in the browser). Such perverters not only require more resources than installing DOM properties, but can also lead to unexpected errors.

As a rule, they are used for elements that have no corresponding DOM properties, such as COLSPAN. Or if their use is really necessary, for example, for HTML properties inheritance (see).

Adding CSS-Styles

Add them in the same way as other properties:

MyElement.Style.marginleft \u003d "2EM"

Some specific properties can be set using .Style, but if you want to get values \u200b\u200bafter some computing, it is better to use the window.getcomputedStyle (). This method receives an item and returns CssStyledeclaration, containing styles both the element itself and its parent:

Window.getComputedStyle (MyeElement) .getPropertyValue ("Margin-Left")

Change DOM

You can move items:

// Adding Element1 as the last element Element2 Element1.appendchild (Element2) // Insert Element2 as a child element Element1 in front of Element3 Element1.insertBefore (Element2, Element3)

If you do not want to move, but you need to insert a copy, we use:

// Creating a clone Const MyElementClone \u003d MyElement.cloneNode () MyParenElement.appendchild (MyelementClone)

The method. CLONENODE () takes a boolean value as an argument, and child elements are also cloned.

Of course, you can create new items:

Const Mynewelement \u003d Document.CreateElement ("Div") Const MynewTextNode \u003d Document.createTextNode ("Some Text")

And then insert them as shown above. It will not work directly to delete, but you can do it through the parent element:

MyParenElement.Removechild (MyeElement)

You can contact and indirectly:

MyElement.parentnode.Removechild (MyeElement)

Methods for elements

Each element has properties such as .innerhtml i.textcontent, they contain HTML code and, accordingly, the text itself. The following example changes the contents of the element:

// Change HTML Myelement.innerhtml \u003d `

New Content

bEEP Boop Beep Boop

`// Thus, the contents are deleted by myElement.innerhtml \u003d null // Add to HTML myElement.innerhtml + \u003d` Continue Reading ...

In fact, the change in HTML is a bad idea, since all changes that have been made earlier are lost, and event handlers are overloaded. It is better to use this method only completely dropping all HTML and replacing it with a copy from the server. Like this:

Const link \u003d document.createElement ("A") Const Text \u003d Document.createTextNode ("Continue Reading ...") Const HR \u003d Document.CreateElement ("HR") link.href \u003d "foo.html" link.appendchild ( Text) MyElement.appendchild (Link) MyeElement.appendchild (HR)

However, this will entail two redrawrs in the browser, while .innerhtml will only lead to one. You can bypass it, if you first add everything to DocumentFragment, and then add a fragment you need:

Const Fragment \u003d Document.CreateDocumentFragment () Fragment.appendchild (Text) Fragment.appendchild (HR) Myelement.appendchild (Fragment)

Event handlers

One of the simplest handlers:

MyElement.ONClick \u003d Function OnClick (event) (Console.log (event.type + "Got Fired"))

But, as a rule, it should be avoided. Here.ONClick is an element property, and in theory you can change it, but you will not be able to add other handlers using another feature that refers to the old one.

To add handlers it is better to use.Addeventlistener (). It takes three arguments: the type of event, the function that will be called every time when triggered, and the configuration object (we will return to it later).

MyElement.AddeventListener ("Click", Function (Console.log))) Myelement.addeventlistener ("Click", Function (Event) (Console.log (Event.Type + " GOT FIRED AGAIN ")))

The Event.target property adds to the element for which the event is fixed.

And so you can access all properties:

// Property `Forms` - array containing links to all forms const MyForm \u003d Document.forms Const MyInPutelements \u003d MyForm.QuerySelectorLall (" Input ") Array.From (MYINPUTElements) .Foreach (el \u003d\u003e (El.addeventlistener (" Change ", FUNCTION (EVENT) (Console.log (event.target.value)))))

Default prevention

To do this, use the method.PreventDefault (), which blocks standard actions. For example, it will block the sending of the form if the authorization on the client side was not successful:

MyForm.AddeventListener ("Submit", Function (Const Name \u003d This.QuerySelector ("# Name") if (name.value \u003d\u003d\u003d "(! Lang: Donald Duck") { alert("You gotta be kidding!") event.preventDefault() } }) !}

Method.stoppropagation () will help if you have a specific event handler, fixed behind the child element, and the second handler of the same event assigned to the parent.

As mentioned earlier, the method .Addeventlistener () takes the third optional argument as an object with the configuration. This object must contain any of the following boolean properties (default all in the FALSE value):

  • capture: The event will be attached to this element before any other item below in DOM;
  • once: the event can only be fixed once;
  • passive: Event.preventDefault () will be ignored (exception during error).

The most common property is .Capture, and it is so common that there is a brief method of recording: instead of passing it in the configuration object, simply specify its value here:

MyElement.addeventlistener (Type, Listener, True)

Processors are deleted using the.RemoveEventListener () method hosting two arguments: Event type and reference to delete handler. For example, the ONC property can be implemented as follows:

MyElement.AddeventListener ("Change", Function Listener (event) (Console.log (event.Type + "Got Triggered On" + this) this.removeEventListener ("Change", Listener)))

Inheritance

Suppose you have an item and you want to add event handler for all his child elements. Then you would have to drive them in the cycle using the MyForm.QuerySelectorLall method ("Input"), as shown above. However, you can simply add items into the form and check their content using event.target.

MyForm.Addeventlistener ("Change", Function (Event) (Const target \u003d event.target if (target.matches ("Input")) (Console.log (target.value))))

And one more plus this method It is that the handler will be automatically attached to the new daughter elements.

Animation

The easiest way to add animation using CSS with the TRANSITION property. But for greater flexibility (for example, JavaScript is best suited.

Call the window.settimeout () method while the animation will not end - not best ideabecause your application may hang, especially on mobile devices. It is better to use window.requestanimationframe () to save all changes to the next redrawing. It takes a function as an argument, which in turn receives a time stamp:

Const Start \u003d window.performance.Now () Const Duration \u003d 2000 Window.RequestanimationFrame (Const Progress \u003d Now - Start Myelement.Style.opacity \u003d Progress / Duration If (Progress< duration) { window.requestAnimationFrame(fadeIn) } }

In this way, a very smooth animation is achieved. In his article, Mark Brown talks on this topic.

We write your library

The fact that in the DOM to perform any operations with the elements all the time has to be sorted out, it may seem very tedious compared to the JQuery $ syntax (". Foo"). CSS ((Color: "Red")). But why not write a few of your own methods, facilitating this task?

Const $ \u003d function $ (Const Elements \u003d Array.From (SELECT.QuerySelectorLall (Selector) Return (Elements, HTML (NewTML) (Element \u003d\u003e Element.innerhtml \u003d newhtml)) Return this), CSS (Element \u003d\u003e Object.Assign (element.style, newcss))) RETURN THIS), ON (Event, Handler, Options) (this.elements .Foreach (Element \u003d\u003e (Element.addeventlistener (Event, Handler, Options))) Return this)))



Pseudocode

$ (". rightarrow"). click (function () (RightarrowParents \u003d this.dom (); //. dom (); Is the Pseudo Function ... IT SHOULD SHOW THE WHOLE ALERT (RIGHTARROWPARENTS);));

Alarm message will be:

body Div.lol a.rightarrow

How can I get it using JavaScript / jQuery?

Using jQuery, like this (followed by a solution that does not use jQuery, with the exception of an event, much less challenges of functions, if it is important):

Real-time example:

$ (". rightarrow"). Click (Function () (VAR RightarrowParents \u003d; $ (this) .Parents (). Addback (). Not ("HTML"). Each (Function () (VAR Entry \u003d This.Tagname .tolowerCase (); if (this.classname) (entry + \u003d "." + this.classname.replace (/ / g, ".");) Rightarrowparents.push (entry);)); Alert (rightarrowparents.join ("")); Return False;));

Click Here

(In the living examples, I updated the Class attribute to DIV as LOL MULTI until you demonstrate the processing of several classes.)

Here is a solution for precise conformity of the element.

It is important to understand that the selector ( Not real), which show chromium tools, uniquely do not identify the element in the DOM. ( For example, it will not distinguish the list of SPAN consecutive elements. No positioning / indexing information)

$ .fn.FullSelector \u003d Function () (Var Path \u003d this.Parents (). Addback (); var quickcss \u003d path.get (). Map (Function (Item) (VAR Self \u003d $ (Item), ID \u003d Item .id? "#" + item.id: "", CLSS \u003d Item.classList.Length? item.classList.Tostring (). Split ("") .map (Function (C) (Return "." + C; )). JOIN (""): "", name \u003d item.nodeName.TolowerCase (), index \u003d self.siblings (name) .length? ": NTH-Child (" + (self.index () + 1) + ")": ""; if (name \u003d\u003d\u003d "HTML" || name \u003d\u003d\u003d "Body") (Return Name;) RETURN NAME + INDEX + ID + CLSS;)). join ("\u003e") ; Return QuickCSS;);

And you can use it like this:

Console.log ($ ("Some-Selector"). FullSelector ());

I moved a fragment from T.j. Crowder for tiny plugin jQuery. I used version of jQuery.Even if he is right, that this is completely extra overhead, but I use it only for debugging purposes, so I do not care.

Using:

Nested Span.
Simple Span.
Pre.


// RESULT (Array): ["Body", "div.sampleclass"] $ ("span"). Getdompath (False) // Result (String): Body\u003e Div.SampleClass $ ("span"). GetDompath ( ) // Result: [Body "," Div # Test "] $ (" Pre "). Getdompath (False) // Result (String): Body\u003e Div # Test $ (" Pre "). GetDompath ()

Var OBJ \u003d $ ("# show-editor-button"), path \u003d ""; While (TagName)! \u003d "undefined") (if (Еbj.attr)) (Path \u003d "." + Obj.attr ("Class"). Replace (/ \\ s / g, ".") + path;) if (obj.attr ("id") (Path \u003d "#" + obj.attr ("ID") + Path;) Path \u003d "" + obj.prop ( "TagName"). TolowerCase () + Path; OBJ \u003d OBJ.Parent ();) Console.log (Path);

hello This feature allows an error associated with the current element not displayed on the way.

Check it out now

$ j (". wrapper"). Click (FUNCTION (EVENT) (EVENT.TARGET); var rightarrowparents \u003d; $ j (event.target) .parents (). Not ("HTML, Body") .each (function () (VAR ENTRY \u003d This.Tagname.TolowerCase (); if (this.classname) (entry + \u003d "." + this.classname.replace (/ / g, ".");) ELSE IF (this.Id) (entry + \u003d "#" + this.id;) entry \u003d replaceall (entry, "..", "."); rightarrowparents.push (entry);)); rightarrowparents.reverse (); //ifhevent.target.nodeName.TolowerCase()\u003d\u003d"a "|| event.target.nodeName.TolowerCase () \u003d\u003d" H1 ") (var entry \u003d event.target.nodeName.TolowerCase (); if (Event.Target.ClassName) (Entry + \u003d "." + event.target.classname.replace (/ / g, ".");) ELSE if (event.target.id) (entry + \u003d "#" + event.target.id;) rightarrowparents.push (entry); //)

Where $ j \u003d jquery variable

also solve the problem with .. in class name

here is a replacement function:

FUNCTION ESCAPEREGEXP (STR) (RETURN STR.REPLACE (/ ([. * +? ^ \u003d!: $ () () | \\ [\\] \\ / \\\\]) / g, "\\\\ $ 1");) FUNCTION REPLACEALL (RETURN STR.REPLACE (NEW REGEXP (ESCAPEREGEXP (FIND), "G"), REPLACE);)

Here is the native version of JS, which returns the jQuery path. I also add identifiers for items if they are. This will give you the opportunity to make the shortest way if you see the identifier in the array.

Var Path \u003d GetDompath (Element); Console.log (Path.join ("\u003e"));

Body\u003e Section: EQ (0)\u003e Div: EQ (3)\u003e Section # Content\u003e Section # Firehose\u003e Div # Firehoselist\u003e Article # Firehose-46813651\u003e Header\u003e H2\u003e Span # title-46813651

Here is a function.

Function GetDompath (EL) (var stack \u003d; while (el.parentnode! \u003d NULL) (Console.log (el.nodeName); var sibcount \u003d 0; var sibindex \u003d 0; for (var i \u003d 0; i< el.parentNode.childNodes.length; i++) { var sib = el.parentNode.childNodes[i]; if (sib.nodeName == el.nodeName) { if (sib === el) { sibIndex = sibCount; } sibCount++; } } if (el.hasAttribute("id") && el.id != "") { stack.unshift(el.nodeName.toLowerCase() + "#" + el.id); } else if (sibCount > 1) (stack.unshift (el.nodeName.TolowerCase () + ": EQ (" + sibindex + ")");) else (stack.unshift (el.nodeName.TolowerCase ());) el \u003d el.parentnode ; ) Return stack.slice (1); // Removes The Html Element)