All Projects β†’ elliotchance β†’ Pepper

elliotchance / Pepper

🌢️ Create reactive frontends without ever writing frontend code.

Programming Languages

go
31211 projects - #10 most used programming language
golang
3204 projects

Projects that are alternatives of or similar to Pepper

Play2 Html5tags
HTML5 form tags module for Play Framework
Stars: ✭ 101 (-7.34%)
Mutual labels:  frontend
Google Auth Library Swift
Auth client library for Swift command-line tools and cloud services. Supports OAuth1, OAuth2, and Google Application Default Credentials.
Stars: ✭ 105 (-3.67%)
Mutual labels:  experimental
Ionic Framework
A powerful cross-platform UI toolkit for building native-quality iOS, Android, and Progressive Web Apps with HTML, CSS, and JavaScript.
Stars: ✭ 45,802 (+41920.18%)
Mutual labels:  frontend
Tachyons Verbose
Functional CSS for humans. Verbose edition.
Stars: ✭ 102 (-6.42%)
Mutual labels:  frontend
Go Tls
A bit safer approach to implement Thread Local Storage (TLS) for Go 1.7+.
Stars: ✭ 104 (-4.59%)
Mutual labels:  experimental
Vue Relay
πŸ–– πŸ”› πŸ—‚ A framework for building GraphQL-driven Vue.js applications.
Stars: ✭ 105 (-3.67%)
Mutual labels:  frontend
Frontend
🍭 Frontend for Home Assistant
Stars: ✭ 1,366 (+1153.21%)
Mutual labels:  frontend
Griddd
A dead simple, customisable, flexbox-based griddd
Stars: ✭ 108 (-0.92%)
Mutual labels:  frontend
Wasmplay
WASM Web "Framework" Playground
Stars: ✭ 105 (-3.67%)
Mutual labels:  frontend
Mithril Isomorphic Example
Example of an isomorphic mithril application
Stars: ✭ 107 (-1.83%)
Mutual labels:  frontend
Frontend knowledge
πŸ“š Important Frontend KnowledgeοΌˆε‰η«―ηŸ₯识汇总)
Stars: ✭ 103 (-5.5%)
Mutual labels:  frontend
Node Frontend
Node.js Docker image with all Puppeteer dependencies installed for frontend Chrome Headless testing and default Nginx config, for multi-stage Docker building
Stars: ✭ 104 (-4.59%)
Mutual labels:  frontend
Suzaku
Suzaku web UI framework for Scala
Stars: ✭ 107 (-1.83%)
Mutual labels:  frontend
Torch Web
🌍 Web interface to tcping servers
Stars: ✭ 103 (-5.5%)
Mutual labels:  frontend
Flowy Vue
Vue Flowy makes creating flowchart or hierarchy chart functionality an easy task. Build automation software, mind mapping tools, organisation charts, or simple programming platforms in minutes by implementing the library into your project.
Stars: ✭ 107 (-1.83%)
Mutual labels:  frontend
Openseedbox
OpenSeedbox - Open Source Multi-User Bittorrent Web UI
Stars: ✭ 101 (-7.34%)
Mutual labels:  frontend
Cyclow
A reactive frontend framework for JavaScript
Stars: ✭ 105 (-3.67%)
Mutual labels:  frontend
2d Unity Experiments
A collection of visual Unity experiments with latest packages (URP, Shader Graph, Cinemachine, etc).
Stars: ✭ 107 (-1.83%)
Mutual labels:  experimental
Biliob Frontend
The frontend part of biliob.
Stars: ✭ 108 (-0.92%)
Mutual labels:  frontend
Webviewhook
Exposed Unity Editor WebView API
Stars: ✭ 107 (-1.83%)
Mutual labels:  experimental

🌢️ pepper

Create reactive frontends without ever writing frontend code.

How Does It Work?

pepper runs a HTTP server that returns a tiny empty HTML page with just a few lines of inline javascript. Immediately after the initial page loads it will connect through a websocket.

All event triggered on the browser will be sent through the websocket where state changes and rerendering occurs. The result is passed back to the websocket to update the UI.

At the moment it returns the whole rendered component. However, this could be optimized in the future to only return the differences.

What Should/Shouldn't I Use It For?

pepper requires a constant connection to the server (for the websocket) so it wouldn't work for anything that must function offline, or used in cases where the internet is flaky.

I imagine some good use cases for pepper would be:

  1. Showing real time data. Streaming metrics, graphs, logs, dashboards, etc.
  2. Apps that rely on a persistent connection. Such as chat clients, timed interactive exams, etc.
  3. Apps that would benefit from persistent state. The entire state can be saved or restored into a serialized format like JSON. Great for forms or surveys with many questions/steps.
  4. Prototyping a frontend app. It's super easy to get up and running and iterate changes without setting up a complex environment, build tools and dependencies.

Handling Offline and Reconnecting

When creating the server you may configure how you want disconnects to be handled. For example:

server := pepper.NewServer()
server.OfflineAction = pepper.OfflineActionDisableForms

By default clients will try to reconnect every second. This can be changed with server.ReconnectInterval.

Important: When reconnecting the server treats the new request as a new client, so all state on the page will be lost.

Testing

Unit Testing

The peppertest package provides tools to make unit testing easier.

RenderToDocument renders then parses the component into a *Document from the github.com/PuerkitoBio/goquery package:

import (
	"github.com/elliotchance/pepper/peppertest"
	"github.com/stretchr/testify/require"
	"testing"
)

func TestPeople_Add(t *testing.T) {
	c := &People{
		Names: []string{"Jack", "Jill"},
	}

	c.Name = "Bob"
	c.Add()

	doc, err := peppertest.RenderToDocument(c)
	require.NoError(t, err)

	rows := doc.Find("tr")
	require.Equal(t, 3, rows.Length())
	require.Contains(t, rows.Eq(0).Text(), "Jack")
	require.Contains(t, rows.Eq(1).Text(), "Jill")
	require.Contains(t, rows.Eq(2).Text(), "Bob")
}

Each of the examples in the examples/ directory include unit tests.

Examples

#1: A Simple Counter

package main

import "github.com/elliotchance/pepper"

type Counter struct {
	Number int
}

func (c *Counter) Render() (string, error) {
	return `
		Counter: {{ .Number }}
		<button @click="AddOne">+</button>
	`, nil
}

func (c *Counter) AddOne() {
	c.Number++
}

func main() {
	panic(pepper.NewServer().Start(func(_ *pepper.Connection) pepper.Component {
		return &Counter{}
	}))
}
  • The Render method returns a html/template syntax, or an error.
  • @click will trigger AddOne to be called when the button is clicked.
  • Any event triggered from the browser will cause the component to rerender automatically.

Try it now:

go get -u github.com/elliotchance/pepper/examples/ex01_counter
ex01_counter

Then open: http://localhost:8080/

#2: Forms

package main

import (
	"github.com/elliotchance/pepper"
	"strconv"
)

type People struct {
	Names []string
	Name  string
}

func (c *People) Render() (string, error) {
	return `
		<table>
			{{ range $i, $name := .Names }}
				<tr><td>
					{{ $name }}
					<button key="{{ $i }}" @click="Delete">Delete</button>
				</td></tr>
			{{ end }}
		</table>
		Add name: <input type="text" @value="Name">
		<button @click="Add">Add</button>
	`, nil
}

func (c *People) Delete(key string) {
	index, _ := strconv.Atoi(key)
	c.Names = append(c.Names[:index], c.Names[index+1:]...)
}

func (c *People) Add() {
	c.Names = append(c.Names, c.Name)
	c.Name = ""
}

func main() {
	panic(pepper.NewServer().Start(func(_ *pepper.Connection) pepper.Component {
		return &People{
			Names: []string{"Jack", "Jill"},
		}
	}))
}
  • Any html/template syntax will work, including loops with {{ range }}.
  • @value will cause the Name property to be bound with the text box in both directions.
  • Since there are multiple "Delete" buttons (one for each person), you should specify a key. The key is passed as the first argument to the Delete function.

Try it now:

go get -u github.com/elliotchance/pepper/examples/ex02_form
ex02_form

Then open: http://localhost:8080/

#3: Nested Components

type Counters struct {
	Counters []*Counter
}

func (c *Counters) Render() (string, error) {
	return `
		<table>
			{{ range .Counters }}
				<tr><td>
					{{ render . }}
				</td></tr>
			{{ end }}
			<tr><td>
				Total: {{ call .Total }}
			</td></tr>
		</table>
	`, nil
}

func (c *Counters) Total() int {
	total := 0
	for _, counter := range c.Counters {
		total += counter.Number
	}

	return total
}

func main() {
	panic(pepper.NewServer().Start(func(_ *pepper.Connection) pepper.Component {
		return &Counters{
			Counters: []*Counter{
				{}, {}, {},
			},
		}
	}))
}
  • This example uses three Counter components (from Example #1) and includes a live total.
  • Components can be nested with the render function. The nested components do not need to be modified in any way.
  • Invoke methods with the call function.

Try it now:

go get -u github.com/elliotchance/pepper/examples/ex03_nested
ex03_nested

Then open: http://localhost:8080/

#4: Ticker

package main

import (
	"github.com/elliotchance/pepper"
	"time"
)

type Clock struct{}

func (c *Clock) Render() (string, error) {
	return `
		The time now is {{ call .Now }}.
	`, nil
}

func (c *Clock) Now() string {
	return time.Now().Format(time.RFC1123)
}

func main() {
	panic(pepper.NewServer().Start(func(conn *pepper.Connection) pepper.Component {
		go func() {
			for range time.NewTicker(time.Second).C {
				conn.Update()
			}
		}()

		return &Clock{}
	}))
}
  • The component is updated once per second so the client sees the active time.

Try it now:

go get -u github.com/elliotchance/pepper/examples/ex04_ticker
ex04_ticker

Then open: http://localhost:8080/

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].