by Oliver
27. June 2015 12:46
In the process of making Camping.Info more mobile-friendly, I've needed to move around pieces of HTML in the DOM time and again. At last, I've come up with two little helper functions that I wrapped into a little jQuery plugin that I want to share in this post. When To Use Move-Restore The DOM tree on every page on Camping.Info is quite large and often convoluted. At least partly this is a consequence of the many UserControls we use to build our pages on the server using ASP.NET WebForms. To achieve a more mobile-friendly layout of these pages we needed to position certain elements differently, hide some and show others, and in the end also move around some critical parts to fit the mobile design. Much of work could and has been done by our designer via CSS but for the rest of them we need to touch the DOM tree. Move-Restore proves especially helpful in the case of a user-agent switching between two different layouts of your site, e.g. the desktop and the mobile layout (in case you have just those two), because it easily allows you to restore elements you previously moved around. How to Use Move-Restore Just call $("#move-me").moveTo("#target") when you want to move something e.g. in your mobile layout and at a later point, e.g. when switching back to your desktop layout, call $("move-me").restore(). That's it. I've also put together a fiddle to show how to use the plugin here. Also, have a look at the usage.html in the below gist. How It Works The beauty of this plugin, in my opinion, lies in the fact that you don't have to manually keep track of where you get an element from to later restore it. Internally, the plugin inserts a <script> element in place of the moved element. The (highly probably) unique id of that script element is stored as a datum on the moved element and later retrievable when we need to restore the element to its original position. Currently, at revision 2 of the gist, there's one option you can tweak to match your scenario: the jQuery method the plugin should use to move the selected element(s) around. By default, move-restore uses appendTo but there are other sensible options, e.g. prependTo, insertAfter, or insertBefore. Just pass the one that fits your needs as the second optional argument to moveTo. Use Move-Restore at Your Convenience I invite everyone to try and use this handy little plugin and am open for feedback. Happy coding!
by Oliver
2. August 2012 15:19
On Camping.Info, we lately had a user report that hitting the enter key while in the login form one would be redirected to the search page instead of being logged in. This happened when the focus was still on the password text box because the “Search” button on the page was the first input element on the page (inside the single <form> element that WebForms allows for) of type submit which would by default handle the enter key press event. To improve the user experience, we set out to find a way to postback the correct UpdatePanel – the one which currently has the focus (or one of its child controls). Using an ASP.NET Panel and the DefaultButton property There is a built-in solution to this problem if you’re willing to slightly change your markup and add a hidden <asp:Button> element to your part of the page you want to asynchronously update. You need to wrap your existing html into an <asp:Panel> element and set its DefaultButton property to the ID of that hidden button. This would look something like this: 1: <div class="form21">
2: <asp:Panel runat="server" DefaultButton="btnHiddenRegister">
3: <asp:TextBox ID="txtEmail" runat="server" />
4: <asp:TextBox ID="txtPassword" runat="server" TextMode="Password" />
5: <asp:LinkButton ID="btnRegister" runat="server">Register</asp:LinkButton>
6: <asp:Button ID="btnHiddenRegister" runat="server"style="display:none" />
7: </asp:Panel>
8: </div>
Of course, you need to hook up the code-behind Click handler for the btnRegister LinkButton to the hidden Button element as well to make this work.
The proposed solution will instead have html like the following:
1: <div class="form21 jq-form">
2: <asp:TextBox ID="txtEmail" runat="server" />
3: <asp:TextBox ID="txtPassword" runat="server" TextMode="Password" />
4: <asp:LinkButton ID="btnRegister" runat="server" CssClass="jq-form-submit">Register</asp:LinkButton>
5: </div>
Let’s see how we get this to work.
Using jQuery
On stackoverflow.com, I quickly found this post about a jQuery replacement for DefaultButton or Link where the this answer points to the original solution from March 2009 proposed by this post on fixing the enter key in ASP.NET with jQuery. I’ve improved on the proposed answer in a few ways, so this is what this post is about.
The original solution
1: $(document).ready(function(){
2: var $btn = $('.form_submit');
3: var $form = $btn.parents('.form');
4:
5: $form.keypress(function(e){
6: if (e.which == 13 && e.target.type != 'textarea') {
7: if ($btn[0].type == 'submit')
8: $btn[0].click();
9: else
10: eval($btn[0].href);
11: return false;
12: }
13: });
14: });
Remarks: works only if the content is present at the time the script executes, and works only for a single pair of form and form-submit elements.
Our improved solution
1: $('body')
2: .off('keypress.enter')
3: .on('keypress.enter', function (e) {
4: if (e.which == 13 && e.target.type != 'textarea') {
5: var $btn = $(e.target).closest('.jq-form').find('.jq-form-submit');
6: if ($btn.length > 0) {
7: if ($btn[0].type == 'submit') {
8: $btn[0].click();
9: }
10: else {
11: eval($btn[0].href);
12: }
13: return false;
14: }
15: }
16: });
Improvements
The first thing to improve is the registration of the keypress event handler. Since Camping.Info is quite a dynamic site with lots of asynchronous postbacks to the server (mostly using WebForms’ UpdatePanels) a lot of the javascript code we need on our page won’t execute properly as is because it expects the elements it works with to be present at the time of the script’s execution. That’s also the case with the original solution: both the submit button element with class .form_submit and the pseudo-form element .form (I’ve called it pseudo-form element since it’s just a parent element of any type marked with the class .form) must be present when the script executes, otherwise nothing will happen. That’s why – using jQuery 1.7 syntax – we register the keypress event handler NOT on the pseudo-form element but directly on the body using on(). We also the event namespace it by appending .enter to its name keypress so that we can safely remove it (using off()) in case we call the script a second time during postback to prevent it from being attached twice. Please note, that at this point neither the pseudo-form nor the form submit element must be present on the page for this to work.
Once the enter key has been pressed anywhere on the page and we’re inside the event handler, we set out to check for a pseudo-form surrounding the element that received the enter key press event and inside it we look for a submit element. [I’ve also changed the class names for those elements by our internal convention to prefix all those that are jQuery related by jq- to set them apart from the CSS class names our designer works with.] Only if we’ve found both those elements, we proceed with either clicking the element if it’s really a button or executing the javascript code that ASP.NET rendered into our LinkButton’s href tag which asynchronously posts back to the server for some update to our page.
A subtle but important detail here is to return false (and thus prevent the event from being processed any further) only in case we actually found the pseudo-form and submit elements, so that in the opposite case the default behavior of the page will be preserved.
Conclusion
The solution presented here works equally well with static and dynamically loaded page content and for any number of forms on your page. It replaces the solution provided by ASP.NET WebForms’ using a Panel and its DefaultButton property, resulting also in cleaner markup.
Happy Coding!
by Oliver
13. March 2012 01:37
Finally, with an update we rolled out last week, (almost) all tooltips on Camping.Info look and behave similar, differing mostly in positioning and size, but not in the general look and feel. We chose the jQuery Tools Tooltip as the base for our own solution and it got us pretty far, but there were some pitfalls and scenarios that we needed to handle ourselves. This post is about what limitations we experienced and how we dealt with them. The original As you can read in the jQuery Tools Tooltip documentation, the tooltip plugin is highly configurable. It can take different elements as the tooltip for any given trigger: the value of title attribute of the trigger element the html element immediately following the trigger the html element immediately following the first parent of the trigger an arbitrary html element on the page. You can also position the tooltip pretty much wherever you want relatively to the trigger: Our adaptations Another way to chose the tooltip We found one more useful way to define the tooltip for a trigger element: if the trigger is e.g. a table cell in an html table and you don’t want to specify a static tooltip for some or all table cells but a different one for each cell or at least a number of cells, it makes sense to define the tooltip element inside the trigger (the table cell). Since this effect was not achievable extending the jQuery Tools Tooltip plugin we started changing their source: Breaking the tooltip out of some parent container We also faced some problem properly showing tooltips that needed to “break out” of some bounding box, inside which they were defined (i.e. their html markup). This problem e.g. occurred inside elements with style position: relative, which we have a few of on Camping.Info. Our first attempt was to clone the tooltip element and show the clone instead of the original. This worked in almost all cases – until we tried to attach some more behavior to elements inside the tooltip. The behavior, e.g. some click event handler that we expected to fire when clicking on some element inside the tooltip, wouldn’t execute, since we were working with the clone! So we decided to simply move the tooltip up in the DOM tree for the time it is being shown, more precisely just beneath the form tag that we have on all our pages. We create a placeholder at the place where we removed the tooltip to reinsert it again once it’s being hidden. The code we added to the show() method looks like this: … and here’s the counterpart in hide(): Now, this works quite well everywhere, independent of the position of the tooltip in the DOM tree. Global tooltip configuration Using inline static configuration One feature we quickly missed was some kind of static tooltip configuration without calling $(...).tooltip({ settings: ... }) for every single tooltip we wanted to create or hook up, respectively. What we came up with is to use the HTML5 data attributes to define any specific configuration statically inside the trigger element’s html markup. Thus, we need to call the tooltip initialization code only once for the whole page. The configuration now looks like this: We use specific prefixes with the data attributes to make them easier to understand, e.g. data-tt-position for an attribute that is used for tooltips and jq-tt-trigger for a class that is used by some jQuery code for tooltips. To process this kind of static configuration we need some custom code that will, at some point, call the original (well, modified by now) plugin code. Unfortunately, the jQuery Tools Tooltip plugin was not designed to allow runtime configuration of the tooltip to show, but we found a way using the onBeforeShow and onHide event handlers. The basic idea is to change the global Tooltip configuration during the first method so that the tooltip we will be showing will be configured correctly, and to reset the global configuration once the tooltip has been hidden again. To achieve this, we iterate over all configuration properties that the jQuery Tools Tooltip plugin supports and search for the respective data attributes on the currently processes trigger element. One example would be the position property: to replace the default value provided by the plugin we look for an attribute that’s called data-tt-position and use its value to temporarily overwrite the default value during the onBeforeShow event handler. Using global profiles Once we had the static configuration working and started to replace all of those clumsy and overly complicated AjaxControlToolkit HoverMenuExtenders, it quickly turned out that we were copy’n’pasting the same configuration in a thousand places. This was not only ugly and violated the DRY principle, it also lead to some unnecessarily bloated html. As a solution to this maintenance nightmare we came up with profiles that comprise a set of configuration options that would else be repeated over and over again: Now, they lead to some really clean html markup: The only change from using the inline static configuration is to use the profile’s properties – everything else stays the same! Conclusion The jQuery Tools Tooltip plugin is a nice, small and highly configurable tool for easy tooltip creation and usage. In a larger web application there a few shortcomings that we’ve addressed here and which we’ve provided working solutions for. We hope to release those changes soon in its own project on our GitHub account. Happy coding!
by Oliver
8. September 2011 02:41
On Camping.info, we use flot 0.7 with jQuery and asynchronous updates for internal click statistics visualization. Unfortunately, we’ve been having some trouble with our flot graph – but only in IE8 and only sometimes! This has been really annoying, especially since we’re expecting a usage growth of this graph and IE8 has a user share of 25%. The problem is that after fetching the graph data from the server the graph sometime won’t appear on the canvas, although it seems to be there as the yellow tooltip suggests when hovering over the graph line. The graph looks like this (broken vs. intact): We were (are?) having a hard time reproducing this bug on our staging system but it would appear from time to time. On our live system we would get it quite often, although a page refresh often helped. Searching the web for flot+IE8 you’ll get a few results most of which point to the “flot-graphs” Google Group. A search on stackoverflow.com led me to an interesting post called IE8 is sometimes displaying my page correctly, sometimes not, which seemed interesting enough to check out. There, they talk about a problem with the display:inline-block CSS rule and that it is error-prone in IE<=8. So I checked the source of excanvas.min.js which is responsible for providing a canvas element to flot in IE browsers below version 9, and sure enough I found this: 1: a.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}
The fix proposed in this answer was to use zoom:1 to force elements with display:inline-block to have a layout in IE (if you really want to know what that means, here’s more background info). Well, I tried that – to no avail.
The next hint came from a post in the flot-graphs Google Group and proposed using flashcanvas instead of excanvas as the canvas provider for flot. Oh well, we haven’t got anything to lose, right? I replaced the script include and saw – exactly the same thing as before: nothing! (Check out the IE developer toolbar on the right – the you can see the Flash object inside the canvas element that was generated by flashcanvas.)
What was left? Oh, I hadn’t tried to check for an updated version of excanvas yet – so I went and did that.
The latest download version dates back to March 2009 but there has been some activity since then so I decide to go for the latest source which is also already a year and a half old but still by a whole year younger that the last official download I replaced the file and haven’t had any problem so far!
We will be keeping an eye on this, but for now my hopes are high that this update might have finally helped.
Happy coding!