This is a question related to the noob problem for those who have been studying web development for several years, but after I couldn’t find the answer either on the software stack or on Google, I decided to ask it here.
I use Express Web Framework for Node.js, but this question does not apply to any web infrastructure or programming language.
Here is a list of games that are requested from the database. Each game object is a single row of a table created using a for loop:
table.table tbody for game in games tr td.span2 img.img-polaroid(src='/img/games/#{game.largeImage}')

Each Rating block, as well as each Buy button / modal dialog, is generated for-loop with an identifier that corresponds to the game. For example, the Buy button for Assassin Creed will have id = "price-assassins-creed". # {variable} is how you refer to a variable in Jade passed 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:
Multiply this by the number of games and the number of built-in scripts that I have on one page.
Even worse, I have to consider the following cases:
- User did not log in: display the script rating above in read-only mode.
- The user has logged in but has not yet voted:
... in this case, use the following script:
script(type='text/javascript') $('#_#{game.slug}').raty({ path: '/img', round : { down: .25, full: .6, up: .76 }, score:
- The user is logged in but suspended from the rating: copy and paste another readable script for this specific if-else condition.
In short, it became a maintenance nightmare, trying to keep all this JavaScript in my .jade files, and my markup looks unacceptably dirty.
What is the 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 code duplication, that would be great too.
The problem is that I am moving the embedded javascript to a separate file, how do I know which game I rate? How to find out which purchase button a user clicked on?
Right now there is no ambiguity, because for N games I have N buy buttons, N modal dialogs and N script ratings. No matter what anyone thinks of this programming style, this is a terrible way to maintain code.
Please share the information with noobie!
Thanks in advance.
Here is the 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!'); }); });