Tagged: example

Crop & Save (Part II)

This is the second (and the last) post discussing cropping images and saving them to the SQL Server database, using C# ASP.NET and jQuery library called Jcrop. In my previous post, I have created a simple database model that can be used for this purpose, so if you haven’t checked it out, please do. Also, I won’t get into details with Jcrop, since its website is very informative and contains a good manual, as well as demos.

I’ve made a simple demo which can be downloaded here, and I’ll quickly guide you through it, so it could be easily understood. The idea is the following: user clicks on ‘Choose file’ button (which opens FileUpload control), picks an image on their computer and clicks on ‘Upload’ button. At that point, the image is uploaded to the app server and saved to a folder in the file system(in this demo, it is TempImages folder). User can then see the uploaded picture and do the cropping, after which ‘Crop’ button should be clicked. Then, a new bitmap will be created, containing only the area of the original photo selected as the crop area. The bitmap will then be saved to the database as the array of bytes.

FIrst of all, here is the aspx page:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="JcropDemo._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Jcrop Demo</title>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js"></script>
    <script type="text/javascript" src="js/jquery.Jcrop.min.js"></script>
    <link rel="Stylesheet" type="text/css" href="css/jquery.Jcrop.min.css" />
    <link rel="Stylesheet" type="text/css" href="css/custom.css" />
    
    <script type="text/javascript">
        jQuery(document).ready(function() {
            
            $preview = $('#preview_pane'),
            $pcnt = $('#preview_pane .preview_container'),
            $pimg = $('#preview_pane .preview_container img'),

            xsize = $pcnt.width(),
            ysize = $pcnt.height();
            
            jQuery(window).load(function() {
                jQuery($('img[bid=EditedImage]')).Jcrop({
                    onChange: doPreviewSave,
                    onSelect: doPreviewSave,
                    boxWidth: 600,
                    boxHeight: 400,
                    aspectRatio: xsize / ysize
                }, function() {

                    // Use the API to get the real image size
                    var bounds = this.getBounds();
                    boundx = bounds[0];
                    boundy = bounds[1];
                    // Store the API in the jcrop_api variable
                    jcrop_api = $('img[bid=EditedImage]');          //this;            

                    // Move the preview into the jcrop container for css positioning
                    $preview.appendTo(jcrop_api.ui.holder);

                });
            });

            function doPreviewSave(c) {
                jQuery($('[id$=HIDDENX]')).val(c.x);
                jQuery($('[id$=HIDDENY]')).val(c.y);
                jQuery($('[id$=HIDDENW]')).val(c.w);
                jQuery($('[id$=HIDDENH]')).val(c.h);

                if (parseInt(c.w) > 0) {
                    var rx = xsize / c.w;
                    var ry = ysize / c.h;

                    $pimg.css({
                        width: Math.round(rx * boundx) + 'px',
                        height: Math.round(ry * boundy) + 'px',
                        marginLeft: '-' + Math.round(rx * c.x) + 'px',
                        marginTop: '-' + Math.round(ry * c.y) + 'px'
                    });
                }
            };
        });       
 
</script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Panel ID="CurrentImagePanel" runat="server">
            <fieldset>
                <legend id="CurrentPhotoLegend" runat="server"></legend>
                    <p>
                    <asp:Image ID="UserImage" runat="server" CssClass="user_photo_large" />
                    <asp:FileUpload ID="ImageUpload" runat="server" />
                </p>
                <p>
                    <asp:Button ID="ImageUploadButton" runat="server" onclick="ImageUploadButton_Click" Text="Upload"/>
                </p>    
                <p>
                    <asp:Label ID="ImageUploadErrorLabel" runat="server"></asp:Label>
                </p>
            </fieldset>            
        </asp:Panel>
        <asp:Panel ID="EditedImagePanel" runat="server">   
            <fieldset>
                <legend><asp:Label ID="EditedPhotoLabel" runat="server"></asp:Label></legend>
                <asp:Image ID="EditedImage" runat="server" bid="EditedImage" />
                <div id="preview_pane">
                    <div class="preview_container">
                        <asp:Image ID="PreviewImage" runat="server" CssClass="jcrop-preview" />
                    </div>
                </div>          
                <p style="clear:both">
                    <asp:Button ID="CropImageButton" runat="server" OnClientClick="return checkCroppedImage();" onclick="CropImageButton_Click" CssClass="custom_button3" Text="Crop"/>
                    <asp:HiddenField ID="HIDDENX" runat="server" />
                    <asp:HiddenField ID="HIDDENY" runat="server" />
                    <asp:HiddenField ID="HIDDENW" runat="server" />
                    <asp:HiddenField ID="HIDDENH" runat="server" />
                </p>  
            </fieldset>              
        </asp:Panel>
    </div>
    </form>
</body>
</html>

So, we have to add references to jQuery and jQueryui, as well as to Jcrop js and css files. There are two panels, CurrentImagePanel, shown before the image has been selected, and EditedImagePanel, where the cropping will take place. Hidden fields HIDDENX, HIDDENY, HIDDENW and HIDDENH are used to store coordinates of the rectangle representing new (cropped) image. On window load, Jcrop’s default behavior is activated. When the selection is made, hidden fields are populated with its values (check doPreviewSave function).

Code behind:

using System;
using System.Linq;
using System.IO;
using System.Web;

namespace JcropDemo
{
    public partial class _Default : System.Web.UI.Page
    {
        private string[] _AllowedExtensions = { ".png", ".jpeg", ".jpg", ".gif" };
        private string _TempImageLocation = "TempImages\\";
        private string _ImagePath = HttpContext.Current.Request.PhysicalApplicationPath + "TempImages\\";
        private int _UserID = 1;

        protected void Page_Load(object sender, EventArgs e)
        {
            if (Session["WorkingImage"] != null)
            {
                EditedImagePanel.Visible = true;
                CurrentImagePanel.Visible = false;

                PreviewImage.ImageUrl = EditedImage.ImageUrl = _TempImageLocation + (string)Session["WorkingImage"];
            }
            else
            {
                EditedImagePanel.Visible = false;
                CurrentImagePanel.Visible = true;

                UserImage.ImageUrl = _TempImageLocation + "noimage.jpg";
            }
        }

        protected void ImageUploadButton_Click(object sender, EventArgs e)
        {
            string imageName = "";
            if (ImageUpload.HasFile)
            {
                string extension = System.IO.Path.GetExtension(ImageUpload.FileName).ToLower();
                if (_AllowedExtensions.Contains(extension))
                {
                    try
                    {
                        imageName = string.Format("{0}_{1}{2}", _UserID, DateTime.Now.ToFileTimeUtc(), extension);
                        ImageUpload.SaveAs(_ImagePath + imageName);

                        EditedImagePanel.Visible = true;
                        CurrentImagePanel.Visible = false;

                        PreviewImage.ImageUrl = EditedImage.ImageUrl = _TempImageLocation + imageName;

                        Session["WorkingImage"] = imageName;
                    }
                    catch (Exception ex)
                    {
                        ImageUploadErrorLabel.Text = "Image could not be uploaded. Details: " + ex.Message;
                    }
                }
                else
                {
                    ImageUploadErrorLabel.Text = "Wrong extension!!!";
                }
            }
        }

        protected void CropImageButton_Click(object sender, EventArgs e)
        {
            if (Session["WorkingImage"] == null)
                return;

            string imageName = Session["WorkingImage"].ToString();

            int w = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENW.Value)));
            int h = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENH.Value)));
            int x = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENX.Value)));
            int y = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENY.Value)));

            byte[] cropImage = Crop(_ImagePath + imageName, w, h, x, y);

            BLL.SaveUserProfilePhoto(_UserID, cropImage);

            File.Delete(_ImagePath + imageName);
            Session["WorkingImage"] = null;

            EditedImagePanel.Visible = false;
            CurrentImagePanel.Visible = true;
        }

        static byte[] Crop(string Img, int Width, int Height, int X, int Y)
        {
            try
            {
                using (System.Drawing.Image originalImage = System.Drawing.Image.FromFile(Img))
                {
                    using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(Width, Height))
                    {
                        bmp.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
                        using (System.Drawing.Graphics Graphic = System.Drawing.Graphics.FromImage(bmp))
                        {
                            Graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                            Graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                            Graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                            Graphic.DrawImage(originalImage, new System.Drawing.Rectangle(0, 0, Width, Height), X, Y, Width, Height, System.Drawing.GraphicsUnit.Pixel);
                            MemoryStream ms = new MemoryStream();
                            bmp.Save(ms, originalImage.RawFormat);
                            return ms.GetBuffer();
                        }
                    }
                }
            }
            catch (Exception Ex)
            {
                throw (Ex);
            }
        }
    }
}

In order for this example to be as simple as possible, there is only one user that the image would be added to, hence the variable _UserID is set to 1. In the ImageUploadButton_Click event, the image is saved to a folder on the server. In CropImageButton_Click, the points of the cropped rectangle (represented in the hidden fields) are used to create array of bytes that is later saved to the database. On successful save, the original image is deleted from the file system.

I’ve put the business logic into separate file:

using System;
using System.Configuration;
using System.Linq;
using System.Web;

namespace JcropDemo
{
    public class BLL
    {
        public static Model Model
        {
            get
            {
                if (!HttpContext.Current.Items.Contains("Model"))
                    HttpContext.Current.Items.Add("Model", new Model());

                return (Model)HttpContext.Current.Items["Model"];
            }
        }

        public static void SaveUserProfilePhoto(int userID, byte[] photoContent)
        {
            User usr = Model.Entities.User.Where(x => x.UserId == userID).FirstOrDefault();
            if (usr != null)
            {
                Image img = new Image();
                img.ImageContent = photoContent;
                img.User.Add(usr);

                Model.Entities.AddToImage(img);
                Model.Entities.SaveChanges(true);
            }
        }
    }

    public class Model
    {
        private TestDbEntities _Entities = null;
        public TestDbEntities Entities
        {
            get
            {
                if (_Entities == null)
                {
                    if (ConfigurationManager.ConnectionStrings["TestDbEntities"] == null)
                    {
                        throw new NullReferenceException("Missing connection string!");
                    }
                    _Entities = new TestDbEntities(ConfigurationManager.ConnectionStrings["TestDbEntities"].ConnectionString);
                }
                return _Entities;
            }
        }
    }
}

Static method SaveUserProfilePhoto uses EF model to save the data to the SQL Server database.

So, once again: you can download the source code here, and remember to change your connection settings in Web.config!

Hope this example is useful, and I’ll be glad to get your feedback on it.

Google Chart in ASP.NET Application – Quick & Easy (Part 2)

In one of my previous posts, I’ve explained how to easily integrate Google charts with ASP.NET application. Here is one more example related to that subject, having some (for me) very cool data filtering features.
Note: For the sake of simplicity, I’ve removed jQuery and AJAX code, so all the processing and chart drawing happens on page load.

Here is C# code:

using System;
using System.Collections.Generic;
using System.Web.Services;
using System.Web.Script.Serialization;

public partial class Default2 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            List<DataItem> dataList = new List<DataItem>();

            dataList.Add(new DataItem("Column 1", 100, "Male", 25));
            dataList.Add(new DataItem("Column 2", 150, "Male", 37));
            dataList.Add(new DataItem("Column 3", 250, "Female", 25));
            dataList.Add(new DataItem("Column 4", 400, "Female", 35));
            dataList.Add(new DataItem("Column 5", 450, "Male", 35));
            dataList.Add(new DataItem("Column 6", 460, "Female", 26));
            dataList.Add(new DataItem("Column 7", 470, "Female", 30));
            dataList.Add(new DataItem("Column 8", 500, "Male", 31));
            dataList.Add(new DataItem("Column 9", 550, "Male", 30));
            dataList.Add(new DataItem("Column 10", 600, "Female", 33));

            JavaScriptSerializer jss = new JavaScriptSerializer();
                        
            ClientScript.RegisterStartupScript(this.GetType(), "TestInitPageScript",
                string.Format("<script type=\"text/javascript\">drawVisualization({0},'{1}','{2}','{3}');</script>",
                jss.Serialize(dataList),
                "Text Example",
                "Name,Value,Gender,Age",
                "--Choose--"));
        }
    }   
}

public class DataItem
{
    #region Internal Members

    private string _ColumnName = "";
    private double _Value1 = 0;
    private string _Value2 = null;
    private int _Value3 = 0;     

    #endregion

    #region Public Properties

    public string ColumnName
    {
        get { return _ColumnName; }
        set { _ColumnName = value; }
    }
    public double Value1
    {
        get { return _Value1; }
        set { _Value1 = value; }
    }
    public string Value2
    {
        get { return _Value2; }
        set { _Value2 = value; }
    }
    public int Value3
    {
        get { return _Value3; }
        set { _Value3 = value; }
    }

    #endregion

    #region Constructors

    public DataItem(string columnName, double value1, string value2, int value3)
    {
        _ColumnName = columnName;
        _Value1 = value1;
        _Value2 = value2;
        _Value3 = value3;
    }

    #endregion
}

ColumnName is the name of a specific item, for example person’s name, or name of the product our company makes. Values(Value1, Value2, Value3) closely represent the item, e.g. person’s age, salary, number of products sold etc. In a project I’m currently working on, I use Google chart to show which products are popular among different age/sex groups.

On page load, the data is compiled into JSON string using JavaScriptSerializer(line 29), and then the script is registered using ClientScript.RegisterStartupScript. This way, the script will be executed when loaded on client. Some additional information is also provided, including chart title(line 30), data column names(line 31), neutral value text in the category dropdown list(line 32).

ASPX & javaScript:


<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Google Charts Example</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js" type="text/javascript"></script>
    <script type="text/javascript" src="//www.google.com/jsapi"></script>
    <script type="text/javascript">
        google.load('visualization', '1.1', { packages: ['controls'] });
    </script>
    <script type="text/javascript">  

        function drawVisualization(dataValues, chartTitle, columnNames, categoryCaption) {
            if (dataValues.length < 1)
                return;

            var data = new google.visualization.DataTable();
            data.addColumn('string', columnNames.split(',')[0]);
            data.addColumn('number', columnNames.split(',')[1]);
            data.addColumn('string', columnNames.split(',')[2]);
            data.addColumn('number', columnNames.split(',')[3]);

            for (var i = 0; i < dataValues.length; i++) {
                data.addRow([dataValues[i].ColumnName, dataValues[i].Value1, dataValues[i].Value2, dataValues[i].Value3]);
            }

            // Define a category picker control for the Gender column
            var categoryPicker = new google.visualization.ControlWrapper({
                'controlType': 'CategoryFilter',
                'containerId': 'CategoryPickerContainer',
                'options': {
                    'filterColumnLabel': columnNames.split(',')[2],
                    'ui': {
                        'labelStacking': 'horizontal',
                        'allowTyping': false,
                        'allowMultiple': false,
                        'caption': categoryCaption,
                        'label': columnNames.split(',')[2]
                    }
                }
            });

            // Define a Pie chart
            var pie = new google.visualization.ChartWrapper({
                'chartType': 'PieChart',
                'containerId': 'PieChartContainer',
                'options': {
                    'width': 600,
                    'height': 350,
                    'legend': 'right',
                    'title': chartTitle,
                    'chartArea': { 'left': 50, 'top': 15, 'right': 0, 'bottom': 0 },
                    'pieSliceText': 'label',
                    'tooltip': { 'text': 'percentage' }
                },
                'view': { 'columns': [0, 1] }
            });

            // Define a table
            var table = new google.visualization.ChartWrapper({
                'chartType': 'Table',
                'containerId': 'TableContainer',
                'options': {
                    'width': '300px'
                }
            });

            // Define a slider control for the Age column.
            var slider = new google.visualization.ControlWrapper({
                'controlType': 'NumberRangeFilter',
                'containerId': 'SliderContainer',
                'options': {
                    'filterColumnLabel': columnNames.split(',')[3],
                    'ui': { 'labelStacking': 'horizontal' }
                }
            });

            new google.visualization.Dashboard(document.getElementById('PieChartExample')).bind([categoryPicker,slider], [pie, table]).draw(data);
        }
        
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <div id="PieChartExample">
            <table>
                <tr style='vertical-align: top'>
                    <td>
                        <div id="CategoryPickerContainer"></div>
                        <div id="SliderContainer"></div>
                    </td>
                </tr>
                <tr>
                    <td >
                        <div style="float: left;" id="PieChartContainer"></div>            
                        <div style="float: left;" id="TableContainer"></div>    
                    </td>
                </tr>
            </table>
        </div>
    </form>
</body>
</html>

Notice that controls package is used here, instead of corechart, that was used in previous example.
drawVisualization is the function where all the magic happens. :)
Two classes are important in this example: ControlWrapper and ChartWrapper. ControlWrapper is a wrapper around a JSON representation of a configured control instance – in this case, the “filtering” controls: CategoryFilter (for choosing sex) and NumberRangeFilter (slider, for defining age range).
Finally, new instance od Dashboard class is created, and its bind method invoked.
(I strongly recommend experimenting in Code Playground, it’s very useful!)

As a result, we get something like this:

GoogleChartsExample

When an item is selected in gender drop down list, or the age range is changed, both pie chart and table values are filtered. It all happens on client and it’s very fast. Also, the design is lite and very Google-like, so users really like it!

Google Charts, ASP.NET, jQuery, AJAX – Quick & Easy

What to do when there is a requirement in your application to make a graphical representation of some statistical data? Well, there are a lot of controls you can use, depending on your needs.

In this post, I’ll show you how to quickly and easily introduce Google charts to your ASP.NET application, getting something like this as a result:

GoogleCharts

Also, I’ll be using jQuery and AJAX for fetching data, although they are not necessary, of course.

First of all, let’s define the data we want to show in the chart. This is really basic example, so my class looks something like this:

public class Data
{
    public string ColumnName = "";
    public int Value = 0;

    public Data(string columnName, int value)
    {
        ColumnName = columnName;
        Value = value;
    }
}

This is really all we need to display the chart: data caption and value.

Next, here is the method for getting the data from the server:

[WebMethod]
public static List<Data> GetData()
{
    List<Data> dataList = new List<Data>();

    dataList.Add(new Data("Column 1", 100));
    dataList.Add(new Data("Column 2", 200));
    dataList.Add(new Data("Column 3", 300));
    dataList.Add(new Data("Column 4", 400));

    return dataList;       
}

This is a web method, since I’ll make AJAX call from the client to get JSON object containing data to be displayed. Bear in mind that there is no need for serializing to JSON string – just make the method return the object, or in this case – list of objects.

As for the client script, let’s take a look at the AJAX method for fetching the data from the server:

  
$(document).ready(function() {
    $.ajax({
        type: 'POST',
        dataType: 'json',
        contentType: 'application/json',
        url: 'Default.aspx/GetData',
        data: '{}',
        success:
            function(response) {
                drawVisualization(response.d);
            }
        });
    })
})

Nothing new here – just defining which server method to invoke and what to do when the data is received.

We need to define where the chart will be drawn:

  
<div id="visualization" style="width: 600px; height: 400px;"></div>

To display the Google chart, we must load three libraries: the Google JSAPI API, the Google Visualization library and the library for the chart itself.

First of all, Google JSAPI API:

  
<script type="text/javascript" src="//www.google.com/jsapi"></script>

Next, load the Google Visualization and chart libraries:

  
<script type="text/javascript">
    google.load('visualization', '1', { packages: ['corechart'] });
</script>

Note:
- ‘visualization’ – loads the google.visualization library, which defines all the core utility classes and functions.
- ’1′ – visualization version to load (’1′ is current production version).
- { packages: ['corechart'] } – here we can define the list of all Google chart libraries needed. The ‘corechart’ library contains most basic charts, including pie chart, which we use in this example.

Finally, here is the function that will actually draw the chart:

  
function drawVisualization(dataValues) {
    var data = new google.visualization.DataTable();
    data.addColumn('string', 'Column Name');
    data.addColumn('number', 'Column Value');

    for (var i = 0; i < dataValues.length; i++) {
        data.addRow([dataValues[i].ColumnName, dataValues[i].Value]);
    }

    new google.visualization.PieChart(document.getElementById('visualization')).
        draw(data, { title: "Google Charts Example" });          
}

The code is self-explanatory: the DataTable is initialized, the columns are added to it, and then the data is entered. Finally, new PieChart is initialized and its draw method is invoked.

The whole example:
cs:

 
using System;
using System.Collections.Generic;
using System.Web.Services;

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        
    }

    [WebMethod]
    public static List<Data> GetData()
    {
        List<Data> dataList = new List<Data>();

        dataList.Add(new Data("Column 1", 100));
        dataList.Add(new Data("Column 2", 200));
        dataList.Add(new Data("Column 3", 300));
        dataList.Add(new Data("Column 4", 400));

        return dataList;       
    }
}

public class Data
{
    public string ColumnName = "";
    public int Value = 0;

    public Data(string columnName, int value)
    {
        ColumnName = columnName;
        Value = value;
    }
}

aspx:

 
<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js" type="text/javascript"></script>
    <script type="text/javascript" src="//www.google.com/jsapi"></script>
    <script type="text/javascript">
        google.load('visualization', '1', { packages: ['corechart'] });
    </script>
    <script type="text/javascript">
        $(document).ready(function() {
            $.ajax({
                type: 'POST',
                dataType: 'json',
                contentType: 'application/json',
                url: 'Default.aspx/GetData',
                data: '{}',
                success:
                    function(response) {
                        drawVisualization(response.d);
                    }

            });
        })

        function drawVisualization(dataValues) {
            var data = new google.visualization.DataTable();
            data.addColumn('string', 'Column Name');
            data.addColumn('number', 'Column Value');

            for (var i = 0; i < dataValues.length; i++) {
                data.addRow([dataValues[i].ColumnName, dataValues[i].Value]);
            }

            new google.visualization.PieChart(document.getElementById('visualization')).
                draw(data, { title: "Google Charts Example" });          
        }

        
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <div id="visualization" style="width: 600px; height: 400px;"></div>
    </form>
</body>
</html>

Google charts are easy to implement and highly customizable. I will be writing more about different types of charts and how to customize them, in my future posts.

jQuery Animate & Highlight Effect

You’ve probably noticed the following feature on facebook: you get the notification that somebody liked your comment on a post; you click on that notification and the post is shown and there is a sliding effect positioning you down to your comment, highlighting it for couple of seconds. It looks pretty cool :)

I’ve thought of adding this effect to the project I’m working on. In my scenario, user types the comment in a textbox and clicks on Save button, or strikes Enter key. The comment is sent to server using ajax and on receiving response that the save was successful, the comment is added to the end of existing list. At that point, there is the animation of scrolling down to that comment and highlighting it.

I’ll present simplified version here, without the actual save on server. Instead, when the text is entered, the comments list is immediately appended. Here is both html and javascript code that you can save on your computer and try for yourself:

<html>
    <head>
        <title>Example</title>
        <link href="//ajax.googleapis.com/ajax/libs/jqueryui/1.9.0/themes/smoothness/jquery-ui.css" rel="stylesheet" type="text/css"/>
        <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
        <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script>
        <script type="text/javascript">
            function addComment() {
                var comment = $('#NewCommentTextBox')[0].value;
                if (comment == '')
                    return;

                $('#commentPanel').append('<div class="comment">' + comment + '</div>');
                $('#NewCommentTextBox')[0].value = '';
                $('html,body').animate({ scrollTop: $('.comment').last().offset().top }, 1000);
                $('.comment').last().effect("highlight", {}, 2000);
            }

            function onCommentEnter(e) {
                if (e.keyCode == 13) {
                    addComment();
                    return false;
                }
            }
        </script>
    </head>
    <body>
        <input id="NewCommentTextBox" type="text" onkeypress="return onCommentEnter(event);" />
        <input id="AddNewCommentButton" type="button" value="Send" onclick="addComment();" />
        <div id="commentPanel"></div>
    </body>
</html>

The example is really simple, so I won’t go into details, only point to couple of things.

First of all, we need to add the reference to jQuery and jQuery UI libraries. They can be hosted on our servers, but I prefer letting Google to do that work, as you can see in code lines 4-6. You can learn more on why that is a good idea and why is the http: in the URLs missing in a good article here. There are several themes that can be used for highlighting effect, or you can define your own, on jQuery UI website.

On line 15, we define the animation – what to scroll to and how fast. On line 16, the highlighting is done, again telling what to highlight and for how long. The documentation can be found here and here.

This is all really easy to implement, in just couple of lines of javascript code, and the users will surely like it.

jQuery Infinite Scroll

Infinite scroll became a kind of standard in modern web apps, so I wanted to implement it in a project I’m working on. I’ve browsed the net looking for the way to accomplish this task, and found this example, which seemed quite cool and easy to implement. Its owner allowed everyone to use the script, as long as they keep the author’s credits. I’ve made few changes to better suit my needs, but the overall script is still the same. This is a short tutorial on how to implement the infinite scroll in C# ASP.Net application, using jQuery.

If you take a look at the original code, you’ll see that the data to be shown is taken from an html file. In my project, I’m fetching the data from server, in chunks of 50 records per call. Since I wanted to keep it as simple as possible and not change the script if not necessary, I’ve created server method that returns the string containing html list item objects to be added to my content div. Here is it how it all looks like:

ASPX:

<div>
    <h2>News Feed</h2>
    <ul id="content"></ul>
    <div id="loading">Wait a moment... it's loading!</div>
    <div id="nomoreresults">Sorry, no more results.</div>
</div>

Note that the “content” element is the list to add the items to.

cs:

[System.Web.Services.WebMethod(true)]
public static string GetNewsFeed(int currentCount)
{
    StringBuilder newsFeedSb = new StringBuilder();
    List notifications = ActionManager.GetNewsFeed(Global.CurrentUser, 50, currentCount*50);

    foreach (BLL.Action notification in notifications)
    {
        string messageText = "";

        //	some magic here, e.g.:
        string notificationDate = notification.ActionDate != null ? ((DateTime)notification.ActionDate).ToString("yyyy-MM-dd") : "date unknown";
        messageText = string.Format("<li><p>{0}: You have updated your profile.</p></li>", notificationDate);
        //      more business logic...

        if (messageText != "")
        {
            newsFeedSb.Append(messageText);
        }
    }

    return newsFeedSb.ToString();
}

In this server method, I’m getting the data from database layer in chunks of 50, and in order to know which page I’m on, I use currentCount, sent by the ajax request.

When it comes to javascript, I’ve made couple of changes to the original script.

First of all, I’ve added a global variable count, to track current page I’m on. It is set to 0 on load. In original scrollpagination.js, I’ve changed loadContent method, so each time the data is successfully received on client, the count value increases by 1. Also, in the ajax call, count value is sent as data parameter. Finally, dataType I need is json, instead of html. All the changes are highlighted in the following code:

scrollpagination.js:

var count = 0;
$.fn.scrollPagination.loadContent = function(obj, opts) {
    var target = opts.scrollTarget;
    var mayLoadContent = $(target).scrollTop() + opts.heightOffset >= $(document).height() - $(target).height();
    if (mayLoadContent) {
        if (opts.beforeLoad != null) {
            opts.beforeLoad();
        }
    $(obj).children().attr('rel', 'loaded');
    $.ajax({
        type: 'POST',
        url: opts.contentPage,
        data: "{'currentCount':'" + count + "'}", //opts.contentData,
        contentType: "application/json; charset=utf-8",
        success: function(data) {
            $(obj).append(data.d);
            var objectsRendered = $(obj).children('[rel!=loaded]');
            count++;
            if (opts.afterLoad != null) {
                opts.afterLoad(objectsRendered);
            }
        },
        error:
            function(XMLHttpRequest, textStatus, errorThrown) {
                alert(errorThrown);
            },
        dataType: "json"
  });
 }
};

Of course, method name and location have to be set, and also, I’ve removed contentData property, since I don’t need it anymore (it is set in scrollpagination.js).

$('#content').scrollPagination({
    'contentPage': window.location.pathname + '/GetNewsFeed',
    'scrollTarget': $(window),
    'heightOffset': 10,
    'beforeLoad': function() {
        $('#loading').fadeIn();
    },
    'afterLoad': function(elementsLoaded) {
        $('#loading').fadeOut();
        var i = 0;
        $(elementsLoaded).fadeInWithDelay();
    }
 });

So, that’s it! Piece of cake :)