📜 ⬆️ ⬇️

Create a similarity to the TypeScript template.

Most recently, Microsoft introduced a new programming language TypeScript . Surely, many people liked the presence of typing and plugins for Microsoft Visual Studio and other editors. To evaluate how useful a language is in development, I decided to play around with it by writing a small code that will help in developing applications.

Everyone came across this


Anyone who has developed applications using HTML + js technology knows that very often they have to solve the problem of templating data on a presentation. There are many solutions: using frameworks ( one , two , three , four, and so on), as well as simple techniques like these:

var StringTemplate = "<div class=\"{Flag}\" >" + "<p class=\"name\" >{Name}</p>" + "<p class=\"message\" >{Text}</p>" + "<p class=\"date\" >{Date}</p>" + "</div>", html = "", Container = document.getElementById('container2'); for (var i = 0; i < TestDataLength; i++) { html += StringTemplate .replace(/\{Flag\}/g, TestData[i].Out ? "message out" : "message") .replace(/\{Name\}/g, TestData[i].Name || "") .replace(/\{Text\}/g, TestData[i].Text || "") .replace(/\{Date\}/g, TestData[i].Date.toLocaleTimeString()); } Container.innerHTML = html; 

The latter method, in my opinion, is the most obvious and simple, however, it does not take into account the need to modify the view after its formation. You can add an id or data-id for each element that we are going to modify, and then use jQuery or querySelector to select the necessary element, but a DOM search is quite a resource-intensive process.

Closures are js developer's favorite friends!


The quickest way to templify, if necessary, the frequent modification of a view is to manually build a DOM. There are as many as three methods for this: document.createElement (), document.createTextNode () and (but not necessarily) document.createAttr (). Working with these methods in different browsers may be even slower than with innerHTML, but at the output we get a pointer to the element and the page code is not cluttered with identifiers.
')
A little sugar

First of all, we will create a Templater module and three functions that will increase performance and make the code more enjoyable to read:
 module Templater { export function Element(TagName: string, Attributes: Object) : HTMLElement { var item:HTMLElement = document.createElement(TagName); for (var p in Attributes) if (typeof(Attributes[p]) == "string") item.setAttribute(p, Attributes[p]); else if (Attributes[p] instanceof Attr) item.setAttributeNode(Attributes[p]); return item; } export function Text(Text: string): Text { return document.createTextNode(Text); } export function Nest(Element: HTMLElement, Items: any[]): HTMLElement { var l = Items.length; for (var i = 0; i < l; i++) if (Items[i]) Element.appendChild(Items[i]); return Element; } // ... 


The first function creates a tag with the specified name and sets the necessary attributes, Nest - creates a hierarchical structure of elements and Text - just a synonym for document.createTextNode ();

Data structures

Since in TypeScript there is such a blessing of civilization as unobtrusive typing, it should be used. So, we have an element on the form and data associated with it. Let's do the obvious thing:
  export class Block { constructor (public Element: HTMLElement, public Binding) { } } 

Well, a supporting structure that will perform actions on the element:
  export class Template { constructor (public Create: { (Element: { (TagName: string, Attributes: Object): HTMLElement; }, Nest: { (Element: HTMLElement, Items: any[]): HTMLElement; }, Text: { (Text: string): Text; }) : Block; }, public Update: { (Element: Block, Data); }) { } public Make(Data) { var item = this.Create(Element, Nest, Text); this.Update(item, Data); return item; } } 


The attentive reader will note that we specify typed anonymous functions as arguments. This is very convenient: both the component developer and the developer using the component will know what the library wants from it.

Usage example

The template is created this way:
 import T = Templater; var myItem = new T.Template( (Element, Nest, Text) => { var Name, Date, Text, Flag, Container; Nest(Container = Element('div', { 'class': Flag = document.createAttribute("class") } ), [ Name = Element('p', { 'class': 'name' }), Text = Element('p', { 'class': 'text' }), Date = Element('p', { 'class': 'date' }) ]); return new T.Block(Container, { Name: Name, Date: Date, Text: Text, Flag: Flag, Container: Container }); }, (Element: T.Block, Data) => { var b = Element.Binding; b.Name.innerText = Data.Name || ""; b.Date.innerText = Data.Date.toLocaleTimeString(); b.Text.innerText = Data.Text || ""; b.Flag.nodeValue = "message " + (Data.Out == true ? "out" : ""); }); 

One more advantage of TypeScript is the shortened write of an anonymous function (arguments) => {code}. It makes the code much more readable.
I pass the arguments (Element, Nest, Text) in the first function for performance reasons: all the same, a direct link to the function works faster than a pointer to the static function of the module. These are the features of Javascript and Typescript to fix the situation in this case simply can not.
What does this code do?
Using the first function (Create), we create an empty document element, and return a pair (the binding element), where the last one represents a kind of ViewBag containing references to the controls you need for the template. QuerySelector for this task is no longer needed.
The second function (Update) binds data from Data to elements from the Binding.

Test Data Set


The code responsible for the test data can be put in a separate module, as in the good old .NET

 module Example { export class ExampleData { constructor (public Name: string, public Date: Date, public Text: string, public Out: bool) { } public static GetExampleData(count: number): any[] { var result = [], ExampleText = "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...", NameA = "John Doe", NameB = "Jane Doe", StartDate = new Date(), Interval = 300; for (var i = 0; i < count; i++) { result.push(new ExampleData( i % 2 == 1 ? NameA : NameB, StartDate, ExampleText, i % 2 == 1)); StartDate = new Date(StartDate.getTime() + Interval); } return result; } } } 

Working with TypeScript you can’t stop being glad that the this keyword in this language means the same thing as C-like programming languages ​​- a link to the current instance of the class object, and not “something-there depending on the context of the function call”.

Run


In order to check the performance of the code, the main function remains:
 window.onload = () => { var TestDataLength = 1500, TestData = Example.ExampleData.GetExampleData(TestDataLength), Container = document.getElementById('container1'); var TestStart = new Date(); for (var i = 0; i < TestDataLength; i++) Container.appendChild(myItem.Make(TestData[i]).Element); var TestEnd = new Date(); console.log("Test completed", { time: TestEnd.getTime() - TestStart.getTime(), count: TestDataLength }); var StringTemplate = "<div class=\"{Flag}\" >" + "<p class=\"name\" >{Name}</p>" + "<p class=\"message\" >{Text}</p>" + "<p class=\"date\" >{Date}</p>" + "</div>"; var html = ""; Container = document.getElementById('container2'); TestStart = new Date(); for (var i = 0; i < TestDataLength; i++) { html += StringTemplate .replace(/\{Flag\}/g, TestData[i].Out ? "message out" : "message") .replace(/\{Name\}/g, TestData[i].Name || "") .replace(/\{Text\}/g, TestData[i].Text || "") .replace(/\{Date\}/g, TestData[i].Date.toLocaleTimeString()); } Container.innerHTML = html; TestEnd = new Date(); console.log("Test with string templater completed", { time: TestEnd.getTime() - TestStart.getTime(), count: TestDataLength }); }; 

And execute a simple command:
tsc templater.ts
After a short wait, the compiler outputs the file compiled into js, ​​which is almost the same in size and readability from the source file. Thank you, Microsoft!

Link to source code

Source: https://habr.com/ru/post/155029/


All Articles