Using Vuetify in Pluto.jl

I have a small application that I’m writing using Pluto.jl. The general idea behind it is to display a list of sliders (dynamically generated) over a couple tabs that can be used to set the numbers for specific variables. Once the numbers have been selected, I would like to use a button to grab the values from the HTML/JavaScript and export it into Julia to use later. Since I have some experience with Vue/Vuetify, I decided to use that to generate the sliders and tabs. I have been able to get the sliders and tabs working in Pluto (it looks really great), but the issue that I’m running into is fetching the numbers from the sliders.

Here’s the Pluto code for what I’m doing:

begin
	
using JSON2
	
d1 = Dict("rates" =>[Dict([("slider", 1), ("max", 10), ("min",0), ("sname","rate1")]),
			Dict([("slider", 1), ("max", 15), ("min",-1), ("sname","rate2")]),
			],
	"conc" =>[Dict([("slider", 1), ("max", 5), ("min",0), ("sname","c1")]),
			Dict([("slider", 1), ("max", 8), ("min",-10), ("sname","c2")]),
			Dict([("slider", 1), ("max", 8), ("min",-10), ("sname","c3")])
			],
		"tab" => "null",
		)
djson1 = JSON2.write(d1)
	
	
 test = HTML("""
<html>

<body>
	
  <div id="app">
		<form>
    <v-app>
      <v-main>
        <template>
          <v-card>
            <v-tabs
              v-model="tab"
              background-color="transparent"

              grow
            >
              <v-tab>
                Rates
              </v-tab>
              <v-tab>
                Concentrations
              </v-tab>

            </v-tabs>

            <v-tabs-items v-model="tab">
              <v-tab-item
              >
                <v-card  flat>
                  <v-card-text>Adjust the reaction rates</v-card-text>

                  <v-slider
                    v-for = "s in rates"
                    v-model="s.slider"
                    :max="s.max"
                    :min="s.min"
                    :label = "s.sname"
                  >
                    <template v-slot:append>
                      <v-text-field
                        v-model="s.slider"
                        class="mt-0 pt-0"
                        hide-details
                        single-line
                        type="number"
                        style="width: 60px"
                      ></v-text-field>
                    </template>
                </v-slider>

				</v-slider>
			  </v-card>
			</v-tab-item>

	<v-tab-item
	>
	  <v-card  flat>
		<v-card-text>Adjust the Concentrations</v-card-text>

		<v-slider
		  v-for = "s in conc"
		  v-model="s.slider"
		  :max="s.max"
		  :min="s.min"
		  step =".1"
		  :label = "s.sname"
		>
		  <template v-slot:append>
			<v-text-field
			  v-model="s.slider"
			  class="mt-0 pt-0"
			  hide-details
			  single-line
			  type="number"
			  style="width: 60px"
			></v-text-field>
		  </template>
	  </v-slider>

	</v-slider>
	</v-card>
	</v-tab-item>


</v-tabs-items>
</v-card>
</template>
<v-btn v-on:click="buttonClick">Update</v-btn>

      </v-main>
    </v-app>
		</form>
  </div>
<head>
		
		
  <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
		
		
</head>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
  <script>
    new Vue({
      el: '#app',
      vuetify: new Vuetify(),
      data () {
      return $djson1
			},
	methods: {
			
			buttonClick: function(event) {
			const form = currentScript.parentElement.querySelector('form')
			console.log(form)
			let rateArrVal = new Array()
			let rateArrNames = new Array()
			let concArrVal = new Array()
			let concArrNames = new Array()
			console.log(form)
			let x = this.rates
			let y = this.conc
			
			for (let i=0; i<x.length;i++){
				rateArrVal.push(x[i].slider)
				rateArrNames.push(x[i].sname)
			}
			
			for (let i=0; i<y.length;i++){
				concArrVal.push(y[i].slider)
				concArrNames.push(y[i].sname)
			}

			
			form.value = rateArrVal
			console.log(form.value)
			form.dispatchEvent(new CustomEvent('input'))
			
			}
    	}
	})
  </script>
</body>
</html>

			
			""");
	nothing
	
end

And in another cell, the code is bound using the bind macro.

@bind c test

Here’s what it looks like when running:

Ideally, the “Update” button at the bottom of the display will update the variable c, such that it grabs an array of values taken from the sliders. Currently, using the JS inside of the Vue instance I’m able to grab the values of the sliders and compile them into an array, but I’m not sure how to link up the Vue instance’s variables/functions with Pluto. Any idea on how to do this or what I’m doing wrong? Thanks in advanced.

@JackC I wanted to try Vue in Pluto as well, so I modified your code a bit and got it working (mostly).

When the “update” button is clicked, it should update the “formdata” julia variable based on the rate1/rate2 vue sliders.

The main changes were:

  • change the mount point of the vue app to the top level tag rather than a div surrounding it. This makes event propagation of the underlying data work because pluto expects the value to be stored as the first child of the cell, not the first child’s first child.
  • add a @bind statement to bind the Vue app’s value (e.g, the value set by the click button) to “formdata”
  • remove the html/body/head in the embedded html. perhaps that would work with a iframe.
  • minor updates to use HyperTextLiteral and JSON3 because they handle escaping better.
  • fix json parsing inside JS to take the initial JSON string and convert it to a javascript object.
### A Pluto.jl notebook ###
# v0.17.1

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
        el
    end
end

# ╔═╡ 4d53a7dc-105a-4fb4-a350-2d1f69bb6854
using JSON3

# ╔═╡ 059fe312-b998-4e25-9582-55e3f16aa5b8
using HypertextLiteral

# ╔═╡ 9dcdccff-c74a-4e0b-bebb-873dae6374f0
d1 = Dict("rates" =>[Dict([("slider", 1), ("max", 10), ("min",0), ("sname","rate1")]),
			Dict([("slider", 1), ("max", 15), ("min",-1), ("sname","rate2")]),
			],
	"conc" =>[Dict([("slider", 1), ("max", 5), ("min",0), ("sname","c1")]),
			Dict([("slider", 1), ("max", 8), ("min",-10), ("sname","c2")]),
			Dict([("slider", 1), ("max", 8), ("min",-10), ("sname","c3")])
			],
		"tab" => "null",
		)

# ╔═╡ 4a86c583-fdf3-4a58-bde3-d5778d90acf0
djson1 = JSON3.write(d1)

# ╔═╡ be128184-526d-4965-a60d-1ff33c957258
@htl("""
  <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">

""")

# ╔═╡ 5fb6bc65-dccf-40de-8588-2dba96b497b1
@htl("""
  <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>""")

# ╔═╡ d7904f32-a2f9-418f-8b9f-14db6e58acfc
@bind formdata @htl("""
		<form id="app">
    <v-app>
      <v-main>
        <template>
          <v-card>
            <v-tabs
              v-model="tab"
              background-color="transparent"

              grow
            >
              <v-tab>
                Rates
              </v-tab>
              <v-tab>
                Concentrations
              </v-tab>

            </v-tabs>

            <v-tabs-items v-model="tab">
              <v-tab-item
              >
                <v-card  flat>
                  <v-card-text>Adjust the reaction rates</v-card-text>

                  <v-slider
                    v-for = "s in rates"
                    v-model="s.slider"
                    :max="s.max"
                    :min="s.min"
                    :label = "s.sname"
                  >
                    <template v-slot:append>
                      <v-text-field
                        v-model="s.slider"
                        class="mt-0 pt-0"
                        hide-details
                        single-line
                        type="number"
                        style="width: 60px"
                      ></v-text-field>
                    </template>
                </v-slider>

				</v-slider>
			  </v-card>
			</v-tab-item>

	<v-tab-item
	>
	  <v-card  flat>
		<v-card-text>Adjust the Concentrations</v-card-text>

		<v-slider
		  v-for = "s in conc"
		  v-model="s.slider"
		  :max="s.max"
		  :min="s.min"
		  step =".1"
		  :label = "s.sname"
		>
		  <template v-slot:append>
			<v-text-field
			  v-model="s.slider"
			  class="mt-0 pt-0"
			  hide-details
			  single-line
			  type="number"
			  style="width: 60px"
			></v-text-field>
		  </template>
	  </v-slider>

	</v-slider>
	</v-card>
	</v-tab-item>


</v-tabs-items>
</v-card>
</template>
<v-btn v-on:click="buttonClick">Update</v-btn>

      </v-main>
    </v-app>
		</form>
  
  <script>
	const dat = JSON.parse($(djson1));
	console.log(dat);
    new Vue({
      el: '#app',
      vuetify: new Vuetify(),
      data () {
      return dat;
			},
	methods: {
			
			buttonClick: function(event) {
			const form = currentScript.parentElement.querySelector('form')
			console.log(form)
			let rateArrVal = new Array()
			let rateArrNames = new Array()
			let concArrVal = new Array()
			let concArrNames = new Array()
			console.log(form)
			let x = this.rates
			let y = this.conc
			
			for (let i=0; i<x.length;i++){
				rateArrVal.push(x[i].slider)
				rateArrNames.push(x[i].sname)
			}
			
			for (let i=0; i<y.length;i++){
				concArrVal.push(y[i].slider)
				concArrNames.push(y[i].sname)
			}

			
			form.value = rateArrVal
			console.log(form.value)
			form.dispatchEvent(new CustomEvent('input'))
			
			}
    	}
	})


  </script>
			""")

# ╔═╡ 6eeed9c2-08e6-4354-b473-7edf5dd58265
formdata

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
HypertextLiteral = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"

[compat]
HypertextLiteral = "~0.9.3"
JSON3 = "~1.9.2"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.7.0-rc2"
manifest_format = "2.0"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.HypertextLiteral]]
git-tree-sha1 = "2b078b5a615c6c0396c77810d92ee8c6f470d238"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.3"

[[deps.JSON3]]
deps = ["Dates", "Mmap", "Parsers", "StructTypes", "UUIDs"]
git-tree-sha1 = "7d58534ffb62cd947950b3aa9b993e63307a6125"
uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
version = "1.9.2"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "ae4bbcadb2906ccc085cf52ac286dc1377dceccc"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.1.2"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.StructTypes]]
deps = ["Dates", "UUIDs"]
git-tree-sha1 = "d24a825a95a6d98c385001212dc9020d609f2d4f"
uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
version = "1.8.1"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
"""

# ╔═╡ Cell order:
# ╠═4d53a7dc-105a-4fb4-a350-2d1f69bb6854
# ╠═059fe312-b998-4e25-9582-55e3f16aa5b8
# ╠═9dcdccff-c74a-4e0b-bebb-873dae6374f0
# ╠═4a86c583-fdf3-4a58-bde3-d5778d90acf0
# ╠═be128184-526d-4965-a60d-1ff33c957258
# ╠═5fb6bc65-dccf-40de-8588-2dba96b497b1
# ╠═6eeed9c2-08e6-4354-b473-7edf5dd58265
# ╠═d7904f32-a2f9-418f-8b9f-14db6e58acfc
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002

1 Like