Triple + Semantic = Trimantec™
🚧
This is still a work in progress. I'm making it public for those who can benefit from it now. This also gives me a place to grow it as I have time. Consequently, I'm not accepting contributions at the moment.
Using an app is like having a conversation with someone. Are their responses appropriate to the context of your conversation? Do they hear you? How can you tell?
To improve a user's experience with an app, I encourage developers to approach the design process as if they are building a representation of themselves with whom their users will have a conversation.
Communication driven design —> actively listens for user initiated events —> responds appropriately in a timely manner —> is made up of application behaviors that are -> consistent -> predictable
To improve the developer's experience coding the app, I encourage them to use this design pattern for reasons that follow. But first, some definitions.
- Subject
- A Subject of a sentence is the person, place, thing, or idea that is doing or being something.
- Predicate
- A Predicate is the part of a sentence containing a verb and states something about the
subject
(e.g., went home in John went home). - Object
- An Object is the entity that is acted upon by the
subject
. - Namespace
- A Namespace is a collection of behaviors, usually represented as an object literal, module, or namespace. This is the term we use in the html
data-attribute
to denote thesubject
of the triple. - e.g.,
View = {}
- Method
- A Method is a specific action to be taken on the object/element passed to it.
- The commonly accepted meaning of Class methods.
- e.g.,
View.show(elem)
- Param(s)
- For the purposes of this conversation, a Param contains the information that provides context or additional data required in the
method
. - e.g.,
View.show(elem)
For the developer, there are several benefits of using this design pattern. The application behavior is tightly coupled with the semantics of the HTML while maintaining a separation of concerns. So, as the developer is writing the semantic markup of the application, (s)he already has a very good idea how the application script needs to be structured as well as what needs to be included.
Conversely, this design pattern works equally well if you're the type of developer that prefers to start with coding the application's behavior. By the time you're done, it becomes obvious what data-attributes need to be included with each interaction element in the HTML.
Or, click handler vs. click handlersssssssssssss
This design pattern turns this:
var $nav = $('nav'),
$firstButton = $nav.find('button:first'),
$secondButton = $nav.find('button:nth-child(2)'),
$thirdButton = $nav.find('button:last');
$firstButton.off('click').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
$('#section-1').siblings().addClass('hidden').end().removeClass('hidden');
});
$secondButton.off('click').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
$('#section-2').siblings().addClass('hidden').end().removeClass('hidden');
});
$thirdButton.off('click').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
$('#final-custom-search-form').siblings().addClass('hidden').end().removeClass('hidden');
});
$('#button-a').off('click').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var $clicked = $(e.target),
$form = $clicked.parentsUntil('form'),
$input = $form.find('input');
$input.val('').focus();
});
$('#button-b').off('click').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var $form = $(e.target).parentsUntil('form');
$.post(apiURL, $form.serialize(), function(data, response, jqXHR) {
if (response.statusText !== "OK") {
// handle error
return;
}
if (!data || !data.length) {
// do stuff with empty response object
return;
}
// do stuff with data
});
});
into this:
$('button').off('click').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var $me = $(e.target),
subject = $me.data('namespace'),
predicate = $me.data('method'),
ahbject = $me.data('param');
[subject][predicate](ahbject);
// If #button-a is clicked this becomes Phorm.clearInputText('custom-search-location-input');
// If #button-b is clicked, this becomes Phorm.customSubmit('final-custom-search-form');
// If the first button in the nav is clicked, this becomes View.show('section-1');
});
Writing fewer lines of code makes you more productive.
This also abstracts away the interaction to a logical module dedicated to that one behavior. This type of abstraction accomplishes a few things:
- Far easer to maintain: If you have more than one interaction point related to that behavior, you don't have to change that logic bound to each and every click event; simply change the behavior in that one place.
- Far easier to read: You won't have to hunt through dozens of click handlers to find the one piece of code you're looking for. Simply read the data-attributes of the interaction point in the HTML and navigate to that Namespace.method.
The following is a brief example to highlight, in a practical way, some of the benefits offered by this design pattern. For a deeper dive, fork/clone this repo.
form.html
<doctype html>
<html>
<head>
<meta name="charset" content="utf-8">
</head>
<body>
<nav class="search-results-nav page-nav">
<button
data-namespace="View"
data-method="show"
data-param="section-1">
Show 1
</button>
<button
data-namespace="View"
data-method="show"
data-param="section-2">
Show 2
</button>
<button
data-namespace="View"
data-method="show"
data-param="final-custom-search-form">
Show Form
</button>
</nav>
<section id="section-1">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. A officiis laudantium nesciunt quam, quidem voluptatem.</p>
</section>
<section id="section-2">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint illum labore perspiciatis iure repudiandae reprehenderit animi recusandae nulla in placeat adipisci dicta distinctio accusantium consequuntur exercitationem nostrum, velit, temporibus, quidem?</p>
</section>
<form id="final-custom-search-form">
<input
id="custom-search-location-input"
type="text"
placeholder="Where are you going?"
form="final-custom-search-form"
maxlength="50"
autocorrect="on"
autocapitalize="words" />
<button id="button-a"
data-namespace="Phorm"
data-method="clearInputText"
data-param="custom-search-location-input">
X
</button>
<button id="button-b"
data-namespace="Phorm"
data-method="customSubmit"
data-param="final-custom-search-form">
Submit
</button>
</form>
<script src="js/jquery.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
main.js
// IIFE (immediately invoked function expression). For the most authoritative definition of an IIFE, see:
// http://benalman.com/news/2010/11/immediately-invoked-function-expression/
var App = (function() {
const apiURL = 'https://whatever.com/api/v1/';
const Phorm = {
customSubmit: (formId) => { // returns a Promise
return $.post(apiURL, $('#' + formId).serialize(), function(response) {
return (response.statusText === "OK") ? true: false;
});
},
clearInputText: (inputId) => {
$('#' + inputId).val('').focus();
}
};
const View = {
init: () => {
// some initialization
return new Promise(resolve => {
// do things and stuff
resolve();
});
},
show: (thing) => {
const $thing = $(thing);
const $siblings = $thing.siblings();
$siblings.hide();
$thing.show();
}
};
var self;
return {
init: function init() {
self = this;
// initialize the View
View.init().then(() => {
// bind click handlers
self.buttonClickHandler();
});
},
buttonClickHandler: function buttonClickHandler() {
$('button').off('click').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var $me = $(e.target),
subject = $me.data('namespace'),
predicate = $me.data('method'),
ahbject = $me.data('param');
[subject][predicate](ahbject);
// If #button-a is clicked this becomes Phorm.clearInputText('custom-search-location-input');
// If #button-b is clicked, this becomes Phorm.customSubmit('final-custom-search-form');
// If the first button in the nav is clicked, this becomes View.show('section-1');
});
}
};
}());
$(function() {
// initialize the app
App.init();
});