SlideShare a Scribd company logo
RUBYFYING JAVASCRIPT:RUBYFYING JAVASCRIPT:
AVOIDING JQUERY SPAGHETTIAVOIDING JQUERY SPAGHETTI
FORREST CHANGFORREST CHANG
FKCHANG2000@YAHOO.COMFKCHANG2000@YAHOO.COM
A JQUERY STORYA JQUERY STORY
Have a problem
Add a little jQuery
Fixed, yay!
AS TIME GOES ONAS TIME GOES ON
Add function, add function, nest function
Insert event handlers in DOM
Add business logic
Get input, dialogs
Ajax
Effects
Update DOM
Assign handlers to id
etc.
RESULTRESULT
JQUERY SPAGHETTIJQUERY SPAGHETTI
by Steve O'Brien in http://steve-obrien.com/javascript-jquery-spaghetti/
Without a strong framework or architecture you end up
with jQuery spaghetti. This is usually because you start
with a small piece of jquery shoved somewhere in the dom
as you add features it all grows out of proportion and
becomes a tangled mess. The most challenging thing is
maintaining state. Relying heavily on jQuery means your
application state information is stores in the dom, this
works well for small features and isolated components
here and there, but in a complex app it quickly becomes
very difficult to manage.
JQUERY SPAGHETTI IS JSAPABO #1JQUERY SPAGHETTI IS JSAPABO #1
JavaScript AntiPatterns Addressed by Opal (JSAPABO)
There are anti patterns addressed by Opal - trying to codify why Opal
makes browser code better, starting w/naming what's wrong
Very much a Work In Progress, apologies
http://funkworks.blogspot.com/2015/04/javascript-antipatterns-
addressed-by.html
OPAL CAN HELPOPAL CAN HELP
What is Opal? TLDR; Ruby in the browser
How can it help?
Ruby > JS
Culture, Conventions
OOP
and More!
See here for more info
REAL LIFE STORYREAL LIFE STORY
Want slide out bar
Need both Left and right slide bars
Would like simple, low cruft
Demo of finished product
ORIGINAL CODEORIGINAL CODE HTTP://JSFIDDLE.NET/DMYTR/37/HTTP://JSFIDDLE.NET/DMYTR/37/
$.asm = {};
$.asm.panels = 1;
function sidebar(panels) {
$.asm.panels = panels;
if (panels === 1) {
$('#sidebar').animate({
left: -180,
});
} else if (panels === 2) {
$('#sidebar').animate({
left: 20,
});
$('#sidebar').height($(window).height() - 50);
}
};
$(function() {
$('#toggleSidebar').click(function() {
if ($.asm.panels === 1) {
$('#toggleSidebar i').addClass('glyphicon-chevron-left');
$('#toggleSidebar i').removeClass('glyphicon-chevron-right');
return sidebar(2);
} else {
$('#toggleSidebar i').removeClass('glyphicon-chevron-left');
$('#toggleSidebar i').addClass('glyphicon-chevron-right');
return sidebar(1);
}
});
});
THE CODETHE CODE
Simple
Does what I want for the left sidebar
HOW ABOUT THE RIGHT SIDEBAR? MY SPIKEHOW ABOUT THE RIGHT SIDEBAR? MY SPIKE
$.asm2 = {};
$.asm2.panels = 1;
function sidebar2(panels) {
$.asm2.panels = panels;
if (panels === 1) {
$('#sidebar-right').animate({
right: -780,
});
} else if (panels === 2) {
$('#sidebar-right').animate({
right: 20,
});
$('#mapCanvas').width($('#mapCanvas').parent().width());
$('#mapCanvas').height($(window).height() - 50);
$('#sidebar-right').height($(window).height() - 50);
}
};
$(function() {
$('#toggleSidebar-right').click(function() {
if ($.asm2.panels === 1) {
$('#toggleSidebar-right i').removeClass('glyphicon-chevron-left');
$('#toggleSidebar-right i').addClass('glyphicon-chevron-right');
return sidebar2(2);
} else {
$('#toggleSidebar-right i').addClass('glyphicon-chevron-left');
$('#toggleSidebar-right i').removeClass('glyphicon-chevron-right');
return sidebar2(1);
}
});
});
KINDA UGLY CODEKINDA UGLY CODE
Original code for a jsfiddle - don't expect a lot
Typical for jQuery examples
by itself not bad. NOT good OO code
Now that concept has been proven, time to make the code "real"
HOW TO CONVERT?HOW TO CONVERT? JUST TRANSLATE?JUST TRANSLATE?
Didn't like it from the beginning
Document.ready? {
Element.find('#toggleSidebar').on :click {
}
}
MIRED IN THE DETAILSMIRED IN THE DETAILS
JSAPABO #6 Stuck in the weeds
What's the big picture
What's my intent?
A BETTER APPROACHA BETTER APPROACH
SEGUESEGUE
Reasons Opal Makes your Browser Code Better #1 (ROMYBCB) - a future
blog series
In Ruby, we Think in Objects
So Start w/objects
HOW I WANT TO USE IT?HOW I WANT TO USE IT?
# Create w/intent
left_sidebar = Sidebar.new('#toggleSidebar', 'left')
# elsewhere manipulate
left_sidebar.hide
BETTERBETTER
Intent revealed
Objects from the get go
We'll see why this matters later
CONVERTING THE JS CLICK HANDLERCONVERTING THE JS CLICK HANDLER
// original code
$(function() {
$('#toggleSidebar').click(function() {
if ($.asm.panels === 1) {
$('#toggleSidebar i').addClass('glyphicon-chevron-left');
$('#toggleSidebar i').removeClass('glyphicon-chevron-right');
return sidebar(2);
} else {
$('#toggleSidebar i').removeClass('glyphicon-chevron-left');
$('#toggleSidebar i').addClass('glyphicon-chevron-right');
return sidebar(1);
}
});
});
WHAT DOES IT DO?WHAT DOES IT DO?
if $.asm.panels = 1 // magic number 1 = closed state
make sidebar handle left facing left
sidebar(2) // set sidebar state to 2 (open state) - slide out
else
make sidebar handle face right
sidebar(1) // set sidebar state to 2 (closed state) - slide in
WHAT DOES IT DO AT A HIGHER LEVELWHAT DOES IT DO AT A HIGHER LEVEL
step away from the details (JSAPABO #6)
If the slider is open close it
else open it
OPAL CLICK HANDLER WITH INTENTION REVEALEDOPAL CLICK HANDLER WITH INTENTION REVEALED
Put it in #initialize(), so it happens for each instance
class Sidebar
def initialize(element_id, side)
@state = :closed
Element.find(element_id).on :click {
if @state == :open
close
else
open
end
}
end
end
WHERE TO HANG THE STATE?WHERE TO HANG THE STATE?
JSAPABO #5
Where do you put state?
Coz not using objects, where put state? Global?
In jQuery, can hang off of jQuery
$.asm.panels // hung off of jQuery
Where would you hang data in Ruby/Opal
instance variable, because you use objects from the get go
easy
@state = :closed
IMPLEMENT OPEN AND CLOSE, ROUND 1IMPLEMENT OPEN AND CLOSE, ROUND 1
def open
icon = Element.find("#{element_id} i")
icon.add_class('glyphicon-chevron-left')
icon.remove_class('glyphicon-chevron-right')
Element.find('#sidebar').animate left: 20
@state = :open
end
def close
icon = Element.find("#{element_id} i")
icon.remove_class('glyphicon-chevron-left')
icon.add_class('glyphicon-chevron-right')
Element.find('#sidebar').animate left: -180
@state = :close
end
ROUND 2: REMOVE DUPLICATIONROUND 2: REMOVE DUPLICATION
def open
set_icon('glyphicon-chevron-left', 'glyphicon-chevron-right')
Element.find('#sidebar').animate left: 20
@state = :open
end
def set_icon(class_to_add, class_to_remove)
icon = Element.find("#{element_id} i")
icon.add_class(class_to_add)
icon.remove_class(class_to_remove)
end
def close
set_icon('glyphicon-chevron-right', 'glyphicon-chevron-left')
Element.find('#sidebar').animate left: -180
@state = :closed
end
ROUND 3: REFACTOR MORE DUPLICATIONROUND 3: REFACTOR MORE DUPLICATION
def open
set_icon('glyphicon-chevron-left', 'glyphicon-chevron-right', 20)
@state = :open
end
def set_icon(class_to_add, class_to_remove, new_position)
icon = Element.find("#{element_id} i")
icon.add_class(class_to_add)
icon.remove_class(class_to_remove)
Element.find('#sidebar').animate left: new_position
end
def close
set_icon('glyphicon-chevron-right', 'glyphicon-chevron-left', -180)
@state = :closed
end
YET ANOTHER PATTERNYET ANOTHER PATTERN
There's another pattern- the state change, so we move that functionality into
set_icon
def open
set_icon('glyphicon-chevron-left', 'glyphicon-chevron-right', 20, :open)
end
def set_icon(class_to_add, class_to_remove, new_position, new_state)
icon = Element.find("#{element_id} i")
icon.add_class(class_to_add)
icon.remove_class(class_to_remove)
Element.find('#sidebar').animate left: new_position
@state = new_state
end
def close
set_icon('glyphicon-chevron-right', 'glyphicon-chevron-left', -180, :closed)
end
NEED A NEW NAMENEED A NEW NAME
set_icon() no longer describes what it's doing
def open
new_state('glyphicon-chevron-left', 'glyphicon-chevron-right', 20, :open)
end
def new_state(class_to_add, class_to_remove, new_position, new_state)
icon = Element.find("#{element_id} i")
icon.add_class(class_to_add)
icon.remove_class(class_to_remove)
Element.find('#sidebar').animate left: new_position
@state = new_state
end
def close
new_state('glyphicon-chevron-right', 'glyphicon-chevron-left', -180, :closed)
end
OPAL CODE THAT MATCHES THE JSFIDDLEOPAL CODE THAT MATCHES THE JSFIDDLE
# Sidebar abstraction
class Sidebar
attr_reader :element_id
def initialize(element_id, side)
@element_id = element_id
@state = :closed
Element.find("#{element_id} .toggles").on :click do
if @state == :open
close
else
open
end
end
end
def open
new_state('glyphicon-chevron-left', 'glyphicon-chevron-right', 20, :open)
end
def new_state(class_to_add, class_to_remove, new_position, new_state)
icon = Element.find("#{element_id} i")
icon.add_class(class_to_add)
icon.remove_class(class_to_remove)
Element.find("#{element_id}").animate left: new_position
@state = new_state
end
def close
new_state('glyphicon-chevron-right', 'glyphicon-chevron-left', -180, :closed)
end
end
Document.ready? {
left_sidebar = Sidebar.new('#sidebar', 'left')
}
CODE IS BETTERCODE IS BETTER
About same lines of code (LOC)
More intention revealing
Code can be reused/repurposed
Can programmaticaly open or close sidebar easily, i.e. left_sidebar.open
Couldn't do that w/original code WITHOUT refactoring
STILL NEED A RIGHT SIDEBARSTILL NEED A RIGHT SIDEBAR
Begin w/the end in mind
Document.ready? {
left_sidebar = Sidebar.new('#sidebar', 'left')
right_sidebar = Sidebar.new('#sidebar-right', 'right)
}
ORIGINAL EVIL CUT AND PASTE CODEORIGINAL EVIL CUT AND PASTE CODE
$.asm2 = {};
$.asm2.panels = 1;
function sidebar2(panels) {
$.asm2.panels = panels;
if (panels === 1) {
$('#sidebar-right').animate({
right: -780,
});
} else if (panels === 2) {
$('#sidebar-right').animate({
right: 20,
});
$('#sidebar-right').height($(window).height() - 50);
}
};
$(function() {
$('#toggleSidebar-right').click(function() {
if ($.asm2.panels === 1) {
$('#toggleSidebar-right i').removeClass('glyphicon-chevron-left');
$('#toggleSidebar-right i').addClass('glyphicon-chevron-right');
return sidebar2(2);
} else {
$('#toggleSidebar-right i').addClass('glyphicon-chevron-left');
$('#toggleSidebar-right i').removeClass('glyphicon-chevron-right');
return sidebar2(1);
}
});
});
NOTESNOTES
Because of JSAPABO #5, needed to store right panel state
Can't use $.asm.panels, cut and paste $.asm2
What if I want a dropdown, $.asm3 ?
Not a problem if dealing with objects from the get go
PARAMETRIZEPARAMETRIZE
Instead of converting the copy pasted code, we could parametrize by side
Add below to #initialize
set_params_for_side(side)
SETTING VALUES FOR :LEFTSETTING VALUES FOR :LEFT
Set values and use them
SETTING LEFTSETTING LEFT
attr_reader :closed_icon_class, :opened_icon_class, :opened_x_position, :closed_x_positio
n
def set_params_for_side(side)
if side == :left
@closed_icon_class = 'glyphicon-chevron-right'
@opened_icon_class = 'glyphicon-chevron-left'
@opened_x_position = 20
@closed_x_position = -180
end
end
def open
new_state(opened_icon_class, closed_icon_class, opened_x_position, :open)
end
def new_state(class_to_add, class_to_remove, new_position, new_state)
icon = Element.find("#{element_id} i")
icon.add_class(class_to_add)
icon.remove_class(class_to_remove)
Element.find("#{element_id}").animate left: new_position
@state = new_state
end
def close
new_state(closed_icon_class, opened_icon_class, closed_x_position, :closed)
end
HANDLE NON LEFT PARAMETERHANDLE NON LEFT PARAMETER
attr_reader :closed_icon_class, :opened_icon_class, :opened_x_position,
:closed_x_position, :x_position_side
def set_params_for_side(side)
if side == :left
@closed_icon_class = 'glyphicon-chevron-right'
@opened_icon_class = 'glyphicon-chevron-left'
@opened_x_position = 20
@closed_x_position = -180
@x_position_side = 'left'
else
@closed_icon_class = 'glyphicon-chevron-left'
@opened_icon_class = 'glyphicon-chevron-right'
@opened_x_position = 20
@closed_x_position = -780
@x_position_side = 'right'
end
end
def open
new_state(opened_icon_class, closed_icon_class, opened_x_position, :open)
end
def new_state(class_to_add, class_to_remove, new_position, new_state)
icon = Element.find("#{element_id} i")
icon.add_class(class_to_add)
icon.remove_class(class_to_remove)
Element.find("#{element_id}").animate x_position_side => new_position
@state = new_state
end
DONE FOR NOWDONE FOR NOW
Does what I need
Exceeds original implementation
Reusable
RESULTING CODERESULTING CODE
class Sidebar
attr_reader :element_id
def initialize(element_id, side)
@element_id = element_id
@state = :closed
set_params_for_side(side)
Element.find("#{element_id} .toggles").on :click do
if @state == :open
close
else
open
end
end
end
attr_reader :closed_icon_class, :opened_icon_class,
:opened_x_position, :closed_x_position,
:x_position_side
def set_params_for_side(side)
if side == :left
@closed_icon_class = 'glyphicon-chevron-right'
@opened_icon_class = 'glyphicon-chevron-left'
@opened_x_position = 20
@closed_x_position = -180
@x_position_side = 'left'
else
@closed_icon_class = 'glyphicon-chevron-left'
@opened_icon_class = 'glyphicon-chevron-right'
@opened_x_position = 20
@closed_x_position = -780
@x_position_side = 'right'
end
end
PAGE 2PAGE 2
def open
new_state(opened_icon_class, closed_icon_class, opened_x_position, :open)
end
def new_state(class_to_add, class_to_remove, new_position, new_state)
icon = Element.find("#{element_id} i")
icon.add_class(class_to_add)
icon.remove_class(class_to_remove)
Element.find("#{element_id}").animate x_position_side => new_position
@state = new_state
end
def close
new_state(closed_icon_class, opened_icon_class, closed_x_position, :closed)
end
end
Document.ready? {
left_sidebar = Sidebar.new('#sidebar', 'left')
right_sidebar = Sidebar.new('#sidebar-right', 'right')
}
CONCLUSIONCONCLUSION
Opal gives you
Better Code
Better functionality
Happiness
Blogged here

More Related Content

KEY
Keyboard Access APIs
toddkloots
 
KEY
Advanced jQuery
sergioafp
 
PDF
sfDay Cologne - Sonata Admin Bundle
th0masr
 
PDF
Business News, Personal Finance and Money News
phobicmistake8593
 
PPTX
Modern JavaScript Engine Performance
Catalin Dumitru
 
PDF
Creating custom views
Mu Chun Wang
 
PDF
Everything you always wanted to know about forms* *but were afraid to ask
Andrea Giuliano
 
PDF
Open Selector
jjdelc
 
Keyboard Access APIs
toddkloots
 
Advanced jQuery
sergioafp
 
sfDay Cologne - Sonata Admin Bundle
th0masr
 
Business News, Personal Finance and Money News
phobicmistake8593
 
Modern JavaScript Engine Performance
Catalin Dumitru
 
Creating custom views
Mu Chun Wang
 
Everything you always wanted to know about forms* *but were afraid to ask
Andrea Giuliano
 
Open Selector
jjdelc
 

What's hot (20)

PDF
Business News, Personal Finance and Money News
eminentoomph4388
 
PPTX
11. delete record
Razvan Raducanu, PhD
 
PPTX
10. view one record
Razvan Raducanu, PhD
 
TXT
Bd venta.sql
Diego Isaac Ramos Meza
 
PDF
Taming forms with React
GreeceJS
 
PDF
Business News, Personal Finance and Money News
acousticassista07
 
PDF
UITableView Pain Points
Ken Auer
 
PDF
Migrare da symfony 1 a Symfony2
Massimiliano Arione
 
PPTX
Hacking Your Way to Better Security - PHP South Africa 2016
Colin O'Dell
 
PDF
Hacking Your Way To Better Security
Colin O'Dell
 
PDF
jQuery - Introdução
Gustavo Dutra
 
DOCX
Actividad 1
JUAN ENRIQUE
 
PPTX
12. edit record
Razvan Raducanu, PhD
 
PDF
Business News, Personal Finance and Money News
bernardwilcox8
 
PDF
Business News, Personal Finance and Money News
organicprosperi63
 
KEY
Unit testing with zend framework PHPBenelux
Michelangelo van Dam
 
PDF
USF ARG seminar Feb 08
JP Allen
 
KEY
JQuery In Rails
Louie Zhao
 
PDF
Business News, Personal Finance and Money News
blogginatl1963
 
TXT
sanya's Bug database
sanyabhasin18
 
Business News, Personal Finance and Money News
eminentoomph4388
 
11. delete record
Razvan Raducanu, PhD
 
10. view one record
Razvan Raducanu, PhD
 
Taming forms with React
GreeceJS
 
Business News, Personal Finance and Money News
acousticassista07
 
UITableView Pain Points
Ken Auer
 
Migrare da symfony 1 a Symfony2
Massimiliano Arione
 
Hacking Your Way to Better Security - PHP South Africa 2016
Colin O'Dell
 
Hacking Your Way To Better Security
Colin O'Dell
 
jQuery - Introdução
Gustavo Dutra
 
Actividad 1
JUAN ENRIQUE
 
12. edit record
Razvan Raducanu, PhD
 
Business News, Personal Finance and Money News
bernardwilcox8
 
Business News, Personal Finance and Money News
organicprosperi63
 
Unit testing with zend framework PHPBenelux
Michelangelo van Dam
 
USF ARG seminar Feb 08
JP Allen
 
JQuery In Rails
Louie Zhao
 
Business News, Personal Finance and Money News
blogginatl1963
 
sanya's Bug database
sanyabhasin18
 
Ad

Similar to Ruby-ying Javascript: Avoiding jQuery Spaghetti (20)

PDF
jQuery (MeshU)
jeresig
 
PDF
Using jQuery to Extend CSS
Chris Coyier
 
PDF
collapsible-panels-tutorial
tutorialsruby
 
PDF
collapsible-panels-tutorial
tutorialsruby
 
PDF
collapsible-panels-tutorial
tutorialsruby
 
PDF
collapsible-panels-tutorial
tutorialsruby
 
PDF
jQuery quick tips
Rochester Oliveira
 
PDF
jQuery (BostonPHP)
jeresig
 
KEY
An in-depth look at jQuery
Paul Bakaus
 
PDF
State of jQuery and Drupal
jeresig
 
KEY
Bcblackpool jquery tips
Jack Franklin
 
PDF
jQuery Presentation to Rails Developers
Yehuda Katz
 
PDF
jQuery (DrupalCamp Toronto)
jeresig
 
PDF
DrupalCon jQuery
Nathan Smith
 
PDF
Download full ebook of Extending jQuery Keith Wood download pdf instant downl...
busicluckesz
 
PDF
Jquery Cookbook Solutions Examples For Jquery Developers Lindley
joettealhadi
 
PPTX
webstudy jquery
Seungho Han
 
KEY
Introduction to jQuery - Barcamp London 9
Jack Franklin
 
PPT
jQuery
Niladri Karmakar
 
KEY
jQuery: Tips, tricks and hints for better development and Performance
Jonas De Smet
 
jQuery (MeshU)
jeresig
 
Using jQuery to Extend CSS
Chris Coyier
 
collapsible-panels-tutorial
tutorialsruby
 
collapsible-panels-tutorial
tutorialsruby
 
collapsible-panels-tutorial
tutorialsruby
 
collapsible-panels-tutorial
tutorialsruby
 
jQuery quick tips
Rochester Oliveira
 
jQuery (BostonPHP)
jeresig
 
An in-depth look at jQuery
Paul Bakaus
 
State of jQuery and Drupal
jeresig
 
Bcblackpool jquery tips
Jack Franklin
 
jQuery Presentation to Rails Developers
Yehuda Katz
 
jQuery (DrupalCamp Toronto)
jeresig
 
DrupalCon jQuery
Nathan Smith
 
Download full ebook of Extending jQuery Keith Wood download pdf instant downl...
busicluckesz
 
Jquery Cookbook Solutions Examples For Jquery Developers Lindley
joettealhadi
 
webstudy jquery
Seungho Han
 
Introduction to jQuery - Barcamp London 9
Jack Franklin
 
jQuery: Tips, tricks and hints for better development and Performance
Jonas De Smet
 
Ad

More from Forrest Chang (11)

PDF
Crystal is a Rubyists friend (quick anecdote)
Forrest Chang
 
PDF
Making terminal based apps w:ruby
Forrest Chang
 
PDF
Working Effectively with Legacy Javascript code in Opal
Forrest Chang
 
PDF
Opal-hot-reloader
Forrest Chang
 
PDF
Rubyconf 2014 recap
Forrest Chang
 
PDF
6 reasons Jubilee could be a Rubyist's new best friend
Forrest Chang
 
PDF
Opal a new_hope
Forrest Chang
 
PDF
Opal chapter 4_a_new_hope
Forrest Chang
 
PDF
Data Intensive RIAs on Rails with very little code (Netzke)
Forrest Chang
 
PDF
Rubyconf2012 recap
Forrest Chang
 
KEY
Opal - Ruby Style!! Ruby in the browser
Forrest Chang
 
Crystal is a Rubyists friend (quick anecdote)
Forrest Chang
 
Making terminal based apps w:ruby
Forrest Chang
 
Working Effectively with Legacy Javascript code in Opal
Forrest Chang
 
Opal-hot-reloader
Forrest Chang
 
Rubyconf 2014 recap
Forrest Chang
 
6 reasons Jubilee could be a Rubyist's new best friend
Forrest Chang
 
Opal a new_hope
Forrest Chang
 
Opal chapter 4_a_new_hope
Forrest Chang
 
Data Intensive RIAs on Rails with very little code (Netzke)
Forrest Chang
 
Rubyconf2012 recap
Forrest Chang
 
Opal - Ruby Style!! Ruby in the browser
Forrest Chang
 

Recently uploaded (20)

PDF
Adobe Illustrator Crack Full Download (Latest Version 2025) Pre-Activated
imang66g
 
PDF
MiniTool Power Data Recovery Crack New Pre Activated Version Latest 2025
imang66g
 
PDF
Balancing Resource Capacity and Workloads with OnePlan – Avoid Overloading Te...
OnePlan Solutions
 
PDF
49784907924775488180_LRN2959_Data_Pump_23ai.pdf
Abilash868456
 
PPTX
The-Dawn-of-AI-Reshaping-Our-World.pptxx
parthbhanushali307
 
PDF
Teaching Reproducibility and Embracing Variability: From Floating-Point Exper...
University of Rennes, INSA Rennes, Inria/IRISA, CNRS
 
PPT
Activate_Methodology_Summary presentatio
annapureddyn
 
PPTX
Presentation about Database and Database Administrator
abhishekchauhan86963
 
PDF
Download iTop VPN Free 6.1.0.5882 Crack Full Activated Pre Latest 2025
imang66g
 
PDF
Bandai Playdia The Book - David Glotz
BluePanther6
 
PDF
Key Features to Look for in Arizona App Development Services
Net-Craft.com
 
PDF
ShowUs: Pharo Stream Deck (ESUG 2025, Gdansk)
ESUG
 
PDF
WatchTraderHub - Watch Dealer software with inventory management and multi-ch...
WatchDealer Pavel
 
PDF
Applitools Platform Pulse: What's New and What's Coming - July 2025
Applitools
 
PDF
On Software Engineers' Productivity - Beyond Misleading Metrics
Romén Rodríguez-Gil
 
PDF
Salesforce Implementation Services Provider.pdf
VALiNTRY360
 
PPTX
Odoo Integration Services by Candidroot Solutions
CandidRoot Solutions Private Limited
 
PDF
Exploring AI Agents in Process Industries
amoreira6
 
PPTX
classification of computer and basic part of digital computer
ravisinghrajpurohit3
 
PDF
Enhancing Healthcare RPM Platforms with Contextual AI Integration
Cadabra Studio
 
Adobe Illustrator Crack Full Download (Latest Version 2025) Pre-Activated
imang66g
 
MiniTool Power Data Recovery Crack New Pre Activated Version Latest 2025
imang66g
 
Balancing Resource Capacity and Workloads with OnePlan – Avoid Overloading Te...
OnePlan Solutions
 
49784907924775488180_LRN2959_Data_Pump_23ai.pdf
Abilash868456
 
The-Dawn-of-AI-Reshaping-Our-World.pptxx
parthbhanushali307
 
Teaching Reproducibility and Embracing Variability: From Floating-Point Exper...
University of Rennes, INSA Rennes, Inria/IRISA, CNRS
 
Activate_Methodology_Summary presentatio
annapureddyn
 
Presentation about Database and Database Administrator
abhishekchauhan86963
 
Download iTop VPN Free 6.1.0.5882 Crack Full Activated Pre Latest 2025
imang66g
 
Bandai Playdia The Book - David Glotz
BluePanther6
 
Key Features to Look for in Arizona App Development Services
Net-Craft.com
 
ShowUs: Pharo Stream Deck (ESUG 2025, Gdansk)
ESUG
 
WatchTraderHub - Watch Dealer software with inventory management and multi-ch...
WatchDealer Pavel
 
Applitools Platform Pulse: What's New and What's Coming - July 2025
Applitools
 
On Software Engineers' Productivity - Beyond Misleading Metrics
Romén Rodríguez-Gil
 
Salesforce Implementation Services Provider.pdf
VALiNTRY360
 
Odoo Integration Services by Candidroot Solutions
CandidRoot Solutions Private Limited
 
Exploring AI Agents in Process Industries
amoreira6
 
classification of computer and basic part of digital computer
ravisinghrajpurohit3
 
Enhancing Healthcare RPM Platforms with Contextual AI Integration
Cadabra Studio
 

Ruby-ying Javascript: Avoiding jQuery Spaghetti

  • 1. RUBYFYING JAVASCRIPT:RUBYFYING JAVASCRIPT: AVOIDING JQUERY SPAGHETTIAVOIDING JQUERY SPAGHETTI FORREST CHANGFORREST CHANG [email protected]@YAHOO.COM
  • 2. A JQUERY STORYA JQUERY STORY Have a problem Add a little jQuery Fixed, yay!
  • 3. AS TIME GOES ONAS TIME GOES ON Add function, add function, nest function Insert event handlers in DOM Add business logic Get input, dialogs Ajax Effects Update DOM Assign handlers to id etc.
  • 5. JQUERY SPAGHETTIJQUERY SPAGHETTI by Steve O'Brien in http://steve-obrien.com/javascript-jquery-spaghetti/ Without a strong framework or architecture you end up with jQuery spaghetti. This is usually because you start with a small piece of jquery shoved somewhere in the dom as you add features it all grows out of proportion and becomes a tangled mess. The most challenging thing is maintaining state. Relying heavily on jQuery means your application state information is stores in the dom, this works well for small features and isolated components here and there, but in a complex app it quickly becomes very difficult to manage.
  • 6. JQUERY SPAGHETTI IS JSAPABO #1JQUERY SPAGHETTI IS JSAPABO #1 JavaScript AntiPatterns Addressed by Opal (JSAPABO) There are anti patterns addressed by Opal - trying to codify why Opal makes browser code better, starting w/naming what's wrong Very much a Work In Progress, apologies http://funkworks.blogspot.com/2015/04/javascript-antipatterns- addressed-by.html
  • 7. OPAL CAN HELPOPAL CAN HELP What is Opal? TLDR; Ruby in the browser How can it help? Ruby > JS Culture, Conventions OOP and More! See here for more info
  • 8. REAL LIFE STORYREAL LIFE STORY Want slide out bar Need both Left and right slide bars Would like simple, low cruft Demo of finished product
  • 9. ORIGINAL CODEORIGINAL CODE HTTP://JSFIDDLE.NET/DMYTR/37/HTTP://JSFIDDLE.NET/DMYTR/37/ $.asm = {}; $.asm.panels = 1; function sidebar(panels) { $.asm.panels = panels; if (panels === 1) { $('#sidebar').animate({ left: -180, }); } else if (panels === 2) { $('#sidebar').animate({ left: 20, }); $('#sidebar').height($(window).height() - 50); } }; $(function() { $('#toggleSidebar').click(function() { if ($.asm.panels === 1) { $('#toggleSidebar i').addClass('glyphicon-chevron-left'); $('#toggleSidebar i').removeClass('glyphicon-chevron-right'); return sidebar(2); } else { $('#toggleSidebar i').removeClass('glyphicon-chevron-left'); $('#toggleSidebar i').addClass('glyphicon-chevron-right'); return sidebar(1); } }); });
  • 10. THE CODETHE CODE Simple Does what I want for the left sidebar
  • 11. HOW ABOUT THE RIGHT SIDEBAR? MY SPIKEHOW ABOUT THE RIGHT SIDEBAR? MY SPIKE $.asm2 = {}; $.asm2.panels = 1; function sidebar2(panels) { $.asm2.panels = panels; if (panels === 1) { $('#sidebar-right').animate({ right: -780, }); } else if (panels === 2) { $('#sidebar-right').animate({ right: 20, }); $('#mapCanvas').width($('#mapCanvas').parent().width()); $('#mapCanvas').height($(window).height() - 50); $('#sidebar-right').height($(window).height() - 50); } }; $(function() { $('#toggleSidebar-right').click(function() { if ($.asm2.panels === 1) { $('#toggleSidebar-right i').removeClass('glyphicon-chevron-left'); $('#toggleSidebar-right i').addClass('glyphicon-chevron-right'); return sidebar2(2); } else { $('#toggleSidebar-right i').addClass('glyphicon-chevron-left'); $('#toggleSidebar-right i').removeClass('glyphicon-chevron-right'); return sidebar2(1); } }); });
  • 12. KINDA UGLY CODEKINDA UGLY CODE Original code for a jsfiddle - don't expect a lot Typical for jQuery examples by itself not bad. NOT good OO code Now that concept has been proven, time to make the code "real"
  • 13. HOW TO CONVERT?HOW TO CONVERT? JUST TRANSLATE?JUST TRANSLATE? Didn't like it from the beginning Document.ready? { Element.find('#toggleSidebar').on :click { } }
  • 14. MIRED IN THE DETAILSMIRED IN THE DETAILS JSAPABO #6 Stuck in the weeds What's the big picture What's my intent?
  • 15. A BETTER APPROACHA BETTER APPROACH
  • 16. SEGUESEGUE Reasons Opal Makes your Browser Code Better #1 (ROMYBCB) - a future blog series In Ruby, we Think in Objects So Start w/objects
  • 17. HOW I WANT TO USE IT?HOW I WANT TO USE IT? # Create w/intent left_sidebar = Sidebar.new('#toggleSidebar', 'left') # elsewhere manipulate left_sidebar.hide
  • 18. BETTERBETTER Intent revealed Objects from the get go We'll see why this matters later
  • 19. CONVERTING THE JS CLICK HANDLERCONVERTING THE JS CLICK HANDLER // original code $(function() { $('#toggleSidebar').click(function() { if ($.asm.panels === 1) { $('#toggleSidebar i').addClass('glyphicon-chevron-left'); $('#toggleSidebar i').removeClass('glyphicon-chevron-right'); return sidebar(2); } else { $('#toggleSidebar i').removeClass('glyphicon-chevron-left'); $('#toggleSidebar i').addClass('glyphicon-chevron-right'); return sidebar(1); } }); }); WHAT DOES IT DO?WHAT DOES IT DO? if $.asm.panels = 1 // magic number 1 = closed state make sidebar handle left facing left sidebar(2) // set sidebar state to 2 (open state) - slide out else make sidebar handle face right sidebar(1) // set sidebar state to 2 (closed state) - slide in
  • 20. WHAT DOES IT DO AT A HIGHER LEVELWHAT DOES IT DO AT A HIGHER LEVEL step away from the details (JSAPABO #6) If the slider is open close it else open it
  • 21. OPAL CLICK HANDLER WITH INTENTION REVEALEDOPAL CLICK HANDLER WITH INTENTION REVEALED Put it in #initialize(), so it happens for each instance class Sidebar def initialize(element_id, side) @state = :closed Element.find(element_id).on :click { if @state == :open close else open end } end end
  • 22. WHERE TO HANG THE STATE?WHERE TO HANG THE STATE? JSAPABO #5 Where do you put state? Coz not using objects, where put state? Global? In jQuery, can hang off of jQuery $.asm.panels // hung off of jQuery Where would you hang data in Ruby/Opal instance variable, because you use objects from the get go easy @state = :closed
  • 23. IMPLEMENT OPEN AND CLOSE, ROUND 1IMPLEMENT OPEN AND CLOSE, ROUND 1 def open icon = Element.find("#{element_id} i") icon.add_class('glyphicon-chevron-left') icon.remove_class('glyphicon-chevron-right') Element.find('#sidebar').animate left: 20 @state = :open end def close icon = Element.find("#{element_id} i") icon.remove_class('glyphicon-chevron-left') icon.add_class('glyphicon-chevron-right') Element.find('#sidebar').animate left: -180 @state = :close end
  • 24. ROUND 2: REMOVE DUPLICATIONROUND 2: REMOVE DUPLICATION def open set_icon('glyphicon-chevron-left', 'glyphicon-chevron-right') Element.find('#sidebar').animate left: 20 @state = :open end def set_icon(class_to_add, class_to_remove) icon = Element.find("#{element_id} i") icon.add_class(class_to_add) icon.remove_class(class_to_remove) end def close set_icon('glyphicon-chevron-right', 'glyphicon-chevron-left') Element.find('#sidebar').animate left: -180 @state = :closed end
  • 25. ROUND 3: REFACTOR MORE DUPLICATIONROUND 3: REFACTOR MORE DUPLICATION def open set_icon('glyphicon-chevron-left', 'glyphicon-chevron-right', 20) @state = :open end def set_icon(class_to_add, class_to_remove, new_position) icon = Element.find("#{element_id} i") icon.add_class(class_to_add) icon.remove_class(class_to_remove) Element.find('#sidebar').animate left: new_position end def close set_icon('glyphicon-chevron-right', 'glyphicon-chevron-left', -180) @state = :closed end
  • 26. YET ANOTHER PATTERNYET ANOTHER PATTERN There's another pattern- the state change, so we move that functionality into set_icon def open set_icon('glyphicon-chevron-left', 'glyphicon-chevron-right', 20, :open) end def set_icon(class_to_add, class_to_remove, new_position, new_state) icon = Element.find("#{element_id} i") icon.add_class(class_to_add) icon.remove_class(class_to_remove) Element.find('#sidebar').animate left: new_position @state = new_state end def close set_icon('glyphicon-chevron-right', 'glyphicon-chevron-left', -180, :closed) end
  • 27. NEED A NEW NAMENEED A NEW NAME set_icon() no longer describes what it's doing def open new_state('glyphicon-chevron-left', 'glyphicon-chevron-right', 20, :open) end def new_state(class_to_add, class_to_remove, new_position, new_state) icon = Element.find("#{element_id} i") icon.add_class(class_to_add) icon.remove_class(class_to_remove) Element.find('#sidebar').animate left: new_position @state = new_state end def close new_state('glyphicon-chevron-right', 'glyphicon-chevron-left', -180, :closed) end
  • 28. OPAL CODE THAT MATCHES THE JSFIDDLEOPAL CODE THAT MATCHES THE JSFIDDLE # Sidebar abstraction class Sidebar attr_reader :element_id def initialize(element_id, side) @element_id = element_id @state = :closed Element.find("#{element_id} .toggles").on :click do if @state == :open close else open end end end def open new_state('glyphicon-chevron-left', 'glyphicon-chevron-right', 20, :open) end def new_state(class_to_add, class_to_remove, new_position, new_state) icon = Element.find("#{element_id} i") icon.add_class(class_to_add) icon.remove_class(class_to_remove) Element.find("#{element_id}").animate left: new_position @state = new_state end def close new_state('glyphicon-chevron-right', 'glyphicon-chevron-left', -180, :closed) end end Document.ready? { left_sidebar = Sidebar.new('#sidebar', 'left') }
  • 29. CODE IS BETTERCODE IS BETTER About same lines of code (LOC) More intention revealing Code can be reused/repurposed Can programmaticaly open or close sidebar easily, i.e. left_sidebar.open Couldn't do that w/original code WITHOUT refactoring
  • 30. STILL NEED A RIGHT SIDEBARSTILL NEED A RIGHT SIDEBAR Begin w/the end in mind Document.ready? { left_sidebar = Sidebar.new('#sidebar', 'left') right_sidebar = Sidebar.new('#sidebar-right', 'right) }
  • 31. ORIGINAL EVIL CUT AND PASTE CODEORIGINAL EVIL CUT AND PASTE CODE $.asm2 = {}; $.asm2.panels = 1; function sidebar2(panels) { $.asm2.panels = panels; if (panels === 1) { $('#sidebar-right').animate({ right: -780, }); } else if (panels === 2) { $('#sidebar-right').animate({ right: 20, }); $('#sidebar-right').height($(window).height() - 50); } }; $(function() { $('#toggleSidebar-right').click(function() { if ($.asm2.panels === 1) { $('#toggleSidebar-right i').removeClass('glyphicon-chevron-left'); $('#toggleSidebar-right i').addClass('glyphicon-chevron-right'); return sidebar2(2); } else { $('#toggleSidebar-right i').addClass('glyphicon-chevron-left'); $('#toggleSidebar-right i').removeClass('glyphicon-chevron-right'); return sidebar2(1); } }); });
  • 32. NOTESNOTES Because of JSAPABO #5, needed to store right panel state Can't use $.asm.panels, cut and paste $.asm2 What if I want a dropdown, $.asm3 ? Not a problem if dealing with objects from the get go
  • 33. PARAMETRIZEPARAMETRIZE Instead of converting the copy pasted code, we could parametrize by side Add below to #initialize set_params_for_side(side)
  • 34. SETTING VALUES FOR :LEFTSETTING VALUES FOR :LEFT Set values and use them
  • 35. SETTING LEFTSETTING LEFT attr_reader :closed_icon_class, :opened_icon_class, :opened_x_position, :closed_x_positio n def set_params_for_side(side) if side == :left @closed_icon_class = 'glyphicon-chevron-right' @opened_icon_class = 'glyphicon-chevron-left' @opened_x_position = 20 @closed_x_position = -180 end end def open new_state(opened_icon_class, closed_icon_class, opened_x_position, :open) end def new_state(class_to_add, class_to_remove, new_position, new_state) icon = Element.find("#{element_id} i") icon.add_class(class_to_add) icon.remove_class(class_to_remove) Element.find("#{element_id}").animate left: new_position @state = new_state end def close new_state(closed_icon_class, opened_icon_class, closed_x_position, :closed) end
  • 36. HANDLE NON LEFT PARAMETERHANDLE NON LEFT PARAMETER attr_reader :closed_icon_class, :opened_icon_class, :opened_x_position, :closed_x_position, :x_position_side def set_params_for_side(side) if side == :left @closed_icon_class = 'glyphicon-chevron-right' @opened_icon_class = 'glyphicon-chevron-left' @opened_x_position = 20 @closed_x_position = -180 @x_position_side = 'left' else @closed_icon_class = 'glyphicon-chevron-left' @opened_icon_class = 'glyphicon-chevron-right' @opened_x_position = 20 @closed_x_position = -780 @x_position_side = 'right' end end def open new_state(opened_icon_class, closed_icon_class, opened_x_position, :open) end def new_state(class_to_add, class_to_remove, new_position, new_state) icon = Element.find("#{element_id} i") icon.add_class(class_to_add) icon.remove_class(class_to_remove) Element.find("#{element_id}").animate x_position_side => new_position @state = new_state end
  • 37. DONE FOR NOWDONE FOR NOW Does what I need Exceeds original implementation Reusable
  • 38. RESULTING CODERESULTING CODE class Sidebar attr_reader :element_id def initialize(element_id, side) @element_id = element_id @state = :closed set_params_for_side(side) Element.find("#{element_id} .toggles").on :click do if @state == :open close else open end end end attr_reader :closed_icon_class, :opened_icon_class, :opened_x_position, :closed_x_position, :x_position_side def set_params_for_side(side) if side == :left @closed_icon_class = 'glyphicon-chevron-right' @opened_icon_class = 'glyphicon-chevron-left' @opened_x_position = 20 @closed_x_position = -180 @x_position_side = 'left' else @closed_icon_class = 'glyphicon-chevron-left' @opened_icon_class = 'glyphicon-chevron-right' @opened_x_position = 20 @closed_x_position = -780 @x_position_side = 'right' end end
  • 39. PAGE 2PAGE 2 def open new_state(opened_icon_class, closed_icon_class, opened_x_position, :open) end def new_state(class_to_add, class_to_remove, new_position, new_state) icon = Element.find("#{element_id} i") icon.add_class(class_to_add) icon.remove_class(class_to_remove) Element.find("#{element_id}").animate x_position_side => new_position @state = new_state end def close new_state(closed_icon_class, opened_icon_class, closed_x_position, :closed) end end Document.ready? { left_sidebar = Sidebar.new('#sidebar', 'left') right_sidebar = Sidebar.new('#sidebar-right', 'right') }
  • 40. CONCLUSIONCONCLUSION Opal gives you Better Code Better functionality Happiness Blogged here