Quantcast
Channel: DEV Community: David Dal Busco
Viewing all 124 articles
Browse latest View live

How To Declare And Pass Parameters To An Ionic + React Modal

$
0
0

Photo by timJ on Unsplash

I’m having fun with Ionic React these days and therefore experimenting different components. One of these, which I use almost without exceptions in any applications, is the modal. Although its dedicated documentation is pretty neat, I went a bit further, as I like to declare them in their own separate components. That’s why I’m writing this new blog post.

Getting Started

To add a modal to an application, we proceed as displayed in the documentation (told you, it is well documented). We use the component IonModal and, in order to trigger its opening and closing, we also use a state (with the help of a useState hook) to modify its property isOpen .

importReact,{useState}from'react';import{IonModal,IonButton,IonContent}from'@ionic/react';exportconstTab1:React.FC=()=>{const[showModal,setShowModal]=useState(false);return(<IonContent><IonModalisOpen={showModal}><p>Thisisthemodalcontent.</p>
<IonButtononClick={()=>setShowModal(false)}>CloseModal</IonButton>
</IonModal>
<IonButtononClick={()=>setShowModal(true)}>ShowModal</IonButton>
</IonContent>
);};exportdefaultTab1;

Note that I have used the *tab starter kit to develop this article, that’s why the above page’s name is Tab1 .*

Create A Component

Modals could quickly become as complicated as pages, that’s why, I am used to declare them in their own components. Let’s then try to create a new one in a separate new file, called for example MyModal.tsx .

importReactfrom'react';import{IonHeader,IonContent,IonToolbar,IonTitle}from'@ionic/react';classMyModalextendsReact.Component{render(){return<><IonHeader><IonToolbarcolor="primary"><IonTitle>MyModal</IonTitle>
</IonToolbar>
</IonHeader>
<IonContentclassName="ion-padding"><p>Thisisthemodalcontent.</p>
</IonContent>
</>
};}exportdefaultMyModal;

Once we have created it, we could use it in our page to replace the previous content of the modal.

importReact,{useState}from'react';import{IonModal,IonButton,IonContent}from'@ionic/react';importMyModalfrom'./MyModal';exportconstTab1:React.FC=()=>{const[showModal,setShowModal]=useState(false);return(<IonContent><IonModalisOpen={showModal}><MyModal></MyModal>
<IonButtononClick={()=>setShowModal(false)}>CloseModal</IonButton>
</IonModal>
<IonButtononClick={()=>setShowModal(true)}>ShowModal</IonButton>
</IonContent>
);};exportdefaultTab1;

To Close The Modal

Super, we achieved the first step, we have now a modal declared in a separate component. But, in our above example, the action to close the modal, respectively the IonButton button which sets the display state to false, is still rendered outside of our component which is a bit unfortunate in term of design, as, I think, it’s quite common to render such an action in the header of the modal itself.

In order to move this button into the modal, I actually found two possible solutions. One with the use of a callback , probably the cleanest one, and another one using references.

There might be more and I would be really happy to hear about them. Therefore please, ping me with your comments and thank you in advance for your shares 👋

Callback

In this solution, we want to pass a callback to the component to close the modal. We enhance it with a new property, which we also use in our header to add the related button.

importReactfrom'react';import{IonHeader,IonContent,IonToolbar,IonTitle,IonButtons,IonButton,IonIcon}from'@ionic/react';typeMyModalProps={closeAction:Function;}classMyModalextendsReact.Component<MyModalProps>{render(){return<><IonHeader><IonToolbarcolor="primary"><IonTitle>MyModal</IonTitle>
<IonButtonsslot="end"><IonButtononClick={()=>this.props.closeAction()}><IonIconname="close"slot="icon-only"></IonIcon>
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContentclassName="ion-padding"><p>Thisisthemodalcontent.</p>
</IonContent>
</>
};}exportdefault({closeAction}:{closeAction:Function})=>(<MyModalcloseAction={closeAction}></MyModal>
)

Once the component modified, we could create a new function (in our page) to set the display state to false and pass it as callback to our component.

importReact,{useState}from'react';import{IonModal,IonButton,IonContent}from'@ionic/react';importMyModalfrom'./MyModal';exportconstTab1:React.FC=()=>{const[showModal,setShowModal]=useState(false);asyncfunctioncloseModal(){awaitsetShowModal(false);}return(<IonContent><IonModalisOpen={showModal}><MyModalcloseAction={closeModal}></MyModal>
</IonModal>
<IonButtononClick={()=>setShowModal(true)}>ShowModal</IonButton>
</IonContent>
);};exportdefaultTab1;

References

Another possible solution could be the usage of a DOM reference to dismiss the modal.

importReact,{RefObject}from'react';import{IonHeader,IonContent,IonToolbar,IonTitle,IonButtons,IonButton,IonIcon}from'@ionic/react';classMyModalextendsReact.Component{headerRef:RefObject<HTMLIonHeaderElement>=React.createRef();asynccloseModal(){if(!this.headerRef||!this.headerRef.current){return;}await(this.headerRef.current.closest('ion-modal')asHTMLIonModalElement).dismiss();}render(){return<><IonHeaderref={this.headerRef}><IonToolbarcolor="primary"><IonTitle>MyModal</IonTitle>
<IonButtonsslot="end"><IonButtononClick={()=>this.closeModal()}><IonIconname="close"slot="icon-only"></IonIcon>
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContentclassName="ion-padding"><p>Thisisthemodalcontent3.</p>
</IonContent>
</>
};}exportdefaultMyModal;

The above method has for effect that our state , used in our page for the display purpose, might end up not being synced anymore with the effective state of the modal, as we closed it using the DOM. To overcome this situation, we could sync the information after the dialog has been dismissed.

importReact,{useState}from'react';import{IonModal,IonButton,IonContent}from'@ionic/react';importMyModalfrom'./MyModal';exportconstTab1:React.FC=()=>{const[showModal,setShowModal]=useState(false);return(<IonContent><IonModalisOpen={showModal}onDidDismiss={()=>setShowModal(false)}><MyModal></MyModal>
</IonModal>
<IonButtononClick={()=>setShowModal(true)}>ShowModal</IonButton>
</IonContent>
);};exportdefaultTab1;

But unfortunately this method has a drawback. As we are modifying the state to synchronize it, our component is going to be “rerendered”. Therefore it is a bit less performant than the solution with callback and that’s why I found this first solution cleaner.

Side note: I spent several hours yesterday evening trying without success to wrap shouldComponentUpdate respectively React.Memo around the modal component in order to not render the page again when the state is modified after the modal is dismissed. It is probably possible and again I’ll be happy to hear any tips about this too 😉

Post Publication Update

Ely Lucas thankfully answered this blog post with a valid and interesting comment: modal could actually be dismissed without using the button. For example by hitting the Esc key or clicking on the backdrop.

Therefore, a handler to onDidDismiss does have in any case to be defined to sync the open state (as we did in the previous chapter) after the dialog would have been closed.

Summarized

Closing the modal with a button through a callback or reference are both possible.

On the other side, listening to onDidDismiss in order to set the state to false, even if it triggers a rerendering, is kind of mandatory.

To Pass Parameters

In the previous examples, we already used a property to pass a callback to close the modal. Likewise, we could use the same approach to define any other properties.

importReactfrom'react';import{IonHeader,IonContent,IonToolbar,IonTitle,IonButtons,IonButton,IonIcon}from'@ionic/react';typeMyModalProps={closeAction:Function;text:string;}classMyModalextendsReact.Component<MyModalProps>{render(){return<><IonHeader><IonToolbarcolor="primary"><IonTitle>MyModal</IonTitle>
<IonButtonsslot="end"><IonButtononClick={()=>this.props.closeAction()}><IonIconname="close"slot="icon-only"></IonIcon>
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContentclassName="ion-padding"><p>{this.props.text}</p>
</IonContent>
</>
};}exportdefault({closeAction,text}:{closeAction:Function,text:string})=>(<MyModalcloseAction={closeAction}text={text}></MyModal>
)

And therefore pass any other parameters from the page to our modal component.

importReact,{useState}from'react';import{IonModal,IonButton,IonContent}from'@ionic/react';importMyModalfrom'./MyModal';exportconstTab1:React.FC=()=>{const[showModal,setShowModal]=useState(false);asyncfunctioncloseModal(){awaitsetShowModal(false);}return(<IonContent><IonModalisOpen={showModal}><MyModalcloseAction={closeModal}text="This is the updated modal content."></MyModal>
</IonModal>
<IonButtononClick={()=>setShowModal(true)}>ShowModal</IonButton>
</IonContent>
);};exportdefaultTab1;

To infinity and beyond 🚀

David


Stylish Cards And Syntax Highlighting With Gatsby

$
0
0

I am a big fan of Carbon! I often use it to share tips or mistakes I did on my Twitter feed.

The other day, while I was cooking dinner, the idea suddenly hit me: why not improving our DeckDeckGo Web Component to highlight code, which use PrismJS under the hood, to display per default such stylish cards instead of “just” displaying naked elements.

I ate my dinner and implemented this idea the same night. I woke up the following days and was still pleased with the result. Therefore I even decided to add it to my personal website developed with Gatsby.

That’s why I’m happy to introduce gatsby-remark-highlight-code, a new Gatsby plugin to add stylish cards and syntax highlighting to code blocks in markdown files.

gatsby-remark-highlight-code helps render such cards for your blocks of code

Features

The main advantages, in my opinion, of this new plugin are the followings:

  1. Use PrismJS to highlight code and load at runtime any languages which are supported by the library. Nothing to develop or install, the component does the job at runtime.
  2. Many styling options through CSS4 variables (see the documentation for the extensive list).
  3. Not yet implemented in the plugin, but the component can also display automatically line numbers or highlight selected rows. These features can be added relatively quickly if you would need these.
  4. Finally, the plugin use our Web Component developed with StencilJS. Therefore, if you are please with the result, you could reuse it easily in any modern web applications regardless of the framework (or none) you are using!

Getting Started

This plugin can be added in the three following steps:

Installation

To install the plugin and the component, run the following command in a terminal:

npm install gatsby-transformer-remark gatsby-remark-highlight-code @deckdeckgo/highlight-code --save

Configuration

In your gatsby-config.js configure (or add) gatsby-transformer-remark:

plugins:[{resolve:`gatsby-transformer-remark`,options:{plugins:[{resolve:`gatsby-remark-highlight-code`},],},},]

Load the component

Stencil components should currently be loaded at runtime in Gatsby (see issue #1724 which should be soon finally solved as I heard in a Stencil chat recently).

For that purpose, load the @deckdeckgo/highlight-code once in one of your pages or components are mounted.

For example add the following in the main file of your website, in your index.js, or in the template of your blog or simply load it where you need it.

asynccomponentDidMount(){try{constdeckdeckgoLoader=require("@deckdeckgo/highlight-code/dist/loader");awaitdeckdeckgoLoader.defineCustomElements(window);}catch(err){console.error(err);}}

That's it, the plugin is configured and ready. After your next build, the code of your blog will be display in stylish cards with syntax highlighting 🎉

Cherry on the cake 🍒🎂

As I explained above, the exact same Web Component can be used in any modern web applications and is, of course, open source. But the other cherry on the cake is the fact that we are using it in our open source web editor for presentations and developer kit.

Therefore, if you are going to showcase code in an upcoming talk, give a try to DeckDeckGo😃

To infinity and beyond 🚀

David

Generate Contrasting Text For Your Random Background Color

$
0
0

I am currently developing a new application to improve, notably, my React Redux know-how. In this new project, users will be able to assign the color of their choice to some entities background. That’s why I had to find a solution to display contrasted texts.

Credits

I was looking to resolve this problem this weekend until I suddenly remembered that the Ionic Color Generator actually already solved it. I had a look at their code (you gotta love open source ❤️) and based the solution on their implementation.

I also had to implement a function to convert hexadecimal to RGB colors. For that purpose, I found a clean regular expression on Stackoverflow provided by xameeramir.

Generate A Contrasting Text

To generate a contrasting text, we are using the following formula defined by the world wide web consortium (W3C) to ensure that foreground and background color combinations provide sufficient contrast when viewed by someone having color deficits or when viewed on a black and white screen:

((Red value X 299) + (Green value X 587) + (Blue value X 114)) / 1000

The above algorithm takes a YIQ color scheme, converted from a RGB formula, as input and outputs a perceived brightness for a color.

Because I’m working with hexadecimal colors, the implementation needs two conversions before being able to calculate the brightness. It first need to convert the the input to RGB and then to YIQ colors.

Finally, with the help of a threshold value, it could determine if the contrast should be dark or light and provide as result a contrasting text “color”, either a black or white.

interfaceRGB{b:number;g:number;r:number;}functionrgbToYIQ({r,g,b}:RGB):number{return((r*299)+(g*587)+(b*114))/1000;}functionhexToRgb(hex:string):RGB|undefined{if(!hex||hex===undefined||hex===''){returnundefined;}constresult:RegExpExecArray|null=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);returnresult?{r:parseInt(result[1],16),g:parseInt(result[2],16),b:parseInt(result[3],16)}:undefined;}exportfunctioncontrast(colorHex:string|undefined,threshold:number=128):string{if(colorHex===undefined){return'#000';}constrgb:RGB|undefined=hexToRgb(colorHex);if(rgb===undefined){return'#000';}returnrgbToYIQ(rgb)>=threshold?'#000':'#fff';}

Demo With Vanilla Javascript

Let’s give a try to the above solution in Vanilla Javascript.

Contrasting text automatically generated for the selected color

In an html page, we add a color picker to select a dynamic value. For that purpose, we use the component we developed for DeckDeckGo, our web open source editor for presentations. We load the component from Unpkg that’s why no dependencies have to be installed locally.

<!DOCTYPE html><htmldir="ltr"lang="en"><head><metacharset="utf-8"><title>Contrast color</title><script type="module"src="https://unpkg.com/@deckdeckgo/color@latest/dist/deckdeckgo-color/deckdeckgo-color.esm.js"></script><script nomodule=""src="https://unpkg.com/@deckdeckgo/color@latest/dist/deckdeckgo-color/deckdeckgo-color.js"></script></head><bodystyle="background: #F78DA7;"><h1style="font-size: 40px;">Hello World</h1><deckgo-color></deckgo-color><!-- Here we will add the contrast function --><!-- Here we will bind the event and modify the colors --></body>

Then we add our above function to generate a contrasting text. Note that we just remove the Typescript part and only parse the Javascript code.

<script>functionrgbToYIQ({r,g,b}){return((r*299)+(g*587)+(b*114))/1000;}functionhexToRgb(hex){if(!hex||hex===undefined||hex===''){returnundefined;}constresult=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);returnresult?{r:parseInt(result[1],16),g:parseInt(result[2],16),b:parseInt(result[3],16)}:undefined;}functioncontrast(colorHex,threshold=128){if(colorHex===undefined){return'#000';}constrgb=hexToRgb(colorHex);if(rgb===undefined){return'#000';}returnrgbToYIQ(rgb)>=threshold?'#000':'#fff';}</script>

Finally, we bind an event to the color picker to listen to the selected color, which we apply to the page background and which we use to generate a contrasted value we ultimately set as color of the text of the page.

<script>document.querySelector('deckgo-color').addEventListener('colorChange',updateColor,true);functionupdateColor($event){document.querySelector('body').style.background=$event.detail.hex;document.querySelector('body').style.color=contrast($event.detail.hex);}</script>

Cherry on the cake 🍒🎂

Our @deckdeckgo/color Web Component is open source. It’s a StencilJS Web Component and therefore it can be use in Vanilla Javascript or with any modern frameworks. It is also relatively lightweight, according Bundlephobia it only adds 418 Bytes (once minified and gzipped) to any bundles. If you want to give it a try in your application, check its documentation and go for it!

To infinity and beyond, merry Xmas 🎄🚀

David

Cover photo by davisco on Unsplash

Dark And Light Mode: OS Color Scheme And Theme Switcher

$
0
0

There are a lot of good tutorials out there about the subject “dark mode” but less rarely one which covers the two main goals, in my opinion, of such an implementation in a real application. Respectively, giving the users the ability to switch between themes but also inheriting per default the OS color scheme given by the platform, specially as both Google and Apple began to rollout such modes in their products.

That’s why I’m writing this new blog post to share the solution we have implemented, with the help of our contributors, in our web open source editor for presentations, DeckDeckGo.

Credits

As staten above, we have developed such a feature with the help of our contributors, more precisely with the help of Grant Herman (GitHub / Twitter). He even helped us implement it in three different applications 🤯

Definitely, not all heroes wear capes, thank you Grant 🙏

Getting Started

To implement our solution, we are relying on two amazing pieces of software:

  • idb-keyval: a super simple small promise-based keyval store implemented with IndexedDB, which we are using to save the user selection.
  • RxJS: a reactive extensions library for JavaScript, which we are using to store and propagate the state representing the theme in memory.

We are going to use a runtime value to switch between themes, therefore, it would be also possible to implement such a solution using a React Redux stored state. Don’t hesitate to ping me if you would like to get such an example, I also have got one ready in another new application I am currently developing 😉

Singleton Service

To handle our theme state, we create a Vanilla singleton service. If you are using a framework like Angular, create a root scoped service as you would always do, or if you rather like to use functions and static values, do so. The important thing is to load and keep in memory only one single state representing the applied theme for your all application.

In our service, we declare a boolean darkTheme, which sets to true means “dark theme active” respectively false for “light theme”. We could have used an enum, if more than two themes would have been available. This possibility of expansion is kind of a cool asset of this implementation 😃.

Note that we are using a ReplaySubject<1> as we want to keep in memory exactly one only state without value until we first figure out, what theme to apply.

import{Observable,ReplaySubject}from'rxjs';import{get,set}from'idb-keyval';exportclassThemeService{privatestaticinstance:ThemeService;privatedarkTheme:ReplaySubject<boolean>=newReplaySubject<boolean>(1);privateconstructor(){// Private constructor, singleton}staticgetInstance(){if(!ThemeService.instance){ThemeService.instance=newThemeService();}returnThemeService.instance;}}

Watch And Switch State

Our service being ready, we have to expose the state for the application and have to provide a method to let our users be able to toggle the theme. We alsot have to save the selection in order to load it next time the app will restart.

watch():Observable<boolean>{returnthis.darkTheme.asObservable();}asyncswitch(dark:boolean){this.darkTheme.next(dark);try{awaitset('dark_mode',dark);}catch(err){console.error(err);}}

Initial Preference

At boot time, we have to load the theme according the following steps:

  • Does the user already have set a preferred theme?
  • Or were we unable to read this information (does an error occurred)?
  • Or should we fallback using the OS default color scheme provided by the platform?

For these reasons, we create a function which implements this logic and use our previous switch method to propagate the theme.

asyncinit():Promise<void>{try{constsaved:boolean=awaitget('dark_mode');// If user already specified once a preferenceif(saved!==undefined){this.switch(saved);return;}}catch(err){this.switch(false);return;}// Otherwise we check the prefers-color-scheme of the OSconstdarkFromMedia:MediaQueryList=window.matchMedia('(prefers-color-scheme: dark)');this.switch(darkFromMedia.matches);}

Apply The Theme To The DOM

On purpose, we did not effectively applied the theme “graphically”, in our service. Therefore, we now have to consume it where we want to apply the modification to the DOM.

In our projects, as we have developed our applications with Stencil Web Components, we have started the initialization in the root component (app-root.tsx).

Moreover, we are watching for changes in the same component, as it won’t be destroyed until the application is closed. Doing so, on each new state emitted, we modify our DOM, more precisely the body element, to apply or remove a CSS class name (in our case dark ).

import{Component,h}from'@stencil/core';import{Subscription}from'rxjs';import{ThemeService}from'./theme.service';@Component({tag:'app-root',styleUrl:'app-root.scss'})exportclassAppRoot{privatesub:Subscription;privatedomBodyClassList:DOMTokenList=document.body.classList;asynccomponentWillLoad(){this.sub=ThemeService.getInstance().watch().subscribe((dark:boolean)=>{this.updatePreferences(dark);});awaitthis.themeService.init();}componentDidUnload(){if(this.sub){this.sub.unsubscribe();}}privateupdatePreferences(dark:boolean){dark?this.domBodyClassList.add('dark'):this.domBodyClassList.remove('dark');}}

Theme Switcher

Until this point was reached, our application was “only” able to handle the OS preferred color scheme and did not gave the users the ability to toggle the themes. That’s why we create a new component which exposes for example an Ionic toggler to switch between light and dark mode.

import{Component,h,State}from'@stencil/core';import{Subscription}from'rxjs';import{ThemeService}from'./theme.service';@Component({tag:'app-theme-switcher'})exportclassAppThemeSwitcher{privatesub:Subscription;@State()privatedarkTheme:boolean;componentWillLoad(){this.sub=ThemeService.getInstance().watch().subscribe((dark:boolean)=>{this.darkTheme=dark;});}componentDidUnload(){if(this.sub){this.sub.unsubscribe();}}asynctoggleTheme(){awaitThemeService.getInstance().switch(!this.darkTheme);}render(){return<ion-togglechecked={this.darkTheme}onClick={()=>this.toggleTheme()}></ion-toggle>
}}

Styling

You could either style the themes using CSS variables or even just properties. In both case, what does matter, is applying the theme according the class names we have just set on the body element, respectively dark .

In case you would use Ionic, you would for example be able to style a specific background and text color in your variables.scss ( :root without selector being the light theme):

:root{--ion-text-color:black;}body.dark{--ion-background-color:black;--ion-text-color:white;}

Or another example in plain HTML/CSS:

<style>body{background:red;color:yellow;}body.dark{background:purple;color:green;}</style>

All Together

Our project, DeckDeckGo, is open source 😺. Therefore, let me share with you the above implementation with some GitHub references of our remote control:

Cherry on the Cake 🍒🎂

Maybe you are actually not interested to split your code in different layers and just want to have one single Web Component which does take care of everything?

Guess what , we have got such a component too, the one we implemented in our documentation for developers 😊

import{Component,h,State}from'@stencil/core';import{set,get}from'idb-keyval';@Component({tag:'app-theme-switcher'})exportclassAppThemeSwitcher{@State()privatedarkMode:boolean=false;privatedomBodyClassList:DOMTokenList=document.body.classList;privateasyncinit():Promise<boolean>{try{constsaved:boolean=awaitget('dark_mode');if(saved!==undefined){returnsaved;}}catch(err){returnfalse;}constdarkFromMedia:MediaQueryList=window.matchMedia('(prefers-color-scheme: dark)');returndarkFromMedia.matches;}privateupdate(){!this.darkMode?this.domBodyClassList.add('dark'):this.domBodyClassList.remove('dark');this.darkMode=!this.darkMode;}asynccomponentWillLoad(){this.darkMode=awaitthis.init();if(this.darkMode){this.domBodyClassList.add('dark');}}privateasynctoggleTheme(){this.update();try{awaitset('dark_mode',this.darkMode);}catch(err){console.error(err);}}render(){return<ion-togglechecked={this.darkMode}onIonChange={()=>this.toggleTheme()}></ion-toggle>
}}

To infinity and beyond 🚀

David

Cover photo by Sincerely Media on Unsplash

Lazy Load YouTube Video iFrame

$
0
0

The Intersection Observer API is often used to lazy load images but did you know that it can be used to defer any types of elements?

This week I developed a new landing page for DeckDeckGo, our web open source editor for presentations, in which I’ll showcase some video. That’s why, for performance reason, I had to postpone their loading and why also, I’m sharing this new blog post.

Soundtrack

In this article we are going to lazy load a music video clip from my hometown friends Maxi Puch Rodeo Club. I could only highly advice you to play the following video in order to stream some great music while reading this blog post 😉

Getting Started

I implemented this experiment with React but the concept could be use with or without any frameworks. Before we actually defer the loading of the video, let’s add it to a component (I collected the iframe embedded code using the share action provided by Youtube).

importReact,{}from'react';constVideo=()=>{return(<div><divstyle={{'display':'block','height':'2000px','background':'violet'}}>MaxiPuchRodeoClub</div>
<div><iframewidth="560"height="315"src="https://www.youtube.com/embed/ol0Wz6tqtZA"frameBorder="0"allow="accelerometer;
                           autoplay;
                           encrypted-media;
                           gyroscope;
                           picture-in-picture"allowFullScreentitle="Maxi Puch Rodeo Club"></iframe>
</div>
</div>
);};exportdefaultVideo;

We can now open our browser and check that it is effectively loaded at the same time that our page. You will notice that the Youtube url is loaded even if the video is not displayed.

Obfuscate The Video

We create a new state to display or not our video. Per default, as we don’t want to load it when our page load, we set it to false.

const[showVideo,setShowVideo]=useState(false);

To defer the loading of the video, we are going to use the Intersection Observer API. It will detect if the element is (or going to be) visible in the viewport (if we don’t specify another root to observe). As soon as such a visibility is detected, it will triggers an event to let us perform a task, respectively to let us effectively load the video.

That’s why we are also wrapping our element in a container, because we do need an element to observe during the page lifecycle, regardless of the state of our video. Furthermore, we also create a reference to it in order to instantiate our observer later on.

importReact,{createRef,useState}from'react';constVideo=()=>{const[showVideo,setShowVideo]=useState(false);constcontainer=createRef();return(<div><divstyle={{'display':'block','height':'2000px','background':'violet'}}>MaxiPuchRodeoClub</div>
<divref={container}>{showVideo?<iframewidth="560"height="315"src="https://www.youtube.com/embed/ol0Wz6tqtZA"frameBorder="0"allow="accelerometer;
                           autoplay;
                           encrypted-media;
                           gyroscope;
                           picture-in-picture"allowFullScreentitle="Maxi Puch Rodeo Club"></iframe>: undefined
}</div>
</div>
);};exportdefaultVideo;

We can test our app in the browser, as we did previously, and should notice that the video is now neither loaded nor displayed.

Lazy Loading

Finally we can create our observer. The rootMargin is used to add a bounding box around the element to compute the intersections and threshold indicates at what percentage of the target’s visibility the observer’s callback should be executed.

constvideoObserver=newIntersectionObserver(onVideoIntersection,{rootMargin:'100px 0px',threshold:0.25});

To instruct it to observe our container, we add a useEffect hook which will be executed according the container. Moreover, we also test if the browser do supports the API (which is supported currently by all modern platforms) and fallback on an “instant” load, if it would not be the case (“Hello darkness IE my old friend” 😅).

useEffect(()=>{if(window&&'IntersectionObserver'inwindow){if(container&&container.current){videoObserver.observe(container.current);}}else{setShowVideo(true);}// eslint-disable-next-line react-hooks/exhaustive-deps},[container]);

Finally, we declare a function which will be triggered when the container reaches the viewport. We use it to modify our state, in order to display the video, and to disconnect our observer, as we do not need it anymore.

functiononVideoIntersection(entries){if(!entries||entries.length<=0){return;}if(entries[0].isIntersecting){setShowVideo(true);videoObserver.disconnect();}}

Voilà, that’s it 🎉 We could perform our test again an notice that the video is only loaded when needed respectively when the container appears 😃

Going Further

Lazy loading is great but you might want also to add some custom control to play and pause your video. For that purpose, we can either code it by ourselves, with the YouTube Player API Reference for iframe Embeds, or use one of the many existing libraries, but, DeckDeckGo is open source and we do split our platform in multiple standalone components, therefore guess what? We do share a Web Component to embed easily Youtube video in your applications 😊

Let’s install it.

npm install @deckdeckgo/youtube --save

And load it in our application.

import{applyPolyfills,defineCustomElements}from'@deckdeckgo/youtube/dist/loader';applyPolyfills().then(()=>{defineCustomElements(window);});

Then, we remove our state to display or not the video, because the Web Component won't load anything until further notice. We replace it with a new function called loadVideo in which we execute the component's method lazyLoadContent which takes care of everything.

asyncfunctionloadVideo(){if(container&&container.current){container.current.lazyLoadContent();}}

Finally, we add two buttons, used to call play and pause and we replace our iframe with the component <deckgo-youtube/>.

importReact,{createRef,useEffect}from'react';import{applyPolyfills,defineCustomElements}from'@deckdeckgo/youtube/dist/loader';applyPolyfills().then(()=>{defineCustomElements(window);});constVideo=()=>{constcontainer=createRef();constvideoObserver=newIntersectionObserver(onVideoIntersection,{rootMargin:'100px 0px',threshold:0.25});useEffect(()=>{if(window&&'IntersectionObserver'inwindow){if(container&&container.current){videoObserver.observe(container.current);}}else{loadVideo();}// eslint-disable-next-line react-hooks/exhaustive-deps},[container]);functiononVideoIntersection(entries){if(!entries||entries.length<=0){return;}if(entries[0].isIntersecting){loadVideo();videoObserver.disconnect();}}asyncfunctionloadVideo(){if(container&&container.current){container.current.lazyLoadContent();}}return(<div><divstyle={{'display':'block','height':'2000px','background':'violet'}}>MaxiPuchRodeoClub</div>
<buttononClick={async()=>awaitcontainer.current.play()}>Start</button>
<buttononClick={async()=>awaitcontainer.current.pause()}>Pause</button>
<deckgo-youtuberef={container}src="https://www.youtube.com/embed/ol0Wz6tqtZA"></deckgo-youtube>
</div>
);};exportdefaultVideo;

We proceed with our final test, notice that the video is lazy loaded, we play with the buttons and we enjoy the awesome music of Maxi Puch Rodeo Club🪕🥁🎵👍

Contribute To Our Project

Even if it does the job, our component can be improved. I notably think that a smoother transition to display the video would be useful. That’s why I opened a good first issue in our repo on GitHub. If you are up to give a hand, your help would be appreciated 🙏.

Cherry on the Cake 🍒🎂

Our component @deckdeckgo/youtube is a Web Component developed with Stencil and therefore it could be use in any modern web applications, with or without any frameworks. Moreover, if like me you tend to be a bit “bundlephobic”, it will add to your application, once minified and gzipped, only 198 bytes.

To infinity and beyond 🚀

David

Cover photo by Julia Joppien on Unsplash

DeckDeckGo: Kick-starting 2020 with so much improvements

$
0
0

We have been relatively quiet since begin of last December, not on purpose, you know, just life, but recently, we increased our efforts and were even able to put together our biggest release so far! What a way of starting 2020.

Therefore, we are happy to release today the new version of our web open source editor for presentations: DeckDeckGo.

It contains many improvements, fixes and notably the following features, which we hope, you are going to like.

Google Fonts

Even if it was, and still is, already included our developer starter kit, we never took the time to add the option to let our users customize easily the font of their decks. As of now, you will be able to use Google Fonts for your presentation.

Private Assets For Every Users

Even though it was actually already the case, we are also really happy to communicate that moreover the content, all custom assets (images or charts data) uploaded in our cloud (powered by the Firebase Storage) are private for every users too.

These are becoming public only once you would share your presentations online.

It might sounds silly to highlight this fact but we invested some times in this particular subject as we worship the principle of “privacy per default”.

Clone Presentations

When you develop a platform you often have to develop more than “just” its core process, about which I tend sometimes to be a bit obsessed 😅, but also utilities around it. Once of these which was still missing, was the ability to clone presentations. This has now been solved.

Landing Page

I’m agree that is maybe not the most meaningful feature for our existing users but without “landing page”, we guess that it was probably difficult for anyone landing on DeckDeckGo to actually understand quickly what’s all about, even if it is possible to create a couple of slides without having to register. Something which, by the way, doesn’t seems to be the norm nowadays on the internet and which is really important to us.

It is worth to notice, notably as DeckDeckGo is a Progressive Web Apps and could be used in any mobile devices, that once registered, the “landing page” doesn’t appear anymore and our feed of recently shared presentations becomes again the welcoming screen.

Accessibility

We just have one thing to say about accessibility: Roy (Twitter / GitHub) is a hero.

A cold Saturday last December, my inbox began to throw notifications about new GitHub issues which were recently opened, one after the other for quite an amount of time. All had to do with accessibility and moreover all were really well documented and totally correct, not questions ask. All these inputs were provided by Roy. He took some times, out of nowhere, to go through DeckDeckGo in order to test its accessibility. Afterwards he even provided three different GitHub issues’ templates which were missing too.

Really what could I say, if you don’t have faith in humanity and open source, Roy’s story is there to show you the opposite.

Thank you so much Roy 🙏

It is also worth to notice that Grant (Twitter / GitHub) contributed again to one of our release which is super cool. Grant you are a hero too, thank you 🙏

Embed Code

The decks you will edit and share with our online editor are as now of embeddable in any web pages. At first I thought that we should solve this with a super complex solution but finally I opted for a standard iFrame solution “à la Youtube”.

Template “Author” New Design

DeckDeckGo is based on templates. The goal is to create presentations which fit any responsive device screens but also to be able to create these quickly. The template “Author” is a perfect example of this as it is using your registered (optional) data to prefill the slides with your informations (name, description, social links).

In this release, we have improved its layout, have added some options to the editor, like for example being able to opt out and not display your profile picture if you would rather like. We have also fix a long time issue and the slides will now display your social handles and not the name of the platform (“@daviddalbusco” instead of “Twitter”).

We hope these features are quite intriguing and slick to you. If you have any feedback, don’t hesitate to reach us on Slack or found me on Twitter.

And of course, the more important, if you have to create a presentation soon, go give a try to DeckDeckGo!

To infinity and beyond,

David

P.S.: Background photo of the cover by SpaceX on Unsplash

I recorded my first ever video walkthrough, what should I improve?

$
0
0

This week we have released our biggest update of DeckDeckGo so far and as I almost always do, I documented the outcome in a blog post.

Even if I really like Gifs and screenshots, I thought that documenting the features in form of a YouTube video walkthrough would be maybe a plus and also an interesting experience at the same time for my self.

But I never did that before and I've to say, video is not a channel I use that often to learn or discover applications. That's why I would be really interesting to hear your feedbacks and thoughts about it.

What should I improve?

I already noticed that my keyboard noise is a bit loud, but not sure how to improve that.

My setup is a 2018 Mac, I used my headphones for microphone and Quicktime + iMovie.

Infinite Scroll with Ionic, Angular and Firestore

$
0
0

Photo by Dan Schiumarini on Unsplash

Once again I find myself having to find a new place to live in Zürich City and therefore once again I have to use search engines and platforms to browse flats, that were never upgraded, in terms of UX and even sometimes design, since the ’90s 🙈.

Yesterday morning, while I was about to visit such websites, I realize that there are no ways to me to undergo the frustration of using these for weeks or months, again 😤. That’s why I spent my Sunday writing a personal crawler with Puppeteer and Google Firebase and why I developed quickly today, an Ionic and Angular app to browse the outcome. Because it is not the first time I program an Infinite Scroll with such a technology stack, I finally came to the idea to share my implementation in this new blog post.

Prerequisites

In this post we are going to use Ionic, Angular, a Google Cloud Firestore database and also AngularFire and RxJS. I am not going to describe how to install and configure each of these requirements. If you would face problems setting up these, don’t hesitate to ping me by leaving a comment behind this article.

Service

One asset I like in Angular is the separation of concern. We create a new service which should take care of interacting with the database.

ionic g service feed

In the following lines I’ll use the interface and generic name Item to represent the data we are looking to fetch and I’ll declare these interfaces in the same class as our service. Replace it with the real description of your data 😉.

import{DocumentReference}from'@angular/fire/firestore';interfaceItemData{title:string;content:string;}interfaceItem{id:string;ref:DocumentReference;data:ItemData;}

In our newly created service, we declare the following variables:

  1. itemsSubject : a state container for our items
  2. lastPageReached : another state, a boolean , to notice if we have or not yet fetched all the data
  3. nextQueryAfter : a reference to the last Firestore document fetched to index our database queries
  4. paginationSub and findSub : two subscriptions to stop observing the changes and to clean the memory when needed

Moreover, we also declare a service to interact with Firestore, a method destroy to unsubscribe the observers and we expose two functions to return our subjects as observables.

import{Injectable}from'@angular/core';import{AngularFirestore,DocumentReference,QueryDocumentSnapshot}from'@angular/fire/firestore';import{BehaviorSubject,Subscription}from'rxjs';@Injectable({providedIn:'root'})exportclassFeedService{privateitemsSubject:BehaviorSubject<Item[]|undefined>=newBehaviorSubject(undefined);privatelastPageReached:BehaviorSubject<boolean>=newBehaviorSubject(false);privatenextQueryAfter:QueryDocumentSnapshot<ItemData>;privatepaginationSub:Subscription;privatefindSub:Subscription;constructor(privatefireStore:AngularFirestore){}destroy(){this.unsubscribe();}privateunsubscribe(){if(this.paginationSub){this.paginationSubscription.unsubscribe();}if(this.findSub){this.findSubscription.unsubscribe();}}watchItems():Observable<Item[]>{returnthis.itemsSubject.asObservable();}watchLastPageReached():Observable<boolean>{returnthis.lastPageReached.asObservable();}}

We have to query the data in Firestore step by step respectively using a pagination because we are going to implement an infinite scroller. For that purpose, Google provides startAfter which instruct the database to “skip” the matching entities before the given start point. It is also worth to notice that in order to be able to perform such queries, we also need to sort these with orderBy and that I limited the pagination to 10 elements per step with the option limit.

find(){try{constcollection:AngularFirestoreCollection<ItemData>=this.getCollectionQuery();this.unsubscribe();this.paginationSub=collection.get().subscribe(async(first)=>{this.nextQueryAfter=first.docs[first.docs.length-1]asQueryDocumentSnapshot<ItemData>;awaitthis.query(collection);});}catch(err){throwerr;}}privategetCollectionQuery():AngularFirestoreCollection<ItemData>{if(this.nextQueryAfter){returnthis.fireStore.collection<ItemData>('/items/',ref=>ref.orderBy('created_at','desc').startAfter(this.nextQueryAfter).limit(10));}else{returnthis.fireStore.collection<ItemData>('/items/',ref=>ref.orderBy('created_at','desc').limit(10));}}

To that point, we have implemented a find function which query the database, therefore we can now develop the part where we collect the results and add these to our state container.

privatequery(collection:AngularFirestoreCollection<ItemData>):Promise<void>{returnnewPromise<void>((resolve,reject)=>{try{this.findSubscription=collection.snapshotChanges().pipe(map(actions=>{returnactions.map(a=>{constdata:ItemData=a.payload.doc.data()asItemData;constid=a.payload.doc.id;constref=a.payload.doc.ref;return{id,ref,data};});})).subscribe(async(items:Item[])=>{awaitthis.addItems(items);resolve();});}catch(e){reject(e);}});}privateaddItems(items:Item[]):Promise<void>{returnnewPromise<void>((resolve)=>{if(!items||items.length<=0){this.lastPageReached.next(true);resolve();return;}this.itemsSubject.asObservable().pipe(take(1)).subscribe((currentItems:Item[])=>{this.itemsSubject.next(currentItems!==undefined?[...currentItems,...items]:[...items]);resolve();});});}

Component: Logic

Our service is ready to go, we could now create a new component for the presentation and interaction with the user:

ionic g component feed

In this newly created component, we declare the following variables:

  1. infiniteScroll : a reference to the component scroller to disable it when there will be nothing left to query
  2. items$ : an observable which will point to our state of data respectively the data we are looking to display
  3. loaded : a boolean to display a message when our application is performing the very first query
  4. lastPageReachedSub : a subscription to free the observer when we are done

Moreover, we are also referencing the service we created previously and we are implementing OnInit , which we are going to implement afterwards, and OnDestroy to unsubscribe our observer.

import{Component,OnDestroy,OnInit,ViewChild}from'@angular/core';import{IonInfiniteScroll}from'@ionic/angular';import{Observable,Subscription}from'rxjs';import{FeedService,Item}from'./feed.service';@Component({selector:'app-feed',templateUrl:'./feed.component.html',styleUrls:['./feed.component.scss'],})exportclassFeedComponentimplementsOnInit,OnDestroy{@ViewChild(IonInfiniteScroll,{static:false})infiniteScroll:IonInfiniteScroll;items$:Observable<Item[]>;loaded=false;privatelastPageReachedSub:Subscription;constructor(privatefeedService:FeedService){}ngOnDestroy(){if(this.lastPageReachedSub){this.lastPageReachedSub.unsubscribe();}}}

To complete our component, we add the following ngOnInit function which will takes care of:

  1. Initializing our state observer
  2. Observing the pagination to disable the infinite scroller component when nothing left to be queried
  3. Notifying when something as been loaded at least once
asyncngOnInit(){this.items$=this.feedService.watchItems();this.lastPageReachedSub=this.feedService.watchLastPageReached().subscribe((reached:boolean)=>{if(reached&&this.infiniteScroll){this.loaded=true;this.infiniteScroll.disabled=true;}});this.feedService.watchItems().pipe(filter(flats=>flats!==undefined),take(1)).subscribe((_items:Item[])=>{this.loaded=true;});}

We add a very last method called findNext which will be triggered by the scroller component when the user reaches the bottom of the page and which will be use to fetch the next data.

asyncfindNext($event){setTimeout(async()=>{awaitthis.feedService.find();$event.target.complete();},500);}

Component: Rendering

Our JavaScript code is ready, we can add the HTML implementation of our component.

<ng-container*ngIf="loaded; else feedLoading;"><ion-card*ngFor="let item of (items$ | async);"><ion-card-header><ion-card-title>{{item.data.title}}</ion-card-title></ion-card-header><ion-card-content><p>{{item.data.content}}</p></ion-card-content></ion-card><ion-infinite-scroll(ionInfinite)="findNext($event)"><ion-infinite-scroll-content></ion-infinite-scroll-content></ion-infinite-scroll></ng-container><ng-template#feedLoading><main>
    Initializing your feed...
  </main></ng-template>

Finally, we define a minimal height for our cards in the related SCSS file. Without it, the scroller component might never been used as it will only trigger its action if the content of the window has effectively a scroll (“no scroll will happens if nothing to scroll”).

ion-card{min-height:1080px;}

Voilà, we have implemented an infinite scroller with Ionic, Angular and Firestore 🎉

Cherry On The Cake 🍒🎂

The above code is related to Angular but Ionic could be use with or without any modern frameworks. We notably use the same approach in our web open source editor for presentations, DeckDeckGo, which is developed with Stencil. If you are interested by such solution, have a look at our source code on GitHub or ping me if you want me to share the solution in a new blog post 😁.

To infinity and beyond 🚀

David


Firebase Storage Gotchas 😅

$
0
0

Photo by Element5 Digital on Unsplash

When was the last time you reverted several working days?

I recently took some time to make the assets, moreover than the content, private for every users of our web editor for presentations, DeckDeckGo.

After two working days, I finally noticed that I misinterpreted one fundamental point of the Google Cloud Storage and I figured out that I had to revert my new implementation, because our assets were actually, already private 😅. That’s why I’m writing this new blog post, hoping that my “unlucky” experience might help someone else in the future.

Rules: Storage Does Not Have Access To Firestore

Sometimes when things are not written down, I'm asking my self if they aren't, because they are not possible or because they actually are possible 🤔.

Being able to write Storage rules by querying Firestore was one of these things and the answer is no. There is currently no possibility to access a Firebase product from another product.

Rules: Users Read And Write Privileges

It is possible to restrict the access, read and write, to the storage to only authenticated users.

rules_version='2';servicefirebase.storage{match/b/{bucket}/o {
match/{allPaths=**}{allowread,write:ifrequest.auth!=null;}}}

But the above rule still implies that users would be able overwrite data provided by other users. To overcome this problem, we can prefix the data of each users in the Storage with their respective userId.

For example, if you are using the Firebase JavaScript SDK, an upload would look like the following:

constref:Reference=firebase.storage().ref(`${userId}/assets/photo.jpg`);awaitref.put(data);

Once the storage files ordered in that structure, we are then be able to define a rule like the following which only allows users to write and read data in their respective folder of the storage:

rules_version='2';servicefirebase.storage{match/b/{bucket}/o {
match/{userId}/assets/{allPaths=**}{allowread,write:ifrequest.auth.uid==userId;}}}

Format: Storage References

To access a public file or a private one, with a granted rule access, the Storage URL could be made of the following parts:

<imgsrc={`https://firebasestorage.googleapis.com/v0/b/${projectId}.appspot.com/o/${encodeURIComponent(path)}?alt=media`}/>

Where ${projectId} is the Id of the Firebase project and ${path} the path to the file in the storage (a string) which has to be encoded with encodeURIComponent in order to be compliant.

Fetch: Access Image Securely With OAuth2

If we define the above rules or any other rules which grant access to the storage only to authenticated users, it is possible with JavaScript to fetch and load an image with OAuth2 as Bryan Burman displayed in his blog post “How To Access Images Securely with OAuth 2.0”.

try{constimgSrc:string='https://firebasestorage.googleapis.com/.../photo.jpg';constrawResponse:Response=awaitfetch(imgSrc,{method:'GET',headers:{'Authorization':`Bearer ${firebase_user_auth_token}`}});if(!rawResponse||!rawResponse.ok){console.error(`Image can not be fetched.`);return;}constblob:Blob=awaitrawResponse.blob();document.querySelector('img').src=URL.createObjectURL(blob);}catch(err){console.error(err);}

downloadURL: Public But Private

This was my biggest “gotcha” and the reason why I reverted hours of work.

For each and every single asset uploaded in the Storage, Firebase create, regardless if you use them or not, a downloadUrl which is public, regardless of your rules, and accessible on the internet. But, because the url contains a token which is essentially impossible for anyone to guess, these urls are, as long as we don’t share these with anyone, private.

Here’s for example downloadUrl where the token is use as parameter to grant the access to the file.

<imgsrc={`https://firebasestorage.googleapis.com/v0/b/${projectId}.appspot.com/o/${path}?alt=media&token=4733325a-78ff-444d-a67c-01fd8ab30fe`}/>

This was a turning point to me in the process and I have to thank Doug Stevenson for having answered my question about it on StackOverflow.

Note that, without being absolutely sure, I think it might be possible if you are using the Cloud Solution from the server side to instruct Firebase to not generate such urls but it is definitely not possible from the Web/Client side.

downloadUrl: Lifecycle

Firebase Storage token does not expire (see StackOverflow). Therefore, without any other modifications, our downloadUrl also never expire and remain available. But, is it possible in the Firebase Console, to invalidate a specific url. But, once invalidated, Firebase will create a new downloadUrl respectively a new token for the selected file.

It is also worth to notice, that for each overwrites, Firebase will also generate a new token . This means that for example if the users of ours applications are able to upload files, each time they would upload again a file without changing its name, a new downloadUrl would be automatically created.

That’s it, I think this is the summary of my learnings. Firebase Storage is definitely an incredible developer friendly piece of software and all the content and assets of every single users of DeckDeckGo is private until they decide to share publicly their presentations.

To infinity and beyond

David

Create A React Custom Hooks For Your Web Components

$
0
0

Photo by Tamara Gore on Unsplash

When I walk up this morning, I said to myself: “Look David, now is the day, you should try to develop a React custom hooks”.

The experiment went well and was implemented faster than I expected, therefore I thought I could take some time to write about it 😁.

Introduction

Web Components are working everywhere, period. That being said, when used in React, the implementation tends to become a bit more verbose, notably because events have to be attached “manually”. For example, you would not be able out of the box with a Stencil Web Component to do the following.

<my-componentonMyEvent={($event)=>console.log($event)}></my-component>

To overcome this issue, you could bundle your Web Component with their related output targets using the stencil-ds-plugins and the problem is solved. But if you don’t, or can’t, then you have to manually register event listeners which, as I said above, could quickly become a bit verbose.

constref=useRef();ref.current.addEventListener('myEvent',($event)=>console.log($event));<my-componentref={ref}></my-component>

Fortunately, it is possible to create custom hooks and therefore possible to create reusable pieces of code for our application to make it more readable.

Let’s Get Started

For the purpose of this article we are going to start with the very begin, by creating a new React app.

npx create-react-app custom-hook-app
cd custom-hook-app

We want to experiment Web Component, let’s now install one to our application. For example, we can use the color picker of our web open source editor for presentations, DeckDeckGo.

npm install @deckdeckgo/color

Once installed, we can import and declare it in you application respectively in src/App.js .

importReact,{useEffect,useRef,useState}from'react';importlogofrom'./logo.svg';import'./App.css';import{defineCustomElements}from'@deckdeckgo/color/dist/loader';defineCustomElements(window);functionApp(){return(<divclassName="App"><headerclassName="App-header"><deckgo-color></deckgo-color>
<imgsrc={logo}className="App-logo"alt="logo"/><p>Edit<code>src/App.js</code> and save to reload.
</p>
<aclassName="App-link"href="https://reactjs.org"target="_blank"rel="noopener noreferrer">LearnReact</a>
</header>
</div>
);}exportdefaultApp;

If everything went according plan, once we run (npm run start ) our application, the default sample page with our color picker should be rendered.

Implement The Events Listener

Before creating our custom hooks, let’s first implement the events listener as we would do without it. We create a reference useRef for our component and a state to render the selected color.

constcolorRef=useRef();const[color,setColor]=useState();return(<divclassName="App"><headerclassName="App-header"><deckgo-colorref={colorRef}></deckgo-color>
<imgsrc={logo}className="App-logo"alt="logo"style={{background:color}}/>
</header>
</div>
);

Finally, to attach the events, we use the hooks useEffect to bind these when our component’s reference is ready.

useEffect(()=>{constref=colorRef.current;constcolorListener=($event)=>{// $event.detail.hex is the selected colorsetColor($event.detail.hex);};// attach the event to the componentref.addEventListener('colorChange',colorListener,false);// remove event on component unmountreturn()=>{ref.removeEventListener('colorChange',colorListener,true);}},[colorRef]);

I’m agree, not the best UX I ever developed 🤣, but still, we should now be able to select colors and apply them to the background of the React logo.

Create A Custom Hooks

Time to have fun by refactoring our previous implementation in order to create a custom hooks. Firstly, we create a function, the hooks itself, which takes the reference to the component as parameter, contains and return a new state.

functionuseColorChange(paramColorRef){const[data,setData]=useState(undefined);return[data];}

To complete our hooks, we move our previous useEffect code to this new hooks and we adjust the component states to the hooks states. The effect watches the reference passed as parameters and the listener applies the selected color to the hooks state.

functionuseColorChange(paramColorRef){const[data,setData]=useState(undefined);useEffect(()=>{constref=paramColorRef.current;constcolorListener=($event)=>{setData($event.detail.hex);};ref.addEventListener('colorChange',colorListener,false);return()=>{ref.removeEventListener('colorChange',colorListener,true);};},[paramColorRef]);return[data];}

Finally, we use our hooks in our application respectively we replace the previous useState and useEffect.

functionApp(){constcolorRef=useRef();const[color]=useColorChange(colorRef);return(<divclassName="App"><headerclassName="App-header"><deckgo-colorref={colorRef}></deckgo-color>
<imgsrc={logo}className="App-logo"alt="logo"style={{background:color}}/>
</header>
</div>
);}

Voilà, isn’t that a cleaner code and pretty cool? And of course, if we redo our test, it should still work out, we should still be able to select a color and apply it to the background of the React logo 😸.

Conclusion

I don’t pretend that the above implementation is the best one, my goal was to try to build a React custom hooks out and to share a comprehensive step by step blog post. I’m pretty sure it could be improved and I would be super duper happy to hear your suggestions about it, ping me with your comments!

To infinity and beyond 🚀

David

Reorder With Google Cloud Firestore

$
0
0


Photo by Héctor J. Rivas on Unsplash

Have you ever had the need to let your users order data as they wish to?

In one of my most recent work, one of the required feature had to do with giving the users the ability to reorder their data using Kanban boards. Likewise, in DeckDeckGo, our web open source editor for presentations, users can sort slides according to their need.

In both cases, I use Cloud Firestore, a scalable NoSQL cloud database from Google, and I implemented the same approach which I’ll try to described the best I can in this blog post.

Approaches

Firestore don’t offer out of box the ability to maintain data in collections ordered dynamically. It does give you the ability to perform sorted queries but doesn’t allow you to specify a custom ordering yet.

There are probably more than the following three solutions but, after thinking about the problem, I figured out that these were probably my best approaches to achieve my goal:

  1. Storing the data in document arrays instead of collections
  2. Using linked list to keep track of the order of the data
  3. Saving the data in sub-collections and maintaining sorted arrays of their references in related documents

For me, the first approach, storing data in arrays, was quickly a no go idea. It would have probably been the fastet solution to implement but I find it not scalable. The second one, linked lists, was interesting but I thought that the realization would be a bit verbose because I could imagine that each time an element of the list is modified, its adjacent nodes have to be updated too.

That’s why the only remaining approach was the third one, using arrays of references, which has the advantages of being scalable and not too verbose.

But, as great power comes with great responsibility, this solution has a small downside: it costs more than the two other solutions since it needs a bit more database operations.

Model

Let’s say that our goal, in this blog post, is being able to sort dynamically the slides of a presentation, respectively of a deck. To follow the above third approach, we are going to save the presentations in a parent-collection decks and the slides in a sub-collection slides. Moreover, as we want to take care of the ordering, we add an array slides_ids in the parent document which will contains the ordered list of ids.

// Query: `/decks/${deckId}`exportinterfaceDeckData{slides_ids:string[];// <-- The ordered slides ids}exportinterfaceDeck{id:string;data:DeckData;}// Query: `/decks/${deckId}/slides/${slideId}`exportinterfaceSlideData{content:string;// Just a dummy content for demo purpose}exportinterfaceSlide{id:string;data:SlideData;}

Implementation

In order to split the implementation in separate parts we proceed with the following execution scenario. First we create a deck followed by the creation of three slides. We then implement a method to print the slides, because it is a good example of a retrieval function, and finally we implement a method to change the ordering of the slides.

(async()=>{try{constdeckId=awaitcreateDeck();awaitcreateSlide(deckId,'Slide 1');awaitcreateSlide(deckId,'Slide 2');awaitcreateSlide(deckId,'Slide 3');awaitprintSlides(deckId);awaitmoveSlide(deckId,1,0);awaitprintSlides(deckId);}catch(err){console.error(err);}})();

Create Data In The Parent-Collection

The creation of the parent data, the deck, isn’t different as any data creation with Firestore. It doesn’t contain specific information regarding the ordering.

asynccreateDeck(){constfirestore=firebase.firestore();constdata={};constdoc=awaitfirestore.collection('decks').add(data);console.log('Deck created',{id:doc.id,data:data});returndoc.id;}

In order to try to keep the demonstrated pieces of code clear and lean, please do note that in these I didn’t amended errors, performances and other subjects which are needed for a real implementation.

Create Data In The Sub-Collection

Likewise, creating the data in the sub-collection themselves, the slides , doesn’t contains any particular data regarding ordering but it does need an extra step to update the parent document because we want to keep track “manually” of the sorting.

asynccreateSlide(deckId,content){constfirestore=firebase.firestore();constdata={content:content};constdoc=awaitfirestore.collection(`/decks/${deckId}/slides`).add(data);console.log('Slide created',{id:doc.id,data:data});awaitupdateDeck(deckId,doc.id);}

This extra step, the update of the deck , can for example be implemented like the following:

asyncupdateDeck(deckId,slideId){constfirestore=firebase.firestore();constsnapshot=awaitfirestore.collection('decks').doc(deckId).get();if(!snapshot.exists){console.error('Deck not found');return;}constdata=snapshot.data();if(!data.slides_ids||data.slides_ids.length<=0){data.slides_ids.slides=[];}// Add the newly created slide ID to the list of slidesdata.slides_ids.push(slideId);awaitfirestore.collection('decks').doc(deckId).set(data,{merge:true});console.log('Deck updated');}

But, in my opinion and because we are already using Firestore, the most reliable solution would be to defer the update of the slides’ list of IDs in a Cloud Functions for Firebase. For demonstration purpose I’ll stick to achieving the update from the client side but if you are implementing this solution in your application, I would suggest you to consider this option.

import*asfunctionsfrom'firebase-functions';exportconstslideCreate=functions.firestore.document('decks/{deckId}/slides/{slideId}').onCreate(watchSlideCreate);asyncfunctionwatchSlideCreate(snapshot,context){constdeckId:string=context.params.deckId;constslideId:string=context.params.slideId;awaitupdateDeck(deckId,slideId);}// And adapt above `updateDeck` function to use 'firebase-admin'

Retrieve Ordered Data

As mentioned in the approach, retrieving the “manually” ordered data costs more than querying these because we have first to get the list of IDs before being actually able to fetch these. But it does solve our goal.

asyncprintSlides(deckId){constfirestore=firebase.firestore();constsnapshot=awaitfirestore.collection('decks').doc(deckId).get();if(!snapshot.exists){console.error('Deck not found');return;}constdata=snapshot.data();if(!data.slides_ids||data.slides_ids.length<=0){console.error('No slides to print');return;}constpromises=data.slides_ids.map((slideId)=>{returnprintSlide(deckId,slideId);});awaitPromise.all(promises);}

As you could notice, above we first fetch the deck and then map every single slides to a dedicated function to retrieve the related data.

asyncprintSlide(deckId,slideId){constfirestore=firebase.firestore();constsnapshot=awaitfirestore.collection(`/decks/${deckId}/slides`).doc(slideId).get();if(!snapshot.exists){console.error('Slide not found');return;}constdata=snapshot.data();console.log('Slide print',data.content);}

It is also worth to notice, something I discovered recently, that it also offers some more flexibility in case you would be interested to develop a custom pagination. I won’t develop this topic in this particular article but if that would be interesting to you, ping me with a comment, I’ll be happy to develop this in a new blog post.

Update Order

If retrieving costs more, the beauty of this solution is maybe the fact that updating the order doesn’t costs much, because the list of sorted data is contained in a single document and therefore a single update query on the indexed array is already enough to define the new order.

asyncmoveSlide(deckId,from,to){constfirestore=firebase.firestore();constsnapshot=awaitfirestore.collection('decks').doc(deckId).get();if(!snapshot.exists){console.error('Deck not found');return;}constdata=snapshot.data();if(!data.slides_ids||data.slides_ids.length<=0){console.error('No slides to move');return;}data.slides_ids.splice(to,0,...data.slides_ids.splice(from,1));awaitfirestore.collection('decks').doc(deckId).set(data,{merge:true});console.log('Deck updated');}

In this particular example we don’t modify any other informations of the slides and that’s why I performed the update of the order from the client side but in the same way as I suggested in a previous chapter, if that would be the case, I would suggest to again defer such update in a cloud function.

Epilogue

I'm honestly not sure my above explanations where that clear. I really wanted to share the subject because it is a feature's requirements I do face often when I implement applications.

I hope this will someday help someone and if you have any comments or ideas, ping me about it or maybe even better, create a presentation with our editor DeckDeckGo and don’t forget to try to order manually your slides 😉

To infinity and beyond 🚀

David

Syntax Highlighting Displayed In Ubuntu Terminal Like

$
0
0

A couple of days ago, Cody Pearce published an article which picked my curiosity. In his post he was displaying how the unique and immediately recognizable design of the Ubuntu terminal can be reproduced with CSS.

In DeckDeckGo, our editor for presentations, we are providing a code highlighter Web Component, developed with Stencil, which can be integrated in your slides, apps or even easily in a Gatsby website.

That’s why I thought it would be fun to merge Cody’s idea and design 😄.

HTML

The quickest way to try out the component is probably to plug it, with the help of Unpkg, in a plain HTML file. Once the references to fetch its code have been added in the page header, it can be consumed.

Regarding the code to highlight, it should be provided to match the slot name code and that’s it, everything you need to highlight the code is ready.

<html><head><script type="module"src="https://unpkg.com/@deckdeckgo/highlight-code@latest/dist/deckdeckgo-highlight-code/deckdeckgo-highlight-code.esm.js"></script><script nomodule=""src="https://unpkg.com/@deckdeckgo/highlight-code@latest/dist/deckdeckgo-highlight-code/deckdeckgo-highlight-code.js"></script></head><body><deckgo-highlight-code><codeslot="code">console.log('Hello World');</code></deckgo-highlight-code></body></html>

Rendered in a browser, the above code looks like the following:

As you may notice, it isn’t yet rendered in a stylish Ubuntu terminal like but in a “Macish” terminal, which is the default behavior. Therefore, to achieve our goal, we just improve the solution by providing the attribute terminal set to ubuntu .

<deckgo-highlight-codeterminal="ubuntu"><codeslot="code">console.log('Hello World');</code></deckgo-highlight-code>

And voilà, nothing more, nothing less 😁

By the way, don’t you feel too that it is over awesome to be able to write what, five lines of code in a plain HTML file and to already have a “complex” and performing element rendered? Damned, I love Web Components 🚀.

Gatsby

As briefly mentioned above, we also do provide a plugin to integrate easily our component in any Gatsby websites and blogs too. At the end of last year I published another post to display how it could be integrated, therefore I won’t cover the setup process again but just I just want to mention that the style could be selected through an optional plugin configuration:

// In your gatsby-config.jsplugins:[{resolve:`gatsby-transformer-remark`,options:{plugins:[{resolve:`gatsby-remark-highlight-code`,options:{terminal:'ubuntu'}},],},},]

If I would apply the above configuration to my personal website then, after rebuild, automatically all of its blocks of code would be encapsulated in Ubuntu terminal like.

Properties And Styling

Beside the default configuration, the components supports many options. Notably, because it is a wrapper around Prism.js, the ability to highlight 205 languages (to be provided though a property language ) and offers many styling options through CSS4 variables. From specifying the color of the highlighted code to customizing the terminal or none, even if it’s a shadowed Web Component, it does expose many options and in case you would need more, ping me!

To infinity and beyond 🚀

David

P.S.: Even though the Ubuntu Terminal like are only going to be unleashed in our next major release, such stylish cards do already also look good in slides, so why not giving a try to DeckDeckGo for your next talk 😜

P.P.S.: Background photo of the cover by MUNMUN SINGH on Unsplash

Introducing A New Web Component To Drag, Resize And Rotate

$
0
0

Today we are happy to unleash a new open source Web Component that we developed for our web editor for presentations, DeckDeckGo. With it you can add drag, resize and rotate features to your applications or websites, regardless of your web framework. Cherry on the cake: it works out of the box -- no extra JavaScript needed.

A demo is worth a thousand words:

Back Story

We really care about performance and dependencies. That’s why we tend to be a bit bundleophibic and, let’s face it, we are also nerds 🤷. Coding is as much a job as it is a hobby. That’s why we like to spend our evenings and weekends developing that kind of stuff.

A couple of months ago, when we were brainstorming new ideas, Nicolas suggested adding a new template which would let users draw technical schemas, directly in our editor. This concept implied two complexities: preserving the aspect ratio of the content of the slide across devices and being able to manipulate (drag, resize and rotate) its content. That’s why it took us some time to schedule and realize it.

While it may look easy to develop at first glance, during development we discovered that the required maths were actually more challenging than expected and therefore obviously even more fun 😉.

Nicolas published today a post in which he details the mathematical problems and solutions. Check it out on his blog.

Getting Started

The easiest way to try out our component is to use it, with the help of Unpkg, in a plain HTML file.

<html><head><script type="module"src="https://unpkg.com/@deckdeckgo/drag-resize-rotate@latest/dist/deckdeckgo-drag-resize/deckdeckgo-drag-resize.esm.js"></script><script nomodule=""src="https://unpkg.com/@deckdeckgo/drag-resize-rotate@latest/dist/deckdeckgo-drag-resize/deckdeckgo-drag-resize.js"></script></head><body></body></html>

Once the imported, the component can be used to drag, resize or rotate any elements. For that purpose, it should be just wrapped around each of these which have to be manipulated. For example, let’s say we have a division element.

<divstyle="background: purple;"></div>

If we want to make it movable, draggable and resizable, we wrap it in our Web Component <deckgo-drr/>, we specify its default size and position with CSS4 variables and … that’s it 🎉.

<deckgo-drrstyle="--width: 10%; --height: 19%; --top: 15%; --left: 12.5%;"><divstyle="background: purple;"></div></deckgo-drr>

All together tested in the browser looks like the following.

Options

The cool thing about this component, I think, is that you don’t have to write any JavaScript to use it. You wrap it around any element and “it works”. It provides a couple of options, which are all documented in the related chapter of our documentation for developers. It notably supports various units (percent, viewport related or pixels) and each action can be disabled separately. Its design can be customized with various CSS4 variables and finally it bubbles two events, one when the component is selected or unselected and another one when it has changed.

It is also worth noticing that it does support both mouse and touch interactions.

What’s Next

What’s coming next is actually up to you 😉. We are open source and are eager to hear your feedback. Ping us on our Slack channel, open an issue in our repo or even provide a Pull Request, you are most welcome to contribute to our pet project DeckDeckGo in any ways or simply by using it to compose your next slides 🙏.

To infinity and beyond 🚀!

David

Introducing: Tie Tracker. A simple, open source and free time tracking app ⏱️

$
0
0

I’m happy to share with you Tie Tracker: a simple, open source and free time tracking app ⏱️.

Back Story

Last December, in between clients' projects, I had some spare time to learn new concepts. Of all of interesting subjects out there, one particular retained my attention: improving my React skills and giving a real try to Redux.

Knowing my self, in comparison to experimenting, I knew I had to implement something concrete to get to feel comfortable with the technologies, specially with Redux. That’s why I came back to my long time idea to implement a time tracking and reporting application which perfectly matches my business processes.

Nevertheless, at this point, I was still unsure to start or not this project. After all, developing an application needs a certain effort. Moreover, I was not that motivated to develop yet again another solution which would need a cloud, authentication and database.

But after much thinking about it, I finally found the last bit of motivation: I would develop the app to run entirely offline, with the help of IndexedDB, and I would give a try to Web Workers to defer “heavy” computation.

And that was it, I was all in. I started my new Ionic app and I developed my new tool 😁.

Features

The application helps track productivity and billable hours for a list of clients and projects. Each entries can be billed. It also supports miscellaneous currencies and optionally a VAT rate.

  • ✅ Simple work hours tracking
  • ✅ Assign time to clients and projects
  • ✅ Mark entries as billed

Reporting

For my company, I use a third party online accounting system to generate my client’s bill. When I send these, I join a report of every worked hours I spent on the projects for the selected period. So far, I was editing these timesheets manually but fortunately, I can now extract these on demand directly from Tie Tracker 😄.

  • ✅ Export open invoices to XLSX timesheets
  • ✅ Weekly work summary
  • ✅ Daily list of activities

Goodies

Of course I had to implement some goodies 😉. One of these is an hourly reminder, through local notifications, about a task in progress. This notably took me some iterations before being stable, mobile development sometimes needs patience 😅.

I was also a little bit concerned about the persistence of the data on mobile devices, specially regarding iOS and its reliability with IndexedDB. To overcom this concern, I implemented a backup process: once a week, the application asks the user, if she/he would like to export all current not billed hours.

  • Light and dark theme
  • Weekly backup
  • Hourly notification for task in progress (only mobile devices)

Open Source

Obviously, at least for the few of you who are reading my articles time to time knowing that I have got an “open source mindset per default”, how could it have been different? Tie Tracker is open source, licensed under license AGPL v3 and above, its source code is available on GitHub and contributions are most welcomed 🙏.

What’s Next

I have used Tie Tracker for three months now and I have billed several clients with it, therefore it already improved my daily work life. However, there are still two features I would like to develop and if there would be a public interests for an online mode in order to save the data in the cloud, I would consider to go further. Get in touch if you are interested!

To infinity and beyond 🚀

David

How To Call The Service Worker From The Web App Context

$
0
0

Photo by [Arindam Saha](https://unsplash.com/@flux_culture?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/day-1?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)Photo by Arindam Saha on Unsplash

I literally just had such a bad idea I already know I might not make it, but well, it is worth a try 😅.

Switzerland goes on lockdown until 19th April 2020 and the state is asking us to remain at home. That’s why, I will try to share one trick a day until the end of the quarantine!

It is probably a bit optimistic, never set my self so far goals in terms of writing and I also already have preordered Disneyplus which suppose to begin is streaming really soon, but hey, you miss 100% of the shots you don’t take — Michael Scott.

How To Call The Service Worker From The App Context

I will not go too deep in details, but let’s just say that I recently merged a nice new feature in DeckDeckGo, our open source editor for presentations 😉. For this purpose I used Workbox and I had to trigger on demand the caching from the web app context.

For example, let’s say you have defined a rule to cache your images.

workbox.routing.registerRoute(/\.(?:png|gif|jpg|jpeg|webp|svg)$/,newworkbox.strategies.CacheFirst({cacheName:'images',plugins:[newworkbox.expiration.Plugin({maxAgeSeconds:30*24*60*60,maxEntries:60})]}));

When you app starts, matching images being part of the current route are going to be cached automatically by Workbox, respectively by the service worker. But, maybe, you do use other images, which are not yet displayed, but which you might also want to cache for an offline use.

This can be solved by requesting the service worker to cache specific routes or urls from your web app content.

asyncfunctionaddToCache(){constlist=['/assets/img1.svg','/assets/img2.svg'];constmyCache=awaitwindow.caches.open('images');awaitmyCache.addAll(list);}

And that’s it. With the help of the above function, taken from the documentation, you can trigger the service worker to cache resources, routes or other requests on demand. Worth to notice that it can be called with or without user interaction, as you rather like.

document.addEventListener('DOMContentLoaded',($event)=>{addToCache();});<buttononclick="addToCache()">Goimagesoffline</button>

That’s it, this was the first of the tricks I will try share during this quarantine but hopefully only one of really few articles.

Stay home, stay safe!

David


Replace Environment Variables In Your Index.html

$
0
0

Yesterday evening I began that crazy challenge to share a blog post each and every day until the quarantine is over here in Switzerland the 19th April 2020, 33 days left until hopefully better days.

In this second series’ article I would like to share with you another trick we have developed in our project DeckDeckGo.

Even if we are open source and even share the credentials of our test environment directly in our GitHub repo, we are keeping some, really few, production tokens hidden. Mostly because these are linked with our private credit cards 😅. That’s why, we have to replace environment variables at build time.

We have developed our frontend eco-system with the amazing compiler and toolchain StencilJS and I’ve already shared our solution to use variables in our code in two distinct posts (see here and there). But, what I did not share so far is, how we replace environment variables in our index.html without any plugins 😃.

Lifecycle NPM Scripts

We want to replace variables after the build as completed. To hook on a corresponding lifecycle we are using npm-scripts most precisely we are using postbuild . In our project, we create a vanilla Javascript file, for example config.index.js , and we reference it in the package.json file.

"scripts":{"postbuild":"./config.index.js",}

Add Variable In Index.html

Before implementing the script to update the variable per se, let’s first add a variable in our index.html . For example, let’s add a variable <@API_URL@> for the url of the API in our CSP rule.

Of course, out of the box, this content security policy will not be compliant as <@API_URL@> isn’t a valid url. Fortunately, in such case, the browser simply ignore the rule, which can be seen as convenient, because we can therefore work locally without any problems and without having to replace the value 😄.

<metahttp-equiv="Content-Security-Policy"content="default-src 'self';
  connect-src 'self' <@API_URLS@>"/>

Update Script

Configuration is in place, variable has been added, we just have now to implement the script. Basically, what it does, it finds all html pages (we use pre-rendering, therefore our bundle contains more than a single index.html ) and for each of these, read the content, replace the variable we have defined with a regex (not the clever one, I’m agree) and write back the results.

#!/usr/bin/env node
constfs=require('fs');constpath=require('path');functionupdateCSP(filename){fs.readFile(`${filename}`,'utf8',function(err,data){if(err){returnconsole.log(err);}constresult=data.replace(/<@API_URLS@>/g,`https://myapi.com`);fs.writeFile(`${filename}`,result,'utf8',function(err){if(err)returnconsole.log(err);});});}functionfindHTMLFiles(dir,files){fs.readdirSync(dir).forEach((file)=>{constfullPath=path.join(dir,file);if(fs.lstatSync(fullPath).isDirectory()){findHTMLFiles(fullPath,files);}elseif(path.extname(fullPath)==='.html'){files.push(fullPath);}});}lethtmlFiles=[];findHTMLFiles('./www/',htmlFiles);for(constfileofhtmlFiles){updateCSP(`./${file}`);}

Voilà, we are updating automatically at build time our environment variables in our application index.html🎉

Generate SHA-256 For Your CSP

The above solution is cool but we actually had to go deeper. Each time we build our app, a script is going to be injected in our index.html in order to load the service worker. As we want to apply strict CSP rules, this script is going to be invalidated until we provide a SHA-256 exception for its representation. Of course, we weren’t looking forward to calculate it on each build and we have automated that task too. To do so, let’s first add a new variable in your index.html .

<metahttp-equiv="Content-Security-Policy"content="default-src 'self';
  connect-src 'self' <@API_URLS@>"script-src'self'<@SW_LOADER@>
/>

Once done, we now enhance the update script with a new function which takes care of finding the loading script (once again, not the cutest detection pattern, I’m agree), once found, generates its SHA-256 value and inject it as a new variable.

#!/usr/bin/env node
constfs=require('fs');constpath=require('path');constcrypto=require('crypto');functionupdateCSP(filename){fs.readFile(`${filename}`,'utf8',function(err,data){if(err){returnconsole.log(err);}letresult=data.replace(/<@API_URLS@>/g,`https://myapi.com`);constswHash=findSWHash(data);if(swHash){result=result.replace(/<@SW_LOADER@>/g,swHash);}fs.writeFile(`${filename}`,result,'utf8',function(err){if(err)returnconsole.log(err);});});}functionfindSWHash(data){constsw=/(<.?script data-build.*?>)([\s\S]*?)(<\/script>)/gm;letm;while((m=sw.exec(data))){if(m&&m.length>=3&&m[2].indexOf('serviceWorker')>-1){return`'sha256-${crypto.createHash('sha256').update(m[2]).digest('base64')}'`;}}returnundefined;}functionfindHTMLFiles(dir,files){fs.readdirSync(dir).forEach((file)=>{constfullPath=path.join(dir,file);if(fs.lstatSync(fullPath).isDirectory()){findHTMLFiles(fullPath,files);}elseif(path.extname(fullPath)==='.html'){files.push(fullPath);}});}lethtmlFiles=[];findHTMLFiles('./www/',htmlFiles);for(constfileofhtmlFiles){updateCSP(`./${file}`);}

That’s it, isn’t this handy?

Summary

As I said above, the regex and selector I used above aren’t the most beautiful one, but you know what, I’m not against improvements. If you are into it, don’t hesitate to send me a Pull Request😁.

Stay home, stay safe!

David

Cover photo by Joshua Earle on Unsplash

Inject JavaScript Or CSS At Runtime And On Demand

$
0
0

I challenged my self to share a blog post each and every single day until end of the current quarantine in Switzerland, the 19th April 2020. Thirty-two days left until hopefully better days.

In this third blog post, I would like to share with you a trick we are using in our open source project DeckDeckGo but also one which has been shared by Cory McArthur, an incredible user experience engineer of Sworkit.

Commonly you are including your dependencies in your app bundle, but some of these might be used only in certain circumstances. For instance, if you are using Firebase UI to handle your authentication flow or if like us, you create a Web Component which act as a wrapper around another library like Prismjs, you might want to load these only when really needed.

Even though a lazy loading pattern might be use in your app, depending of your UX and routing, you might rarely face the case where such libraries are fetched even if actually not needed.

But no worries, here’s a trick to solve such requirement by injecting either a script or css in your page on demand and at runtime.

Load Conditionally A Script

Let’s try to develop a new Web Component with Stencil which fetch a script when mounted. To do so, we run the following commands in a terminal:

npm init stencil
cd my-component
npm install

Once the project created, we edit the component and add a first test in order to verify if our script has not been added to the DOM before, because our component can be use multiple times in a page and we want to load our script only once.

import{Component,h}from'@stencil/core';@Component({tag:'my-component',styleUrl:'my-component.css',shadow:true})exportclassMyComponent{asynccomponentDidLoad(){constscripts=document.querySelector('[myscript-loaded]');if(!scripts){// TODO: load script}}render(){return<div>Hello,World!</div>;
}}

Finally we can add our effective implementation which summarized works like the following: we create a new deferred <script/> which references the library or component we would like to load. Before adding it to the header of our page, we attach two events to handle both success or error .

import{Component,h}from'@stencil/core';@Component({tag:'my-component',styleUrl:'my-component.css',shadow:true})exportclassMyComponent{asynccomponentDidLoad(){constscripts=document.querySelector('[myscript-loaded]');if(!scripts){constscript=document.createElement('script');script.onload=async()=>{script.setAttribute('myscript-loaded','true');};script.onerror=async($err)=>{console.error($err);};script.src='https://unpkg.com/myscript.js';script.defer=true;document.head.appendChild(script);}}render(){return<div>Hello,World!</div>;
}}

And…that’s it 🎉. By injecting the script in the header, the browser notices the change and proceeds it as it would normally do with any scripts.

Cory’s Generic Functions

Above solution is cool but generic functions are way cooler and handier 😉. Therefore here is Cory’s awesome solution to load any JavaScript or CSS on demand:

functioninjectJS(id:string,src:string):Promise<string>{returnnewPromise<string>((resolve,reject)=>{if(!document){resolve();return;}if(document.getElementById(id)){resolve('JS already loaded.');return;}constscript=document.createElement('script');script.id=id;script.async=true;script.defer=true;script.src=src;script.addEventListener('load',()=>resolve('JS loaded.'));script.addEventListener('error',()=>reject('Error script.'));script.addEventListener('abort',()=>reject('Aborted.'));document.head.appendChild(script);});}functioninjectCSS(id:string,src:string):Promise<string>{returnnewPromise<string>((resolve,reject)=>{if(!document){resolve();return;}if(document.getElementById(id)){resolve('CSS already loaded.');return;}constlink=document.createElement('link');link.id=id;link.setAttribute('rel','stylesheet');link.setAttribute('href',src);link.addEventListener('load',()=>resolve('CSS loaded.'));link.addEventListener('error',()=>reject('Error css.'));link.addEventListener('abort',()=>reject('CSS aborted.'));document.head.appendChild(link);});}

Such utilities can notably use to load Firebase UI only when needed:

awaitinjectJS('firebase-ui-script','https://cdn.firebase.com/libs/firebaseui/4.0.0/firebaseui.js');awaitinjectCSS('firebase-ui-css','https://cdn.firebase.com/libs/firebaseui/4.0.0/firebaseui.css');

Summary

One downside of the above solution, I’m agree, is the fact that you are handling a version number in, kind of, the middle of your code but to me, that’s a small trade of to be able to fetch some libraries only when needed because of the particular requirements of the UX of our editor, DeckDeckgo.

I warmly thank Cory for having shared his solution and also for having answered some of my questions, when I developed our authentication’s flow. Not all heroes wear capes, you are super Cory 🙏

Stay home, stay safe!

David

Cover photo by Aditya Saxena on Unsplash

Sometimes You Just Need A Dumb Library

$
0
0

I challenged my self to share a blog post very day until end of the current quarantine in Switzerland, the 19th April 2020. Thirty-one days left until hopefully better days.

One of my client week’s assignment was to track down a bug between libraries. After quite a bit of time, I finally figured out that some code had been duplicated and had also received, of course, an improvement which had not been spread 😒.

Such issues can be avoided by not duplicating code, for example, by sharing it across projects through libraries. But often, if it is not yet something standardized in your company, it will take a bit of time to setup such new libraries.

Because in DeckDeckGo, our open source editor for presentations, we actually had already setup such utilities, I thought it would be not a bad thing to share our recipe.

Our Goal: A Utility To Get Valid Date Objects

Regardless of the JavaScript projects and the frameworks, at some point, I will most probably have to handle dates and most probably, I will have to cast these to proper JavaScript Date objects, or at least, have to ensure their validities. That’s why I created for my self a little function which does such job. Therefore I suggest that our goal in this article is to create a library which exposes such a function.

exportfunctiontoDateObject(myDate:any):Date|undefined{if(!myDate||myDate===undefined){returnundefined;}if(myDateinstanceofString||typeofmyDate==='string'){returnnewDate(`${myDate}`);}if(typeofmyDate==='number'&&!isNaN(myDate)){returnnewDate(myDate);}// It case Firebase Timestamp format tooif(myDate&&myDate.seconds>=0&&myDate.nanoseconds>=0){returnnewDate(myDate.toDate());}returnmyDate;}

Create A Library

Let’s create our library. To begin with, in a terminal, we make a new folder and jump in it.

mkdir utils &&cd utils

To develop and bundle our project we are going to use both Rollup and Typescript. To install and use these, we create a new file package.json which contains the following. Basically, use these two above libraries to prepare and build our library, rimraf to remove the output folder before each build and the information about the library itself respectively which file is going to be its main entry, which one is the module and which one the types definition.

{"name":"utils","version":"1.0.0","devDependencies":{"@types/node":"^13.9.1","rimraf":"^3.0.2","rollup":"^2.1.0","rollup-plugin-commonjs":"^10.1.0","rollup-plugin-typescript":"^1.0.1","tslib":"^1.11.1","typescript":"^3.8.3"},"main":"lib/index.cjs.js","module":"lib/index.js","types":"lib/index.d.ts","scripts":{"prepare":"npm run build","build":"rimraf lib && rollup -c && tsc"},"files":["lib","README.md"]}

Typescript

Before installing anything, we are going now to configure Typescript for example by specifying an ES2017 target in another new file tsconfig.json .

{"compilerOptions":{"target":"ES2017","module":"esnext","declaration":true,"outDir":"lib","strict":true,"removeComments":true},"include":["src/**/*"],"exclude":["node_modules"]}

Rollup

Finally, last piece of the configuration I promise, we create a new rollup.config.js which, I guess according its name you already understand, is the configuration for Rollup. We are going to instruct it to create a CJS module and are also explicitly adding the Typescript support.

Note that you find also the references I used to create this bundle as commentaries in the following piece of code.

// https://github.com/rollup/rollup-starter-lib// https://buzut.net/configurer-rollup-bundles-esm-cjs/// https://dev.to/proticm/how-to-setup-rollup-config-45mkimporttypescriptfrom'rollup-plugin-typescript';importcommonjsfrom'rollup-plugin-commonjs';importpkgfrom'./package.json';exportdefault{input:'./src/index.ts',plugins:[commonjs(),typescript()],output:{format:'cjs',file:pkg.main}}

Installation Of The Dependencies

Everything is in place, we can now install the dependencies. Before doing so, we create an empty new file index.ts in a new folder src , otherwise the installation will end up in error, as a build is chained with the process.

mkdir src &&touch src/index.ts
npm install

Coding

If everything went according plan, we actually already have bundled an empty library 😁 but our goal is to expose the above function toDateObject . Therefore, we copy the related code in src/index.ts and once done, run the command to build the library.

npm run build

And voilà, that’s it, we have a "dumb" library which can be use in all our projects 🎉.

Summary

I don’t pretend to be any Rollup expert, if you notice anything which can be improved, ping me. I would love to hear it, specially as we have developed such libraries in DeckDeckGo.

Speaking of, as we are open source, if you want to have a look, maybe some functions might suits your needs too, checkout our GitHub repo.

Stay home, stay safe!

David

Cover photo by Benjamin Davies on Unsplash

Internationalization with Gatsby

$
0
0

I challenged my self to share a blog post every day until the end of the COVID-19 quarantine in Switzerland, the 19th April 2020. Thirty days left until hopefully better days.

We are starting a new project with two friends, can’t tell much yet about it at this point, but let’s just say for the moment that it aligns our values. For its purpose we need a website which, obviously, is going to be open source and which I’m going to develop with Gatsby.

Even though it is not my first Gatsby’s site, my personal website is developed with the same stack, this is the first time I have to internationalize one.

I expected such implementation to be fairly straight forward but between light documentations, outdated blog posts or even sample projects, it turned out that I actually had to invest two hours this morning to finally achieve my goal.

That’s why I thought that sharing the outcome in this new tutorial might be a good idea.

SEO Friendly Plugin

Your good old friend need different URLs (routes) to crawl and render your pages for each language. For example, if your website supports English and French, Google is going to be happy if you provide https://domain.com/en/ and https://domain.com/fr/ .

To achieve this with Gatsby, the first thing important to have in mind is that all your pages have to be duplicated. To follow above example, that would mean that the website would contains both an index.en.js page and an index.fr.js one.

To help our website understand such routing, we can use the plugin gatsby-plugin-i18n.

npm install gatsby-plugin-i18n —-save

Once installed, we add its required configuration in gatsby-config.js and also add some meta information about the list of supported languages and the default one.

Note that I specified prefixDefault to true in order to use no root routing, even URLs for the default language, English, will have to be prefixed with /en/ . To be perfectly honest with you, one of the reason behind this is also the fact that I was unable to make it happens otherwise 😅.

siteMetadata:{languages:{langs:['en','fr'],defaultLangKey:'en'}},plugins:[{resolve:'gatsby-plugin-i18n',options:{langKeyDefault:'en',useLangKeyLayout:true,prefixDefault:true}}]

Because we are using a prefix in any case, without any other change, accessing the root of our website will display nothing, that’s why we edit gatsby-browser.js to redirect the root requests to the default home page.

exports.onClientEntry=()=>{if(window.location.pathname==='/'){window.location.pathname=`/en`}}

Internationalization Library

Gatsby and the above plugin are either compatible with react-i18next or react-intl. I use i18next in Tie Tracker, therefore I went with the other solution because I like to learn new things. React Intl relies on the Intl APIs, that’s why we are also installing the polyfill intl-pluralrules.

npm install react-intl @formatjs/intl-pluralrules --save

Hands-on Coding

Enough installation and configuration, let’s code. The major modification which we have to apply occurs in layout.js , which by the way, I moved in a subfolder src/components/layout/ for no other particular reason that the fact that I like clean structure.

What happens here you may ask? Summarized, we are adding two new required properties, location and messages . The first one is use to guess the locale which should be applied and the second one contains the list of translations. As you can notice we import React Intl and we do also import a function getCurrentLangKey from ptz-i18n which is actually a utility of the above plugin.

I’m also using the <FormattedMessage/> component to print out an Hello World to ensure that our implementation works out.

importReactfrom"react"importPropTypesfrom"prop-types"import{useStaticQuery,graphql}from"gatsby"importHeaderfrom"../header"import"./layout.css"import{FormattedMessage,IntlProvider}from"react-intl"import"@formatjs/intl-pluralrules/polyfill"import{getCurrentLangKey}from'ptz-i18n';constLayout=({children,location,messages})=>{constdata=useStaticQuery(graphql`
    query SiteTitleQuery {
      site {
        siteMetadata {
          title
          languages {
            defaultLangKey
            langs
          }
        }
      }
    }
  `)const{langs,defaultLangKey}=data.site.siteMetadata.languages;constlangKey=getCurrentLangKey(langs,defaultLangKey,location.pathname);return(<IntlProviderlocale={langKey}messages={messages}><HeadersiteTitle={data.site.siteMetadata.title}/>
<p><FormattedMessageid="hello"/></p>
</IntlProvider>
)}Layout.propTypes={children:PropTypes.node.isRequired,location:PropTypes.node.isRequired,messages:PropTypes.node.isRequired,}exportdefaultLayout

To “extend” the layout for each language and locale, we create a new file per supported languages. For example, in English, we create layout/en.js in which we import both our custom messages and the specific polyfill.

importReactfrom'react';importLayoutfrom"./layout"importmessagesfrom'../../i18n/en';import"@formatjs/intl-pluralrules/dist/locale-data/en"exportdefault(props)=>(<Layout{...props}messages={messages}/>
);

At this point, our code won’t compile because these languages, these messages are missing. That’s why we also create the file for these, for example i18n/en.js .

module.exports={hello:"Hello world",}

As I briefly staten in my introduction, each page is going to be duplicated. That’s why we create the corresponding index page. In case of the default, English, we rename index.js to index.en.js . Moreover, because the layout now expect a location property, we pass it from every pages too.

Note also that, because I have decided to prefix all route, I also modifed the link routing from /page-2/ to /en/page-2 .

importReactfrom"react"import{Link}from"gatsby"importLayoutfrom"../components/layout/en"importSEOfrom"../components/seo/seo"constIndexPage=(props)=>(<Layoutlocation={props.location}><SEO/><h1>Hipeople</h1>
<Linkto="/en/page-2/">Gotopage2</Link>
</Layout>
)exportdefaultIndexPage

The same modifications we have implemented for index should be propagated to every pages, in this example, I also rename page-2.js in page-2.en.js and apply the same modifications as above.

importReactfrom"react"import{Link}from"gatsby"importLayoutfrom"../components/layout/en"importSEOfrom"../components/seo/seo"constSecondPage=(props)=>(<Layoutlocation={props.location}><SEOtitle="Page two"/><p>Welcometopage2</p>
<Linkto="/en/">Gobacktothehomepage</Link>
</Layout>
)exportdefaultSecondPage

Identically, the usage of the <Layout/> component has to be enhanced with the location object in our 404.js page.

importReactfrom"react"importLayoutfrom"../components/layout/layout"importSEOfrom"../components/seo/seo"constNotFoundPage=(props)=>(<Layoutlocation={props.location}><SEO/><h1>NOTFOUND</h1>
</Layout>
)exportdefaultNotFoundPage

And voilà, that’s it, our Gastby site is internationalized 🎉. Of course you might want to add some other languages, to do so, repeat the above English steps and again, duplicate pages.

Summary

Well it was really unexpected to me to have had to spend so much time to unleash internationalization in a new project, that’s why I hope that this small “how to” might help anyone in the future. And as always, if you notice anything which can be improved, don’t hesitate to ping me with a comment or a tweet.

Stay home, stay safe!

David

Cover photo by Nicola Nuttall on Unsplash

How To Declare And Use Ionic Modals With Stencil

$
0
0

I am sharing one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twenty-nine days left until hopefully better days.

This week on Slack, we discussed the usage of Ionic modals in Stencil apps. I shared the solution we have implemented in all applications of DeckDeckGo, our open source editor for presentations, and it seemed to do the trick.

Even though the related Ionic Modal documentation is self explanatory and really well documented, when it comes to vanilla JavaScript or modern frontend frameworks, there isn’t any information regarding the Stencil usage.

That’s why, all in all, I thought I can share an article about this particular subject.

Controllers

Probably the major difference in terms of usage, if you compare to Ionic for React or vanilla Javascript, using Ionic modals in Stencil requires controllers.

For having tested all flavors (except Vue) of Ionic, this is still my favorite solution because I feel more comfortable with, but I’m not sure it will remains like this in the future as, if I understand correctly, many developers including some of the team itself rather like the other solution, without controllers. Therefore, if you read this article in a late future, check first if these still exists or not 😉.

Create A Modal

The modal itself is nothing else than a component. That’s why, if we want to add one to our application, we first create a new component which I rather like to not set as shadowed. Doing so, it will be possible to inherit the CSS properties and style of the application easily.

import{Component,Element,h}from'@stencil/core';@Component({tag:'app-modal'})exportclassAppRemoteConnect{@Element()el:HTMLElement;render(){return[<ion-contentclass="ion-padding">Hello</ion-content>
]}}

Open A Modal

As staten in the introduction, to use modals with Stencil, we are going to use controllers. The trick is to pass the modal tag name as value of the component variable.

import{Component,h}from'@stencil/core';import{modalController}from'@ionic/core';@Component({tag:'app-home',styleUrl:'app-home.css'})exportclassAppHome{privateasyncopenModal(){constmodal:HTMLIonModalElement=awaitmodalController.create({component:'app-modal'});awaitmodal.present();}render(){return(<ion-content><ion-buttononClick={()=>this.openModal()}color="primary"><ion-label>OpenModal</ion-label>
</ion-button>
</ion-content>
);}}

If everything went according plan, once started and opened, it should looks like the following:

Close A Modal

In this chapter we are going to explore the different ways to close the modal.

Button In Modal Header

To close the modal from itself, we use the document reference to find the closest ion-modal element in order to call the method dismiss which is exposed to achieve such a goal.

import{Component,Element,h}from'@stencil/core';@Component({tag:'app-modal'})exportclassAppRemoteConnect{@Element()el:HTMLElement;asynccloseModal(){await(this.el.closest('ion-modal')asHTMLIonModalElement).dismiss();}render(){return[<ion-header><ion-toolbarcolor="secondary"><ion-buttonsslot="start"><ion-buttononClick={()=>this.closeModal()}><ion-iconname="close"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>,
<ion-contentclass="ion-padding">Hello</ion-content>
]}}

Again, if everything went fine, a close button in the header should now be displayed.

Hardware Back Button Support

It’s been a while since I didn’t tested the hardware back button support to close the modal on Android but what I generally do is adding a navigation listener, in the modal, which call the same close function as the one we defined before. This hack is based on the history, that’s why a state has to be pushed when the modal is loaded.

import{Listen}from'@stencil/core';asynccomponentDidLoad(){history.pushState({modal:true},null);}@Listen('popstate',{target:'window'})asynchandleHardwareBackButton(_e:PopStateEvent){awaitthis.closeModal();}

Backdrop Dismiss

Per default, modals can be dismissed through a click on their backdrops. If you wish to disable this option, you have to specify it when at the controller level.

constmodal:HTMLIonModalElement=awaitmodalController.create({component:'app-modal',backdropDismiss:false});

Passing Parameters

In this chapter we are passing parameters from the page to the modal and in the other direction.

Page To Modal

This is probably my favorite thing across all flavors of Ionic modals I tried. Passing parameters with Stencil is super duper easy.

To read parameters in the modals, we only have to define properties (@Prop()).

import{Component,Element,h,Listen,Prop}from'@stencil/core';@Component({tag:'app-modal'})exportclassAppRemoteConnect{@Element()el:HTMLElement;@Prop()greetings:string;@Listen('popstate',{target:'window'})asynchandleHardwareBackButton(_e:PopStateEvent){awaitthis.closeModal();}asynccloseModal(){await(this.el.closest('ion-modal')asHTMLIonModalElement).dismiss();}render(){return[<ion-header><ion-toolbarcolor="secondary"><ion-buttonsslot="start"><ion-buttononClick={()=>this.closeModal()}><ion-iconname="close"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>,
<ion-contentclass="ion-padding">{this.greetings}</ion-content>
]}}

Which we then just pass through the controllers.

privateasyncopenModal(){constmodal:HTMLIonModalElement=awaitmodalController.create({component:'app-modal',backdropDismiss:false,componentProps:{greetings:'Yolo'}});awaitmodal.present();}

Nothing more, nothing left, really easy. I like such solution.

Modal To Page

You might need to pass results from the modal to the page or calling components. To do so, we use the function dismiss , as when did to close the modal, but we pass an object as parameter.

asynccloseModalWithParams(greetings:string){await(this.el.closest('ion-modal')asHTMLIonModalElement).dismiss(greetings);}<ion-buttononClick={()=>this.closeModalWithParams('Hello')}>SayHello!</ion-button>

In our example, I linked this new action with a new button.

Finally, to handle the result, we listen to the onDidDismiss event of the modal and proceed with the details passed as callback.

import{Component,h,State}from'@stencil/core';import{modalController,OverlayEventDetail}from'@ionic/core';@Component({tag:'app-home',styleUrl:'app-home.css'})exportclassAppHome{@State()privategreetingsResult:string;privateasyncopenModal(){constmodal:HTMLIonModalElement=awaitmodalController.create({component:'app-modal',backdropDismiss:false,componentProps:{greetings:'Yolo'}});modal.onDidDismiss().then(async(detail:OverlayEventDetail)=>{this.greetingsResult=detail.data;});awaitmodal.present();}render(){return(<ion-content><ion-buttononClick={()=>this.openModal()}color="primary"><ion-label>OpenModal</ion-label>
</ion-button>
<ion-label>{this.greetingsResult}</ion-label>
</ion-content>
);}}

I used a state as demonstration purpose in order to render the results.

Note that you can use both primitives types, complex objects, callbacks or events as parameters.

Cherry On The Cake

It works exactly the same with popovers.

See It In Action

If you are interested to see Ionic modals used in Stencil apps in action, give a try to DeckDeckGo for your next slides 😁.

Stay home, stay safe!

David

Cover photo by Tirza van Dijk on Unsplash

Viewing all 124 articles
Browse latest View live