Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save salex/b468546d35e699285695b0ea50eb120a to your computer and use it in GitHub Desktop.
Save salex/b468546d35e699285695b0ea50eb120a to your computer and use it in GitHub Desktop.
A spreadsheet like controller for a table with two controllers with shared targets
Since I didn't get any opinions on last post https://discourse.stimulusjs.org/t/one-controller-and-multiple-repeating-target-or-multiple-controller-for-each-area/1087 (Is anyone using this site? Is Stimulus.js still alive?) I decided to try another approach.
Since most of my work is using `accounting like` applications, I have a lot of tables (ledgers etc). One of the applications I'm rewriting deals with keeping scores for a golf groups. You can look at a golf score card in visualize a spreadsheet. You put your scores down for each hole, sum the front and back and come up with a total score. We play teams so there are three or four players on the card, and there our four or so teams. Each team turns in their card with scores (front, back and total) and someone wins.
The scores are then entered into a Rails application and new quotas or a handicap is computed. That's the simplified version of ptgolf.us. The rewrite is de-jquery-ing what little coffeescript I had. One involved entering the scores. I basically display a golf scorecard as a table where the teams are scored by entering what each teammate scored on the front and the back and computes a total. That info is converted to a Game model that has a Round model to post the scores, recompute quotas, who won, how much, etc.
What I ended up with is a page where the form has a scoreTeam controller and a scoreMate controller on each teammate table row.
The scoreMate controller is were the scores are entered and the scoreTeam(s) controller summarizes the results.
The controllers
```javascript
// scoreMate.controller
import { Controller } from "stimulus"
export default class extends Controller {
static targets = ["team","mateFront","mateBack","mateTotal","mateQuota","matePM"]
connect() {
// console.log("score mate")
}
front(){
const f = this.frontVal
const b = this.backVal
if (b != 0 && f != 0) {
const t =f + b
this.mateTotalTarget.value = t
this.matePMTarget.innerHTML = t - this.quotaVal
this.fireCompute()
}
}
back(){
const f = this.frontVal
const b = this.backVal
if (b != 0 && f != 0) {
const t =f + b
this.mateTotalTarget.value = t
this.matePMTarget.innerHTML = t - this.quotaVal
this.fireCompute()
}
}
fireCompute(){
var id = "teamUpdate"+this.teamTarget.value
document.getElementById(id).click()
}
get frontVal(){
const toNum = Number(this.mateFrontTarget.value)
if (isNaN(toNum)) {
return(0)
}else{
return(toNum)
}
}
get backVal(){
const toNum = Number(this.mateBackTarget.value)
if (isNaN(toNum)) {
return(0)
}else{
return(toNum)
}
}
get quotaVal(){
const toNum = Number(this.mateQuotaTarget.value)
if (isNaN(toNum)) {
return(0)
}else{
return(toNum)
}
}
}
```
The scoreMate controller uses information from the Team model and only inputs two values (front, back) and computes the total
The scoreTeam controller
```javascript
\\ scoreTeam.controller
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ "team","teamQuota","teamFront","teamBack","teamTotal","teamPM","mateFront","mateBack","mateTotal" ]
connect() {
// console.log("score team")
}
compute(){
// console.log("something fired and I have to got to work")
var tback = this.teamBackTarget
var tfront = this.teamFrontTarget
var ttotal = this.teamTotalTarget
var tPM = this.teamPMTarget
const quota = Number(this.teamQuotaTarget.value)
const side = quota/2.0
const mfront = this.front
const mback = this.back
const mtotal = mfront + mback
tfront.innerHTML = `${mfront}/${mfront - side}`
tback.innerHTML = `${mback}/${mback - side}`
ttotal.innerHTML = `${mtotal}/${mtotal - quota}`
tPM.innerHTML = mtotal - quota
}
get front(){
const mates = this.mateFrontTargets
var total = 0
for (var i = 0; i < mates.length; i++) {
const toNum = Number(mates[i].value)
if (isNaN(toNum)) {
}else{
total += toNum
}
}
return total
}
get back(){
const mates = this.mateBackTargets
var total = 0
for (var i = 0; i < mates.length; i++) {
const toNum = Number(mates[i].value)
if (isNaN(toNum)) {
}else{
total += toNum
}
}
return total
}
}
```
The scoreTeam controller basically just sums the scores for each team. The two are line using somewhat of a kludge where after each teammate score is entered it trigger a click event on a div that is in the scoreTeam scope,
The Rails code that generates the table (simplified test version and I use slim)
```ruby
- teams = [[{n:'joe',q:28},{n:'john',q:22},{n:'jim',q:18}],[{n:'tom',q:31},{n:'dick',q:24},{n:'harry',q:15}]]
- tquota = []
- teams.each do|t|
- q = 0
- t.each do |m|
- q += m[:q]
- tquota << q
- teams.each_with_index do |team,i|
hr.underline
table.small-table.w60p[data-controller="scoreTeam"]
tr
th colspan="3"
= "Team - #{i+1} Quota:#{tquota[i]} Side: #{tquota[i]/2.0} Hole: #{(tquota[i]/18.0).round(2)}"
= hidden_field_tag("team[#{i+1}][quota]",tquota[i],data:{target:"scoreTeam.teamQuota"})
div[id="teamUpdate#{i}" style="display:none;" data-action="click->scoreTeam#compute"]
th[data-target="scoreTeam.teamFront"]
th[data-target="scoreTeam.teamBack"]
th[data-target="scoreTeam.teamTotal"]
th[data-target="scoreTeam.teamPM"]
tr
th Name
th Quota
th Side
th Front
th Back
th Total
th &#177;
- team.each_with_index do |tm,m|
tr[data-controller="scoreMate"]
- side = tm[:q]/2.0
- opt = pulled_options(tm[:q])
td
= tm[:n]
= hidden_field_tag('team',i,data:{target:"scoreMate.team"})
td
= tm[:q]
= hidden_field_tag("mate[#{m}][quota]",tm[:q],data:{target:"scoreMate.mateQuota"})
td
= tm[:q]/2.0
= hidden_field_tag("mate[#{m}][team]", side)
td = select_tag("mate[#{m}][front]", options_for_select(opt[:side],'Select'),
data:{target:'scoreMate.mateFront scoreTeam.mateFront',action:"change->scoreMate#front"},
class:" w3-select")
td = select_tag("mate[#{m}][back]", options_for_select(opt[:side],'Select'),
data:{target:'scoreMate.mateBack scoreTeam.mateBack',action:"change->scoreMate#back"},
class:" w3-select")
td = select_tag("mate[#{m}][total]", options_for_select(opt[:total],'Select'),
data:{target:'scoreMate.mateTotal scoreTeam.mateTotal'},
class:" w3-select",disabled:true)
td[id="mate#{m}_pmt" data-target="scoreMate.matePM"]
Which generates a form that look like this
![Screen Shot 2020-04-06 at 11 41 39 AM](https://user-images.githubusercontent.com/125716/78584674-184dd380-7828-11ea-8f3a-1c573fd65206.png)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment