Skip to content

Instantly share code, notes, and snippets.

@cmontella
Last active March 3, 2017 19:42
Show Gist options
  • Save cmontella/8ba50b7bd48b338c85c0590c0cba1563 to your computer and use it in GitHub Desktop.
Save cmontella/8ba50b7bd48b338c85c0590c0cba1563 to your computer and use it in GitHub Desktop.
Sandlot Example App
# Example 2 - The Sandlot
## What is this?
This app demonstrates how to create a reusable component and how to inject it into the browser. This app displays a batting order for a baseball team using a `#player-card` component. This component displays information relating to each player on a baseball team -- picture, name, position, and handedness. A `#player-card` can also be configured with additional functionality using optional tags. The app also shows some of the ways `sort` can be used, and how to choose specific records out of a list of records.
## Page Layout
### Containers
I want to break the page down into two sections: one for the active lineup and the bench players, and one for players on injured reserve.
```
commit @browser
[#div class:"container" children:
[#div #active class:"lineup" text:"Batting Order"]
[#div #inactive class:"lineup" text:"Bench"]]
[#div #IR class:"lineup IR" text:"Injured Reserve"]
```
### Active Lineup
The first column drawn should be the active batting order. While I could have specified all the text and contents of each player in this block, note that I used two tags, `#player-card` and `#moveable`, whose contents and behavior get defined later. Since every player listed is going to have the same information, `#player-card` lets me drop the tag in and define all the HTML and content later, which is done in the Player Cards section after this one.
```
search @session @browser
player = [#player not(#bench) not(#injured) lineup]
active = [#active]
bind @browser
active.children += [#div #lineup children: [#player-card #moveable player sort: lineup]]
```
### Bench Players
These are the bench players. Note that `#player-card` is used again, saving me the work of having to define all the same html and content that's used in the active lineup. The big difference here is the ordering of the players. Whereas the players in the lineup have a deliberate and specific order (which makes sense for things like a batting order), I don't need a specific order for the bench players. I do, however, want to know how many there are and make sure they render in the same order each time, so I sort by the `player` variable to get an index, `n`. Each player gets assigned an index, which is how I sort the bench players, and also set `player.lineup` to that index so they get numbered 1, 2, 3, and so on.
```
search @session @browser
player = [#player #bench lineup]
inactive = [#inactive]
n = sort[value:player]
bind @session @browser
player.lineup := n
inactive.children += [#div #lineup children: [#player-card #benched sort:n player]]
```
### Injured Reserve
These are the injured reserve players. This section is functionally identical to the bench player section, except it gets merged into the `#IR` section on the page. There's only one player on the IR and no functionality exists to move him elsewhere, because he is a jerk.
```
search @session @browser
player = [#player #injured not(#bench) lineup]
IR = [#IR]
n = sort[value:player]
bind @session @browser
player.lineup := n
IR.children += [#div #lineup children: [#player-card sort:n player]]
```
## Player Cards
This is where the magic happens. Every time this finds `#player-card` in the browser, it will make a `#div` for each player and spit out a player card. It checks to see if the player has a nickname as well, and just leaves a space between the first and last names if there isn't. It's worth mentioning that because I want this to be as general a template as possible, a handful of architectural decisions were made to facilitate that. In the sections that draw each of the three lists, I use the search parameters there to select the specific players I want and inject those player cards. In the Roster Data, all the players have a `lineup` attribute even though the bench and IR players have an empty string as the value. If you're good at predicting which reusable parts you'll need, you can implement these sorts of things from the outset, or you can figure out where they need to be as you go along and refactor them into your code as you work.
```
search @browser @session
container = [#player-card player]
player = [#player firstname lastname position bats lineup photo]
middle = if player.nickname then " \"{{player.nickname}}\" "
else " "
bind @browser
container <- [#div player class: "lineup-position" children:
[#div class: "bat-number" text: lineup]
[#div class: "player-info" children:
[#img class: "player-photo" src: photo]
[#div class: "player-text" children:
[#div class: "name-line" children:
[#div class: "position" text: position]
[#div class: "first-name" text: firstname]
[#div class: "middle-name" text: middle]
[#div class: "last-name" text: lastname]]
[#div class:"bat-hand" text:"Bats: {{bats}}"]]]]
```
## Buttons
### Move Up
The active roster also features a cool way to use tags. Since I want to be able to reorder my active lineup, each `#player-card` there is also tagged `#moveable`. This checks to see if a `#player-card` is `#moveable` and adds a child `#div` to it with a `#move-up-btn`, unless that player is already at the top of roster and can't move up any further.
```
search @browser @session
container = [#div #moveable player]
not(player.lineup = 1)
bind @browser
container.children += [#div #move-up-btn player class: "ion-chevron-up"]
```
It's like hashtag inception. Just as the `#moveable` tag let Eve know to add a `#move-up-btn`, this block adds another layer of functionality by giving that button some behavior. In this case, when a `#move-up-btn` is clicked, Eve captures who the specific `clicked-player` is, finds whoever is one space above them in the lineup, and switches their places.
```
search @browser @event @session
[#click element: [#move-up-btn player: clicked-player]]
above-clicked = [#player lineup: clicked-player.lineup - 1]
commit
clicked-player.lineup := clicked-player.lineup - 1
above-clicked.lineup := above-clicked.lineup + 1
```
### Move Down
This block is the mirror of the move up buttons. If a `#player-card` is `#moveable`, and not at the bottom of the lineup, which is what the `count` is checking, then a child `#div` gets added with a `#move-down-btn`.
```
search @browser @session
container = [#div #moveable player]
not(player.lineup: count[given: [#player not(#bench) not(#injured)]])
bind @browser
container.children += [#div #move-down-btn player class: "ion-chevron-down"]
```
Once again the reciprocal of the previous section, when a `#move-down-btn` is clicked, Eve captures the specific `clicked-player`, finds the player one space below them in the lineup, and switches their places.
```
search @browser @event @session
[#click element:[#move-down-btn player: clicked-player]]
below-clicked = [#player lineup: clicked-player.lineup + 1]
commit
clicked-player.lineup := clicked-player.lineup + 1
below-clicked.lineup := below-clicked.lineup - 1
```
### Remove from Lineup
If a player is `#moveable`, it means they're in the active lineup and should be able to be removed from the active lineup and sent to the bench. This simply looks for any `#moveable` players and adds a `#deactivate` button to them.
```
search @browser @session
container = [#div #moveable player]
bind @browser
container.children += [#div #deactivate player class: "ion-log-out"]
```
This gives `#deactivate` its behavior. The `clicked-player` is sent to the bench and is stripped of their lineup order. All the players who were below them on the lineup get moved up a spot so that there aren't gaps in the numbering.
```
search @browser @event @session
[#click element: [#deactivate player: clicked-player]]
players-below = [#player lineup > clicked-player.lineup]
commit
clicked-player += #bench
clicked-player.lineup := ""
players-below.lineup := players-below.lineup - 1
```
This block takes care of an edge case for `#deactivate`. Because the previous block finds all the `players-below` the `clicked-player`, if the player is at the bottom of the lineup then there are no `players-below` and the search block fails, preventing the button from doing anything. This makes sure you can move the ninth batter off the lineup onto the bench.
```
search @browser @event @session
[#click element: [#deactivate player: clicked-player]]
commit
clicked-player += #bench
clicked-player.lineup := ""
```
### Add to Lineup
This adds an `#activate` button to the bench players so we can add them to the active lineup, but only if there are fewer than nine batters already in the lineup.
```
search @browser @session
container = [#div #benched player]
active-players = if c = count[given: [#player not(#bench) not(#injured)]] then c else 0
active-players != 9
bind @browser
container.children += [#div #activate player class:"ion-log-in"]
```
This gives `#activate` its behavior. When an `#activate` button is clicked, Eve finds out how many active players are in the lineup then adds the `clicked-player` and assigns them the next number in the lineup.
```
search @browser @event @session
[#click element: [#activate player:clicked-player]]
active-players = if c = count[given:[#player not(#bench) not(#injured)]] then c else 0
active-players != 9
commit
clicked-player -= #bench
clicked-player.lineup := active-players + 1
```
## Roster Data
Our sample roster data.
```
commit
[#player firstname:"Kenny" lastname:"DeNunez" position:"P" bats:"R" lineup:3 photo:"http://i.imgur.com/kaRnA7R.png"]
[#player firstname:"Hamilton" lastname:"Porter" nickname:"Ham" position:"C" bats:"R" lineup:1 photo:"http://i.imgur.com/7C678n2.jpg"]
[#player firstname:"Timmy" lastname:"Timmons" position:"IF" bats:"R" lineup:8 photo:"http://i.imgur.com/JLHupGR.png"]
[#player firstname:"Bertram" lastname:"Grover Weeks" position:"IF" bats:"R" lineup:2 photo:"http://i.imgur.com/mfIMIy6.png"]
[#player firstname:"Alan" lastname:"McClennan" nickname:"Yeah-Yeah" position:"IF" bats:"R" lineup:6 photo:"http://i.imgur.com/ZyLzCDn.jpg"]
[#player firstname:"Benny" lastname:"Rodriguez" nickname:"The Jet" position:"IF" bats:"S" lineup:9 photo:"http://i.imgur.com/UOywuNz.jpg"]
[#player firstname:"Scott" lastname:"Smalls" position:"OF" bats:"R" lineup:5 photo:"http://i.imgur.com/eBZ2m17.jpg"]
[#player firstname:"Michael" lastname:"Palledorous" nickname:"Squints" position:"OF" bats:"R" lineup:4 photo:"http://i.imgur.com/q8KKRz6.jpg"]
[#player firstname:"Tommy" lastname:"Timmons" nickname:"Repeat" position:"OF" bats:"R" lineup:7 photo:"http://i.imgur.com/QPzSCGy.png"]
[#player #bench firstname:"Thelonius" lastname:"Mertle" position:"IF" bats:"L" lineup:"" photo:"http://i.imgur.com/XDA0ftH.jpg"]
[#player #bench firstname:"George Herman" lastname:"Ruth" nickname:"Babe" position:"P" bats:"L" lineup:"" photo:"http://i.imgur.com/kep7Unm.jpg"]
[#player #bench firstname:"Hercules" lastname:"Mertle" nickname:"The Beast" position:"PR" bats:"S" lineup:"" photo:"http://i.imgur.com/WOwMn5c.jpg"]
[#player #injured firstname:"" lastname:"Phillips" position:"IF" bats:"R" lineup:"" photo:"http://i.imgur.com/Qvxya5C.jpg"]
```
## Styles
The app needs a good bit of CSS to organize the page sections and various buttons as well as stylize the player cards.
```css
{there is currently a bug that causes the first CSS block in an Eve program to be disregarded, so for a good time, leave this here}
```
```css
.container {
display: flex;
flex-direction: row;
user-select: none;
font-size: 20px;
text-transform: uppercase;
text-align: center;
overflow: scroll;
height: 1000px;
border-bottom: 1px solid #555;
flex: 1 1 auto;
}
.IR {
width: 515px;
margin-top: 30px;
flex: 1 0 auto;
}
.lineup {
display: flex;
flex-direction: column;
font-size: 20px;
text-transform: uppercase;
text-align: center;
margin-right: 50px;
position: relative;
flex: 1 0 515;
width: 515px;
min-width: 515px;
overflow: scroll;
}
.lineup-position {
list-style: none;
display: flex;
flex-direction: row;
align-items: center;
margin-top: 15px;
position: relative;
}
.bat-number {
order: 1;
font-size: 40px;
font-weight: bold;
color: #b4b4b4;
margin-right: 15px;
width: 50px;
}
.player-info {
display: flex;
flex-direction: row;
margin: 0px 0px;
padding: 0px 0px 0px 0px;
height: 85px;
width: 450px;
background: #ffffff;
border: 1px solid #555;
border-radius: 8px;
order: 2;
overflow: hidden;
}
.name-line {
display: flex;
flex-direction: row;
margin: 10px 10px;
}
.position {
font-size: 14px;
font-weight: bold;
margin-right: 8px;
padding-top: 2px;
height: 16px;
}
.first-name {
font-size: 16px;
text-transform: uppercase;
height: 16px;
}
.middle-name {
font-size: 16px;
text-transform: uppercase;
height: 16px;
font-weight: 600;
white-space: pre;
}
.last-name {
font-size: 16px;
text-transform: uppercase;
height: 16px;
}
.player-photo {
height: 85px;
width: 85px;
border-right: 1px solid #555;
}
.bat-hand {
font-size: 14px;
height: 14px;
text-align: left;
margin: 10px;
}
.ion-chevron-up {
position: absolute;
top: 2px;
right: 15px;
font-size: 24px;
cursor: pointer;
}
.ion-chevron-down {
position: absolute;
bottom: 2px;
right: 15px;
font-size: 24px;
cursor: pointer;
}
.ion-log-out {
position: absolute;
right: 15px;
color: #e65b3c;
font-size: 24px;
cursor: pointer;
}
.ion-log-in {
position: absolute;
right: 15px;
transform: scaleX(-1);
color: #009ee0;
font-size: 24px;
cursor: pointer;
}
```
```css
@media (max-width: 1848px) {
.container {
flex-direction: column;
border-bottom: none;
height: auto;
flex: 0 0 auto;
}
.IR {
width: 515px;
margin-top: 0px;
}
.lineup {
display: flex;
font-size: 18px;
margin-right: 0px;
flex: 0 0 auto;
width: 415px;
min-width: 415px;
min-height: 100px;
margin-bottom: 20px;
}
.lineup-position {
margin-top: 12px;
}
.bat-number {
font-size: 30px;
margin-right: 15px;
width: 50px;
}
.player-info {
height: 45px;
width: 400px;
}
.name-line {
margin: 5px 8px;
}
.position {
font-size: 10px;
margin-right: 8px;
}
.first-name {
font-size: 12px;
}
.middle-name {
font-size: 12px;
}
.last-name {
font-size: 12px;
}
.player-photo {
height: 45px;
width: 45px;
}
.bat-hand {
font-size: 10px;
margin: 5px 8px;
}
.ion-chevron-up {
top: 0px;
right: 15px;
font-size: 16px;
}
.ion-chevron-down {
bottom: 0px;
right: 15px;
font-size: 16px;
}
.ion-log-out {
right: 15px;
font-size: 16px;
}
.ion-log-in {
right: 15px;
font-size: 16px;
}
}
```
```css
@media (max-width: 1200px) {
.container {
flex-direction: column;
border-bottom: none;
height: auto;
flex: 0 0 auto;
}
.IR {
width: 515px;
margin-top: 0px;
}
.lineup {
display: flex;
font-size: 18px;
margin-right: 0px;
flex: 0 0 auto;
width: 100%;
min-width: 150px;
min-height: 100px;
margin-bottom: 20px;
}
.lineup-position {
margin-top: 12px;
}
.bat-number {
font-size: 30px;
margin-right: 0px;
width: 0px;
}
.player-info {
height: 55px;
width: 100%;
}
.name-line {
margin: 2px 8px;
margin-top: 5px;
flex-wrap: wrap;
}
.position {
font-size: 8px;
margin-right: 5px;
}
.first-name {
font-size: 10px;
}
.middle-name {
font-size: 10px;
}
.last-name {
font-size: 10px;
}
.player-photo {
height: 55px;
width: 55px;
}
.bat-hand {
font-size: 10px;
margin: 0px 8px;
}
.ion-chevron-up {
top: 7px;
right: 8px;
font-size: 12px;
}
.ion-chevron-down {
bottom: 6px;
right: 8px;
font-size: 12px;
}
.ion-log-out {
right: 8px;
font-size: 12px;
}
.ion-log-in {
right: 8px;
font-size: 12px;
}
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment