I wanted to build one using JavaScript with one requirement that is it shouldn’t load all images at the same time because if you use big images, it kills the page load performance.
With this goal and an ASP.NET Web API backend in mind, I set about building my ‘FlipView’ component in JavaScript.
Building the FlipView Application
We start off in Visual Studio as always. This time we pick the ASP.NET MVC4 project’s Basic template.Step 1: Adding an MVC Controller and View
We add an empty ASP.NET MVC Controller called HomeController that simply returns an Index viewpublic class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
Next we add a Home folder under Views and add an Index.cshtml file to it.
Importing BootStrap UI
In one of Suprotim’s previous articles Hosting your ASP.NET Web API Services without IIS, he had obtained a Bootstrap bundle using a service called LayoutIt. I’ll use the same bundle here today.The Bootstrap scripts go in the Scripts\BootStrap folder in our solution and the css folder goes under the Content\bootstrap folder as we can see below
Bundling BootStrap
To bundle BootStrap scripts, we add the following to the App_Start\BundleConfig.csbundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
"~/Scripts/bootstrap/bootstrap.min.js",
"~/Scripts/bootstrap/html5shiv.js"));
To bundle BootStrap CSS files, we add the following to the BungleConfig.cs
bundles.Add(new StyleBundle("~/Content/bootstrap/css").Include(
"~/Content/bootstrap/css/bootstrap-responsive.css",
"~/Content/bootstrap/css/bootstrap.css"));
Updating _Layout.cshtml to use BootStrap
Update the _Layout.cshtml as follows:- In the <head> section we remove the default css bundle and add the BootStrap bundle
@*@Styles.Render("~/Content/css")*@
@Styles.Render("~/Content/bootstrap/css")
- Update the <body> with the respective bootstrap CSS as follows
<div class="navbar navbar-inverse">
<div class="navbar-inner">
<div class="brand">
@ViewBag.Title
</div>
</div>
</div>
<div class="container-fluid">
<div class="row-fluid">
@RenderBody()
</div>
</div>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
This gives the familiar black colored Twitter BootStrap menu bar on top with a Title that you can set in the Index.cshtml.
Step 2: Setting up the KO ViewModel
Next we will setup our JavaScript, so add a new file called flip-viewer.js in the Scripts folder. Unlike some approaches where a list of Image URLs are bound to a <ul> and then the <ul> is manipulated into a slideshow, we will only use one image. The issue with <ul> approach is on data bind all images need to be downloaded first. This slows down the entire page if we want big images to be downloaded.Our approach will be to get the list of images, and download only the image that is currently visible.
This ensures there is a minimal delay in first load. Once all the images have been loaded one at a time, the browser uses the local cache automatically.
In our KO View Model we want to do the following:
1. On load save all the images URLs in an array in the ViewModel.
2. Maintain a Current Image index to keep track of the index in the above array pointing to the image that is being displayed currently.
3. Set the image at the Current Index as the source for our Image on Index.cshtml
4. Have a Previous and Next function to manually navigate the images
5. Have an Auto flag that determines if the images should be rotated in the order they appear
When we translate the above into a KO View Model we have the following:
allImages – Observable array holding the list of image URLs.
currentImageIndex – Observable field pointing to the index of the current image being displayed from the allImages array.
auto – Observable field indicating if the Flip View should flip forward automatically.
start – A KO Computed function that is triggered when we Read or Write to the above auto field.
selectedImage – A KO Computer field that return the actual URL of the current image. This is triggered everytime the currentImageIndex changes.
next – Click event handler for the next button. It moves the currentImageIndex forward by 1. If it is at the last image, resets index to 0 thus loops through all images. Also invoked by timer if auto property is set to true. For effect, the new image is faded in using the jQuery FadeIn function.
previous – Click event handler to move the currentImageIndex back by 1. If it is at the first image, resets index to the last image thus loops through all images in the reverse direction.
The final Script is as follows:
function viewModel()
{
var _this = this;
this.currentImageIndex = ko.observable(-1);
this.allImages = ko.observableArray();
this.auto = ko.observable(false);
this.start = ko.computed({
read: function (timeout)
{
if (_this.auto() === true)
{
setTimeout(_this.next, timeout);
}
},
write: function (timeout)
{
if (_this.auto() === true)
{
setTimeout(_this.next, timeout);
}
}
});
this.selectedImage = ko.computed(function ()
{
if (_this.allImages)
{
return _this.allImages()[_this.currentImageIndex()];
}
});
this.next = function ()
{
$("#displayImage").fadeOut(500, function ()
{
if (_this.currentImageIndex() < _this.allImages().length - 1)
{
_this.currentImageIndex(_this.currentImageIndex() + 1);
}
else
{
_this.currentImageIndex(0);
}
$("#displayImage").fadeIn();
_this.start(2000);
});
}
this.previous = function ()
{
$("#displayImage").fadeOut(500, function ()
{
if (_this.currentImageIndex() > 0)
{
_this.currentImageIndex(_this.currentImageIndex() - 1);
}
else
{
_this.currentImageIndex(_this.allImages().length - 1);
}
$("#displayImage").fadeIn();
});
}
}
Step 3: Setting up the Index.cshtml
The Index page is rather simple.- We start off by setting the Title that’s shown in our Menu Bar
- Next we have a div with the class ‘tablet-border’. This is basically a thick border giving the image container a feel as if it’s a Tablet.
- The container div contains two buttons ‘Prev’ and ‘Next’ that are positioned and styles appropriately. Their click events are bound to the view model functions as described earlier.
- Next we have the image that’s src attribute is bound to the selectedImage property of the ViewModel. It is positioned and sized by the tablet-screen class.
- Finally we have a mysterious set of divs that we’ll see in action shortly.
@{
ViewBag.Title = "Fast 'FlipView' using KnockoutJS";
}
<div class="tablet-border">
<button data-bind="click: previous"
class="previous-button">
Prev
</button>
<img id="displayImage" data-bind="attr: { src: selectedImage }" class="tablet-screen" alt="Current Image" />
<button data-bind="click: next"
class="next-button">
Next
</button>
<div style="left: 310px; top: 6px; position: relative">
<div class="window"></div>
</div>
</div>
<div class="form">
<input type="checkbox" id="auto" data-bind="checked: auto" />
Slide Show
</div>
@section Scripts{
<script src="~/Scripts/knockout-2.2.0.debug.js"></script>
<script src="~/Scripts/flip-viewer.js"></script>
}
Step 4: Bringing in Web API
We could have just stuck in an array of images in the JavaScript itself but let’s go the whole hog and build a credible sample, so we’ll add a Web API controller that will serve us a list of Images- We add a Get method that returns a list of images that I’ve added to a folder called Images at the root of the solution. We simply return an array of strings with the URL of the images. These could very well be absolution URLs.
public class ImagesController : ApiController
{
public IList<string> Get()
{
return new List<string>{
"/Images/1.jpg",
"/Images/2.jpg",
"/Images/3.jpg",
"/Images/4.jpg",
"/Images/5.jpg",
"/Images/7.jpg",
"/Images/8.jpg",
"/Images/9.jpg",
"/Images/10.jpg",
"/Images/11.jpg"};
}
}
With the Web API set to return us a list of URLs let’s hook it up and initialize the view model
Step 5: Hooking up the View Model
Back in the flip-viewer.js we add the jQuery $(document).ready function and call our Web API to get the data. Once data is in, initialize the ViewModel and populate it with the data. The script is as follows:$(document).ready(function (data)
{
var vm = new viewModel();
$.ajax({
url: "/api/Images/",
type:"GET"
}).done(function (data)
{
for (i = 0; i < data.length; i++)
{
vm.allImages.push(data[i]);
}
ko.applyBindings(vm);
vm.currentImageIndex(0);
});
});
Now we are all set to run the application.
Demo
When we run the application, the first time we are greeted with the following screenPretty neat! Now do you see the implication of the unexplained/mystery div we saw in the markup? Yes, it magically became a Windows Logo (check bottom of the screenshot), giving our Image viewer the look of a nice tablet.
Click on Prev or Next button to scroll through the image list. Check the Slide Show to checkbox for the images to scroll forward automatically.
Note when you reach the last image, you circle back to the first one.
The Mystery Div and Windows 8 Logo
Well the Windows 8 Logo is created in CSS and the div is placed appropriately. The Original CSS was created by Vasiliy Zubach. I adapted it to the small size and different color. If you are interested, you can either see the original or refer to the windows-8-logo.css in the bootstrap\css folder.Monitoring the Data Transfer to ensure speed
Now that our application is complete, let’s make sure that our original premise is still maintained and we are getting images one at a time, only for the first time. To do this, we’ll monitor Network traffic using IE 10’s Developer ToolsStep 1: Start application and bring up IE10 Developer tools. Navigate to the Network tab and hit Ctrl+R to clear the browser cache. Now Select ‘Start Capturing’ and Refresh the Browser.
As you can see above, the page is loaded for the first time and at the very bottom only one image is loaded.
Step 2: Keep capturing and click on Slide Show. Notice how each image is downloaded the first time (Result columns Shows 200). However, once all the images have been downloaded, the browser no longer requests the server (Result column shows 304 = No Change, highlighted in blue below) and no new data is downloaded.
Thus we have achieved our goal of minimizing simultaneous downloads while creating a pleasant looking Image Viewer that scrolls through a collection of images.
Conclusion
We built a neat looking Image Viewer that kind of mimics the FlipView control in Windows 8. It has a manual and a slideshow mode that we can toggle between. It is efficient as in it downloaded only one image at a time instead of waiting for the entire set to download and then arrange them using JS and CSS. We also pulled the data from a Web API service albeit a service that returns a static array.Download the entire source code of this article (Github)
Would have given a test page with a viewer functionality.. Great post!
ReplyDeleteif possible please come with few article on using html5shib,twitter bostrap usage for complex UI, responsive web design.....thanks
ReplyDelete@Anonymous - Plenty has been written on Bootstrap & Responsive Design. For example check
ReplyDeletehttp://www.dotnetcurry.com/ShowArticle.aspx?ID=859
http://www.dotnetcurry.com/ShowArticle.aspx?ID=877
http://www.dotnetcurry.com/ShowArticle.aspx?ID=906
Nice Post !!
ReplyDelete