Skip to content

Instantly share code, notes, and snippets.

@will-wow
Last active October 15, 2019 00:34
Show Gist options
  • Save will-wow/0e54b3bcbed02114f9e1104f3bb2de92 to your computer and use it in GitHub Desktop.
Save will-wow/0e54b3bcbed02114f9e1104f3bb2de92 to your computer and use it in GitHub Desktop.
Svelte Contacts
import React, { useState } from "react"
const CountDown = () => {
const [count, setCount] = useState(0)
const remaining = 10 - count
const increment = () => {
if (remaining > 0) {
setCount(count + 1)
}
}
return (
<div>
<div>Count: {count}</div>
<div>Remaining: {remaining}</div>
<button onClick={increment}>Click</button>
</div>
)
}
<script>
let count = 0
$: remaining = 10 - count
const increment = () => {
if (remaining > 0) {
count++
}
}
</script>
<div>
<div>Count: {count}</div>
<div>Remaining: {remaining}</div>
<button on:click={increment}>Click</button>
</div>
git clone https://github.com/will-wow/contacts.git --branch rails
rails new contacts --database=postgresql --webpack
rake db:create
rails g scaffold Contact name:string email:string twitter:string phone:string
rake db:migrate
rake webpacker:install:svelte
gem 'webpacker', git: "https://github.com/rails/webpacker.git"
<!-- app/views/contacts/index.html.erb -->
<% @contacts.each do |contact| %>
<tr>
<td><%= contact.name %></td>
<td><%= contact.email %></td>
<td><%= contact.twitter %></td>
<td><%= contact.phone %></td>
<td><%= link_to 'Show', contact, class: "btn btn-info" %></td>
<td>
<%= link_to 'Edit', edit_contact_path(contact), class: "btn btn-primary" %>
</td>
<td>
<%= link_to 'Destroy', contact, method: :delete, class: "btn btn-danger",
data: { confirm: 'Are you sure?' } %>
</td>
</tr>
<% end %>
<!-- app/javascript/src/components/ContactRow.svelte -->
<script>
export let contact
export let onSave
export let onDelete
const handleDelete = () => {
if (confirm("Are you sure?")) {
onDelete()
}
}
</script>
<tr>
<td>
<input
class="form-control"
type="text"
name="name"
bind:value={contact.name}
/>
</td>
<td>
<input class="form-control" name="email" bind:value={contact.email} />
</td>
<td>
<input class="form-control" name="twitter" bind:value={contact.twitter} />
</td>
<td>
<input class="form-control" name="phone" bind:value={contact.phone} />
</td>
<td>
<a class="btn btn-info btn-xs" href="/contacts/{contact.id}>Show</a>
</td>
<td>
<button class="btn btn-primary btn-xs" on:click={handleSave}>
Save
</button>
</td>
<td>
<button class="btn btn-danger btn-xs" on:click={handleDelete}>
Destroy
</button>
</td>
</tr>
<!-- app/javascript/src/components/ContactList.svelte -->
<script>
import Api from "./api"
import ContactRow from "./ContactRow.svelte"
export const saveContact = contact =>
Api.put(`/contacts/${contact.id}.json`, { contact })
const deleteContact = (contact) => {
Api.delete(`/contacts/${contact.id}.json`)
contacts = contacts.filter(({ id }) => id !== contact.id)
}
export let contacts
</script>
{#each contacts as contact}
<!-- bind to update the store from the ContactRow -->
<ContactRow
bind:contact
onSave={() => saveContact(contact)}
onDelete={() => deleteContact(contact)} />
{/each}
gem 'webpacker-svelte'
npm i webpacker-svelte --save-dev
# or
yarn add webpacker-svelte --dev
def svelte_component(component_name, props = {}, options = {})
tag = options.delete(:tag) || :div
data = { data: { "svelte-component" => component_name, "svelte-props" => props.to_json } }
content_tag(tag, nil, options.deep_merge(data))
end
// app/javascript/packs/application.js
import WebpackerSvelte from "webpacker-svelte"
import ContactList from "../src/ContactList.svelte"
WebpackerSvelte.setup({ ContactList })
<!-- app/views/contacts/index.html.erb -->
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Twitter</th>
<th>Phone</th>
<th colspan="3"></th>
</tr>
</thead>
<%= svelte_component("ContactList", {contacts: @contacts}, tag: "tbody") %>
</table>
// app/javascript/src/contact-store.js
import { writable, derived } from "svelte/store"
import Api from "./api"
// Declare the writable store.
export const contactStore = writable([])
export const saveContact = contact =>
Api.put(`/contacts/${contact.id}.json`, { contact })
export const deleteContact = contact => {
// Filter the deleted contact from the store.
contactStore.update(contacts =>
contacts.filter(({ id }) => id !== contact.id)
)
return Api.delete(`/contacts/${contact.id}.json`)
}
<!-- app/javascript/src/components/ContactList.svelte -->
<script>
import { onMount } from "svelte"
import { contactStore, saveContact, deleteContact } from "../contact-store"
import ContactRow from "./ContactRow.svelte"
export let contacts
// Initialize the store from props when the component mounts.
onMount(() => {
contactStore.set(contacts)
})
</script>
<!-- Loop over the store instead of the contacts prop -->
{#each $contactStore as contact}
<ContactRow
bind:contact
onSave={() => saveContact(contact)}
onDelete={() => deleteContact(contact)} />
{/each}
<!-- app/views/contacts/index.html.erb -->
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Twitter</th>
<th>Phone</th>
<th colspan="3"></th>
</tr>
</thead>
<!-- Old component -->
<%= svelte_component("ContactList", {contacts: @contacts}, tag: "tbody") %>
</table>
<!-- New component! -->
<%= svelte_component("NewContactButton") %>
<!-- app/javascript/src/components/NewContactButton.svelte -->
<script>
import { createContact } from "../contact-store"
</script>
<button class="btn btn-primary" on:click={createContact}>New Contact</button>
// app/javascript/src/contact-store.js
import { writable, derived } from "svelte/store"
import Api from './api'
export const contactStore = writable([])
...
export const createContact = async contact => {
const { data: createdContact } = await Api.post("/contacts.json", {
contact
})
// Add the new contact to the store once it's created on the server.
contactStore.update(contacts => [...contacts, createdContact])
}
// app/javascript/src/application.js
import NewContactButton from "../src/NewContactButton"
// Register the new component as something webpacker-svelte can render.
WebpackerSvelte.setup({ ContactList, NewContactButton })
<!-- app/javascript/src/components/ContactCount.svelte -->
<script>
import { contactStore } from "../contact-store"
export let count = null
// Reactive declaration that calculates the count from props or the store.
$: contactCount = count === null ? $contactStore.length : count
</script>
<span class="badge badge-pill badge-info">Total: {contactCount}</span>
// app/javascript/src/application.js
import ContactCount from "../src/ContactCount"
WebpackerSvelte.setup({ ContactList, NewContactButton, ContactCount })
<!-- app/views/contacts/index.html.erb -->
<%= svelte_component("ContactCount") %>
<!-- app/views/contacts/edit.html.erb -->
<%= svelte_component("ContactCount", count: @contact_count) %>
// contact-store.js
export const contactStore = writable([])
export const contactCountStore = derived(
contactStore,
contacts => contacts.length
)
<!-- ContactCount.svelte -->
<script>
import { contactCountStore } from "../contact-store"
export let count = null
$: contactCount = count === null ? $contactCountStore : count
</script>
<span class="badge badge-pill badge-info">Total: {contactCount}</span>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment