Skip to content

Commit

Permalink
Improve the UX of the similar pages page
Browse files Browse the repository at this point in the history
- Add loading indicator
- I think favicons will work better than opengraph images.
  There's some risk but not much to loading favicons (mostly SVGs).
- Update the URL when the form is submitted
- Some error handling
- Currently the data is faked until the API is updated
  • Loading branch information
davidfischer committed Mar 19, 2024
1 parent e13942a commit f8e064b
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 26 deletions.
86 changes: 68 additions & 18 deletions ethicalads-theme/templates/ea/similar-pages.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,11 @@ <h1 class="font-weight-light display-3">{{ page.title }}</h1>

<p>Try our machine-learning powered, automatic campaign targeting: Automatically target your campaign to the most-relevant pages across our advertising network based on your landing and product pages.</p>

<!-- GET url argument from URL after page is loaded -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const urlParams = new URLSearchParams(window.location.search);
const url = urlParams.get('url');
if (url) {
document.getElementById('urlInput').value = url;
}
});
</script>

<form name="similar-pages" hx-get="https://server2.ethicalads.io/api/v1/similar/" hx-trigger="submit" hx-target="#response" hx-swap="innerHTML">
<form name="similar-pages" id="similar-pages" data-bind="submit: getSimilarPages">
<div class="form-row">
<div class="col mb-2">
<label for="urlInput" class="sr-only">Landing/Product Page URL</label>
<input type="text" class="form-control" id="urlInput" name="url" placeholder="http://example.com/product-page">
<input type="text" class="form-control" id="urlInput" name="url" data-bind="value: url" placeholder="http://example.com/product-page">
<small class="form-text text-muted">This works best (and our model was trained) on landing pages or product pages with plenty of marketing copy explaining your developer-focused product offering.</small>
</div>
<div class="col-md-2 col-sm mb-2">
Expand All @@ -31,14 +20,75 @@ <h1 class="font-weight-light display-3">{{ page.title }}</h1>
</div>
</form>


<div id="response" class="mt-5">
<p>Enter your landing page or product page to see similar pages where your ad could appear across our network.</p>
<pre id="responseData"></pre>
</div>

<div class="my-10 text-center">
<a href="/advertisers/#inbound-form" class="btn btn-primary">Start your auto-targeted campaign</a>
<!-- Nothing loaded: prompt the user -->
<div data-bind="if: state() != 'loading' && state() != 'loaded'">
<p>Enter your landing page or product page to see similar pages where your ad could appear across our network.</p>
</div>


<!-- An error occurred -->
<div data-bind="if: state() == 'error'">
<p>An error occurred. Please try again or <a href="/contact/">contact us</a> to learn more about automatic campaign targeting.</p>
</div>


<!-- Loading indicator -->
<div class="text-center" data-bind="if: state() == 'loading'">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>


<!-- Loaded w/ or w/o results -->
<div data-bind="if: state() == 'loaded'">

<div class="text-center" data-bind="if: results().length == 0">
<p>No pages were found across our network that were sufficiently similar to the page you entered. Try another page.</p>
</div>

<div data-bind="if: results().length > 0">

<h2>Similar results</h2>

<div data-bind="foreach: results">

<div class="media mb-3">

<!-- OpenGraph image -->
<div data-bind="if: $data.image">
<img src="#" alt="..." class="mr-3" width="32" height="32" data-bind="attr:{'src': $data.image}">
</div>

<!-- Image placeholder -->
<div data-bind="ifnot: $data.image">
<svg data-bind="ifnot: $data.image" class="bd-placeholder-img mr-3" width="32" height="32" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: 32x32"><title>Placeholder</title><rect fill="none" stroke="black" width="100%" height="100%"></rect></svg>
</div>

<div class="media-body">
<h4 class="my-0">
<a href="#" data-bind="text: $data.title, attr:{'href': $data.url}" rel="nofollow noopener" target="_blank"></a>
</h4>
<h6 class="my-0 text-muted" data-bind="text: $data.url"></h6>
<p class="mt-0" data-bind="text: $data.description"></p>
</div>
</div>

</div>

</div>

<!-- CTA -->
<div class="my-10 text-center">
<a href="/advertisers/#inbound-form" class="btn btn-primary">Start your auto-targeted campaign</a>
</div>
</div>

</div>

</article>
</section>
{% endblock %}
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"flickity": "^2.2.1",
"flickity-fade": "^1.0.0",
"highlightjs": "^9.16.2",
"htmx.org": "^1.9.10",
"imagesloaded": "^4.1.4",
"isotope-layout": "^3.0.6",
"jarallax": "^1.12.1",
Expand Down
8 changes: 1 addition & 7 deletions static-src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,8 @@ import './landkit/js/tooltips.js';
// End Landkit
////////////////////////////////////////////////////////////////////////////

// HTMX
import * as htmx from 'htmx.org';

// HTMX includes some inline styles which can be disabled
//htmx.config.includeIndicatorStyles = false;


// Views
import './views/advertiser-calculator.js';
import './views/publisher-calculator.js';
import './views/inbound-form.js';
import './views/similar-pages.js';
74 changes: 74 additions & 0 deletions static-src/views/similar-pages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as jquery from 'jquery';
const ko = require('knockout');


function SimilarPagesViewModel() {
let params = new URLSearchParams(document.location.search);

const initialUrl = params.get('url') || "";

this.url = ko.observable(initialUrl);
this.results = ko.observable([]);
this.state = ko.observable();


// Gets the current URL with the extra custom URL query parameter
this.getUrl = function () {
let url = window.location.origin + window.location.pathname;
let searchParams = new URLSearchParams({
url: this.url(),
});
return url + "?" + searchParams.toString();
};

// Sets the current URL into the browser history
// This lets users copy/paste the URL on similar pages example
// to show results for a specific product page URL
this.setUrlState = function () {
let state = {
"url": this.url(),
};

window.history.pushState(state, null, this.getUrl());
};

// Handles retrieving similar pages.
// Fired when the user submits the form or when there's a passed URL
this.getSimilarPages = function () {
if (!this.url()) return;

console.debug("Retrieving similar pages to: " + this.url() + "...");

this.setUrlState();
this.state("loading");
this.results([]);

// Replace with an AJAX call to the ad server
setTimeout(function (viewmodel) {
viewmodel.results([{
"url": "https://mongoengine-odm.readthedocs.io/",
"title": "MongoEngine User Documentation",
"image": "https://mongoengine-odm.readthedocs.io/favicon.ico",
"description": "MongoEngine is an Object-Document Mapper, written in Python for working with MongoDB. To install it, simply run python -m pip install -U mongoengine",
},
{
"url": "https://doc.ivre.rocks/en/latest/overview/principles.html",
"title": "Principles - IVRE Documentation",
"image": "https://doc.ivre.rocks/favicon.ico",
"description": "IVRE is a network cartography (or network recon) framework. IVRE has five purposes (we use this word to refer to the different types of data IVRE handles), which can be stored by one or more backend databases:",
},
]);
viewmodel.state("loaded");
}, 1000, this);
};

// Fire the form submission if there's a passed URL
if (initialUrl) {
this.getSimilarPages();
}
}


if ($('form[name="similar-pages"]').length > 0) {
ko.applyBindings(new SimilarPagesViewModel());
}

0 comments on commit f8e064b

Please sign in to comment.