Sunday, February 14, 2021

Compound Components (in React)

Components are a great way of reusing the same code, making it easier for a  website to have a consistent look and easier to maintain.

But what if  we have components nested inside other components, like a book with multiple pages? Our first thought might be to a have a <Book /> component and in it we'd put the logic and which <Pages> that will be rendered. This is fine, but it's less flexible that using compound components pattern, like the following.

Let's consider a <Book> component which is a container that handles the logic and accepts <Pages> components as childs, like this:

<Book>
    <Page1 />
    <Page2 />
    ...
</Book>

The book container for this example will have a state to keep track of the current Page, an input box to select page, and will render a copy(clone) of the child components.

It will look like this:

const Book = ({children}) => {
  const [page, setPage] = React.useState(0);
 
  return <div>
    {React.Children.map(children, (child, index) => {
      var toRender = index == page;
      
      return React.cloneElement(child, { toRender});
    })}
    <p>Jump to page:</p>
    <input type="text" onChange={(e) => setPage(e.target.value)} />
  </div>
}

Then we can add any number of child components and they will automatically render if in the book is in the right page.

const Page1 = ({ toRender}) => (toRender ? "Simple text" : null);
const Page2 = ({ toRender}) => (toRender ? (<div><h1>Hello World strikes back</h1><p>Once upon a time on a far away computer there was a 'Hello World' example...</p></div>) : null);

And have fun adding more pages to your new book.

To sum, compound components is great pattern that handles logic in an elegant way without having to worry (much) on which and how many childrens are passed in.

Sunday, February 7, 2021

JS Type enforcement

JS Type enforcement

Have you ever faced a huge JS project and adding/editing features was a nightmare?

It could be that you had to dig deep into the functions to understand how they are used, and you'll end up inside a rabbit hole in Alice's wonderland. You could even find variables that were simultaneously objects and functions, resembling Shroedinger's cat.
Example:
var a = function() {return "a function";}; a.str="a string";
a() // "a function"
a.str // "a string"

Or perhaps it was less painful to copy-paste some similar functionality in another part of the project and pray for the best.

Fortunately project structure have evolved greatly in the last 1-2 decades.
JS code is now encouraged to be:

- modulated, so we don't have thousands of lines inside each file, making it easier to access the information we want.

- unit tested, so we can be sure that the functions that we write do what we actually want;

- properly documented. Let's face it, no one likes to read/skim through dozens lines of documentation every time when using a new function from a new file. Ideally we would just want to know what it does, what we need to feed it, and what comes out of it.
What it does should be clearly and concisely defined on its name.
What arguments we need to use and what it returns can be handled by type enforcement, which is the topic of this post.

Currently there are 2 main ways of enforcing types: with a static type checker( like Typescript) and dynamic type checkers (like PropTypes).


Typescript / FlowJs

If you're coming from a strongly typed language, such as Java, C# or C++, the odds are that JS is just too wild and confusing. It's a free for all fiesta and is weird to go to a point without a road laid down.

Typescript allows developers to hard type what every variable should be, what every function should return, use interfaces and even allows intellisense, just like a static type language (ex: BE languages).

Unfortunately, typescripts comes with a few downsides. Having every variable with a type defined dilutes the code, with more syntax noise and sometimes making it harder to read.
Also, developers that start using typescript will have a lot to learn before start collecting the benefits of typescript. [1]
Sometimes the benefits of static type languages, like intellisense and catching bugs earlier, outweights the burden of retraining a whole team of developers. Sometimes it doesn't.

But there are other options.

 

PropType

If we don't mind, or want, variables to be checked at runtime, we can use a dynamic type check library like PropTypes.

It has the advantage of allowing type checking on a different project and doesn't get compiled when going for production.

You can get 99% of the benefits of static types, without the extra syntax noise and cognitive overhead of type annotations. [1]

The disadvantage is that you can only catch the error after running the code, and you don't have access to the intellisense.


That said, it's also possible to use both if you wish, although it can also be a bit of an overkill.
Or use none at all at your own risk.

 

[1] https://medium.com/javascript-scene/you-might-not-need-typescript-or-static-types-aa7cb670a77b

Sunday, January 17, 2021

Css Animations

CSS Animations

If you're looking to add an extra zest to your already perfect webpage, why not add some animations?

It could be in the form of smooth transitions and waverings to convey a natural consequence of the users action, rather than cold magic.

So, how do we animate HTML elements?

We just need to add 2 sections of code into your CSS.

First, on the element we want to animate, we'll need to specify the ammount of time it will animate, and the name of the animation.

animation-duration: 2s;
animation-name: rainbowWave;

Second, we'll need to add a new "at-rule" (@keyframes) with the information how the element should look at different times of the animation.
In this example, as soon as the element is rendered the element will start with a red background, then will gradually become green for the next second, and finally transition to blue.

@keyframes rainbowWave {
    from {
        background-color: red;
    }
    50% {
        background-color: green;
    }
    to {
        background-color: blue;
    }
}

At the end of the animations the styles will jump back to the ones specified outside the animation. 

Just a word of caution, while small animations can give a nice feel to your website, too much animation can distract the user too much, make the website look childish and can put a big burden on the rendering engine.

Monday, January 4, 2021

Bind elements

Ever needed to set the text of an element asynchronously?

Sometimes we want to use data that is dynamic or is stored outside our file or project.

In those cases, when we get the data, we can bind it with our elements.

Here's a simple example:

HTML:

<p data-test="p1">Placeholder 1</p>
<p data-test="p2">Placeholder 2</p>
<p data-test="p3">Placeholder 3</p>

JS:

var mockedAsyncData = {
     p1: "Hello",
     p2: "World",
     p3: "!!!!!"
}
var targetElements = document.querySelectorAll("[data-test]");

targetElements.forEach( element => {
    var attrValue = element.getAttribute("data-test");

     element.innerHTML =
mockedAsyncData[attrValue];
});
 

In this example, we set up 3 p elements with placeholders, and when the JS runs, it populate the p elements with the values from the data object.

Using vanilla JS works well, but if you'd like to put less code you can use a famous library: the KnockOutJs. 

It does exactly what's above (and much more) with less lines of code.

HTML:

<p data-bind="text: p1">Placeholder 1</p>
<p data-bind="text: p2">Placeholder 2</p>
<p data-bind="text: p3">Placeholder 3</p>

JS:

var viewModel= { 
    p1: "Hello",
    p2: "World",
    p3: "!!!!!"
};

ko.applyBindings(viewModel);

And that's it. The only thing to remember is to use data-bind="text: ..., and to use ko.appyBlindings() with the data. 


KnockoutJs is also able to iterate through arrays (like displaying a list from a template), read values and much more.

Sunday, December 13, 2020

How to use flex to spread content vertically through all the page

How to use flex to spread content vertically through all the page.

Sometimes we want to have a group of elements that we want to stretch out to fill the whole page, to make it feel filled and balanced.

For the following suggestion, we'll use these elements:

<div class="container">
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
</div> 

When thinking about CSS, the first thing that comes to mind is the display: flex; property and in a column direction; so it centers the content and stacks the element on top of each other:

.container {
    display: flex;
    flex-direction: column;
}

/*To make the boxes distinguishable*/
.box {
    margin: 5px;
    border: 1px solid black;
}

However this isn't enough. It renders 3 lines: the black borders of our 3 divs, with 0 height and 100% width, separated by their CSS margin.

To give height to these divs, we need to change 2 things:

First, we need to set the flex-grow for each of the box classed elements. This will allow these divs to grow if needed according to what the parent element wants, and;

Second, we need to set the height of the container. By default, an element will try to take the minimum ammount of size required to accomodate the inner content.

To fill up the whole height of the screen, we need to set the container to take 100% of the height of the parent elements. Unfortunately, the upper element (<body>) doesn't have a height set up, so it defaults to the minimum. So we need to set to 100% the height of the body and of the next up element, the html.

In the end the CSS will look like this:

.container {
    display: flex;
    flex-direction: column;
}
.box {
    margin: 5px;
    border: 1px solid black;
    flex-grow: 1;
}
html, body, .container{
  height: 100%;
}

To sum up, to stretch your content vertically on a webpage, you'll need to set the hight of the container and parent elements to 100%, add display flex to the container element, and have the stretchin child elements the flex-grow.

Saturday, November 28, 2020

Browser Video Capture, screenshot and compatibilities

Introduction

Hey all,

Ever considered to incorporate to your web page a video capture system to your page?
The process is really simple. We just need to add the video element to the HTML and a one-liner in the JS to have it started. 

Sometimes not even that is needed! 

Let's see how.


HTML Media Capture

This old method uses an HTML form extension to access a device's capture mechanism, such as camera, from within a file upload control.

<input type="file" accept="image/*;capture=camera">

This method is very simple and works well with mobile Chrome, but doesn't have support from some other mobile browsers, like Firefox, and is not supported by computers webcams.

WebRTC

WebRTC is an API that can be used by video-chat Web apps.

With this method we'll need the HTML5 element <video>, with an autoplayt attribute, to automatically display the video.

<video autoplay></video>
Then if you want to take a snapshot, just add a button and somewhere to display the image, either a canvas or an image. I'll showcase both:

  <button data-capture-video>Take Shot</button>
  <canvas></canvas>
  <img src="#"/>
  
Then when we press the button, we take the image from the video and display in our canvas/image element:

  buttonElement.addEventListener("click", () => {
   canvasElement.height = videoElement.videoHeight;
   canvasElement.width = videoElement.videoWidth;
   canvasElement.getContext("2d").drawImage(videoElement, 0, 0);
   imageElement.src = canvasElement.toDataURL("image/jpeg");
  });  

This has the advantage that it's accepted in all major browsers, mobile and non-mobile, and allows the image to be edited. Like on CSS:


  .video--inverted {
      filter: invert(1);
  }

And edit the video element on HTML to:


<video autoplay class="video--inverted"></video>

One my notice that the image quality is not the best. This can happen for 3 reasons: Either the video element is compressing too much the webcam image, or the snapshot image compression is too high, or the camera hasn't got as good resolution as we'd expect.

If we (or anything else) is compressing too much the video image, we can add a constraint to force the image to be bigger


var constrains = {
    video: {width: 9999}
};

If the issue is the compression from video to image is too high, then we'd need to change the parameters on how we convert the image. If we want to use an image format with a lossy compression such as image/jpeg and image/webp, we'd want to increase the 2nd argument to a value near 1 (retain 100% quality)(the default is 0.92), like so:


imageElement.src = canvasElement.toDataURL("image/jpeg", 0.95);

Here's a fiddle with some of the above code.

https://jsfiddle.net/sesteves86/956acpx2/34/

Resources

https://www.html5rocks.com/en/tutorials/getusermedia/intro/

https://www.w3.org/TR/html-media-capture/

https://developer.mozilla.org

Monday, June 8, 2020

Personalize Radio Buttons

Hello all,

When we're creating a webpage, we first put the skeleton of the page with HTML, then add some shiny skin with CSS, and finally add some muscles with JS.

We can add styles (skin) to every HTML element (bones of the skeleton).... except for some form controls. And on this post I'm going to discuss how to sort it out for the radio button.

First let's get to know the radio button. It's an input element with the the type "radio". Usually we add a name attribute so only 1 radio button is checked at any given time, a value attribute to be read by the JS and is usually paired with a label element.
<div>
    <input type="radio" name="groupName" value="option1>
    <label for="option1">Option 1</label>
</div>
<div>
    <input type="radio" name="groupName" value="option2">
    <label for="option2">Option 2</label>
</div>

And we will get the following output:


Now the styling.
We can't add stylings to the radio buttons and we don't want to create from scratch some new radio buttons (see why at the bottom of the post).

The hacky way forward is:
- to create a new element that we can edit, like a <span>;
- hide the radio button element; and
- wrap everything on the label, so the radio functionality persists and is triggered whenever we click on the label.

So, it will look like this:

HTML
<label for="option1" class="container">option1
<input type="radio" name="groupName" id="option1">
<span class="checkmark"></span>
</label>
<label for="option2" class="container">option2
<input type="radio" name="groupName" id="option2">
<span class="checkmark"></span>
</label>
For the CSS, the bulk of the work will be on styling on the checkmark for the different situations, like when it's checked, not checked, and being hovered on:

/* Customize the label (the container) */
.container {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
font-size: 22px;
user-select: none;
}

/* Hide the browser's default radio button */
.container input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}

/* On mouse-over, add a grey background color */
.container:hover input~.checkmark {
background-color: #bbb;
}

/* When the radio button is checked, add a blue background */
.container input:checked~.checkmark {
background-color: #38f;
}


/* Create a custom radio button */
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 24px;
width: 32px;
background-color: #ccc;
border-radius: 12px;
}

/* Create the indicator (the dot/circle - hidden when not checked) */
.checkmark:after {
display: none;
}

/* Show the indicator (dot/circle) when checked */
.container input:checked~.checkmark:after {
content: "";
position: absolute;
display: block;
}

/* Style the indicator (dot/circle) */
.container .checkmark:after {
top: 8px;
left: 11px;
width: 10px;
height: 8px;
border-radius: 5px;
background: #ddd;
}
This will produce an output like this:


It does seem a lot of work (and it is), but it's better than creating a custom component for 2 reasons:
1- It's more accessible. People using a screen reader will more easily understand what's happening, and
2- We don't need to create the JS for this functionality, so less work.

I hope this was helpful and have a great day ;)