Bruk av Opal Ruby med skinner 7

Opal gjør det mulig å skrive webfront-end-kode i Ruby, og produserer dermed svært vedlikeholdbar, produktiv og forståelig kode både på klientsiden og serversiden.

Nedenfor presenterer jeg 3 forskjellige eksempler på bruk av Opal Ruby på Rails 7.

Basic Opal Rails 7 Eksempel

Rails 7 kom nylig ut med forenklede standardinnstillinger, inkludert standard tilbake til Sprockets.

Å sette opp Opal på Rails 7 er derfor en lek.

Du kan følge disse instruksjonene for å få en Hello, World! Opal eksempel som kjører i Rails 7:

Opal Rails 7 Eksempel

1- Generer en ny Rails-app med:

rails new rails_app

2- I Gemfile , legge til:

gem 'opal-rails'

3-
Kjør opal:install Skinnegenerator for å legge til app/assets/javascript til aktiva-pipeline-manifestet ditt i app/assets/config/manifest.js :

bin/rails g opal:install

4- Slett app/javascript/application.js

5- Aktiver følgende linjer i den genererte app/assets/javascript/application.js.rb etter require "opal" :

puts 'hello world!'

require 'native'

$$.document.addEventListener(:DOMContentLoaded) do
  $$.document.body.innerHTML = '<h2>Hello World!</h2>'
end

6- Kjør rails g scaffold welcome

7- Kjør rails db:migrate

8- Fjern app/views/welcomes/index.html.erb (tøm innholdet)

9- Kjør rails s

10- Besøk http://localhost:3000/welcomes

På nettleserens nettside bør du se:

Hei verden!

Du bør også se hello world! i nettleserkonsollen.

Advanced Opal Rails 7 Eksempel

La oss deretter bygge en komplett Rails-applikasjon med Opal Ruby i stedet for JavaScript, kalt baseballkort!

Det vil være en animert applikasjon for å lage baseballkort som ganske enkelt tar et spillernavn, lag og posisjon, og gjengir et baseballkort live mens informasjonen skrives inn i et WYSIWYG-skjema.

Skjemaet ser opp tilfeldige spilleranimerte gifs på Giphy. Hvis du ikke liker det tilfeldig valgte bildet, kan du klikke på "Another Player Image"-knappen for å endre det. Ellers legger skjemaet også til et bilde for den valgte baseballlaglogoen, og det redigerer et SVG-element live som representerer spillerposisjonen (f.eks. hvis spilleren er en spiller på 1. base, lyser den delen av SVG gult). Slik ser skjemaet "Nytt baseballkort" ut:

Normalt må JavaScript være involvert for å bygge baseballkortet interaktivt, men takket være Opal kan vi skrive det meste av koden i ren Ruby i stedet. Merk at noe Opal Native-kode også ble blandet inn (det vil si å bruke haker to execute small bits of JS inside the [Ruby](https://www.ruby-lang.org/) code just like when you use ticks i Cruby for å gå inn i kommandolinjeterminalen), og dermed demonstrere denne Opal-funksjonen også.

Kodeløsningen er inkludert nedenfor (merk at siden det bare er en demo, har jeg stort sett innebygd CSS i elementene i _baseball_card.html.erb delvis).

Opal Rails 7 avansert eksempel (baseballkort)

1- Kjør:

rails new baseball_cards

2- Kjør:

rails g scaffold baseball_cards name:string team:string position:string

3- Kjør:

rails g migration add_image_url_to_baseball_cards image_url:string

4- Kjør:

rails db:migrate

5- I Gemfilen din, legg til følgende og bundle :

gem 'opal-rails'

6- Løp:

bin/rails g opal:install

7- Slett app/javascript/application.js

8- Erstatt innholdet i følgende filer med følgende kode:

app/assets/javascript/application.js.rb

require 'opal'
require 'native'
require 'json'

card_image_updater = proc do
  name_input = $$.document.getElementById('baseball_card_name')
  unless name_input.value.empty?
    url = "http://api.giphy.com/v1/gifs/search?q=#{name_input.value}&limit=20&api_key=fM6ptBz7qPw79xrXOagWvHiPzRBSQK7f"
    xhttp = Native(`new XMLHttpRequest`)
    xhttp.onload = proc do |response|
      if `this.readyState` == 4 && `this.status` == 200
        response_hash = JSON.parse(`this.responseText`)
        image_url = response_hash['data'].sample['url']
        image_url = "https://media1.giphy.com/media/#{image_url.split('-').last}/giphy.gif"
        card_element = $$.document.querySelectorAll('.card')[0]
        card_element.style['background-image'] = "url(#{image_url})"
        hidden_image_url_field = $$.document.getElementById('baseball_card_image_url')
        hidden_image_url_field.value = image_url
      end
    end
    xhttp.open('GET', url, true)
    xhttp.send
  end
end

$$.document.addEventListener(:DOMContentLoaded) do
  name_input = $$.document.getElementById('baseball_card_name')

  name_input&.addEventListener(:change) do
    card_name = $$.document.getElementById('card_name')
    card_name.innerHTML = name_input.value
    card_image_updater.call
  end

  team_select = $$.document.getElementById('baseball_card_team')

  team_select&.addEventListener(:change) do
    card_team_image = $$.document.getElementById('card_team')
    card_team_value = team_select.value.downcase.gsub(' ', '-')
    card_team_value = 'redsox' if card_team_value == 'red-sox' # special case for the red sox
    image_url = "https://sportslogosvg.com/wp-content/uploads/2020/09/#{card_team_value}-1200x864.png"
    card_team_image.style['display'] = 'inline-block'
    card_team_image.src = image_url
  end

  position_select = $$.document.getElementById('baseball_card_position')

  position_select&.addEventListener(:change) do
    card_position_image = $$.document.getElementById('card_position')
    card_position_image.style['display'] = 'inline-block'
    svg_element_id = "text-#{position_select.value.downcase.gsub(' ', '-')}"
    $$.document.querySelectorAll('svg text').to_a.each { |text| text.style['fill'] = 'transparent'}
    $$.document.getElementById(svg_element_id).style['fill'] = 'yellow'
  end

  update_card_player_image_button = $$.document.getElementById('update_card_player_image')

  update_card_player_image_button&.addEventListener(:click) do |event|
    Native(event).preventDefault
    card_image_updater.call
  end
end

config/routes.rb

Rails.application.routes.draw do
  resources :baseball_cards
  root "baseball_cards#index"
end

app/models/baseball_card.rb

class BaseballCard < ApplicationRecord
  TEAMS = [
    {town: 'Chicago', team: 'White Sox'},
    {town: 'Cleveland', team: 'Guardians'},
    {town: 'Detroit', team: 'Tigers'},
    {town: 'Kansas City', team: 'Royals'},
    {town: 'Minnesota', team: 'Twins'},
    {town: 'Baltimore', team: 'Orioles'},
    {town: 'Boston', team: 'Red Sox'},
    {town: 'New York', team: 'Yankees'},
    {town: 'Tampa Bay', team: 'Rays'},
    {town: 'Toronto', team: 'Blue Jays'},
    {town: 'Houston', team: 'Astros'},
    {town: 'Los Angeles', team: 'Angels'},
    {town: 'Oakland', team: 'Athletics'},
    {town: 'Seattle', team: 'Mariners'},
    {town: 'Texas', team: 'Rangers'},
    {town: 'Chicago', team: 'Cubs'},
    {town: 'Cincinnati', team: 'Reds'},
    {town: 'Milwaukee', team: 'Brewers'},
    {town: 'Pittsburgh', team: 'Pirates'},
    {town: 'St. Louis', team: 'Cardinals'},
    {town: 'Atlanta', team: 'Braves'},
    {town: 'Miami', team: 'Marlins'},
    {town: 'New York', team: 'Mets'},
    {town: 'Philadelphia', team: 'Phillies'},
    {town: 'Washington', team: 'Nationals'},
    {town: 'Arizona', team: 'Diamondbacks'},
    {town: 'Colorado', team: 'Rockies'},
    {town: 'Los Angeles', team: 'Dodgers'},
    {town: 'San Diego', team: 'Padres'},
    {town: 'San Francisco', team: 'Giants'},
  ]

  POSITIONS = [
    'Pitcher',
    'Catcher',
    '1st Base',
    '2nd Base',
    '3rd Base',
    'Shortstop',
    'Left Field',
    'Center Field',
    'Right Field',
  ]

  validates :name, presence: true
  validates :image_url, presence: true
end

app/controllers/baseball_cards_controller.rb

class BaseballCardsController < ApplicationController
  before_action :set_baseball_card, only: %i[ show edit update destroy ]

  # GET /baseball_cards or /baseball_cards.json
  def index
    @baseball_cards = BaseballCard.all
  end

  # GET /baseball_cards/1 or /baseball_cards/1.json
  def show
  end

  # GET /baseball_cards/new
  def new
    @baseball_card = BaseballCard.new
  end

  # GET /baseball_cards/1/edit
  def edit
  end

  # POST /baseball_cards or /baseball_cards.json
  def create
    @baseball_card = BaseballCard.new(baseball_card_params)

    respond_to do |format|
      if @baseball_card.save
        format.html { redirect_to baseball_card_url(@baseball_card), notice: "Baseball card was successfully created." }
        format.json { render :show, status: :created, location: @baseball_card }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @baseball_card.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /baseball_cards/1 or /baseball_cards/1.json
  def update
    respond_to do |format|
      if @baseball_card.update(baseball_card_params)
        format.html { redirect_to baseball_card_url(@baseball_card), notice: "Baseball card was successfully updated." }
        format.json { render :show, status: :ok, location: @baseball_card }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @baseball_card.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /baseball_cards/1 or /baseball_cards/1.json
  def destroy
    @baseball_card.destroy

    respond_to do |format|
      format.html { redirect_to baseball_cards_url, notice: "Baseball card was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_baseball_card
      @baseball_card = BaseballCard.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def baseball_card_params
      params.require(:baseball_card).permit(:name, :team, :position, :image_url)
    end
end

app/helpers/baseball_cards_helper.rb

module BaseballCardsHelper
  def team_options_for_select(selected=nil)
    teams = BaseballCard::TEAMS.reduce({}) do |hash, town_team_hash|
      hash.merge(town_team_hash.values.join(' ') => town_team_hash[:team])
    end
    options_for_select(teams, selected)
  end
end

app/views/baseball_cards/_baseball_card.html.erb

<% baseball_card ||= @baseball_card %>

<div class="card" style="float: left; margin: 10px; position: relative; background-size: cover; width: 200px; height: 300px; background-position-x: center; box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); background-image: url(<%= baseball_card&.image_url %>);">
  <div style="position: absolute; bottom: 0px;">
    <img id="card_team" src="<%= "https://sportslogosvg.com/wp-content/uploads/2020/09/#{baseball_card&.team&.downcase == 'red sox' ? 'redsox' : baseball_card&.team&.downcase&.sub(' ', '-')}-1200x864.png" %>" height="30" style="display: <%= baseball_card&.team ? 'inline-block' : 'none' %>; vertical-align: middle;" />

    <span id="card_name" style="display: inline-block; vertical-align: middle; text-align: center; color: white; font-size: 16px; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;">
      <%= baseball_card&.name %>
    </span>

    <svg
       xmlns:dc="http://purl.org/dc/elements/1.1/"
       xmlns:cc="http://web.resource.org/cc/"
       xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
       xmlns:svg="http://www.w3.org/2000/svg"
       xmlns="http://www.w3.org/2000/svg"
       xmlns:xlink="http://www.w3.org/1999/xlink"
       xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
       xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
       height="35"
       viewBox="0 0 611.73914 511.06744"
       id="card_position"
       style="display: <%= baseball_card&.position ? 'inline-block' : 'none' %>; vertical-align: middle;"
       sodipodi:version="0.32"
       inkscape:version="0.45.1"
       version="1.0"
       sodipodi:docbase="C:\Documents and Settings\Chris\Desktop\baseball"
       sodipodi:docname="Baseball C.svg"
       inkscape:output_extension="org.inkscape.output.svg.inkscape">
      <defs
         id="defs4">
        <linearGradient
           id="linearGradient6183">
          <stop
             style="stop-color:#7e4317;stop-opacity:1;"
             offset="0"
             id="stop6185" />
          <stop
             style="stop-color:#953100;stop-opacity:1;"
             offset="1"
             id="stop6187" />
        </linearGradient>
        <linearGradient
           id="linearGradient5141">
          <stop
             style="stop-color:#ffffff;stop-opacity:1;"
             offset="1"
             id="stop5143" />
          <stop
             style="stop-color:#ffffff;stop-opacity:0;"
             offset="1"
             id="stop5145" />
        </linearGradient>
        <radialGradient
           inkscape:collect="always"
           xlink:href="#linearGradient5141"
           id="radialGradient5147"
           cx="408.7468"
           cy="-181.38609"
           fx="408.7468"
           fy="-181.38609"
           r="306.80814"
           gradientTransform="matrix(0.1303747,0.4367551,-1.3559209,0.404753,-20.407009,433.33976)"
           gradientUnits="userSpaceOnUse" />
        <radialGradient
           inkscape:collect="always"
           xlink:href="#linearGradient5141"
           id="radialGradient5170"
           gradientUnits="userSpaceOnUse"
           gradientTransform="matrix(-0.1020632,0.3143125,-0.2847171,-9.2452958e-2,409.38007,231.54454)"
           cx="992.91998"
           cy="429.55511"
           fx="992.91998"
           fy="429.55511"
           r="306.80814" />
        <radialGradient
           inkscape:collect="always"
           xlink:href="#linearGradient6183"
           id="radialGradient6191"
           cx="528.15991"
           cy="389.72467"
           fx="528.15991"
           fy="389.72467"
           r="306.91226"
           gradientTransform="matrix(-0.466682,0.4905325,-0.4878269,-0.46411,806.88847,412.71494)"
           gradientUnits="userSpaceOnUse" />
        <radialGradient
           inkscape:collect="always"
           xlink:href="#linearGradient6183"
           id="radialGradient13054"
           gradientUnits="userSpaceOnUse"
           gradientTransform="matrix(-0.466682,0.4905325,-0.4878269,-0.46411,806.88847,412.71494)"
           cx="528.15991"
           cy="389.72467"
           fx="528.15991"
           fy="389.72467"
           r="306.91226" />
        <linearGradient
           inkscape:collect="always"
           xlink:href="#linearGradient6183"
           id="linearGradient13056"
           gradientUnits="userSpaceOnUse"
           x1="319.04822"
           y1="771.89484"
           x2="288.61502"
           y2="646.47705" />
      </defs>
      <sodipodi:namedview
         id="base"
         pagecolor="#ffffff"
         bordercolor="#666666"
         borderopacity="1.0"
         gridtolerance="10000"
         guidetolerance="10"
         objecttolerance="10"
         inkscape:pageopacity="0.0"
         inkscape:pageshadow="2"
         inkscape:zoom="1"
         inkscape:cx="287.62199"
         inkscape:cy="295.73785"
         inkscape:document-units="px"
         inkscape:current-layer="layer1"
         inkscape:window-width="1024"
         inkscape:window-height="721"
         inkscape:window-x="-4"
         inkscape:window-y="-4" />
      <metadata
         id="metadata7">
        <rdf:RDF>
          <cc:Work
             rdf:about="">
            <dc:format>image/svg+xml</dc:format>
            <dc:type
               rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
          </cc:Work>
        </rdf:RDF>
      </metadata>
      <g
         inkscape:label="Layer 1"
         inkscape:groupmode="layer"
         id="layer1"
         transform="translate(-74.602823,-339.39469)">
        <g
           id="g14033">
          <g
             transform="translate(-4,40)"
             id="g13047">
            <g
               id="g10125">
              <path
                 transform="matrix(2.5051227,0,0,1.1727609,-111.7863,-106.80524)"
                 sodipodi:open="true"
                 sodipodi:end="6.2831853"
                 sodipodi:start="3.1333741"
                 d="M 76.00412,469.36483 A 122,122 0 1 1 320,468.36218"
                 sodipodi:ry="122"
                 sodipodi:rx="122"
                 sodipodi:cy="468.36218"
                 sodipodi:cx="198"
                 id="path5135"
                 style="fill:url(#radialGradient13054);fill-opacity:1;fill-rule:evenodd;stroke:#010000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
                 sodipodi:type="arc" />
              <path
                 transform="matrix(5.8551503,0,0,3.3940826,-1489.0661,-1875.1041)"
                 d="M 320,772.66144 L 293.88965,727.437 L 267.77931,682.21256 L 320,682.21255 L 372.22069,682.21255 L 346.11034,727.437 L 320,772.66144 z "
                 inkscape:randomized="0"
                 inkscape:rounded="0"
                 inkscape:flatsided="false"
                 sodipodi:arg2="2.6179939"
                 sodipodi:arg1="1.5707963"
                 sodipodi:r2="30.14963"
                 sodipodi:r1="60.299255"
                 sodipodi:cy="712.36218"
                 sodipodi:cx="320"
                 sodipodi:sides="3"
                 id="path5133"
                 style="fill:url(#linearGradient13056);fill-opacity:1;fill-rule:evenodd;stroke:#010000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
                 sodipodi:type="star" />
              <rect
                 transform="matrix(0.7006506,0.7135045,-0.7135045,0.7006506,0,0)"
                 y="86.912979"
                 x="638.72125"
                 height="164.22331"
                 width="164.22331"
                 id="rect7192"
                 style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.02508163;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
            </g>
          </g>
          <path
             transform="matrix(0.5974603,0,0,0.5974603,148.07055,602.81434)"
             d="M 470 263.36218 A 83 83 0 1 1  304,263.36218 A 83 83 0 1 1  470 263.36218 z"
             sodipodi:ry="83"
             sodipodi:rx="83"
             sodipodi:cy="263.36218"
             sodipodi:cx="387"
             id="path13062"
             style="fill:#833e11;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4.05200005;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
             sodipodi:type="arc" />
        </g>
        <text
           xml:space="preserve"
           style="font-size:56px;font-style:normal;font-weight:normal;fill:<%= baseball_card&.position == '1st Base' ? 'yellow' : 'transparent' %>;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial"
           x="459.22034"
           y="641.42615"
           id="text-1st-base"><tspan
             sodipodi:role="line"
             id="text14046"
             x="459.22034"
             y="641.42615">1B</tspan></text>
        <text
           xml:space="preserve"
           style="font-size:56px;font-style:normal;font-weight:normal;fill:<%= baseball_card&.position == '2nd Base' ? 'yellow' : 'transparent' %>;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial"
           x="417.87878"
           y="554.88501"
           id="text-2nd-base"><tspan
             sodipodi:role="line"
             id="tspan14056"
             x="417.87878"
             y="554.88501">2B</tspan></text>
        <text
           xml:space="preserve"
           style="font-size:56px;font-style:normal;font-weight:normal;fill:<%= baseball_card&.position == '3rd Base' ? 'yellow' : 'transparent' %>;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial"
           x="230.89096"
           y="641.27338"
           id="text-3rd-base"><tspan
             sodipodi:role="line"
             id="text14058"
             x="230.89096"
             y="641.27338">3B</tspan></text>
        <text
           xml:space="preserve"
           style="font-size:56px;font-style:normal;font-weight:normal;fill:<%= baseball_card&.position == 'Shortstop' ? 'yellow' : 'transparent' %>;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial"
           x="273.08279"
           y="554.52954"
           id="text-shortstop"><tspan
             sodipodi:role="line"
             id="tspan14064"
             x="273.08279"
             y="554.52954">SS</tspan></text>
        <text
           xml:space="preserve"
           style="font-size:56px;font-style:normal;font-weight:normal;fill:<%= baseball_card&.position == 'Right Field' ? 'yellow' : 'transparent' %>;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial"
           x="562.85089"
           y="455.53461"
           id="text-right-field"><tspan
             sodipodi:role="line"
             id="text14066"
             x="562.85089"
             y="455.53461">RF</tspan></text>
        <text
           xml:space="preserve"
           style="font-size:56px;font-style:normal;font-weight:normal;fill:<%= baseball_card&.position == 'Left Field' ? 'yellow' : 'transparent' %>;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial"
           x="119.05273"
           y="454.13171"
           id="text-left-field"><tspan
             sodipodi:role="line"
             id="text14074"
             x="119.05273"
             y="454.13171">LF</tspan></text>
        <text
           xml:space="preserve"
           style="font-size:56px;font-style:normal;font-weight:normal;fill:<%= baseball_card&.position == 'Center Field' ? 'yellow' : 'transparent' %>;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial"
           x="344.05273"
           y="394.13171"
           id="text-center-field"><tspan
             sodipodi:role="line"
             id="text14078"
             x="344.05273"
             y="394.13171">CF</tspan></text>
        <text
           xml:space="preserve"
           style="font-size:56px;font-style:normal;font-weight:normal;fill:<%= baseball_card&.position == 'Catcher' ? 'yellow' : 'transparent' %>;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial"
           x="360.05273"
           y="786.13171"
           id="text-catcher"><tspan
             sodipodi:role="line"
             id="text14082"
             x="360.05273"
             y="786.13171">C</tspan></text>
        <text
           xml:space="preserve"
           style="font-size:56px;font-style:normal;font-weight:normal;fill:<%= baseball_card&.position == 'Pitcher' ? 'yellow' : 'transparent' %>;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial"
           x="367.49219"
           y="676.40515"
           id="text-pitcher"><tspan
             sodipodi:role="line"
             id="text14086"
             x="367.49219"
             y="676.40515">P</tspan></text>
        <path
           sodipodi:type="arc"
           style="opacity:0.31111115;fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4.05200005;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
           id="path14108"
           sodipodi:cx="281.5"
           sodipodi:cy="150.56744"
           sodipodi:rx="45.5"
           sodipodi:ry="45.5"
           d="M 327 150.56744 A 45.5 45.5 0 1 1  236,150.56744 A 45.5 45.5 0 1 1  327 150.56744 z"
           transform="translate(98.60282,611.39469)" />
      </g>
    </svg>
  </div>
</div>

app/views/baseball_cards/_form.html.erb

<%= form_with(model: baseball_card) do |form| %>
  <% if baseball_card.errors.any? %>
    <div style="color: red">
      <h2><%= pluralize(baseball_card.errors.count, "error") %> prohibited this baseball_card from being saved:</h2>

      <ul>
        <% baseball_card.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div>
    <%= form.label :name, style: "display: block" %>
    <%= form.text_field :name %>
  </div>

  <div>
    <%= button_tag 'Another Player Image', type: '', id: :update_card_player_image %>
  </div>

  <div>
    <%= form.label :team, style: "display: block" %>
    <%= form.select :team, team_options_for_select(@baseball_card&.team) %>
  </div>

  <div>
    <%= form.label :position, style: "display: block" %>
    <%= form.select :position, BaseballCard::POSITIONS %>
  </div>

  <div>
    <%= form.hidden_field :image_url %>
    <%= form.submit %>
  </div>
<% end %>

<br />

<%= render @baseball_card %>

app/views/baseball_cards/index.html.erb

<p style="color: green"><%= notice %></p>

<h1>Baseball cards</h1>

<%= link_to "New baseball card", new_baseball_card_path %>

<div id="baseball_cards">
  <% @baseball_cards.each do |baseball_card| %>
    <%= link_to baseball_card do %>
      <%= render baseball_card, baseball_card: baseball_card %>
    <% end %>
  <% end %>
</div>

app/views/baseball_cards/new.html.erb

<h1>New baseball card</h1>

<div>
  <%= link_to "Back to baseball cards", baseball_cards_path %>
</div>

<br>

<%= render "form", baseball_card: @baseball_card %>

app/views/baseball_cards/edit.html.erb

<h1>Editing baseball card</h1>

<div>
  <%= link_to "Show this baseball card", @baseball_card %> |
  <%= link_to "Back to baseball cards", baseball_cards_path %>
</div>

<br>

<%= render "form", baseball_card: @baseball_card %>

9- Løp:

rails s

10- Besøk:

http://localhost:3000

Opal jQuery Eksempel

La oss deretter refaktorere koden for å bruke Opal jQuery i Ruby i stedet for vanlig Opal. Dette forenkler koden i app/assets/javascript/application.js.rb ganske mye:

1- I Gemfilen, legg til følgende og bunt:

gem 'opal-jquery'

2- Last ned jquery.js fra https://code.jquery.com/jquery-3.6.0.js og lagre på denne plasseringen (for senere å kunne legge til require 'jquery' til Opal-koden):

app/assets/javascript/jquery.js

3- Erstatt innholdet i filen app/assets/javascript/application.js.rb med følgende:

require 'opal'
require 'native'
require 'jquery'
require 'opal-jquery'

card_image_updater = proc do
  name_input = Element['#baseball_card_name']
  if !name_input.val.empty?
    url = "http://api.giphy.com/v1/gifs/search?q=#{name_input.value}&limit=20&api_key=fM6ptBz7qPw79xrXOagWvHiPzRBSQK7f"
    HTTP.get(url) do |response|
      if response.ok?
        response_hash = response.json
        image_url = response_hash['data'].sample['url']
        image_url = "https://media1.giphy.com/media/#{image_url.split('-').last}/giphy.gif"
        card_element = Element['.card']
        card_element.css('background-image', "url(#{image_url})")
        hidden_image_url_field = Element['#baseball_card_image_url']
        hidden_image_url_field.val(image_url)
      end
    end
  end
end

Document.ready? do
  name_input = Element['#baseball_card_name']

  name_input.on(:change) do
    card_name = Element['#card_name']
    card_name.html(name_input.value)
    card_image_updater.call
  end

  team_select = Element['#baseball_card_team']

  team_select.on(:change) do
    card_team_image = Element['#card_team']
    card_team_value = team_select.value.downcase.gsub(' ', '-')
    card_team_value = 'redsox' if card_team_value == 'red-sox' # special case for the red sox
    image_url = "https://sportslogosvg.com/wp-content/uploads/2020/09/#{card_team_value}-1200x864.png"
    card_team_image.css('display', 'inline-block')
    card_team_image.attr('src', image_url)
  end

  position_select = Element['#baseball_card_position']

  position_select.on(:change) do
    card_position_image = Element['#card_position']
    card_position_image.css('display', 'inline-block')
    svg_element_id = "text-#{position_select.value.downcase.gsub(' ', '-')}"
    Element['svg text'].each { |text| text.css('fill', 'transparent')}
    Element["##{svg_element_id}"].css('fill', 'yellow')
  end

  update_card_player_image_button = Element['#update_card_player_image']

  update_card_player_image_button.on(:click) do |event|
    event.prevent
    card_image_updater.call
  end
end

4- Kjør:

rails s

5- Besøk:

http://localhost:3000

Den samme appen skal fortsette å fungere, men med mer vedlikeholdbar Opal jQuery Ruby-kode!

En kanonisk versjon av prosjektet er tilgjengelig på GitHub:

https://github.com/AndyObtiva/baseball_cards

Også vert på Heroku:

http://animated-baseball-cards.herokuapp.com

Skål!