Going the SPA way

AngularJS Hackathon

The SPAs are very popular lately! And I’m not talking about what everybody thinks of when they hear SPA, but about Single Page Applications. I guess every week I keep reading at least 1-2 posts in my RSS reader about the subject.

There are tons of articles already on the subject, plenty of books to read and so many frameworks aimed at making the development of SPAs easier that it’s really difficult to pick the right one for you in case you are new to the subject (just like me…).

About 2 months ago I read/watched via RSS one article written by Dan Wahlin called Video Tutorial: AngularJS Fundamentals in 60-ish Minutes. This is without any doubt the best 70 minutes I’ve spent on YouTube in a long long time. The video is very well organized, very easy to follow and introduces you to the AngularJS framework created by Google. It doesn’t go into details as it’s impossible to do it in only 70 minutes, but it shows the power of AngularJS and makes you want to find out more!

I was really impressed by what you can do with very little code and effort, so I wanted to dig a bit deeper… There are plenty of resources online for learning and understanding AngularJS and below I’ll enumerate just a few that I’ve found to be really good:

  •  AngularJS API reference – very good point to start directive, modules etc… also easy to discuss problems and get help as every topic has a Disqus thread associated.
  • Egghead.io – very good collection of short videos on AngularJS topics (the AngularJS official site points actually to this collection of videos)
  • Scoop.it! – has a very good collection of AngularJS resources that it’s worth taking a look at

As probably the best way to learn something new is to do something practical, I decided to write a small SPA in order to show the events listed on Cyprus Events in a more mobile friendly way (their “mobile” interface is old and rather unfriendly to use).

For the purpose I’ve use the following technology stack:

  • AngularJS
  • Bootstrap – cause I can’t design anything and Twitter Bootstrap is really amazing
  • jQuery
  • UnderscoreJS – a really nice JS utility library that makes life easier… it contains some nice “extension methods” for collections that helped me write “LINQ like queries” on an array – thanks to .NET Rocks and Derik Whittaker for mentioning this library in show 873.

The result is available online here… the app is not impressive from functionality point of view, but in order to make it work I’ve got my hands dirty with few AngularJS concepts: modules, routing, controllers and views, created a service to retrieve the events for a specific feed (as I couldn’t load the RSS feeds directly with jQuery’s AJAX methods I’ve used a small PHP code snippet found here), used promises and even created a factory which I’ve used to share data between 2 controllers.

One aspect that I would like to improve would be caching of events as currently the feed is loaded every time the user selects a category. One idea would be to use the HTML5 local storage if available using also the date when the feed was last loaded and reload it on user request (some button for refresh) or if the data is older than 1 hour (could be even more)… but, still I’m trying to understand what caching options is AngularJS offering (suggestions are welcomed in comments 🙂 )…

The result is working properly on desktop browsers (Chrome, Firefox, IE, Opera) and mobile phones: checked on Android phones and also iPhone and iPad emulators (available as plugins for Web Matrix), but it’s not working on my Windows Phone device (WP 7.8) – trying to find a way to understand why the feeds are not loaded…

The project in it’s current stage is available for download on my SkyDrive

As soon as I’ll get some free time I’ll try to implement something similar, but using the approach John Papa is promoting: Durandal, Breeze (which looks really interesting), KnockoutJSHot Towel… and probably use jQuery Mobile for the interface…

Any comments or suggestions are welcomed!

Enhanced by Zemanta

Playing with jQuery, Uploadify and LightBox

Recently I started working on a project in ASP.NET and one of the “features” I had to implement was to allow the user to upload photos. As user experience is important, I didn’t want to have a simple page which would post back refreshing the page, or just a “one by one” upload process.

Since the FileUpload control is one of the controls that don’t work inside the Update Panel, I had to look at some other options. As I am still learning jQuery and trying to improve in this area, I decided to look for some upload plugin written in jQuery. After some research on the Internet I managed to find Uploadify which seems like one of the best options out there: simple, good looking and easy to play with.

In one of my previous blog entries I was showing how to upload, store an image to a database and then retrieve it and add a watermark to the image. I will use this article as a starting point for this new application and just modify it in order to make the upload process more user friendly.

First step to use the Uploadify plugin is to import it to our project and in order to use it we need to use a simple input element of type “file”. Once we defined the input element in our page we need to call the uploadify method on it and specify the default values we want. After this small step, the upload page should look like:

HTML
  1. <form id=“form1” runat=“server”>
  2. <div>
  3. <script type=“text/javascript”>
  4. // <![CDATA[
  5. var id = “55”;
  6. var theString = “asdf”;
  7. $(document).ready(function() {
  8. $(‘#fileInput’).uploadify({
  9. ‘uploader’: ‘uploadify/uploadify.swf’,
  10. ‘script’: ‘Upload.ashx’,
  11. ‘scriptData’: { ‘id’: id, ‘foo’: theString },
  12. ‘cancelImg’: ‘uploadify/cancel.png’,
  13. ‘auto’: false,
  14. ‘multi’: true,
  15. ‘fileDesc’: ‘Image Files’,
  16. ‘fileExt’: ‘*.jpg;*.png;*.gif;*.bmp;*.jpeg’,
  17. ‘queueSizeLimit’: 5,
  18. ‘sizeLimit’: 512000,
  19. ‘buttonText’: ‘Choose Images’,
  20. ‘folder’: ‘/uploads’,
  21. ‘onComplete’: function(event, queueID, fileObj, response, data) {
  22. },
  23. ‘onAllComplete’: function(event, queueID, fileObj, response, data) {
  24. }
  25. });
  26. });
  27. // ]]></script>
  28. <input id=“fileInput” name=“fileInput” type=“file” />
  29. <a href=“javascript:$(‘#fileInput’).uploadifyUpload();”>Upload Files</a>
  30. </div>
  31. </form>

I will not go into explaining all the parameters used for setting up Uploadify, I will just highlight 3 parameters that were of interest for me:

  • queueSizeLimit: according to the official documentation it allows you to set the maximum number of files that can exist in the queue at one time.  If the user tries to add more the the maximum limit, the onQueueFull function is triggered.  The multioption should be used in conjunction with this option.
  • sizeLimit: allows you to set the maximum file size allowed per file in bytes.  If the file exceeds the size limit, it will return an error on upload.
  • script: is required and points to the back-end script that will process your files.  The back-end script can be written in any server-side language.

As you can see I set my upload control to allow a maximum of 5 files at one time and the maximum size allowed for one file is 512000 bytes. The Upload.ashx script holds the server code which is responsible for getting the image the user tries to upload, store it in the database and reply back to the client. Since we allow the user to upload an image, I want to allow them to delete the posted image in case they changed their mind. And I want to do it by the simple click of the mouse, no page postbacks… and why not show the uploaded images in an elegant way with LightBox?

Well, let’s try to do that…

We’ll need to modify our upload page just a bit to accommodate an area where the images will be displayed after upload and also add a small piece of code which will display the images in case of successful upload – the code which will display the images will be send as response by the Upload.ashx script. I added one div element at the end of my previous code and gave it an id=”uploaded_photos” and also modified the code in the onComplete event of the uploadify method (changes are in italic font):

HTML
  1. <form id=“form1” runat=“server”>
  2. <div>
  3. <script type=“text/javascript”>
  4. // <![CDATA[
  5. var id = “55”;
  6. var theString = “asdf”;
  7. $(document).ready(function() {
  8. $(‘#fileInput’).uploadify({
  9. ‘uploader’: ‘uploadify/uploadify.swf’,
  10. ‘script’: ‘Upload.ashx’,
  11. ‘scriptData’: { ‘id’: id, ‘foo’: theString },
  12. ‘cancelImg’: ‘uploadify/cancel.png’,
  13. ‘auto’: false,
  14. ‘multi’: true,
  15. ‘fileDesc’: ‘Image Files’,
  16. ‘fileExt’: ‘*.jpg;*.png;*.gif;*.bmp;*.jpeg’,
  17. ‘queueSizeLimit’: 5,
  18. ‘sizeLimit’: 512000,
  19. ‘buttonText’: ‘Choose Images’,
  20. ‘folder’: ‘/uploads’,
  21. ‘onComplete’: function(event, queueID, fileObj, response, data) {
  22. $(‘#uploaded_photos’).append(response);
  23. },
  24. ‘onAllComplete’: function(event, queueID, fileObj, response, data) {
  25. }
  26. });
  27. });
  28. // ]]></script>
  29. <input id=“fileInput” name=“fileInput” type=“file” />
  30. <a href=“javascript:$(‘#fileInput’).uploadifyUpload();”>Upload Files</a>
  31. </div>
  32. </form>
  33. <div id=“uploaded_photos”></div>

Since our upload page is ready we need to start writing some code to get the images, store them and reply to the client in case the upload is successful. To add a new generic handler right-click on the project and then select Add –> New Item. In the dialog window that appears select Generic Handler and name it Upload.ashx. The code for the Upload.ashx looks like this:

C#
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.Web.Services;
  6. using System.Text;
  7. namespace UploadImages
  8. {
  9. /// <summary>
  10. /// Summary description for $codebehindclassname$
  11. /// </summary>
  12. [WebService(Namespace = “http://tempuri.org/”)]
  13. [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  14. public class Upload : IHttpHandler
  15. {
  16. public void ProcessRequest(HttpContext context)
  17. {
  18. try
  19. {
  20. HttpPostedFile file= context.Request.Files[“Filedata”];
  21. byte[] image_data = new byte[file.ContentLength];
  22. file.InputStream.Read(image_data, 0, file.ContentLength);
  23. ImagesDbEntities2 ent = new ImagesDbEntities2();
  24. images img = new images();
  25. img.name = file.FileName;
  26. img.data = image_data;
  27. img.type = file.ContentType;
  28. ent.AddToimages(img);
  29. ent.SaveChanges();
  30. #region update the number of images the user can upload
  31. int imgs = 5; // the number of images by default – in case of error set to 0
  32. // try to limit the allowed images to 5
  33. IEnumerable<images> images = from i in ent.images select i;
  34. if (images != null && images.Count() != 0)
  35. {
  36. imgs = 5 images.Count();
  37. if (imgs < 0) imgs = 0;
  38. }
  39. else
  40. imgs = 5;
  41. #endregion
  42. // create the delete link ID which will be used in the jquery selector
  43. string deleteLinkID = “deleteImage” + img.id;
  44. StringBuilder sb = new StringBuilder();
  45. sb.Append(“<script>”);
  46. sb.Append(“$(\”a[class=gallery]\”).lightBox();”);
  47. sb.Append(“$(‘#fileInput’).uploadifySettings(‘queueSizeLimit’,” + imgs.ToString() + “);”);
  48. sb.Append(“$(\”#” + deleteLinkID + \”).click(function(){ “);
  49. sb.Append(“if(confirm(\”Are you sure you want to delete the image?\”)) { “);
  50. // start creating the script which will delete the image by using AJAX call to a web service
  51. sb.Append(“$.ajax({“);
  52. sb.Append(“type: \”POST\”,”);
  53. // define the service method that will be used to delete the image
  54. sb.Append(“url: \”ImagesService.asmx/DeleteImage\”,”);
  55. sb.Append(“data: \”{id: “ + img.id + “}\”,”);
  56. sb.Append(“contentType: \”application/json; charset=utf-8\”,”);
  57. sb.Append(“dataType: \”json\”,”);
  58. sb.Append(“success: function(msg) {“);
  59. // if the call is successful append the result so we’ll hide the image and update the# of images the user can select
  60. sb.Append(“if(msg.d != \”\”) { $(\”#uploaded_photos\”).append(msg.d); }”);
  61. sb.Append(“}});”);
  62. sb.Append(” } “); // the matching } for the if(confirm)
  63. sb.Append(” })”);
  64. sb.Append(“</script>”);
  65. string response = “<div id=\”divImage” + img.id +
  66. \” style=\”float:left; padding: 5px; text-aling: center;\”><a class=\”gallery\”
  67. href=\”GetImage.aspx?id=” + img.id + \”><img src=\”GetImage.aspx?size=t&id=” +
  68. img.id + \”></a><br /><a id=\” + deleteLinkID +
  69. \” href=\”#\” title=\”Delete\”>Delete</a></div>” + sb.ToString();
  70. context.Response.Write(response);
  71. }
  72. catch(Exception ex)
  73. {
  74. context.Response.Write(“”);
  75. }
  76. }
  77. public bool IsReusable
  78. {
  79. get
  80. {
  81. return false;
  82. }
  83. }
  84. }
  85. }

The last step is to create the ImagesService, by right-clicking on the project and then selecting Add –> New Item. In the dialog window that appears select Web Service and name it ImagesService with the .asmx extension. I had to uncomment one line in the file which was generated: I had to remove the comment for the [System.Web.Script.Services.ScriptService] attribute.

After removing the default method created generated by Visual Studio for me, I created my DeleteImage method which is called by the jQuery AJAX method in the Upload.ashx handler:

C#
  1. [WebMethod]
  2. public string DeleteImage(string id)
  3. {
  4. try
  5. {
  6. long imageID = long.Parse(id);
  7. ImagesDbEntities2 ent = new ImagesDbEntities2();
  8. images image = (from img in ent.images where img.id == imageID select img).FirstOrDefault();
  9. if (image != null)
  10. {
  11. ent.DeleteObject(image);
  12. ent.SaveChanges();
  13. }
  14. #region update the number of images the user can upload
  15. int imgs = 5; // the number of images by default – in case of error set to 0
  16. // try to limit the allowed images to 5
  17. IEnumerable<images> images = from i in ent.images select i;
  18. if (images != null && images.Count() != 0)
  19. {
  20. imgs = 5 images.Count();
  21. if (imgs < 0) imgs = 0;
  22. }
  23. else
  24. imgs = 5;
  25. #endregion
  26. StringBuilder sb = new StringBuilder();
  27. sb.Append(“<script>”);
  28. // update the number of images the user can select
  29. sb.Append(“$(‘#fileInput’).uploadifySettings(‘queueSizeLimit’,” + imgs.ToString() + “);”);
  30. sb.Append(“$(\”#divImage” + id + \”).fadeOut();”); // fadeout the image
  31. sb.Append(“</script>”);
  32. return sb.ToString();
  33. }
  34. catch (Exception ex)
  35. {
  36. return “”;
  37. }
  38. return “”; // by default
  39. }

In the screenshots below you can see the above code in action during the various steps:

default

1. the plugin before any action

images_selected

2. after some files were selected

images_uploaded

3. images inserted in the page after successful upload

lightbox_image

4. see images using the LightBox plugin

confirm_delete

5. clicking on the delete link first brings a confirmation dialog

after_delete

6. after deleting an image

The above pieces of code are working and can be used by someone looking to implement such a functionality in a project, but they can be extended and why not improved. In my real application I am associating the uploaded images to the logged in user, I send some additional info during the upload of images using the scriptData option of Uploadify plugin and of course I improved the looks of the page using some extra CSS.

Hope that the above pieces of code will be helpful for some and I am opened for suggestions or comments related to the code posted here.

UPDATE: I uploaded the code for this sample here.

Enjoy!

Nice intro screencast: Learn jQuery with FireBug, jQuerify and SelectorGadget

Twitter proved to be useful once again and provided me a nice link to an article from Encosia: Hear me talk jQuery and ASP.NET on the jQuery Podcast. While listening to the podcast (very interesting and full of useful information) I found another article on the site: Updated: See how I used Firebug to learn jQuery. The article is about 1 year old, but still, I found it useful! I’ve been using Firebug for some time to debug JavaScript and jQuery, but never paid attention to the console.

The article contains a 10 minutes screencast in which Dave Ward shows how we can use the built-in console from Firebug to learn and test jQuery scripts. He also uses two scripts:

  • jQuerify – a little bookmarklet to load jQuery on pages that don’t already have it
  • SelectorGadget – an open source bookmarklet that makes CSS selector generation and discovery on complicated sites a breeze

which can make your life easier when it comes to playing around with jQuery.

Watching the screencast I decided to implement the same “feature” on my blog just for test. In order to do that I had to edit two files in the admin interface of WordPress:

  • first of all I had to add jQuery to my blog and decided to use the CDN provided by jQuery. For that I edited header.php and added on line inside the <head> tag:
Javascript
  1. <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
  • second and last step, I edited the footer.php file and right before the closing </body> tag I added the following script:
Javascript
  1. <script>
  2. $(document).ready( function() {
  3. $("h3").mouseover( function() {
  4. $(this).css("cursor", "pointer");
  5. })
  6. .mouseout( function() {
  7. $(this).css("cursor", "default");
  8. }).click( function() {
  9. $(this).next().slideToggle();
  10. });
  11. });
  12. </script>

The result is the same as in the screencast: if you click on the headers from the right side bar you will hide/show the contents of that list. The only difference is that I used method chaining to change the cursor style based on the mouseover and mouseout events to make it clear for the user that clicking on the header will actually do something.

Here is the screencast from the article:

 

Enjoy!