We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Old-school Javascript in Rails

00:00

Formal Metadata

Title
Old-school Javascript in Rails
Title of Series
Number of Parts
88
Author
License
CC Attribution - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Identifiers
Publisher
Release Date
Language
Producer
Production Year2018
Production PlacePittsburgh

Content Metadata

Subject Area
Genre
Abstract
Sometimes vanilla Javascript and Rails out-of-the box UJS is good enough, especially when it comes to building things like admin panels and other types of content management. We'll discuss strategies for building rich user interfaces using minimal vanilla Javascript that leverages as much of Rails UJS as we can.
38
Thumbnail
07:23
57
Thumbnail
05:41
60
64
Barrelled spaceHypermediaTwitterMultiplication signPresentation of a groupSlide ruleSoftware developerDebuggerProjective planeFile formatGroup actionProcess (computing)Demo (music)DiagramComputer animation
Context awarenessUniform resource locatorContent (media)Operating systemInteractive kioskAnalytic continuationSystem administratorTouchscreenEvent horizonLocal ringComputer animation
Software frameworkUser interfaceConsistencyBuildingFile formatData modelInternet forumVolumenvisualisierungContent (media)DebuggerPlug-in (computing)Order (biology)Operating systemSystem administratorForm (programming)Front and back endsQuicksortBlock (periodic table)Drag (physics)Group actionDefault (computer science)Dependent and independent variablesMobile appAreaSystem callGame controllerReal numberCuboidWeb browserInsertion lossConsistencyLibrary (computing)Web pageSoftware frameworkPartial derivativeEscape characterKeyboard shortcutComputer fileConnectivity (graph theory)Bootstrap aggregatingCone penetration testLoop (music)Green's functionLink (knot theory)Term (mathematics)Flash memoryReading (process)User interfaceContext awarenessRevision controlDemo (music)Exception handlingRemote procedure callFile formatSinc functionView (database)Continuum hypothesisEndliche ModelltheorieProcess (computing)Query languageMultiplication signInteractive kioskBitCodeComputer animation
Message passingElectronic visual displayFlash memoryVolumenvisualisierungPartial derivativeMathematicsLink (knot theory)Function (mathematics)Functional (mathematics)Key (cryptography)Attribute grammarView (database)Message passingFlash memoryMereologyVolumenvisualisierungRow (database)Form (programming)Table (information)Link (knot theory)Electronic program guideArtistic renderingCodePartial derivativeWeb pageQuicksortRepresentational state transferPattern languageType theoryDemo (music)HypermediaClique-widthReal numberBitVirtualizationGame controllerGroup actionDifferent (Kate Ryan album)Dependent and independent variablesString (computer science)Cartesian coordinate systemMathematicsCASE <Informatik>Strategy gameElectronic mailing list2 (number)Endliche ModelltheorieRight anglePointer (computer programming)Parameter (computer programming)Computer animation
VolumenvisualisierungKeilförmige AnordnungAttribute grammarContent (media)Interior (topology)Function (mathematics)Event horizonFront and back endsConnectivity (graph theory)SubsetElectronic mailing listDifferent (Kate Ryan album)SynchronizationFlow separationTable (information)Order (biology)Position operatorMultiplication signCodeSpacetimeWrapper (data mining)System callWeb pageEvent horizonDependent and independent variablesDrop (liquid)QuicksortDrag (physics)Type theoryPhysical systemRow (database)Entire functionBlock (periodic table)Standard deviationLibrary (computing)BitPlug-in (computing)ConsistencyMessage passingFlash memoryAttribute grammarFunctional (mathematics)BuildingEndliche ModelltheorieView (database)Default (computer science)Cartesian coordinate systemHash functionGroup actionTask (computing)Parameter (computer programming)MathematicsCuboidCASE <Informatik>DebuggerCategory of beingNumberLimit (category theory)Canadian Mathematical SocietyWordSet (mathematics)Server (computing)Uniform resource locatorElement (mathematics)Boilerplate (text)Rule of inferenceImplementationPattern languageMereologyWeb browserComputer animation
Mobile WebSoftwareConnected spaceWeb pageFile formatClient (computing)Roundness (object)Local area networkMultiplication signServer (computing)Dependent and independent variablesComputer animation
Term (mathematics)Mobile WebComa BerenicesMathematicsView (database)Frame problemComputer clusterJava appletComputer iconCASE <Informatik>Dot productQuicksortSoftwareDependent and independent variablesClosed setTouch typingWeb browserInteractive televisionGroup actionSlide ruleRight angleTerm (mathematics)Mobile appWeb pageAttribute grammarSingle-precision floating-point formatTrailParameter (computer programming)BuildingServer (computing)NP-hardMultiplication signAddress spaceFeedbackState of matterStructural loadComputer animation
Coma BerenicesBlock (periodic table)Data typeXMLComputer animation
Transcript: English(auto-generated)
I don't know how to start these things. Thank you all for coming to the 75th JavaScript talk, airrailsconf.
I have seen some really great presentations this week so far. Did anyone see the Three Mile Island disaster presentation? I really liked that one. Like I had really great slides. My slides are not great.
So let me introduce myself. My name's Graham Konzett. I've been a software developer for 10 or 15 years. And I really wanna leave time at the end of this for questions or discussion. I've run through this presentation a couple times and it's always right up until the wire so I'm gonna try and go quickly
because I wanna talk to everybody. I don't do Twitters but I do have a GitHub and because it's RailsConf, this presentation is actually a Rails project. So you can clone it right now. Go follow along. It'll save me some time for having to alt tab
between demos and slides. I doesn't mean I have to click the buttons at the bottom though. Hopefully that makes up for my bad quality slide layout. Okay. So before we talk about what I mean by old school JavaScript, I wanted to provide some background
as to why you might wanna go down this road. And this is not a modern JavaScript sucks talk. This is not like React is terrible. It's more about picking the right tool for the right job. There's no silver bullet. I'm actually heavily involved in the React user group
in Columbus and used it on several large projects. I actually do like JavaScript. So story time. I work for a company called Orange Barrel Media. They're out of Columbus, Ohio. They are, it says advertising in there and when you think of advertising in the text base,
you probably think of Google, AdWords, that kind of thing. But we are actually a large format outdoor advertising company. We're also hiring. If you're a kick ass front end developer, we're looking for you. Come see me afterwards. So you might be wondering why a outdoor advertising company
would deploy software developers. And that's because we also operate and have these interactive kiosks. And they're kind of like giant location aware content aggregators. So you can think of the giant iPad sticking out of the ground. But it does have local content and events
that the cities manage and coordinate. And the little operating system that you see, maybe you can kind of see on the screen there is just kind of the tip of the iceberg. You have lots of content. There's lots of content that cities and admins have to manage.
So, story continues. Recently we've had a lot of growth in this area and we have kind of an old, couple year old Rails app that served the back end well when it was really tiny.
But our team is growing and the Rails back end wasn't quite cutting it anymore. We were using an admin framework and it was good because it let us focus on features, gave us a consistent user interface, some nice UX niceties and by niceties I mean things like drag and drop,
undo, copying, those sorts of things. But it was becoming really inflexible for what our users and admins wanted to build. And another, there's other sad things about it too. Like we have lots of dependencies and plugins and it's hard to upgrade Rails when you have 15 admin panel dependencies.
So we went looking for something else and we wanted to still keep all the stuff we liked about the admin framework, the top three green check marks, but see if we can do better in the other areas. So after going back and forth, we're like let's just use Rails.
So this is RailsConf talk, that makes sense. Rails provides a lot of what we need out of the box. So here's my little vanilla ice cream cone there, just plain vanilla Rails. It lets us focus on features still and by features I mean the stuff that ends up in the operating system
on the front end of those kiosks. It provides a consistent user interface in the form of whether we're bringing in like a bootstrap or a component library or just relying on partials. So that's nice and obviously it's flexible and we can build anything we want for our users.
The question mark was around these UX niceties. If we're just sticking with vanilla Rails out of the box, how do we get those nice drag and drop, that sort of WYSIWYG style stuff that users have come to expect really. So we were wondering how far we could get
with just the JavaScript library that Rails itself provides. Modern browser support has come a really long way and you might not have to bring in a React, an Angular, what have you to get the job done.
So what do I mean by old school? This is what I have dubbed the sprinkle continuum of JavaScript frameworks. And in a nutshell, since we're just using the out of the box UJS library that Rails has to provide this functionality,
old school in the context of this talk is about using the Rails request response lifecycle with some JavaScript that's executed when the page re-renders to get what you want in terms of those UX niceties. So we're not even using jQuery as you can see.
There's nothing that stops you from using ES6 or battle or anything like that. And even ES5, it's more about leveraging some of the modern browser technologies and it really depends on what your target audience is. So I'm gonna call it ES what works in your users' browsers or whatever works best for your users.
Some of you who have used Rails for a long time probably remember RJS and that was kind of dropped or supplanted by UJS and I won't go into that too much but basically that was writing Ruby code to execute JavaScript on the client and we'll be doing a little bit of that in these demos.
All right, so this is a basic example. This is sort of a contrived version of what one of our admin panels for displaying posters that rotate on the front of those kiosks. Basically we have a playlist is what we're calling it and these posters inside of it are called playlist items
and basically they get a duration and an order for how long they show. This is just a very basic HTML version where we'll be building on it throughout the talk. So select list, you can pull up things to do, add it to the list, you can delete it
and you can see that the page is refreshing each time. This is all just vanilla Rails under the hood so far. Just basic model view controller, nothing fancy. So take a look at the controller real quick just to prove that, no, probably not.
So this is the same controller for all the examples and the only thing we really added is this format JS call here in our respond to block and that's just because by default in Rails when you're using these remote asynchronous actions
it'll just call the HTML format block and we wanna do special stuff. So this should all be fairly familiar to everyone. There's some to-do's in there but it's just a few basic actions that now have the format JS block on it.
All right, so I kinda lied in the first example. Remote create is actually in Rails by default now. When you use form with in Rails 5 you actually have to specify local true in order to make it just use basic HTML post anymore.
While that page was actually just a post redirect get loop we had to make it do it out of the box. So if you want your form to be asynchronous don't do anything anymore. It used to be you put remote true in there but form with does that by default.
Okay, so let's look at stuff that is new in here. Taking a look at the create JS ERB file that comes back we're basically gonna render our playlist item and you'll see that special J character there.
That just is the shortcut for escape JavaScript. You may have seen that before. So that's gonna build HTML that allows it to be inserted into the page when the request comes back. We're gonna assign that to a variable and we're gonna stick it into the end of our playlist items list.
Notice that we're not using jQuery or anything here. Once again browsers have come a long way so we can leverage things like insert adjacent HTML and things like query selector. So at the end here you can see we're just adding the nicety of clearing the form
once the user has submitted it. So let's take a look at that looks like the look pretty much like the other thing except it's a little bit faster. Partially this demo is running locally so even the HTTP version with the redirect
is gonna be fairly fast. But basically what happens is that that response that view gets executed and gets inserted into the DOM. You've probably seen this before and I promise we'll get to more detailed examples of leveraging this. But you can see when we destroy it it's still synchronous.
You saw a little flash of the page there. We're gonna fix that up next. So looking at remote destroy this is another one that's fairly common if you're reading through the Rails guides. We're just going to change our delete link that we have in our partial there to have a remote true.
And this will submit it via Ajax now with a media type that lets it know that it should respond with the JavaScript partial. So that's all you have to do there. We added a little bit of disable width stuff and we'll go into that a little later. Probably familiar with that too. So let's take a look at the actual view
that we're rendering for our destroy. And we're gonna introduce some helpers here. And once again we're using all of Rails, all of the Rails Buffalo. And we use these partials quite a bit in our application. This hide once it's deleted
is going to hide the item in the list and then we're gonna render a flash message. This is the part that I always consider this strategy is most similar to RJS of old. And that is that we're actually using Ruby code to do JavaScript things. In this case it's just partials or helpers
and not a Ruby that's been transpiled to JavaScript. So you can see here in our application helper we've got this hide and it's just returning a raw string that finds our model by the DOM ID and sets it to display none. DOM ID is a helper included in Rails if you're not familiar with it
that just constructs an ID based on the model name and its ID from the primary key. So now if we had our new item here and we can delete it. And we'll notice that we have nice flash messages
and everything deleting quickly. Once again it might be hard to see the difference because we're localhosted fast but it is much faster and faster for the user when they're working in there. All right, so the one thing we did change
is we added this render flash method as well. And I think this is worth pointing out because this is a pattern that keeps coming up where we're sort of sharing a partial between an HTML view and a JavaScript view. And this is useful because if somebody
has JavaScript disabled in this day and age I don't know why they would but it's a possibility. Or you just have pages that you might not necessarily want to have this functionality. You can still share the partials between your code for the JavaScript rendering or server-side rendering. So render flash just takes a partial that we've extracted
and a corg splat and render it and insert it into the flash container in the layout. The flash container also calls the partial here in the HTML. All right, so we're continuing with the crud theme.
This one is probably the most straightforward but I couldn't leave it out since you're already doing create and delete. And it's not really shown a whole lot in the Rails guides but basically we're just changing the duration that we saw on the table to a form that we can update. So we'll just put an inline form there.
You can see that it's doing its remote thing. It's updated and I can refresh the page here. Show you that it actually updated and not, don't fall asleep.
I promise there's other cool stuff. So we did add a view for the inline update and basically all that did is render our special flash message to say, yeah, it was updated. The only reason I point this out is that there's,
we don't always have, it's not always comments you use for things like create and update, especially if you're coming at it from a JSON API-based world. There's usually either like an empty body that's rendered or you might just render the show view after you create. But we create all kinds of different views
for doing the different stuff you wanna do based on user action. All right, finally something a little different. We're gonna look at soft deletes. So I'm gonna take a little bit of a digression here to talk about soft deletes in REST.
And I mean, this is Rails after all so we're embracing all of Rails, including REST and HTTP. So we're gonna be adding a destroyed playlist items controller. And the reason we're gonna do this instead of just having an action that deletes or undeletes is that we kinda need to respect the 404 in HTTP.
If we delete an item, we shouldn't be able to go back and refresh it and have that ID still return 200. It should be gone. However, how do we delete it or undelete it via REST API if we can't find it anymore?
And the solution that I like is just to add a different controller, a different route, basically treat these as different resources now and consider them deleted playlist items. If you have an undelete functionality across the bulk of your app, you might have numerous undelete controllers. And DHH would be proud, no SQL here.
We're just finding all of the playlist items that are not destroyed to set in the playlist item. And then in our strong parameters, the only thing we're permitting is a destroyed at timestamp. Now, probably wanna make this a virtual attribute in real life, but for the sake of the demo
and simplicity, just went with that. So the undo functionality itself in the UI, basically when we destroy an item, we're gonna modify our flash message, actually, to return a special button.
And what the button does is it will actually be a remote function that updates the record. And you can see that here highlighted in the second argument here. We're gonna be passing destroy that nil to reset and essentially undelete the playlist item. You'll see we're passing a method patch, so this link essentially updates it.
And then, if you remember back to our render flash message, there was a, you could pass in and override the partial that you were passing into it. So here we're sending a special undo partial, which includes a link that appears over on the right to do this. We'll demo that here in a second.
So we can kill off cityscape here, and you'll see that we now have our undo method rendered here. We hit that, we'll say undone, back in there, it's set a patch, it's updated the record, and it provides a very nice user experience, similar to like a Gmail where you undelete something.
So far we have quite a bit of nice functionality going on with very little JavaScript written, no frameworks, and not even any jQuery. So if you notice the flash message there, we have, it's saying specifically, undone, and because we have a unique controller
representing this action, that gives us the opportunity to sort of customize what we say in response to a user doing this. So in the update view for the destroyed playlist item, all we basically do is say it's undone, and then we're gonna call our show helper to basically unhide or reveal any deleted record
that was hidden as part of the delete. So that's why we hit it before, instead of actually removing it from the DOM.
So the next thing we wanted to bring over into the CMS was this notion of copying. There are a lot of repetitive tasks in here, and wanted to find a way to make sure that users could perform those actions easily, would not have to repeatedly type stuff in. So this is the first time we're gonna dip
into sort of the model layer to add some functionality, and basically we're gonna just add a simple function that returns a hash of attributes that you want to copy. And you can actually add this to your application record as well, and just by default grab a model's attributes and get rid of the timestamps,
get rid of the primary keys, and that's usually good enough for most of them, and you can override that in your different models as needed. In the case of our playlist items, we just need to copy the duration and the poster ID. So the changes that we'll make to the playlist item itself,
and it seems a little complicated, but we basically just have another button that we're creating a record with an action here, and we're passing in those parameters that we're grabbing off of our record to copy. Once again, just changing the method to create, because all we're really doing is creating a new copy of that record
with the attributes that we've specified. So this is really similar to the undo functionality in a lot of ways, just a different set of parameters that's dynamic and a different method. So we've added a button here, now we copy it, copy, copy, copy,
and you can see our delete functionality is still there. Oops, we went too far, and undo. So a word of warning on the copying stuff, obviously this only works in cases where you have a very limited number of attributes. It's very easy to go overboard,
especially with deeply nested structures, and start ballooning the front end HTML code for every item there, with all of the potential properties you wanna copy. So use judiciously, but it can provide a really nice user experience.
Okay, so let me get into reordering I mentioned. And this is the first time we're really gonna do some more custom JavaScript here. And I focus more on the approach than the JavaScript itself. The playlist items, there's a requirement that they need to be reordered.
So we could reach for a plugin, a jQuery or something like that, or build something crazier in React, but as we mentioned earlier, browsers have come a long way, it's 2018, and we have a lot of built-in functionality for dragging and dropping.
So we're gonna follow kind of a similar pattern that the Rails UJS library itself uses, which is adding additional functionality to an HTML element based on data dash attributes. So here we've given a URL
to our table body of playlist items here, and that's gonna be used by our JavaScript code to know where to post this reordering update to. You notice that in the backend we've been ordering these based on position here. The ordering is actually done on the server side,
so we already know there's a position attribute on the playlist item, and we won't forget to make our playlist item itself draggable. So here, this works. You can see that they're now draggable and properly return a reordered flash message
when you reorder it. So this is one of those examples that wouldn't really be possible without the usage of sort of that request response lifecycle pattern, and that's because when you update a playlist item's position on the backend, we're gonna make sure that all the other playlist items
are kept in order and updated as well. So when you reorder one of these, it's going to rerender the entire table to make sure it's consistent and in sync. Now, that's one way to do it. There's several other ways, but you often run into issues doing client-side reordering. Things can get out of sync. Someone can come along and update something on the backend while you're working on the frontend,
different things like that. So while it's less performant to update all the records, if you have a limited subset like we do, it could be nicer to do it on the backend and just rerender this whole view when you change one record. So that's what we're gonna look at next. So this is a little bit more verbose than it needs to be,
but I kinda left it in because I wanted to show that even when it's bad, it's not that bad. So we have this, an if-else block here, and basically the if is gonna check to see if that record changed or that position changed after it was updated. If it has, we're gonna rerender
the entire list of records and then stick them into the table so that we know we have a consistent view of all the records and their order. So if somebody came along and updated, if two users were in the system updating something on the backend and somebody reordered something, the whole list would be refreshed
and they would make sure they would see the correct order. Down here at the bottom, that part of that if-else block, basically if it's changed, we're gonna render a custom flash that says it's reordered and if not, we're gonna ignore it and just say it's updated. For example, if we update the duration.
We do also need to call make reorderable again in our implementation and this is because we've replaced the entire DOM element here. We need to make sure that our hand rule of UJS function goes back and applies all that stuff to it again,
which is what we'll look at next. The actual reorderable JS code, this is mostly just to demonstrate the boilerplate that you need to set up for drag and drop in general in HTML. API is a little weird. So basically this make reorderable function
just finds that data dash reorderable and adds the requisite drag start, drag over, and drop events. You'll see that on drop, we have a reorder here. That's a function that's not shown on this page but all that really does is kick off the AJAX call to the back end to update that playlist item
and we're actually reusing the Rails UJS AJAX wrapper there but I didn't have space to squeeze it in here. But you have the code so you can go and look at it. So this is getting towards the end of what I wanted to demo.
I did have some, I cut some of this stuff for time because like I said I wanted to do questions and discussion and go over a few other things before I wrapped up. I did start going down the path of trying to implement a combo box with HTML5 data list component. Would love to talk about that.
It's kind of interesting but the API is also a little strange. It's not exactly like a combo box. So there are some things you have to change and fix up on the back end. In Rails to kind of make it work but you can still provide a nice out of the box kind of type down AJAX based experience that users have come to expect there
without bringing in plugins or libraries or anything like that. So leaning on standards once again. So the next piece I wanted to touch on before we move on and this is one of the shortcomings of using
this request response lifecycle format is that you are going to the server every time for making round trips to update your client page. And that can be very slow and while we're on a fast local network here it doesn't always, it doesn't look as sharp
and as crisp if you're on a bad connection or a noble bad network or anything like that. However, in adding a lot of the helpers and the UJS stuff with the remote creates we did take care, you may have seen,
to add some of the data attributes that sort of replace text and let the user know that there's an action happening while we're doing those changes. So let me just go ahead and drop my network down here.
Slow 3G sounds about right. And that will make my actual slide transition slow. So you'll see when we deleted them it was probably too fast to notice before
but oh, that was unexpected, did I, closing dev tools do that? Hold on a minute, I don't know how it was. So deleting something now on a really slow 3G network
you can see we added three little dots that could be a spinner or an icon or something that says loading. But it's important to give that feedback to the user because while most of the time it's gonna be fast and responsive there are going to be times where it isn't. So the nice thing is that Rails takes that into account with a lot of its UJS stuff
and it's just important to remember to add all of those to each one. So the last thing I wanted to talk about
is what we didn't do and showcase in here. Obviously a lot is the answer but that's kind of why I want to talk with people and have some discussions around
what are some really hard UI, UX stuff that we think we can only do with single page apps that we might be able to do with this sort of old school style request response lifecycle JavaScript. Obviously single page apps or many single page apps within a larger app are still king
in terms of rich interactivity. If I was gonna build Photoshop in the browser obviously this would not be the way I was going to do it and you did notice there are a few other things that we didn't touch on that are still covered under the request response lifecycle sort of approach and that would be things like paging, sorting, filtering
and intentionally omitted those because there's a lot of edge cases that happen around merging parameters or filtering and sorting, messing with the state of the URL and the address bar and I've gone down that path and it just does not work out very well
in terms of providing a better user experience. So manipulating that all in JavaScript doesn't work as well or it's harder to do than the other stuff that we demoed here. So recommendation there is to probably just use a regular old posts or get to the server
and rely on Turbolinks to smooth out some of the performance there for you. It's much easier than trying to merge in all those parameters and keep track of paging and that kind of thing. Okay, that is all I have. I managed to be under time actually. So thank you very much for watching
another JavaScript talk at RailsConf and please let me know if you have any questions. Thank you.