Foliotek Developer Blog

Cropping Images with Javascript and Croppie.js

Profile images are a popular thing on web sites. If you sign into a site, chances are you probably have a profile image for that site.

Sure, there are services like gravatar, but let's face it - not all of your users are going to have a gravatar account. That's why it's important to make profile images easy to configure for your users.

We rely heavily on images in Foliotek, not just for profile images, but also background images on Identity Pages. So a flexible solution was a must, as well.

The solution needed to be able to crop images into a square or a circle, needed to work on mobile devices, and needed to be simple to understand. We're not trying to provide the user with a photoshop equivalent of image manipulation. We just wanted to allow them to zoom in on a certain part of an image, and only return that cropped image.

...it's important to make profile images easy to configure...

Enter Croppie.js

That's why Foliotek built Croppie.js. In this post, I'm going to demonstrate how easy it is to set up croppie to allow users to crop images on your site.

How to use Croppie

First, you need to grab the croppie.js and croppie.css files from the Github Repository. Add them to your site like this:

<html>  
<head>  
<link href="path/to/croppie.css" rel="Stylesheet" />  
</head>  
<body>  
<!-- Your Html Here -->  
<script src="croppie.js"></script>  
</body>  
</html>  

If you have them installed, you can also use bower install croppie or npm install croppie to grab those files.

Next, you'll need an element to house your croppie element and all the components that go along with it. A single div should suffice.

<div class="my-croppie-element"></div>  

Now let's write some javascript to initialize our croppie instance. For simplicity purposes, I'll use jQuery, but Croppie isn't jQuery dependent. You can write anything you see below without jQuery.

var $element = $('.my-croppie-element');  
$element.croppie({
    viewport: {
        width: 100,
        height: 100,
        type: 'circle'
    },
    boundary: {
        width: 350,
        height: 350
    }
});

The simplest way to describe the difference beteween the boundary and the viewport is this: The boundary is the outer container of the croppie. The viewport is the portion of the image that will be cropped.

Now we have our croppie instantiated. If you load your page, you'll see an empty croppie. That's because we haven't told our croppie which image we're cropping. Let's do that now.

$element.croppie('bind', 'path/to/my/image.jpg');

bob image

Now we have an image bound to our croppie, and we can drag and zoom around on this image. You can zoom with the mouse wheel, or if you're on a mobile device you can pinch zoom. But this isn't useful yet unless we can get the resulting image that the user crops.

To do that we need to call the result method on our croppie instance. There are two different types of results that can be returned. One of which is an html result. This will return a div with our image inside of it. The div's overflow is hidden, and the image is positioned and scaled in such a way that the cropped result is the only thing visible.

The result type that we're going to use (and is probably going to be used most of the time), is canvas. This will draw the cropped image to a canvas, and using canvas.toDataURL(), will return a base64 image of the resulting image. Let's see how it's done by putting our result in a separate img tag.

<img id="result-image" />  
$element.croppie('result', 'canvas').then(function (result) {
   $('#result-image').attr('src', result);
});

That's it. That's really all you need to crop images with croppie.js. There are several different ways to customize croppie to fit your needs, check out the documentation to see all of them.

To be continued

Next time I'll show how to send the base64 image to your server, and save it for use at a later time.


Visual Studio Custom Start Page

Feature Driven Development

More than a year ago, the Foliotek development team moved to a Feature Driven Development Cycle (FDD). If you’ve never researched into this form of agile development I would recommend looking into it, because I won’t go into too much detail about it. Here’s a quick overview of how we use this development process here at Foliotek (assuming you have at least a basic understanding of SVN):

In each project repository, trunk is our production copy. This means that trunk needs to build without any errors, and is under a strict testing cycle. When we start work on a feature we will create a branch off of trunk. All the work for that feature goes into that branch, therefore we’re never checking in incomplete code into the production copy. This means that several developers can work on multiple features at one time, without interfering with each other. Testing can be done separately in these feature branches as well. After a feature has been approved, we reintegrate the branch into trunk, and it is ready for production.

The Problem

The problem we ran into with this cycle was the amount of time and effort that went into setting up a feature for development. We’d have to

  1. Create the branch with an ID based on the case in our project managment software.
  2. Check it out onto our computer (using TortoiseSVN)
  3. Set up an IIS entry (including virtual directories) so we could run the feature branch on our machine.
    This allows testers and developers to access the work at http://computername/task_12345

After all is said and done it takes 10-15 minutes just to set each branch up (not to mention maintain it), and some of us can have up to 5 or 6 features open at a time. This also gets really hard to manage when you’re working on multiple features for separate projects. Meanwhile other people are reintegrating their features into trunk, and in order to avoid conflicts in SVN you need to be constantly merging the latest changes in trunk into your feature branches. As anyone with merging experience in SVN can tell you, if you don’t do this reintegrating your branch can result in a ton of conflicts. It didn’t take long to realize that we needed a better way to streamline this process.

The Solution – The Foliotek Start Page!

We settled on developing a Custom Start Page for Visual Studio, that would help us keep track of all of our branches. It would also help us keep them updated, reintegrate them, and allow easier access to them. Since we’ve had so much success with this tool, we wanted to share it, in case any other teams were looking for a similar solution. This will be a hybrid introduction and setup guide. If you’re only interested in what the final product is, you can scroll to the bottom.
Note: We’ve not spent much time on the UI of this tool – you’ve been warned…

Prerequisites

  • Visual Studio 2010 (not Express Versions)
  • IIS 7
  • Tortoise SVN (1.6 or 1.7)

Before even downloading the start page, you’ll need to set up a settings xml document. Copy the contents of the following xml sample (or download the settings file) to the following location and replace all the values with your project’s details.

C:\Users\[USERNAME]\Documents\Visual Studio 2010\Settings\StartPageSettings.xml
(or whatever the path is to your Visual Studio 2010 Settings folder)

I recommend setting each Project element’s Path to an empty directory to allow the start page to start from a clean slate.

<?xml version="1.0" encoding="utf-8" ??>  
<settings>  
    <projects>
        <project>
            <name>Example Project</name>
            <shortname>example</shortname>
            <svnurl>https://svn.example.com/svn/example</svnurl>
            <path>C:\Users\USERNAME\Documents\Visual Studio 2010\Projects\Example</path>
            <webfolderpath>Web</webfolderpath>
            <solutionfolder></solutionfolder>
            <iisuser></iisuser>
            <iispassword></iispassword>
            <iisapppool>ExampleAppPool</iisapppool>
            <iisapppoolversion>4.0</iisapppoolversion>
            <virtualdirectories>
            <virtualdirectory>
            <name>Resources</name>
            <path>\\shareddirectory\resources</path>
            </virtualdirectory>
            <virtualdirectory>
            <name>Resources2</name>
            <path>\\shareddirectory\resources2</path>
            </virtualdirectory>
            </virtualdirectories>
        </project>
    </projects>
    <svnversion>1.7</svnversion>
    <username></username>
    <email></email>
    <fogbugzurl></fogbugzurl>
    <fogbugzusername></fogbugzusername>
    <fogbugzpassword></fogbugzpassword>
    <websvnurl></websvnurl>
</settings>  

Installation

After you’re finished setting up the settings document, we can download the start page and get started. Once you’ve downloaded and installed the Foliotek Start Page Extension, It will ask you to restart Visual Studio, and you’ll notice that your start page isn’t set yet. You’ll have to go to (in visual studio)

Tools -> Options -> Environment -> Startup -> Customize Start Page -> Select the Foliotek Start Page.
Setting the start page in Visual Studio 2010

After you click Ok Visual Studio will open the start page. It might give you a dialog that mentions that the directory doesn’t exist so it needs to create it, which is fine. Once the start page is loaded, if you chose a fresh directory, you’ll see this…



In order to do Feature Driven Development you’re going to need a local copy of trunk, so you click Get Trunk and the start page will open a Tortoise dialog asking you to download the trunk of this project’s repository. After you download the copy of trunk you’ll have the following options on your start page…




Ignoring the buttons on the trunk control (we’ll explain those later), let’s say we’re ready to start on one of our features. We could click on “Create Task”, and type in our branch name into the Name textbox and click add.


After we click add, the start page will create the branch (if it doesn’t already exist), then we’ll get the TortoiseSVN dialog to download the branch.


After we click Ok on the Tortoise dialog, the start page will check out the branch and create an IIS entry for the branch. You can see in the image below we have an entry for /foliotek (trunk) and /foliotektaskDemo.


Now you’re ready to start developing!! The above process is the base functionality of the start page. Before the start page, this Demo branch would have taken around 10-15 minutes to set up before I could even develop. Now I have it ready in under a minute. In addition to improving the set up process, we realized that we could improve the maintenance and management of the project with a few extra buttons.
Foliotek Start Page - Individual Task

Some of the buttons are self explanatory, some of them aren’t, so I’ll just go from left to right and explain each one.

  1. The Visual Studio button that opens the solution.
  2. The windows explorer button that opens that individual task’s folder, then the browser button which actually just opens the project in your default browser, not IE.
  3. Open the SVN Repository Url in the browser
  4. Open the WebSVN Url (if one exists). We use WebSVN because it is a nicer UI for browsing source, but the only one that’s mandatory is an SVN url
  5. Next we have a FogBugz (our bug/feature tracking software) button that opens the feature’s description.
  6. Update button grabs the latest changes from the SVN repo
  7. The local build button builds the solution without opening it in Visual Studio.
  8. The Merge from Trunk button opens a TortoiseSVN merge dialog, with the fields auto-populated, and all you have to do is click next.
  9. The Reintegrate button opens a TortoiseSVN dialog, but unfortunately it doesn’t auto populate the fields for you. In the future we hope to get that working, but for right now you’ll have to enter the reintegration fields yourself.
  10. The big red “X” will delete the folder from your computers, and also remove the IIS entry associated with the branch.

Conclusion

This start page has improved our development process immensely, and we thought if anyone else is using this development cycle, they might benefit from it as well. Even if your team isn’t using Feature Driven Development, the start page can still be useful to teams using SVN and IIS. If anyone is interested in trying it, you can get it from its extension gallery page, or you can find it in the extension manager in visual studio.

Visual Studio -> Tools -> Extension Manager-> Online Gallery -> Search for “Foliotek Start Page
Our plans for the unforeseeable future include open sourcing this extension, and also implementing an architecture that would allow people to write their own actions to perform on branches, and adding some screens to add projects and edit settings.

Let us know if you have any feedback or problems setting it up, I’d be more than happy to help.


Datagrid Checkbox Column

In Foliotek, there are a lot of instances where we have a table with a check box column that allows users to select rows and do some sort of action to them. This leads to a lot of redundancy in our markup and code. For example, here is what our datagrids looked like when we placed a checkbox in them (we’re using a custom server control for our datagrid):

Datagrid with a checkbox

<extendeddatagrid id="dgItems" runat="server">  
  <columns>
    <templatecolumn>
      <headertemplate>
        <input class="check" onclick="FLTK.checkbox.selectAll(this.checked, 'chkSelect');" type="checkbox"></input>
      </headertemplate>
      <itemtemplate>
        <input class="check" id="chkSelect" runat="server" type="checkbox"></input>
      </itemtemplate>
    </templatecolumn>
    <templatecolumn>
      <itemtemplate>

      </itemtemplate>
    </templatecolumn>
  </columns>
</extendeddatagrid>  

Check All JS

Here’s our javascript code that controls the select/deselect all functionality. See this blog post in reference to the :asp() selector, and this post for more information on the select all functionality.

FLTK.checkbox = {  
    selectAll: function (checked, endingwith) {  
        var $checkboxes = $(":asp(" + endingwith + ")");  
        // we don’t want to check a box that is hidden on the page  
        if (checked) {  
            $checkboxes = $checkboxes.filter(":visible").not(":disabled");  
        }  
        $checkboxes.attr("checked", checked);  
    }  
} 

Get selected rows (C#)

Then, to get the selected items in the code behind and perform some action on them, we’d have to do the following:

foreach (DataGridItem item in dgItems.Items)  
    {
        if (((HtmlInputCheckBox)item.FindControl(“chkSelect”)).Checked)
        {
            // perform action  
        }
    }

    // which can also be written as…

    foreach (DataGridItem in dgItems.Items.Cast<datagriditem>().Where(i => ((HtmlInputCheckBox)i.FindControl(“chkSelect”)).Checked))
    {
        // perform action  
    }

Creating the Custom Datagrid Checkbox Column

Since the above code and markup is used so frequently in Foliotek, we wanted to abstract this logic into something that was easier to use. As I stated above, we use a custom server control that extends the datagrid control, but if you don’t have a custom server control it shouldn’t be hard to create one.

I would suggest reading up on custom server controls, if you’re interested in how the following code works.

Below is our server controls for the custom DataGrid, CheckBoxColumn, and CheckBoxTemplate. Keep in mind the Checked and VisibilityDataField on the CheckBoxColumn are optional.

namespace Components  
{
    // here is our custom datagrid control  
    public class ExtendedDataGrid : DataGrid
    {
        public IEnumerable<datagriditem> GetSelectedItems()
        {
            if (!this.Columns.Cast<datagridcolumn>().Any(c => c is CheckBoxColumn))
            {
                throw new Exception("ExtendedDataGrid must have a 'CheckBoxColumn' in order to use GetSelectedItems");
            }

            return this.Items.Cast<datagriditem>().Where(i => ((CheckBox)i.FindControl("cb_" + this.ID)).Checked);
        }
    }

    // Usage: <checkboxcolumn checked="false" visibilitydatafield="Show"></checkboxcolumn>  
    // Alternative Usage: VisiblityDataField=”!Hide”  
    public class CheckBoxColumn : TemplateColumn
    {
        public string VisibilityDataField { get; set; }
        public bool Checked { get; set; }

        public CheckBoxColumn() : base()
        {
        }

        public override void InitializeCell(TableCell cell, int columnIndex, ListItemType itemType)
        {
            if (this.Owner != null)
            {
                this.HeaderTemplate = new CheckBoxTemplate(this.Owner.ID, VisibilityDataField, true, Checked);
                this.ItemTemplate = new CheckBoxTemplate(this.Owner.ID, VisibilityDataField, false, Checked);
            }
            base.InitializeCell(cell, columnIndex, itemType);
        }
    }

    public class CheckBoxTemplate : ITemplate
    {
        private string _tableID;
        private bool _isHeader;
        private string _visibilityDataField;
        private bool _isChecked;

        public CheckBoxTemplate(string tableID, string visibilityDataField, bool isHeader, bool isChecked)
        {
            _tableID = tableID;
            _isHeader = isHeader;
            _visibilityDataField = visibilityDataField;
            _isChecked = isChecked;
        }

        public void InstantiateIn(Control c)
        {
            if (_isHeader)
            {
                // If the template container is the header, then we need to add the check all functionality to the checkbox  
                HtmlInputCheckBox input = new HtmlInputCheckBox();
                input.Attributes["onclick"] = "FLTK.checkbox.selectAll(this.checked, 'cb_" + this._tableID + "');"; // This id is determined below.  
                input.Checked = _isChecked; // Set the checked status by default  
                c.Controls.Add(input);
            }
            else
            {
                CheckBox cb = new CheckBox();
                cb.ID = "cb_" + this._tableID;
                if (!String.IsNullOrEmpty(_visibilityDataField))
                {
                    cb.DataBinding += new EventHandler(cb_DataBinding); // doing this in the databind event allows us to access properties in the dataitem.  
                }
                cb.Checked = _isChecked;
                c.Controls.Add(cb);
            }
        }

        void cb_DataBinding(object sender, EventArgs e)
        {
            var cb = (CheckBox)sender;
            var dataitem = ((DataGridItem)cb.NamingContainer).DataItem;

            bool show = true;
            if (!String.IsNullOrEmpty(this._visibilityDataField))
            {
                bool not = this._visibilityDataField.StartsWith("!");
                show = (bool)DataBinder.Eval(dataitem, (not ? this._visibilityDataField.Substring(1) : this._visibilityDataField));

                show = not ? !show : show;
            }

            cb.Visible = show;
        }
    }
} 

New markup and code behind

Here’s our new markup. Notice how we only have one line now for the checkbox column, compared to 8 lines before.

<extendeddatagrid id="dgItems" runat="server">  
  <columns>
    <checkboxcolumn></checkboxcolumn>
    <templatecolumn>
      <itemtemplate></itemtemplate>
    </templatecolumn>
  </columns>
</extendeddatagrid>  

… and our code behind, which definitely simplifies the prior statement…

foreach(DataGridItem item in dgItems.GetSelectedItems())  
{  
    //perform action  
}  

Databinding Nested Repeaters

Many of you might already know about this, but I’ll post it for those of you like me who didn’t. Occasionally I?ll nest a repeater inside of another repeater, and when I do I always attach an ItemDataBound event handler to the parent repeater so I can set the DataSource of the child repeater.

<repeater id="rptManufacturers" onitemdatabound="rptManufacturers_ItemDataBound" runat="server">  
    <itemtemplate>
        <repeater id="rptModels" runat="server">
            <headertemplate></headertemplate>
            <itemtemplate>
                1.
            </itemtemplate>
            <footertemplate></footertemplate>
        </repeater>
    </itemtemplate>
</repeater>  
protected void rptManufacturers_ItemDataBound(object sender, RepeaterItemEventArgs e)  
{
    Manufacturer man = (Manufacturer)e.Item.DataItem;
    Repeater rptModels = (Repeater)e.Item.FindControl("rptModels");
    rptModels.DataSource = man.Models; rptModels.DataBind();
}

I always found it annoying that I had to create a ItemDataBound function when the only thing I needed to do was bind the child repeater. However, recently I found out you don’t need to bother with all the above code. You can just do this:

<repeater id="rptManufacturers" runat="server">  
    <itemtemplate>
        <repeater datasource="<%# Eval(" Models") %>
            " id="rptModels" runat="server"><headertemplate>
            </headertemplate><itemtemplate>
                1.
            </itemtemplate><footertemplate></footertemplate>
        </repeater>
    </itemtemplate>
</repeater>  

This is a really simple solution when the only thing you need to do is bind a child control ( Repeater, DataGrid, GridView, etc ). The solution doesn’t really apply if you need to do more logic on the ItemDataBound event.


Radio button within a repeater problem

Recently I was developing a system to create tests and test questions.? For these tests our client wanted multiple choice questions.? To implement this I decided to have a list of textboxes for the answer text, and a radio button for each textbox to select the correct answer.?? I knew that a RadioButtonList couldn’t have anything other than a radio button and text, so I went with a repeater.

<repeater id="rptRadios" runat="server">  
    <headertemplate></headertemplate>
    <itemtemplate>
        1. <radiobutton groupname="RadioGroup" id="rbRadio" runat="server"></radiobutton>
        <textbox id="txtRadio" runat="server"></textbox>
    </itemtemplate>
    <footertemplate></footertemplate>
</repeater>  

Doing it this way caused the group name of each radio button to be inconsistent, because of the repeater. After a while of researching and not finding any good solutions I decided to try changing the group name of the radio buttons using jQuery.

$("input:radio").attr('name', 'RadioGroup');

That gave me the radio button functionality that I wanted, but it prevented me from getting the selected radio button on postback. So I decided to just implement the radio button functionality manually.

var radios = $("input:radio");  
radios.click(function () {  
    radios.removeAttr('checked');
    $(this).prop("checked", true);
    //$(this).attr('checked', 'checked'); 
    // jQuery Which gave me the correct functionality and I could still get the selected radio button and textbox on postback. Probably not the most elegant solution, but I couldn't find any other way to do it.
});

Also I needed to make sure at least one of the radio buttons was selected so I added a CustomValidator that called a javascript function.

function ValidateRadioButtons(sender, args) {  
    args.IsValid = $("input:radio:checked").size() > 0;
}