Skip to content

Instantly share code, notes, and snippets.

@DoctorBud
Last active September 18, 2018 06:08
Show Gist options
  • Save DoctorBud/1add9f148ae458faa604c64319455c7e to your computer and use it in GitHub Desktop.
Save DoctorBud/1add9f148ae458faa604c64319455c7e to your computer and use it in GitHub Desktop.
Smartdown+Go Producer/Consumer Poster
license: none
height: 700
scrolling: yes
border: no

Smartdown Meets Go, as a SmartPoster

Best viewed with this link: raw

Adding Go to Smartdown in Two Beers

A couple weeks ago on a Friday evening, I set my mind to integrating a non-Javascript language into Smartdown in order to learn/teach such a language via Smartdown. Smartdown's serverless nature demanded that I find a language that could be compiled in the browser. My initial motivation was to learn Julia, which a colleague of mine had shown off earlier that day. I could not find a credible Javascript version, so I moved on to other languages, including ClojureScript, ScalaJS, and KotlinJS. For each of these, I tried to find an example of in-browser compilation of the language, and could not (remember, I'm under a two beer time limit, so the search was cursory).

None of these languages had compilers that worked offline, so I considered Go, which had a Javascript implementation GopherJS, and more importantly, a working example of in-browser compilation at GopherJS Playground. So I proceeded to learn Go as well as adapt its Javascript compiler and the Playground into a form suitable for Smartdown integration.

It took more than two beers

I'm going to write a more detailed article about this project, but I eventually succeeded in learning Go, adapting the GopherJS Playground to work as a UI-less body of code, and integrating this code into Smartdown in a reasonable way. This document is an example of the potential of GoDown, the Smartdown+Go integration. This document is also an example Smartdown Tableau, a tool for displaying multiple Smartdown documents in a gridded layout while maintaining the shared data space expected of Smartdown.

Producer/Consumer Example

This demonstrates the use of multiple Go packages, the use of the GopherJS DOM package, and the use of channels to communicate between multiple processes.

Click the Produce A and Produce B buttons, which will emit an A or B, respectively, to the shared channel ch defined in the main package. The consumer package listens on this shared channel and displays the accumulated messages received.

ProducerA

Creates a Button that emits an "A" to the channel

package producerA

import (
  "honnef.co/go/js/dom"
  "github.com/gopherjs/gopherjs/js"
)

func Producer(ch chan string) {
  d := dom.GetWindow().Document()

  myDivId := js.Global.Get("godownDiv_producerA").String()
  println("myDivId", myDivId)
  div := d.GetElementByID(myDivId).(*dom.HTMLDivElement)
  div.SetInnerHTML("")

  child := d.CreateElement("button").(*dom.HTMLButtonElement)
  child.Style().SetProperty("color", "purple", "")
  child.SetTextContent("Produce A")
  div.AppendChild(child)
  child.AddEventListener("click", false, func(event dom.Event) {
    ch <- "A"
  })
}

ProducerB

Creates a Button that emits a "B" to the channel

package producerB

import (
  "honnef.co/go/js/dom"
  "github.com/gopherjs/gopherjs/js"
)

func Producer(ch chan string) {
  d := dom.GetWindow().Document()

  myDivId := js.Global.Get("godownDiv_producerB").String()
  println("myDivId", myDivId)
  div := d.GetElementByID(myDivId).(*dom.HTMLDivElement)
  div.SetInnerHTML("")

  child := d.CreateElement("button").(*dom.HTMLButtonElement)
  child.Style().SetProperty("color", "purple", "")
  child.SetTextContent("Produce B")
  div.AppendChild(child)
  child.AddEventListener("click", false, func(event dom.Event) {
    ch <- "B"
  })
}

Consumer

Reads from the channel and displays a log of all received messages.

Reads from the channel and displays a log of all received messages.

package consumer

import (
  "honnef.co/go/js/dom"
  "github.com/gopherjs/gopherjs/js"
)

func Consumer(ch chan string) {
  d := dom.GetWindow().Document()
  consumed := ""
  myDivId := js.Global.Get("godownDiv_consumer").String()
  println("myDivId", myDivId)
  div := d.GetElementByID(myDivId).(*dom.HTMLDivElement)

  for {
    div.SetInnerHTML("<h1>" + consumed + "</h1>")
    consumedChar, more := <- ch
    if more {
      consumed += consumedChar
    } else {
      break
    }
  }
}

Main program invokes Producers and Consumer and then Chills

All Go programs start with main. In this example, we just use main to tie together the various producer/consumer components and give them a shared channel for communication. It is totally possible, and reasonable to combine producerA and producerB into a parametrized package producer; but this demo evolved to show off the multi-package capability.

package main

import (
  "producerA"
  "producerB"
  "consumer"
  "github.com/gopherjs/gopherjs/js"
)

var ch chan string

func main() {
  js.Global.Set("Godown_Shutdown", js.InternalObject(func(msg string) {
    close(ch)
  }))

  ch = make(chan string, 5)
  go producerA.Producer(ch)
  go producerB.Producer(ch)
  go consumer.Consumer(ch)

  println("All Done")
}

Coming up Next

  • Integrate Go's channels with Smartdown's reactivity mechanism, so that Cells and Playables can form an ensemble more easily.
  • Better compiler error messages delivered to the author.
  • Better demo that merges multiple async streams and exhibits interesting behavior.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Smartdown + Go Poster</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<link rel="stylesheet" href="https://unpkg.com/smartdown/docs/lib/smartdown.css">
<link rel="stylesheet" href="https://unpkg.com/smartdown/docs/lib/fonts.css">
<script src="https://smartdown.site/lib/smartdown.js"></script>
<!-- <link rel="stylesheet" href="https://127.0.0.1:4000/lib/smartdown.css"> -->
<!-- <script src="https://127.0.0.1:4000/lib/smartdown.js"></script> -->
<link rel="stylesheet" href="https://doctorbud.com/smartdown_vuegridlayout/vuegridlayout.css">
<script src="https://unpkg.com/js-yaml/dist/js-yaml.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-grid-layout/dist/vue-grid-layout.umd.min.js"></script>
<script>
window.godownBase = 'https://rawgit.com/DoctorBud/godown/master/godown/';
</script>
<script src="https://rawgit.com/DoctorBud/godown/master/godown/godown.js"></script>
<!--
<script>
window.godownBase = 'http://127.0.0.1:8484/';
</script>
<script src="http://127.0.0.1:8484/godown.js"></script>
-->
</head>
<body
class="container container-fluid xsmartdown-theme-chat"
id="bodyID">
<div
v-cloak
v-bind:class="{ 'disable-select': isDragging() }"
id="layoutApp">
<div
class="navigationArea">
<div class="row" v-if="tableaux.length > 0">
<div class="col-xs-10" style="padding:0;">
<button
v-for="t in tableaux"
class="btn btn-default btn-xs tableau-button"
v-bind:class="{ current: isCurrentTableau(t) }"
v-bind:disabled="isCurrentTableau(t)"
v-on:click="loadTableauByName(t)">
{{t}}
</button>
</div>
<div class="col-xs-2" style="padding:0;">
<button
class="btn btn-default btn-xs"
v-on:click="loadHomeTableau()">
Home
</button>
</div>
</div>
<div class="row">
<div class="col-xs-10" style="padding:0;">
<button
v-for="item in tableau.layout.cells"
class="btn btn-default btn-xs card-button"
v-bind:class="{ focused: isCardFocused(item.name) }"
v-on:click="toggleFocusOnCard(item.name)">
{{item.name}}
</button>
</div>
<div class="col-xs-2" style="padding:0;">
<button
class="btn btn-default btn-xs card-button"
v-on:click="focusOnCard('')">
None
</button>
</div>
</div>
</div>
<div
v-if="!kioskMode"
class="authorArea">
<div
class="toggle-author-area-button" @click="toggleAuthorMode()">
&nbsp;&equiv;&nbsp;
</div>
<div class="row" v-if="authorMode">
<div class="col-xs-8">
<input
type="file"
id="selectedFiles"
accept=".md"
v-on:change="filesSelected"
multiple/>
</div>
<div class="col-xs-4">
<button class="btn btn-default btn-xs" v-on:click="exportTableau">Export Tableau</button>
</div>
<div class="col-xs-12">
<button class="btn btn-default btn-xs" v-on:click="switchToColumnLayout">Column Layout</button>
<button class="btn btn-default btn-xs" v-on:click="switchToRowLayout">Row Layout</button>
<button class="btn btn-default btn-xs" v-on:click="switchToStaggeredLayout">Staggered Layout</button>
<button class="btn btn-default btn-xs" v-on:click="switchToXLayout">X Layout</button>
</div>
</div>
<div class="row" v-if="authorMode">
<div class="col-xs-12">
<div class="layoutItem" v-for="item in tableau.layout.cells">
<b>{{item.i}}</b>: [{{item.x}}, {{item.y}}, {{item.w}}, {{item.h}}]
</div>
</div>
</div>
</div>
<div id="content" class="smartdown-container">
<grid-layout
ref="layout"
:layout="tableau.layout.cells"
:col-num="numCols"
:row-height="rowHeight"
:margin="[5, 5]"
:is-draggable="draggable"
:is-resizable="resizable"
:vertical-compact="true"
:use-css-transforms="true">
<grid-item
v-for="item in tableau.layout.cells"
:key="item.i"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
v-bind:class="{ 'in-focus': isCardFocused(item.name) }"
@resized="resizedEvent"
drag-allow-from=".vue-draggable-handle"
drag-ignore-from=".no-drag">
<div
class="vue-draggable-handle"
v-bind:class="{ 'hide-draggable': !draggable }"
v-on:click="dragStart">
</div>
<div
class="no-drag smartdown-tile"
v-bind:id="item.divID">
</div>
</grid-item>
</grid-layout>
</div>
</div>
<script>
//
// The options below control how the grid_helper behaves.
// They are designed to support publishing via bl.ocks.org, a blog, or Wordpress
//
// kioskMode - 'true' means disable the Settings checkbox
// tableaux - A list of names of tableaux in the tableaux/ directory
// defaultTableauName - The tableau to load by default. Default is tableaux[0]
//
window.gridHelperOptions = {
kioskMode: false,
tableaux: [],
defaultTableauName: 'Tableau',
};
</script>
<script src="https://cdn.rawgit.com/smartdown/poster/master/grid_helper.js"></script>
</body>
</html>
layout:
cells:
- posX: 1
posY: 0
dimW: 11
dimH: 5
contentURL: demo1.md
- posX: 0
posY: 5
dimW: 11
dimH: 4
contentURL: demo2.md
- posX: 3
posY: 9
dimW: 9
dimH: 4
contentURL: demo3.md
- posX: 1
posY: 20
dimW: 5
dimH: 3
contentURL: demo4.md
- posX: 6
posY: 20
dimW: 5
dimH: 3
contentURL: demo5.md
- posX: 2
posY: 16
dimW: 8
dimH: 4
contentURL: demo6.md
- posX: 0
posY: 13
dimW: 12
dimH: 3
contentURL: demo7.md
- posX: 1
posY: 23
dimW: 10
dimH: 3
contentURL: demo8.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment