📜 ⬆️ ⬇️

Creating an application on Htmlix with a router on the server and client side

This article will describe how to make a frontend on Htmlix for filtering by categories and product cards, and also create a router on the client side, so that when you click on the history in the browser, we will have a page that is relevant to this address. The application will consist of two parts:


The entire application code can be downloaded: here, everything in the router folder,
and the app.js file applies to our application.

You can click on a similar option (without the server side) here: here .
')
For those who are not familiar with Htmlix, you can read more easy to understand material here,

Files index.pug, card.pug and the folder includes is what the server will give in the first request to it
if localhost: 3000 / or localhost: 3000 / categories / category (num) - give index.pug, if the request is localhost : 3000 / cards / card? id = (num) - give card.pug with one file in the includes folder in quality as a template that he chooses based on category_id (category number).

Then, from the client side, the application will “load” one variant of templates from the template folder in the fetch request if the address was localhost : 3000 / categories / category (num) will load the card.html file if the request was localhost : 3000 / cards / card? Id = (num) will load cards.html, and in any case will load one option from the json folder, depending on which category we are currently allocated (on which the class ".hover-category" stands)

On the server side, we will have express.js and the pug template engine, the server side in this article will hardly be described, all we need to know about it is that when we request localhost: 3000 / - we will get a list of products from the first category (6 pcs .), when querying localhost: 3000 / categories / category (num) - we will get products from num categories (only 4 categories starting from 1), and when querying localhost: 3000 / cards / card? id = (num) will give us the item card itself by id number (there may be 6 numbers in total starting from 0) if num of the category or item is not created yet, page 404 will be displayed.

The entire application will not consist of components, and depending on the route, one component will be shown in the url and another will be hidden, there will be 6 components in total: categories, cards, cardsingle, variants1, 2, 2, 3 categories are the left side of the screen with a list of categories - visible on all url, cards addresses - the list of filtered product cards is visible only at addresses-localhost: 3000 / and localhost: 3000 / categories / category (num) and cardsingle - the product card that was clicked is visible on localhost: 3000 / cards / card ? id = (num), shows additional information, as well as one of the options 1, 1, 2, 2, 3 micro template for card product.

In order not to write different code for different route options, our application will use the router to determine which route is now and load first of all the components that should be displayed at this stage, and load the rest with the template using the fetch request. For example, if the route is now localhost: 3000 / categories / category (num) then the components will be initialized first: categories and cards and if localhost: 3000 / cards / card? Id = (num) then categories, cardsingle and one option from under the templates depending on from id-category, for example, variants2.

In order to specify which components to load first and which others, and also tell which component to hide and which show to create the routes object at which route, and pass it along with the description of the Stste application to the HTMLixRouter (State, routes) function, create a routes object:

In the html code, the router is specified by adding data-router = “router” to the div in which the presentation will change.

In javascript:

var routes = { ["/"]: { first: ["cards", "categories"], //       ,     ,   "/" routComponent: "cards", //    templatePath: "/router/template/card.html" //   ""  }, ["/categories/category*"]: { // * -   /categories/category(num) -  ,        first: ["cards", "categories"], routComponent: "cards", templatePath: "/router/template/card.html" }, ["/cards/card*"]: { first: ["cardsingle", "categories"], routComponent: "cardsingle", templatePath: "/router/template/cards.html" }, } 

That is, depending on the address, components are given to us, and the rest we “load” from the template folder of the template in the fetch request and initialize them immediately after the first.

Next you need to create all components in html, pug and javascript files.

First, create the application structure in javascript file /router/example.js:

 /*        ( ),    ,    ,       ,     ,      */ var State = {//  categories: {//  -  c     container: 'categori',//      (      ) props: [/*     */], methods: { //     }, }, cards:{// -  -    container: 'card',//   arrayProps: [/*    cards ( div      )*/], arrayMethods: { //    }, props: [/*    (   ) 'card' */], methods: { ///    'card' } }, cardsingle: {// -  -        container: 'cardsingle',//       , ..      props: [/*   */], methods: { //    }, }, variants1: {//-      cardsingle   "render" container: "variant1", //  props: [/**/], methods: { }, } },///    -   -    variants2: { {//  container: "variants2", props: [], methods: { }, }, }, variants3: { //  container: "variant3", props: [], methods: { } }, }, //  ,     /*         this.emiter.prop,     - this.rootLink.eventProps["emiter-  "]   getEventProp()  setEventProp( )*/ eventEmiters: { ["emiter-single-id"]: {// id      cardsingle prop: "0" }, ["emiter-fetch-posts"]: {///           prop: "", }, ["emiter-click-category"]: {///     prop: 0, }, ["emiter-chose-variant"]: {///           prop: "", }, }, stateMethods: { fetchPosts: function(nameFile, callb){ ///     json     nameFile   callb  . }, }, 

Now in more detail, let's create the components:

We will not “load” the categories component (it is present on all addresses of the router)
therefore, it will be present only in pug - when files are first sent from the server

 -var categori_rout = "/categories/"; -var category_name = ["category1", "category2", "category3", "category4"] | ul(data-categories="array") each val, index in category_name li(data-categori="container" data-categori-clickcategory="click") a(href=categori_rout+category_name[index] class=index==category_id? "hover-category" : '' data-categori-listenclick="emiter-click-category" data-categori-categoryclass="class" data-categori-category_href="href")= category_name[index] <!-- Html       --> <ul data-categories="array"><!--  --> <li data-categori="container" data-categori-clickcategory="click"><!-- â„–1 --> <a data-categori-listenclick="emiter-click-category" data-categori-categoryclass="class" data-categori-category_href="href" class="hover-category" href="/htmlix_examples/router/category/category1.html">category1</a> </li><--"hover-category"         --> <li data-categori="container" data-categori-clickcategory="click"><!-- â„–2 --> <a data-categori-listenclick="emiter-click-category" data-categori-categoryclass="class" data-categori-category_href="href" href="/htmlix_examples/router/category/category2.html">category2</a> </li> <li data-categori="container" data-categori-clickcategory="click"><!-- â„–3 --> <a data-categori-listenclick="emiter-click-category" data-categori-categoryclass="class" data-categori-category_href="href" href="/htmlix_examples/router/category/category3.html">category3</a> </li> <li data-categori="container" data-categori-clickcategory="click"> <!-- â„–4 --> <a data-categori-listenclick="emiter-click-category" data-categori-categoryclass="class" data-categori-category_href="href" href="/htmlix_examples/router/category/category4.html">category4</a> </li> </ul><!--  categories --> 


In the code above, we created a list of categories using a template engine, and specified the “hover-category” class for the category whose number will be in the query string, and also designated all the properties that we need in javascript:

data-categories = “array” link to the categories component itself;
data-categori = “container” link to component containers;
data-categori-clickcategory = “click” - property - listener of the “click” event;
data-categori-listenclick = “emiter-click-category” is the listener property of the user event “emiter-click-category” in order to get rid of the “hover-category” class when clicking on another category
data-categori-categoryclass = "class" - property - access to classes within this category;
data-categori-category_href = "href" - property - access to the attribute "href"

Now create this component in javascript:

The main function of this component is to search in the href attribute of the category name and pass it to the fetchPosts method (this.stateMethods.fetchPosts (nameFile, ...)) to load json samples of this category. (nameFile - matches the category name)

In the clickcategory method itself, this points to the property of the event handler, to go to other properties of this container, call this.parentContainer.props. - further the name of the property we need.

 categories: {//   container: 'categori', //  props: ["clickcategory", "listenclick", "categoryclass", "category_href"], //    methods: {//      clickcategory: function(event){//   event.preventDefault(); //    var href = this.parentContainer.props.category_href.getProp(); /// category_href       ///        ,      this.rootLink.router.setRout(href); //      - category_href,             (    ) var nameFile= href.split("/").slice(-1)[0]; //     href   /// eventProp    (this)    fetchPosts var eventProp = this.rootLink.eventProps["emiter-fetch-posts"]; //   ,    ,    "emiter-fetch-posts"        cards      listenfetch   cards this.rootLink.stateMethods.fetchPosts( nameFile, function(jsonData){ eventProp.setEventProp(jsonData) } ); //   "emiter-click-category"   id    listenclick  -  this.rootLink.eventProps["emiter-click-category"].setEventProp(this.parentContainer.id) }, listenclick: function(){//   "hover-category"      (),     //  "emiter-click-category"         id        "hover-category" if(this.parentContainer.id == this.emiter.prop){ this.parentContainer.props.categoryclass.setProp("hover-category"); }else{ this.parentContainer.props.categoryclass.removeProp("hover-category"); } } }, }, 

Next, create the component cards - this is an array of containers that displays the list according to the filter data (json), it can be sent from the server at the first request if url = localhost: 3000 / or localhost: 3000 / categories / category (num), or maybe “ reloading the fetch request depending on the current url, so it will be in the index.pug file and the /template/cards.html file. For simplicity, we will analyze how it looks in the html file:

 <div class=" row" data-cards="array" data-cards-listenfetch="emiter-fetch-posts" data-cards-listenrout="emiter-router"><!--  -  cards --> <div data-card="container" class="col-4 card-in"><!--  --> <h5 data-card-title="text"> 1</h5> <a data-card-click="click" data-card-href="href" href="/cards/card?id=0"> <img data-card-srcimg="src" src="../../img/images.jpg" /> </a> <p data-card-paragraf="text">  1</p> </div><!--   --> </div> 

In the / template / template, it is not necessary to specify many containers in the array, since to create a template, only the first one is taken for cloning, the rest are ignored.

Next javascript code:
There is a common method for the entire array arrayMethods - a listenfetch that listens to the [“emiter-fetch-posts”] event and deletes all containers as it occurs and creates new ones based on data from the server.

As well as the method for containers, click this when clicking on the container causes the router to send a new route to it, and the router, based on the route, looks at which component to hide and which one to show, in our case, the cards component will hide and the “cardsingle” component will appear, This method causes the event ["emiter-single-id"]. In which we transfer the new cardId and oldHref data so that the component “cardsingle” updates its presentation based on them and shows the card corresponding to the id transmitted in the event.

 cards:{ container: 'card', arrayProps: ["listenfetch"], arrayMethods: { listenfetch: function(){//    ["emiter-fetch-posts"]              var newArray = this.emiter.prop; this.rootLink.clearContainer(this.pathToContainer); for(var i =0; i< newArray.length; i++){ ///          var container = this.rootLink.createContainerInArr(this.pathToContainer, { title: newArray[i].title, paragraf: newArray[i].paragraf_short, href: newArray[i].href, srcimg: newArray[i].srcimg }); } this.rootLink.stateProperties.cards = newArray; ///            }//  listenfetch }, props: ['title','paragraf',"click", 'srcimg', "href"], //       methods: { click: function(event){//      href ,   id       this.rootLink.router.setRout            (  ),            event.preventDefault(); var href = this.parentContainer.props.href.getProp(); var cardId = href.split("?")[1].split("=")[1]; var oldHref = window.location.href; this.rootLink.router.setRout(href, this.rootLink.state["cardsingle"]); ///       cardsingle this.rootLink.eventProps["emiter-single-id"].setEventProp([cardId, oldHref]); } } }, 

Next we create the cardsingle component which is a container without an array in which the card is shown when it is clicked, it will also be in card.pug if the first request to the server goes directly to the card and to template / card.html if we “load it” in the fetch request.

Here, for simplicity, we analyze only the html option:

 <div data-cardsingle="container" data-cardsingle-listenid="emiter-single-id" class="card-single"> <div class="row"> <div class="col-7 card-left-column"> <h5 data-cardsingle-title="text"></h5> <img data-cardsingle-srcimg="src" src="../../img/Thul_300x300.png"/> <p data-cardsingle-paragraf="text"> </p> <p >: <span data-cardsingle-category="text"> category 1 </span> </p> <a data-cardsingle-clickback="click" data-cardsingle-href_back="href" href="/"> <  </a> </div> <div class="col-5 right-columt"> <div data-cardsingle-render="render-variant"> <!---     --> </div> <p >  : <span data-cardsingle-listenchosevariant="emiter-chose-variant" data-cardsingle-chosetext="text" style="color: red;"> <span> </p> </div> </div><!--row --> </div> 

In it properties:

data-cardsingle = "container" - link to the container;
data-cardsingle-listenid = "emiter-single-id" is a property of a custom event listener;
data-cardsingle-title = "text" property - access to the name of the card
data-cardsingle-srcimg = "src" - the address of the picture
data-cardsingle-paragraf = "text" - full description text
data-cardsingle-category = "text" - from which category
data-cardsingle-clickback = "click" - click on the "back" button
data-cardsingle-listenchosevariant = “emiter-chose-variant” - listens to which variant from the list is selected and displays it in the chosetext property
data-cardsingle-render = "render-variant" - displays the current template version for each card

Next javascript:
The main method is listenid, which listens to the event. “emiter-single-id” receives the element id sent to the event which is clicked and, based on it, takes data from the corresponding json object and updates all its properties, thereby updating the presentation, as well as updating the version of its micro template. .

 cardsingle: {//  container: 'cardsingle', //   props: ["render", "category", "title","srcimg", "paragraf", "href_back", "clickback", "listenid", "listenchosevariant","listenvariant", "chosetext"],//   methods: { clickback: function(event){//        event.preventDefault(); var href = this.parentContainer.props.href_back.getProp(); this.rootLink.router.setRout(href); }, listenchosevariant: function(){///  "emiter-chose-variant"     this.parentContainer.props.chosetext.setProp(this.emiter.prop); }, listenid: function(){//  "emiter-single-id"         var id = this.emiter.prop[0];/// id   var href = this.emiter.prop[1]; var cards = this.rootLink.stateProperties.cards; this.parentContainer.props.title.setProp(cards[id].title); this.parentContainer.props.paragraf.setProp(cards[id].paragraf); this.parentContainer.props.href_back.setProp(href); this.parentContainer.props.srcimg.setProp(cards[id].srcimg); this.parentContainer.props.category.setProp(cards[id].category); this.parentContainer.props.chosetext.setProp(""); //  - ,         if(this.rootLink.state[cards[id].variant_template].type== "array"){ this.rootLink.clearContainer(cards[id].variant_template);///   for(var i =0; i< cards[id].variants.length; i++){ this.rootLink.createContainerInArr(cards[id].variant_template, { text: cards[id].variants[i], }); } ///   this.parentContainer.props.render.setProp(cards[id].variant_template); } }, 

Further, by the same principle, we create three variants of micro templates for the goods card.

 <ul data-variants1="array"><!--  --> <li data-variant1="container" data-variant1-clickvariant="click" ><!--  --> <a data-variant1-text="text" href="/"> â„–1</a> </li> </ul> <form data-variants2="container"><!--    --> <div class="form-group"> <label for="exampleFormControlSelect1"> :</label> <select data-variants2-clickvariant2="click" data-variants2-select="select" class="form-control" id="exampleFormControlSelect1"> <option>1</option> <option>2</option> <option>3</option> <option>4</option> <option>5</option> </select> </div> </form> <form data-variants3="array"><!--  --> <div data-variant3="container" class="form-check"><!--  --> <input data-variant3-clickvariant="click" class="form-check-input" type="radio" name="exampleRadios" id="exampleRadios1" value="option1" checked> <label data-variant3-text="text" class="form-check-label" for="exampleRadios1">  1 </label> </div> </form> 

Javascript:

 variants1: { container: "variant1", props: ["clickvariant", "text"], methods: { clickvariant: function(event){ event.preventDefault(); /*           "emiter-chose-variant"   listenchosevariant  cardsingle    */ this.rootLink.eventProps["emiter-chose-variant"].setEventProp(this.parentContainer.props.text.getProp()); }, } }, variants2: { container: "variants2", props: ["clickvariant2", "select"], methods: { clickvariant2: function(event){ event.preventDefault(); this.rootLink.eventProps["emiter-chose-variant"].setEventProp(this.parentContainer.props.select.getProp()); }, }, }, variants3: { container: "variant3", props: ["clickvariant", "text"], methods: { clickvariant: function(event){ this.rootLink.eventProps["emiter-chose-variant"].setEventProp(this.parentContainer.props.text.getProp()); } }, }, 

There are not so many methods in the framework, you can get acquainted with them by looking at the source code where there is a brief description of the main ones used during the work. So far, htmlix is ​​in the test version, but now with the help of it you can solve many typical frontend development tasks.

Brief documentation on all basic properties as well as tutorials to some sample applications can be read here .

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


All Articles