Constructing a Type in PHP Utilizing DOMDocument

No Comments

Templating makes the net go spherical. The synthesis of knowledge and construction into content material. It’s our coolest superpower as builders — seize some information, then make it work for us, in no matter presentation we want. An array of objects can turn out to be a desk, a listing of playing cards, a chart, or no matter we expect is most helpful to the person. Whether or not the info is our personal weblog posts in Markdown information, or on-the-minute international trade charges, the markup and ensuing UX are as much as us as front-end builders.

PHP is a tremendous language for templating, offering some ways to merge information with markup. Let’s get into an instance of utilizing information to construct out an HTML type on this publish.

Wish to get your palms soiled straight away? Bounce to the implementation.

In PHP, we are able to inline variables into string literals that use double quotes, so if we’ve got a variable $title = ‘world’, we are able to write echo “Whats up, {$title}”, and it prints the anticipated Whats up, world. For extra advanced templating, we are able to all the time concatenate strings, like: echo “Whats up, ” . $title . “.”.

For the old-schoolers, there’s printf(“Whats up, %s”, $title). For multiline strings, you should utilize Heredoc (the one which begins like <<<MYTEXT). And, final however definitely not least, we are able to sprinkle PHP variables inside HTML, like <p>Whats up, <?= $title ?></p>.

All of those choices are nice, however issues can get messy when loads of inline logic is required. If we have to construct compound HTML strings, say a type or navigation, the complexity is doubtlessly infinite, since HTML parts can nest inside one another.

What we’re making an attempt to keep away from

Earlier than we go forward and do the factor we wish to do, it’s price taking a minute to contemplate what we don’t wish to do. Take into account the next abridged passage from the scripture of WordPress Core, class-walker-nav-menu.php, verses 170-270:

<?php // class-walker-nav-menu.php
// …
$output .= $indent . ‘<li’ . $id . $class_names . ‘>’;
// …
$item_output = $args->earlier than;
$item_output .= ‘<a’ . $attributes . ‘>’;
$item_output .= $args->link_before . $title . $args->link_after;
$item_output .= ‘</a>’;
$item_output .= $args->after;
// …
$output .= apply_filters( ‘walker_nav_menu_start_el’, $item_output, $merchandise, $depth, $args );
// …
$output .= “</li>{$n}”;

As a way to construct out a navigation <ul> on this operate, we use a variable, $output, which is a really lengthy string to which we hold including stuff. This sort of code has a really particular and restricted order of operations. If we needed so as to add an attribute to the <a>, we should have entry to $attributes earlier than this runs. And if we needed to optionally nest a <span> or an <img> contained in the <a>, we’d must writer a complete new block of code that might change the center of line 7 with about 4-10 new traces, relying on what precisely we wish to add. Now think about you should optionally add the <span> , after which optionally add the <img>, both contained in the <span> or after it. That alone is three if statements, making the code even much less legible.

It’s very straightforward to finish up with string spaghetti when concatenating like this, which is as enjoyable to say as it’s painful to keep up.

The essence of the issue is that once we attempt to cause about HTML parts, we’re not eager about strings. It simply so occurs that strings are what the browser consumes and PHP outputs. However our psychological mannequin is extra just like the DOM — parts are organized right into a tree, and every node has many potential attributes, properties, and kids.

Wouldn’t it’s nice if there have been a structured, expressive method to construct our tree?

Enter…

The DOMDocument class

PHP 5 added the DOM module to it’s roster of Not So Strictly Typed™ sorts. Its foremost entry level is the DOMDocument class, which is deliberately just like the Internet API’s JavaScript DOM. If you happen to’ve ever used doc.createElement or, for these of us of a sure age, jQuery’s $(‘<p>Hello there!</p>’) syntax, this may most likely really feel fairly acquainted.

We begin out by initializing a brand new DOMDocument:

$dom = new DOMDocument();

Now we are able to add a DOMElement to it:

$p = $dom->createElement(‘p’);

The string ‘p’ represents the kind of component we wish, so different legitimate strings could be ‘div’, ‘img’ , and many others.

As soon as we’ve got a component, we are able to set its attributes:

$p->setAttribute(‘class’, ‘headline’);

We will add kids to it:

$span = $dom->createElement(‘span’, ‘This can be a headline’); // The 2nd argument populates the component’s textContent
$p->appendChild($span);

And at last, get the entire HTML string in a single go:

$dom->appendChild($p);
$htmlString = $dom->saveHTML();
echo $htmlString;

Discover how this model of coding retains our code organized in response to our psychological mannequin — a doc has parts; parts can have any variety of attributes; and parts nest inside each other without having to know something about one another. The entire “HTML is only a string” half is available in on the finish, as soon as our construction is in place.

The “doc” here’s a bit completely different from the precise DOM, in that it doesn’t must signify a complete doc, only a block of HTML. The truth is, if you should create two related parts, you could possibly save a HTML string utilizing saveHTML(), modify the DOM “doc” some extra, after which save a brand new HTML string by calling saveHTML() once more.

Getting information and setting the construction

Say we have to construct a type on the server utilizing information from a CRM supplier and our personal markup. The API response from the CRM appears like this:

{
“submit_button_label”: “Submit now!”,
“fields”: [
{
“id”: “first-name”,
“type”: “text”,
“label”: “First name”,
“required”: true,
“validation_message”: “First name is required.”,
“max_length”: 30
},
{
“id”: “category”,
“type”: “multiple_choice”,
“label”: “Choose all categories that apply”,
“required”: false,
“field_metadata”: {
“multi_select”: true,
“values”: [
{ “value”: “travel”, “label”: “Travel” },
{ “value”: “marketing”, “label”: “Marketing” }
]
}
}
]
}

This instance doesn’t use the precise information construction of any particular CRM, nevertheless it’s reasonably consultant.

And let’s suppose we wish our markup to appear like this:

<type>
<label class=”discipline”>
<enter sort=”textual content” title=”first-name” id=”first-name” placeholder=” ” required>
<span class=”label”>First title</span>
<em class=”validation” hidden>First title is required.</em>
</label>
<label class=”discipline checkbox-group”>
<fieldset>
<div class=”alternative”>
<enter sort=”checkbox” worth=”journey” id=”category-travel” title=”class”>
<label for=”category-travel”>Journey</label>
</div>
<div class=”alternative”>
<enter sort=”checkbox” worth=”advertising and marketing” id=”category-marketing” title=”class”>
<label for=”category-marketing”>Advertising</label>
</div>
</fieldset>
<span class=”label”>Select all classes that apply</span>
</label>
</type>

What’s that placeholder=” “? It’s a small trick that permits us to trace in CSS whether or not the sphere is empty, without having JavaScript. So long as the enter is empty, it matches enter:placeholder-shown, however the person doesn’t see any seen placeholder textual content. Simply the form of factor you are able to do once we management the markup!

Now that we all know what our desired result’s, right here’s the sport plan:

Get the sphere definitions and different content material from the APIInitialize a DOMDocumentIterate over the fields and construct each as requiredGet the HTML output

So let’s stub out our course of and get some technicalities out of the best way:

<?php
operate renderForm ($endpoint) {
// Get the info from the API and convert it to a PHP object
$formResult = file_get_contents($endpoint);
$formContent = json_decode($formResult);
$formFields = $formContent->fields;

// Begin constructing the DOM
$dom = new DOMDocument();
$type = $dom->createElement(‘type’);

// Iterate over the fields and construct each
foreach ($formFields as $discipline) {
// TODO: Do one thing with the sphere information
}

// Get the HTML output
$dom->appendChild($type);
$htmlString = $dom->saveHTML();
echo $htmlString;
}

To this point, we’ve gotten the info and parsed it, initialized our DOMDocument and echoed its output. What can we wish to do for every discipline? First off, let’s construct the container component which, in our instance, needs to be a <label>, and the labelling <span> which is frequent to all discipline sorts:

<?php
// …
// Iterate over the fields and construct each
foreach ($formFields as $discipline) {
// Construct the container `<label>`
$component = $dom->createElement(‘label’);
$element->setAttribute(‘class’, ‘discipline’);

// Reset enter values
$label = null;

// Add a `<span>` for the label whether it is set
if ($field->label) {
$label = $dom->createElement(‘span’, $field->label);
$label->setAttribute(‘class’, ‘label’);
}

// Add the label to the `<label>`
if ($label) $element->appendChild($label);
}

Since we’re in a loop, and PHP doesn’t scope variables in loops, we reset the $label component on every iteration. Then, if the sphere has a label, we construct the component. On the finish, we append it to the container component.

Discover that we set lessons utilizing the setAttribute methodology. In contrast to the Internet API, there sadly is not any particular handing of sophistication lists. They’re simply one other attribute. If we had some actually advanced class logic, since It’s Simply PHP™, we may create an array after which implode it:
$label->setAttribute(‘class’, implode($labelClassList)).

Single inputs

Since we all know that the API will solely return particular discipline sorts, we are able to swap over the sort and write particular code for each:

<?php
// …
// Iterate over the fields and construct each
foreach ($formFields as $discipline) {
// Construct the container `<label>`
$component = $dom->createElement(‘label’);
$element->setAttribute(‘class’, ‘discipline’);

// Reset enter values
$enter = null;
$label = null;

// Add a `<span>` for the label whether it is set
// …

// Construct the enter component
swap ($field->sort) {
case ‘textual content’:
case ‘e mail’:
case ‘phone’:
$enter = $dom->createElement(‘enter’);
$input->setAttribute(‘placeholder’, ‘ ‘);
if ($field->sort === ‘e mail’) $input->setAttribute(‘sort’, ‘e mail’);
if ($field->sort === ‘phone’) $input->setAttribute(‘sort’, ‘tel’);
break;
}
}

Now let’s deal with textual content areas, single checkboxes and hidden fields:

<?php
// …
// Iterate over the fields and construct each
foreach ($formFields as $discipline) {
// Construct the container `<label>`
$component = $dom->createElement(‘label’);
$element->setAttribute(‘class’, ‘discipline’);

// Reset enter values
$enter = null;
$label = null;

// Add a `<span>` for the label whether it is set
// …

// Construct the enter component
swap ($field->sort) {
//…
case ‘text_area’:
$enter = $dom->createElement(‘textarea’);
$input->setAttribute(‘placeholder’, ‘ ‘);
if ($rows = $field->field_metadata->rows) $input->setAttribute(‘rows’, $rows);
break;

case ‘checkbox’:
$element->setAttribute(‘class’, ‘discipline single-checkbox’);
$enter = $dom->createElement(‘enter’);
$input->setAttribute(‘sort’, ‘checkbox’);
if ($field->field_metadata->initially_checked === true) $input->setAttribute(‘checked’, ‘checked’);
break;

case ‘hidden’:
$enter = $dom->createElement(‘enter’);
$input->setAttribute(‘sort’, ‘hidden’);
$input->setAttribute(‘worth’, $field->field_metadata->worth);
$element->setAttribute(‘hidden’, ‘hidden’);
$element->setAttribute(‘model’, ‘show: none;’);
$label->textContent = ”;
break;
}
}

Discover one thing new we’re doing for the checkbox and hidden circumstances? We’re not simply creating the <enter> component; we’re making modifications to the container <label> component! For a single checkbox discipline we wish to modify the category of the container, so we are able to align the checkbox and label horizontally; a hidden <enter>‘s container also needs to be fully hidden.

Now if we had been merely concatenating strings, it might be inconceivable to alter at this level. We must add a bunch of if statements relating to the kind of component and its metadata within the prime of the block. Or, perhaps worse, we begin the swap means earlier, then copy-paste loads of frequent code between every department.

And right here is the true great thing about utilizing a builder like DOMDocument — till we hit that saveHTML(), every little thing continues to be editable, and every little thing continues to be structured.

Nested looping parts

Let’s add the logic for <choose> parts:

<?php
// …
// Iterate over the fields and construct each
foreach ($formFields as $discipline) {
// Construct the container `<label>`
$component = $dom->createElement(‘label’);
$element->setAttribute(‘class’, ‘discipline’);

// Reset enter values
$enter = null;
$label = null;

// Add a `<span>` for the label whether it is set
// …

// Construct the enter component
swap ($field->sort) {
//…
case ‘choose’:
$element->setAttribute(‘class’, ‘discipline choose’);
$enter = $dom->createElement(‘choose’);
$input->setAttribute(‘required’, ‘required’);
if ($field->field_metadata->multi_select === true)
$input->setAttribute(‘a number of’, ‘a number of’);

$choices = [];

// Monitor whether or not there is a pre-selected choice
$optionSelected = false;

foreach ($field->field_metadata->values as $worth) {
$choice = $dom->createElement(‘choice’, htmlspecialchars($value->label));

// Bail if there is no worth
if (!$value->worth) proceed;

// Set pre-selected choice
if ($value->chosen === true) {
$option->setAttribute(‘chosen’, ‘chosen’);
$optionSelected = true;
}
$option->setAttribute(‘worth’, $value->worth);
$choices[] = $choice;
}

// If there is no such thing as a pre-selected choice, construct an empty placeholder choice
if ($optionSelected === false) {
$emptyOption = $dom->createElement(‘choice’);

// Set choice to hidden, disabled, and chosen
foreach ([‘hidden’, ‘disabled’, ‘selected’] as $attribute)
$emptyOption->setAttribute($attribute, $attribute);
$input->appendChild($emptyOption);
}

// Add choices from array to `<choose>`
foreach ($choices as $choice) {
$input->appendChild($choice);
}
break;
}
}

OK, so there’s rather a lot occurring right here, however the underlying logic is similar. After establishing the outer <choose>, we make an array of <choice>s to append inside it.

We’re additionally performing some <choose>-specific trickery right here: If there is no such thing as a pre-selected choice, we add an empty placeholder choice that’s already chosen, however can’t be chosen by the person. The objective is to put our <label class=”label”> as a “placeholder” utilizing CSS, however this method could be helpful for every kind of designs. By appending it to the $enter earlier than appending the opposite choices, we ensure it’s the first choice within the markup.

Now let’s deal with <fieldset>s of radio buttons and checkboxes:

<?php
// …
// Iterate over the fields and construct each
foreach ($formFields as $discipline) {
// Construct the container `<label>`
$component = $dom->createElement(‘label’);
$element->setAttribute(‘class’, ‘discipline’);

// Reset enter values
$enter = null;
$label = null;

// Add a `<span>` for the label whether it is set
// …

// Construct the enter component
swap ($field->sort) {
// …
case ‘multiple_choice’:
$choiceType = $field->field_metadata->multi_select === true ? ‘checkbox’ : ‘radio’;
$element->setAttribute(‘class’, “discipline {$choiceType}-group”);
$enter = $dom->createElement(‘fieldset’);

// Construct a alternative `<enter>` for every choice within the fieldset
foreach ($field->field_metadata->values as $choiceValue) {
$choiceField = $dom->createElement(‘div’);
$choiceField->setAttribute(‘class’, ‘alternative’);

// Set a singular ID utilizing the sphere ID + the selection ID
$choiceID = “{$field->id}-{$choiceValue->worth}”;

// Construct the `<enter>` component
$alternative = $dom->createElement(‘enter’);
$choice->setAttribute(‘sort’, $choiceType);
$choice->setAttribute(‘worth’, $choiceValue->worth);
$choice->setAttribute(‘id’, $choiceID);
$choice->setAttribute(‘title’, $field->id);
$choiceField->appendChild($alternative);

// Construct the `<label>` component
$choiceLabel = $dom->createElement(‘label’, $choiceValue->label);
$choiceLabel->setAttribute(‘for’, $choiceID);
$choiceField->appendChild($choiceLabel);

$input->appendChild($choiceField);
}
break;
}
}

So, first we decide if the sphere set needs to be for checkboxes or radio button. Then we set the container class accordingly, and construct the <fieldset>. After that, we iterate over the out there decisions and construct a <div> for each with an <enter> and a <label>.

Discover we use common PHP string interpolation to set the container class on line 21 and to create a singular ID for every alternative on line 30.

Fragments

One final sort we’ve got so as to add is barely extra advanced than it appears. Many types embody instruction fields, which aren’t inputs however just a few HTML we have to print between different fields.

We’ll want to succeed in for an additional DOMDocument methodology, createDocumentFragment(). This enables us so as to add arbitrary HTML with out utilizing the DOM structuring:

<?php
// …
// Iterate over the fields and construct each
foreach ($formFields as $discipline) {
// Construct the container `<label>`
$component = $dom->createElement(‘label’);
$element->setAttribute(‘class’, ‘discipline’);

// Reset enter values
$enter = null;
$label = null;

// Add a `<span>` for the label whether it is set
// …

// Construct the enter component
swap ($field->sort) {
//…
case ‘instruction’:
$element->setAttribute(‘class’, ‘discipline textual content’);
$fragment = $dom->createDocumentFragment();
$fragment->appendXML($field->textual content);
$enter = $dom->createElement(‘p’);
$input->appendChild($fragment);
break;
}
}

At this level you could be questioning how we discovered ourselves with an object known as $enter, which really represents a static <p> component. The objective is to make use of a standard variable title for every iteration of the fields loop, so on the finish we are able to all the time add it utilizing $element->appendChild($enter) whatever the precise discipline sort. So, yeah, naming issues is tough.

Validation

The API we’re consuming kindly supplies a person validation message for every required discipline. If there’s a submission error, we are able to present the errors inline along with the fields, reasonably than a generic “oops, your dangerous” message on the backside.

Let’s add the validation textual content to every component:

<?php
// …
// Iterate over the fields and construct each
foreach ($formFields as $discipline) {
// construct the container `<label>`
$component = $dom->createElement(‘label’);
$element->setAttribute(‘class’, ‘discipline’);

// Reset enter values
$enter = null;
$label = null;
$validation = null;

// Add a `<span>` for the label whether it is set
// …

// Add a `<em>` for the validation message whether it is set
if (isset($field->validation_message)) {
$validation = $dom->createElement(’em’);
$fragment = $dom->createDocumentFragment();
$fragment->appendXML($field->validation_message);
$validation->appendChild($fragment);
$validation->setAttribute(‘class’, ‘validation-message’);
$validation->setAttribute(‘hidden’, ‘hidden’); // Initially hidden, and will probably be unhidden with Javascript if there’s an error on the sphere
}

// Construct the enter component
swap ($field->sort) {
// …
}
}

That’s all it takes! No must fiddle with the sphere sort logic — simply conditionally construct a component for every discipline.

Bringing all of it collectively

So what occurs after we construct all the sphere parts? We have to add the $enter, $label, and $validation objects to the DOM tree we’re constructing. We will additionally use the chance so as to add frequent attributes, like required. Then we’ll add the submit button, which is separate from the fields on this API.

<?php
operate renderForm ($endpoint) {
// Get the info from the API and convert it to a PHP object
// …

// Begin constructing the DOM
$dom = new DOMDocument();
$type = $dom->createElement(‘type’);

// Iterate over the fields and construct each
foreach ($formFields as $discipline) {
// Construct the container `<label>`
$component = $dom->createElement(‘label’);
$element->setAttribute(‘class’, ‘discipline’);

// Reset enter values
$enter = null;
$label = null;
$validation = null;

// Add a `<span>` for the label whether it is set
// …

// Add a `<em>` for the validation message whether it is set
// …

// Construct the enter component
swap ($field->sort) {
// …
}

// Add the enter component
if ($enter) {
$input->setAttribute(‘id’, $field->id);
if ($field->required)
$input->setAttribute(‘required’, ‘required’);
if (isset($field->max_length))
$input->setAttribute(‘maxlength’, $field->max_length);
$element->appendChild($enter);

if ($label)
$element->appendChild($label);

if ($validation)
$element->appendChild($validation);

$form->appendChild($component);
}
}

// Construct the submit button
$submitButtonLabel = $formContent->submit_button_label;
$submitButtonField = $dom->createElement(‘div’);
$submitButtonField->setAttribute(‘class’, ‘discipline submit’);
$submitButton = $dom->createElement(‘button’, $submitButtonLabel);
$submitButtonField->appendChild($submitButton);
$form->appendChild($submitButtonField);

// Get the HTML output
$dom->appendChild($type);
$htmlString = $dom->saveHTML();
echo $htmlString;
}

Why are we checking if $enter is truthy? Since we reset it to null on the prime of the loop, and solely construct it if the sort conforms to our anticipated swap circumstances, this ensures we don’t by chance embody sudden parts our code can’t deal with correctly.

Hey presto, a customized HTML type!

Bonus factors: rows and columns

As you could know, many type builders permit authors to set rows and columns for fields. For instance, a row would possibly include each the primary title and final title fields, every in a single 50% width column. So how would we go about implementing this, you ask? By exemplifying (as soon as once more) how loop-friendly DOMDocument is, in fact!

Our API response contains the grid information like this:

{
“submit_button_label”: “Submit now!”,
“fields”: [
{
“id”: “first-name”,
“type”: “text”,
“label”: “First name”,
“required”: true,
“validation_message”: “First name is required.”,
“max_length”: 30,
“row”: 1,
“column”: 1
},
{
“id”: “category”,
“type”: “multiple_choice”,
“label”: “Choose all categories that apply”,
“required”: false,
“field_metadata”: {
“multi_select”: true,
“values”: [
{ “value”: “travel”, “label”: “Travel” },
{ “value”: “marketing”, “label”: “Marketing” }
]
},
“row”: 2,
“column”: 1
}
]
}

We’re assuming that including a data-column attribute is sufficient for styling the width, however that every row must be it’s personal component (i.e. no CSS grid).

Earlier than we dive in, let’s suppose by means of what we want in an effort to add rows. The essential logic goes one thing like this:

Monitor the newest row encountered.If the present row is bigger, i.e. we’ve jumped to the following row, create a brand new row component and begin including to it as a substitute of the earlier one.

Now, how would we do that if we had been concatenating strings? Most likely by including a string like ‘</div><div class=”row”>’ at any time when we attain a brand new row. This type of “reversed HTML string” is all the time very complicated to me, so I can solely think about how my IDE feels. And the cherry on prime is that because of the browser auto-closing open tags, a single typo will lead to a gazillion nested <div>s. Similar to enjoyable, however the reverse.

So what’s the structured method to deal with this? Thanks for asking. First let’s add row monitoring earlier than our loop and construct a further row container component. Then we’ll ensure to append every container $component to its $rowElement reasonably than on to $type.

<?php
operate renderForm ($endpoint) {
// Get the info from the API and convert it to a PHP object
// …

// Begin constructing the DOM
$dom = new DOMDocument();
$type = $dom->createElement(‘type’);

// init monitoring of rows
$row = 0;
$rowElement = $dom->createElement(‘div’);
$rowElement->setAttribute(‘class’, ‘field-row’);

// Iterate over the fields and construct each
foreach ($formFields as $discipline) {
// Construct the container `<label>`
$component = $dom->createElement(‘label’);
$element->setAttribute(‘class’, ‘discipline’);
$element->setAttribute(‘data-row’, $field->row);
$element->setAttribute(‘data-column’, $field->column);

// Add the enter component to the row
if ($enter) {
// …
$rowElement->appendChild($component);
$form->appendChild($rowElement);
}
}
// …
}

To this point we’ve simply added one other <div> across the fields. Let’s construct a new row component for every row contained in the loop:

<?php
// …
// Init monitoring of rows
$row = 0;
$rowElement = $dom->createElement(‘div’);
$rowElement->setAttribute(‘class’, ‘field-row’);

// Iterate over the fields and construct each
foreach ($formFields as $discipline) {
// …
// If we have reached a brand new row, create a brand new $rowElement
if ($field->row > $row) {
$row = $field->row;
$rowElement = $dom->createElement(‘div’);
$rowElement->setAttribute(‘class’, ‘field-row’);
}

// Construct the enter component
swap ($field->sort) {
// …
// Add the enter component to the row
if ($enter) {
// …
$rowElement->appendChild($component);

// Routinely de-duped
$form->appendChild($rowElement);
}
}
}

All we have to do is overwrite the $rowElement object as a brand new DOM component, and PHP treats it as a brand new distinctive object. So, on the finish of each loop, we simply append regardless of the present $rowElement is — if it’s nonetheless the identical one as within the earlier iteration, then the shape is up to date; if it’s a brand new component, it’s appended on the finish.

The place can we go from right here?

Kinds are a terrific use case for object-oriented templating. And eager about that snippet from WordPress Core, an argument could be made that nested menus are a great use case as nicely. Any process the place the markup follows advanced logic makes for a great candidate for this method. DOMDocument can output any XML, so you could possibly additionally use it to construct an RSS feed from posts information.

Right here’s the complete code snippet for our type. Be happy it adapt it to any type API you end up coping with. Right here’s the official documentation, which is sweet for getting a way of the out there API.

We didn’t even point out DOMDocument can parse current HTML and XML. You may then search for parts utilizing the XPath API, which is kinda just like doc.querySelector, or cheerio on Node.js. There’s a little bit of a studying curve, nevertheless it’s an excellent highly effective API for dealing with exterior content material.

Enjoyable(?) truth: Microsoft Workplace information that finish with x (e.g. .xlsx) are XML information. Don’t inform the advertising and marketing division, nevertheless it’s attainable to parse Phrase docs and output HTML on the server.

A very powerful factor is to keep in mind that templating is a superpower. Having the ability to construct the correct markup for the correct scenario could be the important thing to a terrific UX.

The publish Constructing a Type in PHP Utilizing DOMDocument appeared first on CSS-Tips. You may assist CSS-Tips by being an MVP Supporter.

    About Marketing Solution Australia

    We are a digital marketing company with a focus on helping our customers achieve great results across several key areas.

    Request a free quote

    We offer professional SEO services that help websites increase their organic search score drastically in order to compete for the highest rankings even when it comes to highly competitive keywords.

    Subscribe to our newsletter!

    More from our blog

    See all posts

    Leave a Comment