Note This article is a part of a series of tutorials "Creating an Electron application with Svelte 3 and TypeScript".
- Part 1 - Introduction and setup our dev environment
- Part 2 - Define our goals and implement an interactive search bar
- Part 3 - Display search results and call the API
- Part 4 - Coming soon... Stay tuned! 👀
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:
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
Search result
, and Article
, both based on returned value from the APIThat'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).
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.
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.
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:
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.