Scout APM is PHP application performance monitoring designed for developers. With tracing logic that ties issues back to the line of code causing them, you can pinpoint n+1 queries, memory leaks, and other abnormalities in real time so you can knock them out and get back to building a great product. Start your free 14-day trial today and get the performance insight you need in less than 4 minutes.

Adding a subscription form to the web views of Mailcoach

Original – by Freek Van der Herten – 4 minute read

After sending a new edition of my newsletter, I usually tweet out a webview URL together with a URL where people can subscribe to the newsletter. A webview is a hard to guess URL that people who didn't subscribe can visit to read the content of the newsletter.

Until last week, the webview URL just displayed the content of the sent campaign. Recently, I added a subscription form to that webpage, so people don't need to go to a separate page to subscribe. Here's how that looks like:

screenshot

In this blog post, I'd like to share how that subscription form was added.

Customizing the webview

It probably comes as no surprise that I use our homegrown Mailcoach package to sent out my newsletter. Whenever a campaign (that's Mailcoach speak for newsletter) is sent, Mailcoach makes the campaign available as webview.

You can get to the URL of the webview of a campaign:

$campaign->webViewUrl();

Up until last week, I just used the webViewUrl in the Blade view that displays all past campaigns

@foreach($pastCampaigns as $campaign)
    <div>
        <a href="{{ $campaign->webViewUrl() }}">{{ $campaign->subject }}</a>
        <div class="text-gray-700 text-xs">sent on {{ $campaign->sent_at->format('jS F Y') }}</div>
    </div>
@endforeach

The first thing I did was create a route newsletter.show of my own to take control of which view is rendered when people see an archived newsletter.

@foreach($pastCampaigns as $campaign)
    <div>
        <a href="{{ route('newsletter.show', $campaign->id) }}">{{ $campaign->subject }}</a>
        <div class="text-gray-700 text-xs">sent on {{ $campaign->sent_at->format('jS F Y') }}</div>
    </div>
@endforeach

The controller that gets called to serve the newsletter.show route, returns the newsletter.blade.php view.

From using an iframe...

This was my first naive approach to display a form above the webview. The HTML page just started with a subscription form, followed by an iframe that displays that webview URL. I used an iframe to prevent the CSS from the main site to bleed through in the campaign HTML.

<html>
    <head>
        <style>{!! file_get_contents(public_path('css/app.css')) !!}</style>
        <link rel="stylesheet" href="https://cloud.typography.com/6194432/6581412/css/fonts.css"/>
        <title>{{ $campaign->subject }}</title>
    </head>
    <body>
        <header class="w-full mb-4 p-4 sm:p-6 md:px-8 md:py-7 bg-orange-100 border-b-2 border-orange-200 text-xs text-gray-700">
            <div class="max-w-lg mx-auto space-y-2">
                <p>
                    Every two weeks I send out a newsletter like this one, containing lots of interesting stuff for the modern
                    PHP
                    developer.
                </p>
                <p>
                    Subscribe to get the next edition in your mailbox.
                </p>
                @include('front.newsletter.partials.form')
            </div>
        </header>

        <iframe href="{{ $campaign->webViewUrl() }}" />
    </body>
</html>

This would get me 95% of the way there. A challenging problem that I couldn't solve entirely on my own was the janky scroll behavior caused by having an iframe on the page.

Here an example of that "janky" feel:

... to using a web component

Luckily my colleague Seb had an excellent solution: web components. You can think of a web component as a custom HTML tag in which a separate DOM, called the shadow DOM, is rendered. This shadow DOM does not get the CSS of the main page applied to it.

Here's that view that display a subscription form + newsletter again, but this time a web component is used. A bit of JavaScript is needed at the top to configure the web component. The $webview contains the full page content returned by $campaign->webViewUrl().

<html>
    <head>
        <style>{!! file_get_contents(public_path('css/app.css')) !!}</style>
        <link rel="stylesheet" href="https://cloud.typography.com/6194432/6581412/css/fonts.css"/>
        <title>{{ $campaign->subject }}</title>
        <script>
            window.customElements.define('campaign-webview', class NewsletterEmbed extends HTMLElement {
                connectedCallback() {
                    const shadow = this.attachShadow({ mode: 'closed' });
                    shadow.innerHTML = this.getAttribute('contents');
                }
            })
        </script>
    </head>
    <body>
        <header class="w-full mb-4 p-4 sm:p-6 md:px-8 md:py-7 bg-orange-100 border-b-2 border-orange-200 text-xs text-gray-700">
            <div class="max-w-lg mx-auto space-y-2">
                <p>
                    Every two weeks I send out a newsletter like this one, containing lots of interesting stuff for the modern
                    PHP
                    developer.
                </p>
                <p>
                    Subscribe to get the next edition in your mailbox.
                </p>
                @include('front.newsletter.partials.form')
            </div>
        </header>

        <campaign-webview contents="{{ $webview }}"></campaign-webview>
    </body>
</html>

This is pretty neat! It does exactly what we want: the main page's CSS doesn't bleed through, and there's no janky scroll behavior.

To know more about web components and the shadow DOM, read this excellent blog post by Seb.

Stay up to date with all things Laravel, PHP, and JavaScript.

Follow me on Twitter. I regularly tweet out programming tips, and what I myself have learned in ongoing projects.

Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

Webmentions

Luigi Cruz liked on 27th October 2020
Mike liked on 26th October 2020
Tauseef shah liked on 26th October 2020
Edwin I Arellano liked on 26th October 2020
Cyril de Wit liked on 26th October 2020
José Cage liked on 26th October 2020
Helder Willian liked on 26th October 2020
Willan Correia liked on 26th October 2020
LaravelCph liked on 26th October 2020
LaravelCph retweeted on 26th October 2020
Vaggelis Yfantis liked on 26th October 2020
PHP Synopsis retweeted on 26th October 2020
Robin Dirksen liked on 26th October 2020
StoicDojo liked on 26th October 2020