6

This is a somewhat noob-question for someone who had a few years of web development experience, but after not finding the answer on either Programmer Stack Exchange or Google, I have decided to ask it here.

I am using Express web framework for Node.js, but this question is not specific to any web framework or programming language.

Here's a list of games that are queried from the database. Each game entity is a single table row, generated using a for-loop:

    table.table
      tbody
        for game in games
          tr
            td.span2
              img.img-polaroid(src='/img/games/#{game.largeImage}')   
              // continues further  

enter image description here

Each Rating block, as well as each Buy button/modal dialog are generated by the for-loop with an id that matches the game. For example, the Buy button for Assassin's Creed will have id="price-assassins-creed". #{variable} - is how you reference a variable in Jade, passed in from the server.

button.btn.btn-primary.btn-mini(id='price-#{game.slug}', href='#buyModal', role='button', data-toggle='modal')

and

.modal.hide.fade(id='modal-#{game.slug}', tabindex='-1', role='dialog', aria-labelledby='myModalLabel', aria-hidden='true')
            .modal-header
              span.lead Game Checkout
                img.pull-right(src='/img/new_visa_medium.gif')
            .modal-body
              label
                i.icon-user
                |  Name on Card
              input.input-medium(type='text')
              label
                i.icon-barcode
                |  Card Number
              input.input-medium(type='text', placeholder='•••• •••• •••• ••••', maxlength=16)

              label
                i.icon-time
                |  Expiration Date
              input.input-mini(type='text', placeholder='MMYY', maxlength=4)
              label
                i.icon-qrcode
                |  Card Code
              input.input-mini(type='text', placeholder='CVC', maxlength=4)
            .modal-footer
              button.btn(data-dismiss='modal', aria-hidden='true') Cancel
              button.btn.btn-primary(id='#{game.slug}') Buy

and

script(type='text/javascript')
  $('#_#{game.slug}').raty({
    path: '/img',
    round : { down: .25, full: .6, up: .76 },
    score: #{game.rating}/#{game.votes},
    readOnly: true
  });

Multiply that by the number of games and that's how many inline scripts I have on one page.

Worse yet, I have to account for the following cases:

  • User's not logged-in: display above rating script in read-only mode.
  • User's logged-in, but hasn't voted yet:

...in that case, use the following script:

script(type='text/javascript')
                    $('#_#{game.slug}').raty({
                      path: '/img',
                      round : { down: .25, full: .6, up: .76 },
                      score: #{game.rating}/#{game.votes},
                      readOnly: false,
                      click: function (score, event) {
                        var self = this;
                        $.meow({
                          message: 'Thanks for voting. Your rating has been recorded.',
                          icon: 'http://png-3.findicons.com/files/icons/1577/danish_royalty_free/32/smiley.png'
                        });
                        $.ajax({
                          type: 'POST',
                          url: '/games/rating',
                          data: {
                            slug: $(self).attr('id').slice(1),
                            rating: score
                          },
                          success: function () {
                            console.log('setting to read-only');
                            $(self).raty('readOnly', true);
                          }
                        });
                      }
                    });
  • User's logged-in but suspended from rating: Copy and paste yet another read-only script for this particular if-else condition.

Long story short, it has become a maintenance nightmare trying to maintain all this JavaScript in my .jade template files, and my markup looks unacceptably dirty.

What's a solution for this? This seems like such a common scenario for CRUD applications. Ideally I would like to move all javascript to a separate .js file. But if I could remove some code duplication, that would be great too.

The problem is if I move inline javascript to a separate file how do I know which game am I rating? How do I know which Buy button has user clicked on?

Right now there is no ambiguity because for N games I have N buy buttons, N modal dialogs and N rating scripts. Regardless of what anyone thinks of this style of programming, it's an awful way to maintain the code.

Please share some insight with a noobie!

Thank you in advance.

Here's a complete code snippet of my games.jade file:

extends layout

block content
  br
  ul.nav.nav-pills
    if heading === 'Top 25'
      li.active
        a(href='/games') Top 25
    else
      li
        a(href='/games') Top 25

    if heading === 'Action'
      li.active
        a(href='/games/genre/action') Action
    else
      li
        a(href='/games/genre/action') Action

    if heading === 'Adventure'
      li.active
        a(href='/games/genre/adventure') Adventure
    else
      li
        a(href='/games/genre/adventure') Adventure

    if heading === 'Driving'
      li.active
        a(href='/games/genre/driving') Driving
    else
      li
        a(href='/games/genre/driving') Driving

    if heading === 'Puzzle'
      li.active
        a(href='/games/genre/puzzle') Puzzle
    else
      li
        a(href='/games/genre/puzzle') Puzzle

    if heading === 'Role-Playing'
      li.active
        a(href='/games/genre/role-playing') Role-Playing
    else
      li
        a(href='/games/genre/role-playing') Role-Playing

    if heading === 'Simulation'
      li.active
        a(href='/games/genre/simulation') Simulation
    else
      li
        a(href='/games/genre/simulation') Simulation

    if heading === 'Strategy'
      li.active
        a(href='/games/genre/strategy') Strategy
    else
      li
        a(href='/games/genre/strategy') Strategy

    if heading === 'Sports'
      li.active
        a(href='/games/genre/sports') Sports
    else
      li
        a(href='/games/genre/sports') Sports


  if games.length == 0
    .alert.alert-warning
      | Database query returned no results.
  else
    table.table
      tbody
        for game in games
          .modal.hide.fade(id='modal-#{game.slug}', tabindex='-1', role='dialog', aria-labelledby='myModalLabel', aria-hidden='true')
            .modal-header
              span.lead Game Checkout
                img.pull-right(src='/img/new_visa_medium.gif')
            .modal-body
              label
                i.icon-user
                |  Name on Card
              input.input-medium(type='text')
              label
                i.icon-barcode
                |  Card Number
              input.input-medium(type='text', placeholder='•••• •••• •••• ••••', maxlength=16)

              label
                i.icon-time
                |  Expiration Date
              input.input-mini(type='text', placeholder='MMYY', maxlength=4)
              label
                i.icon-qrcode
                |  Card Code
              input.input-mini(type='text', placeholder='CVC', maxlength=4)
            .modal-footer
              button.btn(data-dismiss='modal', aria-hidden='true') Cancel
              button.btn.btn-primary(id='#{game.slug}') Buy
          tr
            td.span2
              img.img-polaroid(src='/img/games/#{game.largeImage}')
            td
              a(href='/games/#{game.slug}')
                strong
                  = game.title
              |  

              if user.userName
                button.btn.btn-primary.btn-mini(id='price-#{game.slug}', href='#modal-#{game.slug}', role='button', data-toggle='modal')
                  i.icon-shopping-cart.icon-white
                  =  game.price
                if user.purchasedGames && user.purchasedGames.length > 0
                  for mygame in user.purchasedGames
                    if mygame.game.slug == game.slug
                      script(type='text/javascript')
                        $('#price-#{game.slug}').removeAttr('href');
                        $('#price-#{game.slug}').html('<i class="icon-shopping-cart icon-white"></i> Purchased');

              div
                span(id='_' + game.slug)
                span(id='votes', name='votes')
                |  (#{game.votes} votes)
              div
                small.muted
                  div #{game.releaseDate} | #{game.publisher}
                  div #{game.genre}
              p
                =game.description

          // logged-in users
          if user.userName
            if game.votedPeople.length > 0
              for voter in game.votedPeople
                if voter == user.userName || user.suspendedRating
                  script(type='text/javascript')
                    $('#_#{game.slug}').raty({
                      path: '/img',
                      round : { down: .25, full: .6, up: .76 },
                      score: #{game.rating}/#{game.votes},
                      readOnly: true
                    });
                else
                  script(type='text/javascript')
                    $('#_#{game.slug}').raty({
                      path: '/img',
                      round : { down: .25, full: .6, up: .76 },
                      score: #{game.rating}/#{game.votes},
                      readOnly: false,
                      click: function (score, event) {
                        var self = this;
                        $.meow({
                          message: 'Thanks for voting. Your rating has been recorded.',
                          icon: 'http://png-3.findicons.com/files/icons/1577/danish_royalty_free/32/smiley.png'
                        });
                        $.ajax({
                          type: 'POST',
                          url: '/games/rating',
                          data: {
                            slug: $(self).attr('id').slice(1),
                            rating: score
                          },
                          success: function () {
                            console.log('setting to read-only');
                            $(self).raty('readOnly', true);
                          }
                        });
                      }
                    });
            else
              if (user.suspendedRating)
                script(type='text/javascript')
                  $('#_#{game.slug}').raty({
                    path: '/img',
                    round : { down: .25, full: .6, up: .76 },
                    score: #{game.rating}/#{game.votes},
                    readOnly: true
                  });
              else
                script(type='text/javascript')
                  $('#_#{game.slug}').raty({
                        path: '/img/',
                        round : { down: .25, full: .6, up: .76 },
                        score: #{game.rating}/#{game.votes},
                        readOnly: false,
                        click: function (score, event) {
                          var self = this;
                          $.meow({
                            message: 'Thanks for voting. Your rating has been recorded.',
                            icon: 'http://png-3.findicons.com/files/icons/1577/danish_royalty_free/32/smiley.png'
                          });
                          $.ajax({
                            type: 'POST',
                            url: '/games/rating',
                            data: {
                              slug: $(self).attr('id').slice(1),
                              rating: score
                            },
                            success: function () {
                              console.log('setting to read-only');
                              $(self).raty('readOnly', true);
                            }
                          });
                        }
                      });
          else
            script(type='text/javascript')
              $('#_#{game.slug}').raty({
                path: '/img',
                round : { down: .25, full: .6, up: .76 },
                score: #{game.rating}/#{game.votes},
                readOnly: true
              });

          script(type='text/javascript')
            $('##{game.slug}').click(function() {
              var game = this;
              $.ajax({
                type: 'post',
                url: '/buy',
                data:  {
                  slug: $(game).attr('id')
                }
              }).success(function () {
                $('#price-#{game.slug}').attr('disabled', 'true');
                $('#modal-' + $(game).attr('id')).modal('hide');
                humane.log('Your order has been submitted!');
              });
            });
1
  • 3
    Big wall of text but a pretty good question. +1 Commented Nov 27, 2012 at 7:36

1 Answer 1

3

That was way too long to read. Anyways, I think I get the gist of what you're saying and would use a format like this:

<div id="some_container">
    <!-- The following div would be generated in each iteration of the for loop you speak of -->
    <div class="item-container" data-game-name="Your game name" data-game-id="23">
        <span class="button delete-button">X</span>
    </div>
</div>

And your script would be something with delegation (to minimize the number of bindings greatly):

$(document).ready(function () {
    $("#some_container").on("click", ".delete-button", function () {
        var $this = $(this);
        var $container = $this.closest(".item-container");
        var game_name = $container.data("game-name");
        var game_id = $container.data("game-id");
        // Do whatever with the above 2 variables
    });
});

As for the modal stuff, you could either create a <div> that has a "template" for what to display for whatever. Then, when you click on whatever button, you use logic like the above (get the first parent .item-container to get details on the item, them fill in the "template" in the modal with this information. Then that way, the "OK" button doesn't need a million hardcoded things - just one - grab the information from the template and make whatever call or submit the form.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, I ended up using something similar by putting my server data into HTML data-* attributes. Although I can't put too much information, like all usernames who voted on a particular game (privacy concerns), so I end up doing some logic in my template which will result in a boolean variable True/False which will be passed to client-side inside data-* attribute.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.