Creating an Electron application with Svelte 3 and TypeScript - Part 2

Note This article is a part of a series of tutorials "Creating an Electron application with Svelte 3 and TypeScript".

Check out the rest if you haven't yet 😉.

Before starting to work on our project, let's define a plan and a set of goals.

We know that we want to interact with Wikipedia's API, so what do we need for this? A first version would be like this. I will keep our goals simple for now, we can always add more things later.

hand drawn mock-up

Abstractly we have 3 components:

  • a simple search for in a menu bar available at the top of the application. The first version will only have an input text and a button to call the API.
  • a listing of search results in a left pan
  • a right pan to view the selected article

That's pretty basic in term of features but should already cover a fair amount of Svelte features, and would be a "useful" application.

From that simple mock-up we can infer the following

  • by default both pans (list of articles and an article detail) are empty as we haven't done any search yet, we need a fall-back message in that case
  • the search input should somehow be bound to a model that we can send to the API once the button is clicked
  • search results should be communicated to our component listing articles
  • once a search result is selected from the list, the full article should be loaded in the component rendering the article view, that may require an extra call to the API
  • we currently only have one "screen", no need for a routing mechanism
  • we have two entities: Search result, and Article, both based on returned value from the API

That's our general plan for a first usable version. Now let's start the implementation 😁. If not already done, open a console in your project directory and start the Electron dev environment by running npm run dev-electron, then wait for both the Electron application and the dev server to start-up (you may need to reload the Electron application if it opened before the server was ready, just do a Ctrl+R or Command+R depending on your OS).

Implementing a simple search bar

This one will be quick, Svelte makes it fairly simple to create input fields bound to a model that we can then use for API calls.

But first thing thing, let's create a directory src/components in which we will have all our Svelte components.

Static search bar

Then we follow up with the creation of the file (and parent directory) src/components/TopMenu/SearchBar.svelte.

<!-- src/components/TopMenu/SearchBar.svelte -->

<header id="topmenu">
	<div class="actions">
		<input placeholder="Type your search term" />
		<button>Let's find out 🕵️‍♀️</button>
	</div>
</header>

<style>
	.actions {
		text-align: center;
		padding-top: 4px;
		padding-bottom: 4px;
		background-color: rgb(239, 239, 239);
		border-bottom: 1px solid lightgrey;
		box-shadow: 0 0 4px #00000038;
	}

	.actions input,
	.actions button {
		margin: 0;
	}
</style>

That should be enough for the static version of our search bar. To see the result in the Electron application, we need to import it in our entry point src/App.svelte:

<!-- src/App.svelte -->

<script lang="ts">
	import SearchBar from "./components/TopMenu/SearchBar.svelte"
</script>

<SearchBar />

On Windows 10 the result looks like this, that may be look slightly different on your system, if that's the case feel free to edit the styling to make it look good.

Interactive search

We have our base markup, our next step is to make it dynamic. By this I mean that we want to have the following interactions:

  1. enter our search terms in the input text. That requires the input value to be somehow bound to a JavaScript variable.
  2. trigger a search by clicking on the button. We need to handle the onClick event of the button, by fetching search results, then handle the response.

At the top of src/components/TopMenu/SearchBar.svelte, add a <script> block, and define a variable that will be used for the input value. The binding is done by using the attribute bind:value={...} on the <input> tag:

<!-- src/components/TopMenu/SearchBar.svelte -->

<script lang="ts">
	let searchTerms: string // 1. 👈 define a variable that will be used for the binding

    // 2. create a simple event handler to see our variable value
	function handleClick() {
		alert(`You searched for: ${searchTerms}`)
	}
</script>

<header id="topmenu">
	<div class="actions">
        // 3. register our variable 👇
		<input bind:value={searchTerms} placeholder="Type your search term" />

        // 4. then register our event handler to be triggered on `click` events
        //                             👇
		<button on:click={handleClick}>Let's find out 🕵️‍♀️</button>
	</div>
</header>
...

Now when you click on the button you should see a small pop up window with the input value.

We now have an event handler for our button, we could make it call the API directly with just a fetch(), but instead let start with a mock version, that way we don't depend on Wikipedia's and our network availability while we are still early in the development of our application.

Hopefully that's very simple to do in JavaScript, a standard use of fetch() is like this:

// example of fetch() usage to get an idea of what we have to mock

const response = await fetch(`https://api.example.org/search?q=hello`)
if (!response.ok) {
    throw new Error(`request failed: status code ${response.status}`)
}
const values: ResponsePayloadType = await response.json()
doSomething(values)

We can simulate something similar by returning a Promise object that will be fulfilled in the future with our dummy data.

<!-- src/components/TopMenu/SearchBar.svelte -->

<script lang="ts">
	let searchTerms: string

    // 1. 👇 we define a type for our results. Given search terms, the API endpoint that we will use returns a list article titles and their URL
	interface SearchResult {
		title: string
        url: string
	}

    // 2. 👇 we define a variable for our search results, it should be a Promise because we want to do something while it is being fetch
	let searchResults: Promise<SearchResult[]>

    // 3. 👇 our mock search function, it should be marked as async
	async function search() {
		const duration = 2000 // equal 2s
		await new Promise(resolve => setTimeout(resolve, duration))
        // 4. ☝ we fulfil the promise in 2 seconds, thus won't continue until that's done. That's simulating network activity and allows us to see the pending state

		const dummyResults: SearchResult[] = [
			{
				title: "Samuel El-Borai",
				url: "sam.elborai.me",
			},
			{
				title: "Another Sam",
				url: "example.org/other-sam",
			},
        ]
		return dummyResults // 5. 👈 return our dummy values
	}

	function handleClick() {
        // 6. 👇 trigger the search if we have something in our input field
		if (searchTerms) searchResults = search()
	}
</script>

<header id="topmenu">
	<div class="actions">
        // 7. 👇 #await is part of Svelte's templating, the content will only be displayed when the referenced Promise is pending. In our case we disable buttons.
		{#await searchResults}
			<input disabled type="text" bind:value={searchTerms} />
			<button disabled>Let's find out 🕵️‍♀️</button>
			<p>Awaiting a response ...</p> // 👈 this text will be displayed only while the Promise is pending

        // 8. 👇 :then defines what to display once the Promise is completed
		{:then}
			<input placeholder="Type your search term" bind:value={searchTerms} />
			<button on:click={handleClick}>Let's find out 🕵️‍♀️</button>

       		// 9. 👇 check if we have results, then display them
			{#if searchResults}
				<pre>{JSON.stringify(searchResults)}</pre>
			{/if}
		{/await}
	</div>
</header>

// 10. 👇 optional: add some styling for the result printout, just to make it a bit nicer to our eyes
<style>
  ...

	pre {
		text-align: left;
		margin: auto;
		margin-top: 20px;
		padding: 30px;
		background-color: rgb(36, 36, 44);
		color: white;
		overflow: auto;
		width: 80%;
		border-radius: 4px;
	}
</style>

Ok, there is a lot of things going on here. The scripting part is standard TypeScript and shouldn't be too difficult to follow if you're familiar with how Promise works in a JavaScript environment. Otherwise I would recommend to read MDN documentation on the topic, they have the best doc pages around and have lot of good examples.

The fancy thing here is the use of #await, and :then to handle the transition between the request state machine, that's a quite nice feature from Svelte. Note that in a real situation we would have to handle :catch in case our Promise fails, but our mock is always successful. And #if is used to only display the result if we actually have something. By default the array of results is empty which evaluate to false, so nothing is displayed.

And that's it for now! I'm trying to avoid to have too long articles, we will continue the implementation next time.