KEMBAR78
Ruby-ying Javascript: Avoiding jQuery Spaghetti | PDF
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

Ruby-ying Javascript: Avoiding jQuery Spaghetti

  • 1.
    RUBYFYING JAVASCRIPT:RUBYFYING JAVASCRIPT: AVOIDINGJQUERY SPAGHETTIAVOIDING JQUERY SPAGHETTI FORREST CHANGFORREST CHANG FKCHANG2000@YAHOO.COMFKCHANG2000@YAHOO.COM
  • 2.
    A JQUERY STORYAJQUERY STORY Have a problem Add a little jQuery Fixed, yay!
  • 3.
    AS TIME GOESONAS 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.
  • 4.
  • 5.
    JQUERY SPAGHETTIJQUERY SPAGHETTI bySteve 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 ISJSAPABO #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 HELPOPALCAN 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 STORYREALLIFE STORY Want slide out bar Need both Left and right slide bars Would like simple, low cruft Demo of finished product
  • 9.
    ORIGINAL CODEORIGINAL CODEHTTP://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 Doeswhat I want for the left sidebar
  • 11.
    HOW ABOUT THERIGHT 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 CODEKINDAUGLY 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?HOWTO CONVERT? JUST TRANSLATE?JUST TRANSLATE? Didn't like it from the beginning Document.ready? { Element.find('#toggleSidebar').on :click { } }
  • 14.
    MIRED IN THEDETAILSMIRED IN THE DETAILS JSAPABO #6 Stuck in the weeds What's the big picture What's my intent?
  • 15.
    A BETTER APPROACHABETTER APPROACH
  • 16.
    SEGUESEGUE Reasons Opal Makesyour Browser Code Better #1 (ROMYBCB) - a future blog series In Ruby, we Think in Objects So Start w/objects
  • 17.
    HOW I WANTTO 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 fromthe get go We'll see why this matters later
  • 19.
    CONVERTING THE JSCLICK 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 ITDO 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 HANDLERWITH 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 HANGTHE 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 ANDCLOSE, 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: REMOVEDUPLICATIONROUND 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: REFACTORMORE 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 PATTERNYETANOTHER 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 NEWNAMENEED 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 THATMATCHES 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 BETTERCODEIS 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 ARIGHT 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 CUTAND 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 convertingthe 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 LEFTPARAMETERHANDLE 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 NOWDONEFOR NOW Does what I need Exceeds original implementation Reusable
  • 38.
    RESULTING CODERESULTING CODE classSidebar 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 defopen 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 BetterCode Better functionality Happiness Blogged here