Showing posts with label jquery. Show all posts
Showing posts with label jquery. Show all posts

Wednesday, September 28, 2011

jQuery Mobile Displaying A Dialog

While working on a mobile web app that is using jQuery Mobile I wanted to display a dialog without making an Ajax call to the server to load it which is the "out of the box" way to do it.

Initially I couldn't figure out how to do this but after some reading and thinking it's ridiculously easy.

You have to use a multi-page template which means you have more then one jQuery Mobile "page" container. Example:
<div data-role="page" data-theme="a">
    <div data-role="header" class="header">
        <a href="#menu" data-icon="grid" data-theme="b" data-iconpos="notext" data-transition="pop">&nbsp;</a>
        <h1>My Web App</h1>
        
    </div>

    <div id="map" data-role="content" data-theme="d">
        <p>This is my main content</p>
    </div>
    
    <div data-role="footer">
        <div>
        by <a href="http://cdeutsch.com" rel="external">CDeutsch</a>
        </div>
    </div>
</div>

<!--start menu-->
<div id="menu" data-role="dialog" data-theme="a" data-url="menu">
    <div data-role="header" class="header">
        <h1>Main Menu</h1>
    </div>
    <div data-role="content" data-theme="d">
        <ul data-role="listview" data-theme="c">
            <li><a href="http://blog.cdeutsch.com" rel="external">My Blog</a></li>
            <li><a href="http://twitter.com/cdeutsch" rel="external">Twitter</a></li>
        </ul>
    </div>
</div>

I'm using a button on the left side of the header to trigger showing the dialog. Just set the href to the id of the dialog (in this case menu) and you're all set. No more repeatedly hitting the server for a frequently used resource.

See this jsfiddle for a working example.

Wednesday, April 6, 2011

Sort Table Rows By Drag and Drop Using jQuery

jQuery UI allows you to make a list of elements sortable, but if you want to use it to sort table rows you have to do a few workarounds. This is the code I started with.

$(document).ready(function () {

    //make table rows sortable
    $('#myTable tbody').sortable({    
        helper: function (e, ui) {
            ui.children().each(function () {
                $(this).width($(this).width());
            });
            return ui;
        },
        scroll: true,
        stop: function (event, ui) {
            //SAVE YOUR SORT ORDER                    
        }
    }).disableSelection();

});



The helper function (courtesy of The Foliotek Dev Blog) fixes the width of the row which collapses as soon as you remove it from the table.

This should work for most scenarios, but I've found a bug in Firefox where the row will jump up and float above the cursor when there are scrollbars present on the web page. It seems to be a position issue caused by parent elements of the table. I still haven't figured out exactly what the magic combination is that causes this but I can replicate it with HTML5 Boilerplate when using the default style sheet and table below.

I've replicated the bug on jsFiddle here:
http://jsfiddle.net/cdeutsch/2Yxw2/

<div id="container">
<header>

</header>
<div id="main" role="main">
    <div style="position: relative;">
    <table id="myTable">
        <tbody>
        <tr>
            <td>1</td>
            <td>Blah Blah Blah Blah</td>
        </tr>
        <tr>
            <td>2</td>
            <td>2222 22222 22222 2222 22222</td>
        </tr>
        <tr>
            <td>3</td>
            <td>Test Test Test Test Test</td>
        </tr>
        <tr>
            <td>4</td>
            <td>4444 4444 4444 4444 4444</td>
        </tr>
        <tr>
            <td>5</td>
            <td>Hi Hi Hi Hi Hi Hi</td>
        </tr>
        <tr>
            <td>6</td>
            <td>Bye Bye Bye Bye Bye Bye Bye</td>
        </tr>
        </tbody>
    </table>
    </div>
</div>
<footer>

</footer>
</div> <!--! end of #container -->


If this happens use the code below (inspired by this Stackoverflow question with some tweaks) to fix the issue.

Try it in jsFiddle here:
http://jsfiddle.net/cdeutsch/WysJL/

$(document).ready(function () {
    //make table rows sortable
    $('#myTable tbody').sortable({
        start: function (event, ui) {
            //fix firefox position issue when dragging.
            if (navigator.userAgent.toLowerCase().match(/firefox/) && ui.helper !== undefined) {
                ui.helper.css('position', 'absolute').css('margin-top', $(window).scrollTop());
                //wire up event that changes the margin whenever the window scrolls.
                $(window).bind('scroll.sortableplaylist', function () {
                    ui.helper.css('position', 'absolute').css('margin-top', $(window).scrollTop());
                });
            }
        },
        beforeStop: function (event, ui) {
            //undo the firefox fix.
            if (navigator.userAgent.toLowerCase().match(/firefox/) && ui.offset !== undefined) {
                $(window).unbind('scroll.sortableplaylist');
                ui.helper.css('margin-top', 0);
            }
        },
        helper: function (e, ui) {
            ui.children().each(function () {
                $(this).width($(this).width());
            });
            return ui;
        },
        scroll: true,
        stop: function (event, ui) {
            //SAVE YOUR SORT ORDER                    
        }
    }).disableSelection();
});


Hope this helps someone. Took me about 1/2 a day to fix the Firefox issue.

Sunday, February 20, 2011

Preserve Telerik MVC Grid Checkboxes When Paging

This article explains how to preserve the state of input checkboxes in a Telerik ASP.NET MVC Grid control when paging, sorting, and filtering. This solution only works for client side Data Binding. If you're using Server Side Data Binding you'll need to come up with a different solution.

I'm a big fan of the Grid control. It's a real time saver and fills in a big gap that ASP.NET MVC is missing over WebForms!

The first step is to maintain a list of checkbox states. Below is the client side javascript you'll need to add to the page that has your MVC Grid. Note, jQuery is required for this to work.

var selectedIds = [];

$(document).ready(function () {
    //wire up checkboxes.
    $('#YOUR_GRID_ID').on('change', ':checkbox', function (e) {
        var $check = $(this);
        //console.log($check);
        if ($check.is(':checked')) {
            //add id to selectedIds.
            selectedIds.push($check.val());
        }
        else {
            //remove id from selectedIds.
            selectedIds = $.grep(selectedIds, function (item, index) {
                return item != $check.val();
            });
        }
    });
});

NOTE: You'll want to change YOUR_GRID_ID to match what you set your Grid's Name to.

The code above uses the global variable selectedIds to store the values of each selected checkbox in the grid. I use the jQuery live method to attach the change event to every checkbox in the grid. The nice thing about live is that it will catch new checkboxes that are loaded when paging, filtering, or sorting.

The next step is to restore the checkbox states after each ajax request that changes the grid.

function onDataBound(e) {
    //restore selected checkboxes.
    $('#YOUR_GRID_ID :checkbox').each(function () {
        //set checked based on if current checkbox's value is in selectedIds.
        $(this).attr('checked', jQuery.inArray($(this).val(), selectedIds) > -1);
    });
}

NOTE: You'll want to change YOUR_GRID_ID to match what you set your Grid's Name to.

I use the Grid's OnDataBound client side event, which fires after the Grid is finished loading new data, to parse threw all the new checkboxes and set their checked state based on if their value is in our selectedIds global variable.

Add the .ClientEvents line below to wire up the onDataBound event:

@(Html.Telerik().Grid<Web.Models.YOURMODEL>()
    .Name("VideosGrid")
    .ClientEvents(events => events.OnDataBound("onDataBound"))
)

If this helped you or you have a suggestion to improve it, let me know via the comments!

Wednesday, February 16, 2011

Plupload and ASP.NET MVC3

This post will explain howto integrate Plupload into an ASP.NET MVC3 project. It will probably work with lesser version of MVC without too many changes.

Integrating it into my project went smoother then any upload plugin I've ever used before. My previous favorite was NeatUpload but I've been having issues with it with large files, and since Plupload supports chunked uploads I figured I'd give it a try.

Here's what I did to get the Custom Upload example working in MVC.

First add the following to one of your Controllers or create a new Controller to hold this Action. (most of the credit goes to these two Stackoverflow questions: Question 1, Question 2)

/// <summary>
/// Handles chuncked file uploads like the ones from plupload.
/// </summary>
/// <param name="chunk"></param>
/// <param name="name"></param>
/// <returns></returns>
[HttpPost]
public ActionResult Upload(int? chunk, string name)
{
    var fileUpload = Request.Files[0];
    var uploadPath = Server.MapPath("~/App_Data/Uploads");
    chunk = chunk ?? 0;

    //UPDATE 2/17/2011: Removed this since it doesn't work. I recommend setting the unique_names param client side if you want unique names.
    ////find a free filename if this is the first chunk
    //if (!chunk.HasValue || chunk < 1)
    //{
    //    int xx = 1;
    //    while (System.IO.File.Exists(uploadedFilePath))
    //    {
    //        uploadedFilePath = Path.Combine(uploadPath, Path.GetFileNameWithoutExtension(name) + "_" + xx + Path.GetExtension(name));
    //        xx++;
    //    }
    //}
    
    //TODO: cleanup old files

    //write chunk to disk.
    string uploadedFilePath = Path.Combine(uploadPath, name);
    using (var fs = new FileStream(uploadedFilePath, chunk == 0 ? FileMode.Create : FileMode.Append))
    {
        var buffer = new byte[fileUpload.InputStream.Length];
        fileUpload.InputStream.Read(buffer, 0, buffer.Length);
        fs.Write(buffer, 0, buffer.Length);
    }

    return Content("Success", "text/plain");
}

NOTE: If you want to secure this Action and you're using Forms Authentication add the [Authorize] attribute above [HttpPost]

NOTE 2: I'm uploading files to ~/App_Data/Uploads. Create this folder or change the code above.

Now open or create a View file that you want to add the upload to and add the following HTML:

<div id="container">
    <div id="filelist">No runtime found.</div>
    <br />
    <a id="pickfiles" href="#">[Select files]</a>
    <a id="uploadfiles" href="#">[Upload files]</a>
</div>

Add the following Javascript to your View assuming you've placed everything from the js folder in the Plupload zip into the ~/Scripts/plupload/ folder of your project:

<!--Load 3rd party plupload scripts-->
<script src="@Url.Content("~/Scripts/plupload/gears_init.js")" type="text/javascript"></script>
<script type="text/javascript" src="http://bp.yahooapis.com/2.4.21/browserplus-min.js"></script>
<!--Load plupload and all its runtime scripts-->
<script src="@Url.Content("~/Scripts/plupload/plupload.full.min.js")" type="text/javascript"></script>

<script type="text/javascript">

    $(document).ready(function () {
        var uploader = new plupload.Uploader({
            runtimes: 'gears,html5,flash,silverlight,browserplus',
            browse_button: 'pickfiles',
            container: 'container',
            max_file_size: '2048mb',
            url: '@Url.Action("upload", "home")',
            flash_swf_url: '@Html.ScriptPath("plupload/plupload.flash.swf")',
            silverlight_xap_url: '@Html.ScriptPath("plupload/plupload.silverlight.xap")',
            filters : [
                {title : "Image files", extensions : "jpg,gif,png"},
                {title : "Zip files", extensions : "zip"}
            ]
        });

        uploader.bind('Init', function (up, params) {
            $('#filelist').html("<div>Current runtime: " + params.runtime + "</div>");
        });

        $('#uploadfiles').click(function (e) {
            uploader.start();
            e.preventDefault();
        });

        uploader.init();

        uploader.bind('FilesAdded', function (up, files) {
            $.each(files, function (i, file) {
                $('#filelist').append('<div id="' + file.id + '">' + file.name + ' (' + plupload.formatSize(file.size) + ') <b></b>' + '</div>');
            });

            up.refresh(); // Reposition Flash/Silverlight
        });

        uploader.bind('UploadProgress', function (up, file) {
            $('#' + file.id + " b").html(file.percent + "%");
        });

        uploader.bind('Error', function (up, err) {
            $('#filelist').append("<div>Error: " + err.code + ", Message: " + err.message + (err.file ? ", File: " + err.file.name : "") + "</div>");

            up.refresh(); // Reposition Flash/Silverlight
        });

        uploader.bind('FileUploaded', function (up, file) {
            $('#' + file.id + " b").html("100%");
        });
    });

</script>

NOTE: I'm using Razor as my View Engine. If you are not do a search for @* in the HTML above and replace with <%= %> type syntax.

NOTE 2: Update the url param in the javascript to point to where your Upload Action is. Mine is in the Home Controller

You will also need to have jQuery included. I left that out since most people already do, but if you don't you can use this:

<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
    google.load("jquery", "1.3");
</script>

The only other thing you may want to immediately do is adjust the filters option in the javascript to allow/disallow file types as needed.

That's it! Hope this was as easy for you as it was for me!

Wednesday, December 22, 2010

How We're Surprising The Kids with a Disney World Trip

We booked a trip to Disney World for my fiance's 8 year old daughter and 10 year old son as their Christmas present. Which means we needed a good way to surprise them. I did some Googling and liked a couple ideas:
  • Wrapping up a mylar helium balloon with tickets attached so when the gift is opened the balloon floats out.
  • Putting a "gold ticket" inside a chocolate bar
  • Scavenger hunt
Since I'm not that good at "arts and crafts" I decided to go with what I knew and make a website. 
(best viewed using the Google Chrome web browser)

We plan to surprise the kids Christmas night, so if you know us keep it a secret for a few more days.  ;)

For any "non-programmers" reading this you can probably stop now. ;)

For the geeks, I used StaticMatic and TextMate to do the coding. The site is a single page of HTML5 and jQuery (and jQuery UI). You can get the source code here on GitHub.

The snow was Seb Lee-Delisle's work. It's a bit CPU intensive so if the application feels slow it's mostly due to the snow. The computer we'll be using to surprise the kids is fast enough to not be an issue.

I modified the jQuery TickerType plugin to type out the "You're going to Disney World" message at the end. 

If you've never used StaticMatic it's pretty cool. It allows you to use Haml and Sass to do your markup which is a fun quick way to code HTML. Great for a quick static website.

Since I use pieces of Paul Irish's HTML5 Boilerplate in just about every site I do now (I'm even updating old sites to use it as I work on them) I went looking for a StaticMatic plugin for it and thankfully found staticmatic-boilerplate by Aaron Cruz on GitHub.

Overall it was a fun break to use StaticMatic and TextMate (and OS X for that matter) versus the Visual Studio 10 environment I currently spend most of my time in.

But there are two things I'd love to see improved with StaticMatic and/or the Boilerplate plugin:
1) Make it easier to add jQuery UI to your site.
2) Make it easier to switch between uncompressed Javascript while developing and the production minified and combined javascript.