Live views and components for golang

live

Real-time user experiences with server-rendered HTML in Go. Inspired by and borrowing from Phoenix LiveViews.

Live is intended as a replacement for React, Vue, Angular etc. You can write an interactive web app just using Go and its templates.

Compatible with net/http, so will play nicely with middleware and other frameworks.

I am starting to use this in production where I work. As such, I will be fixing any issues I find and changing the API surface to make it as easy to use as possible.

Getting Started

Install

go get github.com/jfyne/live

See the examples for usage.

First handler

Here is an example demonstrating how we would make a simple thermostat. Live is compatible with net/http.

package live

import (
	"bytes"
	"context"
	"html/template"
	"io"
	"log"
	"net/http"
)

// Model of our thermostat.
type ThermoModel struct {
	C float32
}

// Helper function to get the model from the socket data.
func NewThermoModel(s *Socket) *ThermoModel {
	m, ok := s.Assigns().(*ThermoModel)
	// If we haven't already initialised set up.
	if !ok {
		m = &ThermoModel{
			C: 19.5,
		}
	}
	return m
}

// thermoMount initialises the thermostat state. Data returned in the mount function will
// automatically be assigned to the socket.
func thermoMount(ctx context.Context, r *http.Request, s *Socket) (interface{}, error) {
	return NewThermoModel(s), nil
}

// tempUp on the temp up event, increase the thermostat temperature by .1 C. An EventHandler function
// is called with the original request context of the socket, the socket itself containing the current
// state and and params that came from the event. Params contain query string parameters and any
// `live-value-` bindings.
func tempUp(ctx context.Context, s *Socket, p Params) (interface{}, error) {
	model := NewThermoModel(s)
	model.C += 0.1
	return model, nil
}

// tempDown on the temp down event, decrease the thermostat temperature by .1 C.
func tempDown(ctx context.Context, s *Socket, p Params) (interface{}, error) {
	model := NewThermoModel(s)
	model.C -= 0.1
	return model, nil
}

// Example shows a simple temperature control using the
// "live-click" event.
func Example() {

	// Setup the handler.
	h, err := NewHandler(NewCookieStore("session-name", []byte("weak-secret")))
	if err != nil {
		log.Fatal("could not create handler")
	}

	// Mount function is called on initial HTTP load and then initial web
	// socket connection. This should be used to create the initial state,
	// the socket Connected func will be true if the mount call is on a web
	// socket connection.
	h.Mount = thermoMount

	// Provide a render function. Here we are doing it manually, but there is a
	// provided WithTemplateRenderer which can be used to work with `html/template`
	h.Render = func(ctx context.Context, data interface{}) (io.Reader, error) {
		tmpl, err := template.New("thermo").Parse(`
            <div>{{.C}}</div>
            <button live-click="temp-up">+</button>
            <button live-click="temp-down">-</button>
            <!-- Include to make live work -->
            <script src="/live.js"></script>
        `)
		if err != nil {
			return nil, err
		}
		var buf bytes.Buffer
		if err := tmpl.Execute(&buf, data); err != nil {
			return nil, err
		}
		return &buf, nil
	}

	// This handles the `live-click="temp-up"` button. First we load the model from
	// the socket, increment the temperature, and then return the new state of the
	// model. Live will now calculate the diff between the last time it rendered and now,
	// produce a set of diffs and push them to the browser to update.
	h.HandleEvent("temp-up", tempUp)

	// This handles the `live-click="temp-down"` button.
	h.HandleEvent("temp-down", tempDown)

	http.Handle("/thermostat", h)

	// This serves the JS needed to make live work.
	http.Handle("/live.js", Javascript{})

	http.ListenAndServe(":8080", nil)
}

Notice the script tag. Live's javascript is embedded within the library for ease of use, and is required to be included for it to work. You can also use the companion npm package to add to any existing web app build pipeline.

Live components

Live can also render components. These are an easy way to encapsulate event logic and make it repeatable across a page. The components examples show how to create components. Those are then used in the world clocks example.

package page

import (
	"context"
	"io"
	"log"
	"net/http"

	"github.com/jfyne/live"
)

// NewGreeter creates a new greeter component.
func NewGreeter(ID string, h *live.Handler, s *live.Socket, name string) (*Component, error) {
	return NewComponent(
		ID,
		h,
		s,
		WithMount(func(ctx context.Context, c *Component, r *http.Request) error {
			c.State = name
			return nil
		}),
		WithRender(func(w io.Writer, c *Component) error {
			// Render the greeter, here we are including the script just to make this toy example work.
			return HTML(`
                <div class="greeter">Hello {{.}}</div>
                <script src="/live.js"></script>
            `, c).Render(w)
		}),
	)
}

func Example() {
	h, err := live.NewHandler(
		live.NewCookieStore("session-name", []byte("weak-secret")),
		WithComponentMount(func(ctx context.Context, h *live.Handler, r *http.Request, s *live.Socket) (*Component, error) {
			return NewGreeter("hello-id", h, s, "World!")
		}),
		WithComponentRenderer(),
	)
	if err != nil {
		log.Fatal(err)
	}

	http.Handle("/", h)
	http.Handle("/live.js", live.Javascript{})
	http.ListenAndServe(":8080", nil)
}

Navigation

Live provides functionality to use the browsers pushState API to update its query parameters. This can be done from both the client side and the server side.

Client side

The live-patch handler should be placed on an a tag element as it reads the href attribute in order to apply the URL patch.

<a live-patch href="?page=2">Next page</a>

Clicking on this tag will result in the browser URL being updated, and then an event sent to the backend which will trigger the handler's HandleParams callback. With the query string being available in the params map of the handler.

h.HandleParams(func(s *live.Socket, p live.Params) (interface{}, error) {
    ...
    page := p.Int("page")
    ...
})

Server side

Using the Socket's PatchURL func the serverside can make the client update the browsers URL, which will then trigger the HandleParams func.

Redirect

The server can also trigger a redirect if the Socket's Redirect func is called. This will simulate an HTTP redirect using window.location.replace.

Features

Click Events

  • live-capture-click
  • live-click
  • live-value-*

The live-click binding is used to send click events to the server.

<div live-click="inc" live-value-myvar1="val1" live-value-myvar2="val2"></div>

See the buttons example for usage.

Focus / Blur Events

  • live-window-focus
  • live-window-blur
  • live-focus
  • live-blur

Focus and blur events may be bound to DOM elements that emit such events, using the live-blur, and live-focus bindings, for example:

<input name="email" live-focus="myfocus" live-blur="myblur"/>

Key Events

  • live-window-keyup
  • live-window-keydown
  • live-keyup
  • live-keydown
  • live-key

The onkeydown, and onkeyup events are supported via the live-keydown, and live-keyup bindings. Each binding supports a live-key attribute, which triggers the event for the specific key press. If no live-key is provided, the event is triggered for any key press. When pushed, the value sent to the server will contain the "key" that was pressed.

See the buttons example for usage.

Form Events

  • live-auto-recover
  • live-trigger-action
  • live-disable-with
  • live-feedback-for
  • live-submit
  • live-change

To handle form changes and submissions, use the live-change and live-submit events. In general, it is preferred to handle input changes at the form level, where all form fields are passed to the handler's event handler given any single input change. For example, to handle real-time form validation and saving, your template would use both live-change and live-submit bindings.

See the form example for usage.

Rate Limiting

  • live-throttle
  • live-debounce

Dom Patching

  • live-update

A container can be marked with live-update, allowing the DOM patch operations to avoid updating or removing portions of the view, or to append or prepend the updates rather than replacing the existing contents. This is useful for client-side interop with existing libraries that do their own DOM operations. The following live-update values are supported:

  • replace - replaces the element with the contents
  • ignore - ignores updates to the DOM regardless of new content changes
  • append - append the new DOM contents instead of replacing
  • prepend - prepend the new DOM contents instead of replacing

When using live-update If using "append" or "prepend", a DOM ID must be set for each child.

See the chat example for usage.

JS Interop

  • live-hook

Hooks

Hooks take the following form. They allow additional javscript to be during a page lifecycle.

/**
 * Hooks supplied for interop.
 */
export interface Hooks {
    [id: string]: Hook;
}

/**
 * A hook for running external JS.
 */
export interface Hook {
    /**
     * The element has been added to the DOM and its server
     * LiveHandler has finished mounting
     */
    mounted?: () => void;

    /**
     * The element is about to be updated in the DOM.
     * Note: any call here must be synchronous as the operation
     * cannot be deferred or cancelled.
     */
    beforeUpdate?: () => void;

    /**
     * The element has been updated in the DOM by the server
     */
    updated?: () => void;

    /**
     * The element is about to be removed from the DOM.
     * Note: any call here must be synchronous as the operation
     * cannot be deferred or cancelled.
     */
    beforeDestroy?: () => void;

    /**
     * The element has been removed from the page, either by
     * a parent update, or by the parent being removed entirely
     */
    destroyed?: () => void;

    /**
     * The element's parent LiveHandler has disconnected from
     * the server
     */
    disconnected?: () => void;

    /**
     * The element's parent LiveHandler has reconnected to the
     * server
     */
    reconnected?: () => void;
}

/**
 * The DOM management interace. This allows external JS libraries to
 * interop with Live.
 */
export interface DOM {
    /**
     * The fromEl and toEl DOM nodes are passed to the function
     * just before the DOM patch operations occurs in Live. This
     * allows external libraries to (re)initialize DOM elements
     * or copy attributes as necessary as Live performs its own
     * patch operations. The update operation cannot be cancelled
     * or deferred, and the return value is ignored.
     */
    onBeforeElUpdated?: (fromEl: Element, toEl: Element) => void;
}

In scope when these functions are called:

  • el - attribute referencing the bound DOM node,
  • pushEvent(event: { t: string, d: any }) - method to push an event from the client to the Live server
  • handleEvent(event: string, cb: ((payload: any) => void)) - method to handle an event pushed from the server.

See the chat example for usage.

Integrating with your app

There are two ways to inegrate javascript into your applications. The first is the simplest, using the built in javascript handler. This includes client side code to initialise the live handler and automatically looks for hooks at window.Hooks. All of the examples use this method.

See the chat example for usage.

The second method is suited for more complex apps, there is a companion package published on npm. The version should be kept in sync with the current go version.

> npm i @jfyne/live

This can then be used to initialise the live handler on a page

import { Live } from '@jfyne/live';

const hooks = {};

const live = new Live(hooks);
live.init();

This allows more control over how hooks are passed to live, and when it should be initialised. It is expected that you would then build your compiled javsacript and serve it. See the alpine example.

Errors and exceptions

There are two types of errors in a live handler, and how these are handled are separate.

Unexpected errors

Errors that occur during the initial mount, initial render and web socket upgrade process are handled by the handler ErrorHandler func.

Errors that occur while handling incoming web socket messages will trigger a response back with the error.

Expected errors

In general errors which you expect to happen such as form validations etc. should be handled by just updating the data on the socket and re-rendering.

If you return an error in the event handler live will send an "err" event to the socket. You can handle this with a hook. An example of this can be seen in the error example.

Loading state and errors

By default, the following classes are applied to the handlers body:

  • live-connected - applied when the view has connected to the server
  • live-disconnected - applied when the view is not connected to the server
  • live-error - applied when an error occurs on the server. Note, this class will be applied in conjunction with live-disconnected if connection to the server is lost.

All live- event bindings apply their own css classes when pushed. For example the following markup:

<button live-click="clicked" live-window-keydown="key">...</button>

On click, would receive the live-click-loading class, and on keydown would receive the live-keydown-loading class. The css loading classes are maintained until an acknowledgement is received on the client for the pushed event.

The following events receive css loading classes:

  • live-click - live-click-loading
  • live-change - live-change-loading
  • live-submit - live-submit-loading
  • live-focus - live-focus-loading
  • live-blur - live-blur-loading
  • live-window-keydown - live-keydown-loading
  • live-window-keyup - live-keyup-loading

Broadcasting to different nodes

In production it is often required to have multiple instances of the same application running, in order to handle this live has a PubSub element. This allows nodes to publish onto topics and receive those messages as if they were all running as the same instance. See the cluster example for usage.

Comments
  • examples/form: losing current input value during validation

    examples/form: losing current input value during validation

    I'm exploring the examples in the project and noticed that in the examples/form, when typing less than 10 characters we get the proper validation error message. But once we cross the length of 10 character and the error div clears, so does the current value in the input text field. I've been trying to figure out the way to preserve this value in Live and can't tell if its something straightforward on the client page side, or something specific to the Live dom updates not preserving the text input value. I've also tried to add a form struct field to capture the LastMessage and propagate it back to the input value, but that has the unwanted effect of the cursor position staying at the 0 index as you type, and get backwards values. Is it possible to update the examples/form to show how to properly preserve the input value when the validation logic swaps away the error div?

    I'm testing this in latest Chrome on Ubuntu and Go 1.15.5

  • examples/clock: works with Firefox but not Chrome

    examples/clock: works with Firefox but not Chrome

    Environment

    go version go1.16.5 darwin/amd64 macOS 10.14.6 chrome 91.0.4472.164 firefox 90.0.1

    To replicate

    git clone [email protected]:jfyne/live-examples.git
    cd live-examples/clock
    go build .
    cd ..
    clock/clock
    

    Point browser at http://localhost:8080/clock

    What I expected

    Incrementing clock

    What I saw

    With Firefox, everything is OK.

    With Chrome, current time is displayed, but does not increment.

    tcpdump shows that websocket messages are being sent every second:

    $ sudo tcpdump -i lo0 -nn -s0 -A tcp port 8080
    ...
    08:47:28.831040 IP6 ::1.8080 > ::1.50406: Flags [P.], seq 1:269, ack 810, win 6359, options [nop,nop,TS val 1005537220 ecr 1005537219], length 268: HTTP: HTTP/1.1 101 Switching Protocols
    `....,[email protected].#...v.....4.....
    ;.G.;.G.HTTP/1.1 101 Switching Protocols
    Connection: Upgrade
    Sec-Websocket-Accept: J1GHBBuREgeNFGWhZQUIYbi8YQ4=
    Sec-Websocket-Extensions: permessage-deflate; client_no_context_takeover; server_no_context_takeover
    Upgrade: websocket
    Date: Thu, 22 Jul 2021 07:47:28 GMT
    
    
    08:47:28.831077 IP6 ::1.50406 > ::1.8080: Flags [.], ack 269, win 6367, options [nop,nop,TS val 1005537220 ecr 1005537220], length 0
    `..f. [email protected] /.....(.....
    ;.G.;.G.
    08:47:28.831177 IP6 ::1.8080 > ::1.50406: Flags [P.], seq 269:288, ack 810, win 6359, options [nop,nop,TS val 1005537220 ecr 1005537220], length 19: HTTP
    `[email protected] /...v.....;.....
    ;.G.;.G...{"t":"connect"}..
    08:47:28.831197 IP6 ::1.50406 > ::1.8080: Flags [.], ack 288, win 6367, options [nop,nop,TS val 1005537220 ecr 1005537220], length 0
    `..f. [email protected] B.....(.....
    ;.G.;.G.
    08:47:29.834266 IP6 ::1.8080 > ::1.50406: Flags [P.], seq 288:471, ack 810, win 6359, options [nop,nop,TS val 1005538221 ecr 1005537220], length 183: HTTP
    `[email protected] B...v...........
    ;.K.;.G..~..{"t":"patch","d":[{"Path":[0,0],"Action":2,"HTML":"\u003ctitle\u003e 08:47:29 \u003c/title\u003e"},{"Path":[1,0],"Action":2,"HTML":"\u003ctime\u003e08:47:29\u003c/time\u003e"}]}..
    08:47:29.834302 IP6 ::1.50406 > ::1.8080: Flags [.], ack 471, win 6364, options [nop,nop,TS val 1005538221 ecr 1005538221], length 0
    `..f. [email protected] ......(.....
    ;.K.;.K.
    08:47:30.839156 IP6 ::1.8080 > ::1.50406: Flags [P.], seq 471:654, ack 810, win 6359, options [nop,nop,TS val 1005539224 ecr 1005538221], length 183: HTTP
    `[email protected] ....v...........
    ;.O.;.K..~..{"t":"patch","d":[{"Path":[0,0],"Action":2,"HTML":"\u003ctitle\u003e 08:47:30 \u003c/title\u003e"},{"Path":[1,0],"Action":2,"HTML":"\u003ctime\u003e08:47:30\u003c/time\u003e"}]}..
    08:47:30.839203 IP6 ::1.50406 > ::1.8080: Flags [.], ack 654, win 6361, options [nop,nop,TS val 1005539224 ecr 1005539224], length 0
    `..f. [email protected]!......(.....
    ;.O.;.O.
    08:47:31.842699 IP6 ::1.8080 > ::1.50406: Flags [P.], seq 654:837, ack 810, win 6359, options [nop,nop,TS val 1005540226 ecr 1005539224], length 183: HTTP
    `[email protected]!....v...........
    ;.S.;.O..~..{"t":"patch","d":[{"Path":[0,0],"Action":2,"HTML":"\u003ctitle\u003e 08:47:31 \u003c/title\u003e"},{"Path":[1,0],"Action":2,"HTML":"\u003ctime\u003e08:47:31\u003c/time\u003e"}]}..
    08:47:31.842764 IP6 ::1.50406 > ::1.8080: Flags [.], ack 837, win 6358, options [nop,nop,TS val 1005540226 ecr 1005540226], length 0
    `..f. [email protected]"g.....(.....
    ;.S.;.S.
    

    I don't see any errors in browser console, although network shows stuck at "101 Switching Proto..."

    image

    Additional info

    With both Chrome and Firefox, closing the browser tab logs the following at the server side:

    2021/07/22 08:47:01 ws closed with status (-1): writing to socket error: failed writeTimeout: json: error calling MarshalJSON for type json.RawMessage: invalid character 'i' in literal false (expecting 'l')
    

    I suspect an error message starting with the word "fail" has been put in RawMessage field without being quoted, e.g. see https://play.golang.org/p/w9Vuc8Ko_fN

    EDIT: now raised separately as #26

  • Render on client

    Render on client

    I like your simple approach . It’s very clean .

    Also your using a web socket lib that works on everything including when I compile to wasm

    so I don’t know if you are into the idea but you can use gioui to render on the client

    then your sending the data change event to the client only . A CUD of Created, Updated or Deleted .

    There are plenty examples in gioui

    if your curious I can point you to some

    It’s also crazy fast and will work on web, desktop and mobile

    mit can also work inside a webview .

  • Button example has patch error

    Button example has patch error

    I'll do that today! It's early here so gonna get a coffee first. Also as an aside, thanks for this repo! I started using Phoenix live view and instantly started thinking about how great it would be to do that in golang, and here it is!

    Originally posted by @AnaelBerrouet in https://github.com/jfyne/live/issues/25#issuecomment-950332198

  • Broadcasting events on global level?

    Broadcasting events on global level?

    Hi, is there a way how to use broadcasting events on app global level? I mean, in app we are using live.Socket.Broadcast for sending information to all connected users. This is related to live.Socket from particular user, so its user triggered event actually. What if we would like to broadcast general events like "current time" to all connected users. When Live app starts, we should start broadcasting events. Is there way how to do it right now? Hopefully it's clear ...

    David

  • PatchURL for full path - not only query params

    PatchURL for full path - not only query params

    Nice library so far! Good work, I really enjoy using this on my new application. Thanks for the work.

    Is there a reason, why "PatchURL" and its counterpart "HandleParams" only support query parameters and not the full path?

    In my use-case I would like to handle the overall navigation with live, so the user clicks an a-Tag (e.g. user is on /list and clicks /detail/lorem-ipsum), which triggers popstate and the server pushes the changed content without a full page reload and the user stays connected with the websocket.

    It makes no sense to handle different complex pages with this single handler, but for mostly similar pages with the same events allover this could save some bandwith for the user.

  • Invalid renderings

    Invalid renderings

    I've seen an invalid render on firefox and had a minimal way to reproduce the issue with a minimal example. It seems like an issue with the DOM patching?

    This is using v0.12.1

    image

    image

    Click the Inside button first.

    package main
    
    import (
    	"bytes"
    	"context"
    	"io"
    	"log"
    	"net/http"
    	"sync"
    	"text/template"
    
    	"github.com/jfyne/live"
    )
    
    type State struct {
    	Inside  int
    	Outside int
    }
    
    func main() {
    	var mu sync.Mutex
    	var inside, outside int
    	h, err := live.NewHandler(live.NewCookieStore("session-name", []byte("weak-secret")))
    	if err != nil {
    		log.Fatal("could not create handler")
    	}
    	h.Render = func(ctx context.Context, data interface{}) (io.Reader, error) {
    		tmpl, err := template.New("thermo").Parse(`
    
    Hello<br/>
    Outside is: {{.Outside}}
    <button live-click="inside">Inside</button>
    <button live-click="outside">Outside</button>
    <button live-click="nothing">Nothing</button>
    <details>
    <summary>Advanced</summary>
    Inside is: {{.Inside}}
    <button live-click="inside">Inside</button>
    <button live-click="outside">Outside</button>
    <button live-click="nothing">Nothing</button>
    </details>
    <script src="/live.js"></script>
    	`)
    		if err != nil {
    			return nil, err
    		}
    		var buf bytes.Buffer
    		if err := tmpl.Execute(&buf, data); err != nil {
    			return nil, err
    		}
    		return &buf, nil
    	}
    	h.HandleEvent("inside", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
    		mu.Lock()
    		defer mu.Unlock()
    		inside += 1
    
    		return &State{inside, outside}, nil
    	})
    	h.HandleEvent("outside", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
    		mu.Lock()
    		defer mu.Unlock()
    		outside += 1
    
    		return &State{inside, outside}, nil
    	})
    	h.HandleEvent("nothing", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
    		mu.Lock()
    		defer mu.Unlock()
    		return &State{inside, outside}, nil
    	})
    
    	http.Handle("/", h)
    	http.Handle("/live.js", live.Javascript{})
    	http.ListenAndServe(":8080", nil)
    }
    
  • Details is always closed whenever the DOM is patched

    Details is always closed whenever the DOM is patched

    This is using v0.12.1 on Firefox.

    The details element will always revert to being closed whenever the DOM is patched. Try by opening the 'Advanced' details section, then pressing the Inside or Outside buttons. I added the Nothing button to show that it is only when things are modified (and not just the triggering of a live event).

    Here is the source code used:

    package main
    
    import (
    	"bytes"
    	"context"
    	"io"
    	"log"
    	"net/http"
    	"sync"
    	"text/template"
    
    	"github.com/jfyne/live"
    )
    
    type State struct {
    	Inside  int
    	Outside int
    }
    
    func main() {
    	var mu sync.Mutex
    	var inside, outside int
    	h, err := live.NewHandler(live.NewCookieStore("session-name", []byte("weak-secret")))
    	if err != nil {
    		log.Fatal("could not create handler")
    	}
    	h.Render = func(ctx context.Context, data interface{}) (io.Reader, error) {
    		tmpl, err := template.New("thermo").Parse(`
    
    Hello<br/>
    Outside is: {{.Outside}}
    <button live-click="inside">Inside</button>
    <button live-click="outside">Outside</button>
    <button live-click="nothing">Nothing</button>
    <details>
    <summary>Advanced</summary>
    Inside is: {{.Inside}}
    <button live-click="inside">Inside</button>
    <button live-click="outside">Outside</button>
    <button live-click="nothing">Nothing</button>
    </details>
    <script src="/live.js"></script>
    	`)
    		if err != nil {
    			return nil, err
    		}
    		var buf bytes.Buffer
    		if err := tmpl.Execute(&buf, data); err != nil {
    			return nil, err
    		}
    		return &buf, nil
    	}
    	h.HandleEvent("inside", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
    		mu.Lock()
    		defer mu.Unlock()
    		inside += 1
    
    		return &State{inside, outside}, nil
    	})
    	h.HandleEvent("outside", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
    		mu.Lock()
    		defer mu.Unlock()
    		outside += 1
    
    		return &State{inside, outside}, nil
    	})
    	h.HandleEvent("nothing", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
    		mu.Lock()
    		defer mu.Unlock()
    		return &State{inside, outside}, nil
    	})
    
    	http.Handle("/", h)
    	http.Handle("/live.js", live.Javascript{})
    	http.ListenAndServe(":8080", nil)
    }
    
  • Client always using ws protocol

    Client always using ws protocol

    When navigating on a https page, there's a mixed content problem when trying to connect to websocket

    https://github.com/jfyne/live/blob/a92e46c71ee688788513cfc7e9e8dbdd06998ea9/web/src/socket.ts#L19

  • fix to build on mac, and maybe other OS's

    fix to build on mac, and maybe other OS's

    build.sh was not running the correct typescript compiler

    fix is:

    cd web && npm install typescript
    go generate web/build.go
    go generate internal/embed/embed.go
    embedmd -w README.md
    
  • feat: Sessions are now extendable with additional info.

    feat: Sessions are now extendable with additional info.

    Refs:

    • https://github.com/jfyne/live/discussions/31

    This PR has a breaking change:

    The Session is no longer a struct with an ID string on it. It is now a map[string]interface{} which should allow people to store arbitrary data in it.

    To replace calls to Session.ID I have added a helper function SessionID(s Session) string which will return the session ID.

  • File uploads

    File uploads

    Was playing around with this project and wanted to be able to do file uploads. My form submits the event, but I'm noticing that the event is missing all the data.

    {"t":"upload","i":1,"d":{"myFile":{}}}
    

    I just skimmed the project, but from what I can tell, you'd need to slice the file into smaller chunks to send over WebSocket stream, then reassemble the file on the server side. It'd probably require reworking LiveEvent and Socket.send to support this lower level operation?

    Maybe I'm missing something and this already works, if so it'd be a good example to have.

    Originally posted by @silasb in https://github.com/jfyne/live/discussions/47

Material Design Components for use with Vecty in the most minimalistic fashion.

mdc Material Design Components for use with Vecty in the most minimalistic, dead simple fashion. Currently on MDC version 13.0.0. Based on the good wo

Mar 6, 2022
Golang-WASM provides a simple idiomatic, and comprehensive API and bindings for working with WebAssembly for Go and JavaScript developers
Golang-WASM provides a simple idiomatic, and comprehensive API and bindings for working with WebAssembly for Go and JavaScript developers

A bridge and bindings for JS DOM API with Go WebAssembly. Written by Team Ortix - Hamza Ali and Chan Wen Xu. GOOS=js GOARCH=wasm go get -u github.com/

Dec 22, 2022
Aes for go and java; build go fo wasm and use wasm parse java response.

aes_go_wasm_java aes for go and java; build go fo wasm and use wasm parse java response. vscode setting config settings.json { "go.toolsEnvVars":

Dec 14, 2021
WebAssembly for Proxies (Golang host implementation)

WebAssembly for Proxies (GoLang host implementation) The GoLang implementation for proxy-wasm, enabling developer to run proxy-wasm extensions in Go.

Dec 29, 2022
A WASM Filter for Envoy Proxy written in Golang

envoy-proxy-wasm-filter-golang A WASM Filter for Envoy Proxy written in Golang Build tinygo build -o optimized.wasm -scheduler=none -target=wasi ./mai

Nov 6, 2022
Istio wasm api demo with golang

istio-wasm-api-demo 1. Setup the latest Istio Setup k8s cluster: e.g. kind create cluster --name test Download the latest Istioctl from the GitHub rel

Nov 1, 2022
DOM library for Go and WASM

Go DOM binding (and more) for WebAssembly This library provides a Go API for different Web APIs for WebAssembly target. It's in an active development,

Dec 23, 2022
Go compiler for small places. Microcontrollers, WebAssembly, and command-line tools. Based on LLVM.

TinyGo - Go compiler for small places TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and comma

Dec 30, 2022
WebAssembly interop between Go and JS values.

vert Package vert provides WebAssembly interop between Go and JS values. Install GOOS=js GOARCH=wasm go get github.com/norunners/vert Examples Hello W

Dec 28, 2022
Fast face detection, pupil/eyes localization and facial landmark points detection library in pure Go.
Fast face detection, pupil/eyes localization and facial landmark points detection library in pure Go.

Pigo is a pure Go face detection, pupil/eyes localization and facial landmark points detection library based on Pixel Intensity Comparison-based Objec

Dec 24, 2022
A package to build progressive web apps with Go programming language and WebAssembly.
A package to build progressive web apps with Go programming language and WebAssembly.

Go-app is a package for building progressive web apps (PWA) with the Go programming language (Golang) and WebAssembly (Wasm). Shaping a UI is done by

Dec 30, 2022
A package to build progressive web apps with Go programming language and WebAssembly.
A package to build progressive web apps with Go programming language and WebAssembly.

Go-app is a package for building progressive web apps (PWA) with the Go programming language (Golang) and WebAssembly (Wasm). Shaping a UI is done by

Jan 2, 2023
Bed and Breakfast web app written in Go

BOOKINGS AND RESERVATIONS This repository contains the files for my RareBnB application RareBnB is an "AirBnB" style clone providing a user the abilit

Jan 11, 2022
This project will help you to create Live img.shields.io Badges which will Count YouTube Stats (Subscriber, Views, Videos) without YouTube API
This project will help you to create Live img.shields.io Badges which will Count YouTube Stats (Subscriber, Views, Videos) without YouTube API

Free YouTube Stats Badge This project will help you to create Live img.shields.io Badges which will Count YouTube Stats (Subscriber, Views, Videos) wi

Oct 11, 2022
RecordLite: a library (and executable) that declaratively maintains SQLite tables and views of semi-structured data

RecordLite RecordLite is a library (and executable) that declaratively maintains

May 29, 2022
Mar 7, 2022
A simple go application that uses Youtube Data API V3 to show the real-time stats for a youtube channel such as the subs, views, avg. earnings etc.
A simple go application that uses Youtube Data API V3 to show the real-time stats for a youtube channel such as the subs, views, avg. earnings etc.

Youtube-channel-monitor A simple go application that uses Youtube Data API V3 to show the real-time stats for a youtube channel such as the subs, view

Dec 30, 2021
[爬虫框架 (golang)] An awesome Go concurrent Crawler(spider) framework. The crawler is flexible and modular. It can be expanded to an Individualized crawler easily or you can use the default crawl components only.

go_spider A crawler of vertical communities achieved by GOLANG. Latest stable Release: Version 1.2 (Sep 23, 2014). QQ群号:337344607 Features Concurrent

Dec 30, 2022
[爬虫框架 (golang)] An awesome Go concurrent Crawler(spider) framework. The crawler is flexible and modular. It can be expanded to an Individualized crawler easily or you can use the default crawl components only.

go_spider A crawler of vertical communities achieved by GOLANG. Latest stable Release: Version 1.2 (Sep 23, 2014). QQ群号:337344607 Features Concurrent

Jan 6, 2023
⚙️ Concept of Golang HTML render engine with frontend components and dynamic behavior
⚙️ Concept of Golang HTML render engine with frontend components and dynamic behavior

An HTML render engine concept that brings frontend-like components experience to the server side with native html/template on steroids. Supports any s

Nov 25, 2022