2
\$\begingroup\$

I had a need to build a standardized layout for content on many of the pages of my website based on data returned from my web service but the project I'm doing this on is already bloated with stuff from previous developers and since I don't know what I can take out without destroying functionality, I needed a way to do it without adding anymore bloat.

I used a template control and regex in string replacement which serves my purposes very well and in the simplest manner that I can think of. My only query is whether or not I can do it without putting the template on the page.

The designer looks after the markup and styles for the template; my job is to just make sure that the content on the site follows it.

Weird thing for me is that I have my content for the page and then at the bottom I have this base template that the content is built from. It seems like an odd extra that probably isn't necessary in an ideal situation. The boss doesn't like the idea of storing the template in the database so that's off the table...

<template id="vacancyTemplate">
    <article class="post-content">
        <div class="comments">
            <ul class="comment-list">
                <li>
                    <article class="comment">
                        <div class="comment-content">
                            <div style="padding:0;" class="cell-2">
                                <img style="width:100%; height:100%" src="{img}" />
                            </div>
                            <div class="cell-10">
                                <h5 class="comment-author">
                                    <span class="author-name"><a href="{link}">{title}</a></span>
                                    <span class="author-name"><a href="#">{code}</a></span>
                                    <a class="comment-reply main-bg" onclick="{favoriteLink}" title="{favoriteTitle}" style="cursor: pointer;"><i class="fa fa-star"></i></a>
                                </h5>
                                <div style="display:flex;flex-direction:column" class="infomation">
                                    <span>{company}</span>
                                    <span><br/></span>
                                    <span><strong>{location}</strong></span>
                                    <span><br /></span>
                                    <span>{details}... <a style="padding-left: 15px" href="{link}">VIEW MORE</a></span>
                                </div>
                            </div>                                    
                        </div>
                    </article>
                </li>
                <li>
                    <div class="post-info-container post-meta">
                        <div class="post-info post-meta">
                            <div class="cell-9">
                                <ul style="display: flex; flex-direction: column; float: left; margin-top: 3.5%; margin-bottom: 3%; width: 100%;" class="post-meta">
                                    <li class="meta-user"><i class="fa fa-user"></i><a href="#">{experience}</a></li>
                                    <li><i class="fa fa-folder-open"></i> <a href="#">{salary}</a></li>
                                    <li class="meta-comments"><i class="fa fa-pencil"></i>{employment}</li>
                                </ul>
                            </div>
                            <div style="margin-top:4.3%;" class="cell-3">
                                <ul class="post-meta">
                                    <li>Post : {postDate}<br /></li>
                                    <li>Closing date : {closeDate}</li>
                                </ul>
                            </div>
                        </div>                                
                    </div>
                </li>
            </ul>
        </div>
    </article>
</template>

<script>
    const options = { method: "GET", cache: "no-cache", headers: { "Content-Type": "application/json" } };
    window.onload = async function () {
        await loadVacanciesAsync();
    }

    async function loadVacanciesAsync() {
        const vacancyPromise = new Promise((resolve, reject) => {
            fetch("/Services/VacancyWebService.asmx/GetDriverVacancies", options)
                .then((resp) => resp.json())
                .then((json) => {
                    const data = json.d;
                    if (data.Result === "Success") {
                        const vacancies = data.Data;
                        resolve(vacancies);
                    }
                    else {
                        if (data.Error.Message === "Non-static method requires a target.") {
                            const container = document.querySelector(".blog-posts");
                            const a = document.createElement("a");
                            a.setAttribute("href", "/Users/MyAccountInformation");
                            a.innerText = "updating your profile.";

                            const p = document.createElement("p");
                            p.innerText = "No vacancies were found to match your profile. Consider ";
                            p.appendChild(a);
                            container.appendChild(p);
                        }
                        reject();
                    }
                });
        });
        const vacancies = await vacancyPromise;
        if (typeof vacancies !== 'undefined' && vacancies.length > 0) {
            const favoritesPromise = new Promise((resolve, reject) => {
                fetch("/Services/VacancyWebService.asmx/GetFavorites", options)
                    .then((resp) => resp.json())
                    .then((json) => {
                        const data = json.d;
                        if (data.Result === "Success") {
                            const favorites = data.Data;
                            resolve(favorites);
                        }
                        else {
                            // Do something here if there's an error getting favorites.
                        }
                    });
            });
            const favorites = await favoritesPromise;

            sessionStorage.setItem("pageData", JSON.stringify(vacancies));
            sessionStorage.setItem("pageFavorites", JSON.stringify(favorites));
            loadData();

                const pagerMarkup = loadPager();

                const pager = document.querySelector(".pager");
                pager.innerHTML = pagerMarkup;
        }
        else {
            const container = document.querySelector(".blog-posts");
            const a = document.createElement("a");
            a.setAttribute("href", "/Users/MyAccountInformation");
            a.innerText = "updating your profile.";

            const p = document.createElement("p");
            p.innerText = "No vacancies were found to match your profile. Consider ";
            p.appendChild(a);
            container.appendChild(p);
        }
    }
    function loadData(start) {
        // Load data out of session storage.
        if (start === undefined) {
            start = 0;
        }
        else {
            start = (start - 1) * pageSize;
        }
        let end = start + pageSize;
        const sessionData = JSON.parse(sessionStorage.getItem("pageData"));
        const subset = sessionData.slice(start, end);

        buildVacancyArticles(subset);
    }
    function buildVacancyArticles(vacancies) {
        const favorites = JSON.parse(sessionStorage.getItem("pageFavorites"));

        const container = document.querySelector(".blog-posts");
        container.innerHTML = ""; // Clear the markup for the previous set.
        let template = document.getElementById("vacancyTemplate").innerHTML;
        for (const vacancy of vacancies) {
            let t = template;
            const isFavorite = favorites.includes(vacancy);
            if (isFavorite) {
                t = t.replace(/{favoriteLink}/g, `removeFavorite(this, ${vacancy.Id});`);
                t = t.replace(/{favoriteTitle}/g, "Remove from Favorites");
            }
            else {
                t = t.replace(/{favoriteLink}/g, `addFavorite(this, ${vacancy.Id});`);
                t = t.replace(/{favoriteTitle}/g, "Add to Favorites");
            }

            let imgSrc = "";
            if (vacancy.LogoType !== undefined && vacancy.LogoType !== "") {
                if (vacancy.LogoImage !== null) {
                    imgSrc = `${vacancy.LogoType};base64=${vacancy.LogoImage}`;
                }
            }
            t = t.replace(/{img}/g, imgSrc);
            t = t.replace(/{link}/g, `/Vacancies/Vacancy_Full.aspx/${vacancy.Id}`);
            t = t.replace(/{title}/g, vacancy.Title);
            t = t.replace(/{code}/g, vacancy.LicenseCode);

            const company = vacancy.Business !== undefined ? vacancy.Business.Name : "Company Name";
            t = t.replace(/{company}/g, company)

            t = t.replace(/{location}/g, vacancy.Location);
            t = t.replace(/{details}/g, vacancy.Details);
            t = t.replace(/{experience}/g, vacancy.Experience);
            t = t.replace(/{salary}/g, vacancy.Salary);
            t = t.replace(/{employment}/g, vacancy.EmploymentType);
            t = t.replace(/{postDate}/g, vacancy.DateString);
            t = t.replace(/{closeDate}/g, vacancy.CloseDateString);

            container.innerHTML += t;
        }
    }
</script>
\$\endgroup\$
1
\$\begingroup\$

From a quick review;

  • You could host your templates as separate HTML files, retrievable with a fetch

    • You would have the overhead of extra http calls
    • HTML tooling might be worse for HTML templates/snippets
  • I agree with your boss, html code does not belong in a database

  • template should be const

      let template = document.getElementById("vacancyTemplate").innerHTML;
      for (const vacancy of vacancies) {
          let t = template;
    
  • You could consider a less manual approach to filling in the values

    function fillOutTemplate(t, object){
        Object.keys(object).forEach(key => t = t.replace(new RegExp(key, 'g'), object[key]) );
        return t; 
    }
    

    and calling t = fillOutTemplate(t, Vacancy); could save a ton of coding once you harmonize the Vacancy properties and the template.

  • It looks odd that not finding entries return the message "Non-static method requires a target.", it makes the hacker in me find out that this is most likely a LINQ error and wonder if I can pass different parameters to abuse this knowledge

  • You create 3 'a' link tags from scratch, consider a helper routine for this.

  • I know someone else runs with the styling, but that inline styling is rough though..

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.