Click here to Skip to main content
15,861,125 members
Articles / Web Development / ASP.NET

Introduction to Knockout.js and CRUD Operations in ASP.Net Web Forms Using Knockout.JS

Rate me:
Please Sign up or sign in to vote.
4.91/5 (59 votes)
26 Nov 2014CPOL10 min read 146K   8.9K   136   39
This article is an introduction to Knockout.js and CRUD Operations in ASP.NET Web Forms using Knockout.JS.

I) Introduction

The development paradigm has been changing rapidly for a last few years. The out of the box technologies have been introduced to develop applications with fast, scalable, extensible structure, easy to maintain and easy to use. Knockout.JS is also one of the examples of such emerging technologies. I take an opportunity to explain the concept and topic my way.

We’ll be discussing the technology from basic to advanced level by just following a road-map.

II) Our Road-map

Image 1

We’ll learn Knockout.JS in the following two parts:

  1. Part 1: Introduction to Knockout.js and CRUD Operations in ASP.NET Web Forms using Knockout.JS and Entity Framework.
  2. Part 2: Complete end to end CRUD operations using Knockout.JS and Entity Framework in MVC4 application.

III) Part 1: Introduction to Knockout.js and CRUD Operations in ASP.NET Web Forms using Knockout.JS

We’ll discuss this part by starting with an introduction to knockout, MVVM and observer pattern. Then by setting up a basic environment in knockout.js, thereby creating an ASP.NET web forms application and performing CRUD operations.

IV) Knockout

In today's changing trend of development, data driven apps depend largely on JavaScript and JS based libraries such as jQuery. The client side programming appears to become more and more complex because the user interface becomes richer. In scenarios like this, the data binding and dependency tracking are highly desirable in the applications for further extensibility of the application. Knockout JS fulfills these rich requirements on client side programming and makes a developers' life easy and joyful. Let's discuss KO in detail.

Image 2

Knockout.JS (KO) is basically a JS library that enables Declarative Bindings using an ‘Observable’ ViewModel on the client (browser) following observer pattern approach, enabling UI to bind and refresh itself automatically whenever the data bound is modified. Knockout.JS provides its own templating pattern that helps us to bind our view model data easily. KO works on MVVM pattern i.e. Model-View-ViewModel.

As the architecture is shown, Views interact with View Models in a two way binding manner, i.e., when model is changed, view updates itself and when view is updated, model too updates itself instantaneously.

KO provides 3 most important features like:

  • Automatic Refresh of UI
  • Two way binding
  • Templating

The whole idea of KO derives from these three major functionalities. KO also helps in developing single page applications (SPAs). SPAs are out of the box new way of developing rich internet applications (RIAs) in today's era.

V) Model-View-View Model (MVVM)

When we develop a rich UI internet based application, we create Views (UI like HTML and aspx pages) using server controls, HTML controls and then extend our application by writing business logic behind those views like event handling, property binding, creating entities. This approach increases complexities when the application is too large. Here, we require separation of concerns and maintainability of the application, especially on client side.

The MVVM pattern includes three key parts:

  1. Model (Business rule, data access, model classes, Data displayed in UI)
  2. View (User interface (html, aspx, cshtml…))
  3. ViewModel (Event handling, binding, business logic)

Model refers to our application data and domain model, i.e., entities. In a traditional ASP.NET web application, the data is basically stored inside database or files and UI fetches the data using client-server request like Ajax or direct bind itself.

View Model contains the User Interface level operations/methods/functions, performed on model data to bind the outcome to view. The operations include business logic validations and checks to be performed before binding data to UI. View models act as interface between model and views and act as a wrapper over model prior binding to Views.

View is the user interface of our application. View talks to View Model to invoke certain methods/operations as explained above. View gets updated automatically whenever data from the View Model changes.

MVVM provides a clear separation of concerns between the user interface (UI) and the business logic.

In the MVC pattern, a view acts as the broker agent between the Model (the data displayed in the View) and the Controller (server-side endpoint that takes data from the View and performs some action on that data and provides a response.

VI) Observables for Two Way Binding

KO provides Observables in the library to be bound to UI elements and simultaneously code is written to view models, so that when view updates the data the model updates itself and vice versa, for example, in the following code:

HTML
<tr>
    <td>Batch :</td>
    <td>
        <input data-bind="value: Batch" /></td>
    <td><span data-bind="text: Batch" /></td>
</tr>
<tr>
    <td>Address :</td>
    <td>
        <input data-bind="value: Address" /></td>
    <td><span data-bind="text: Address" /></td>
</tr>
<tr>
    <td>Class :</td>
    <td>
        <input data-bind="value: Class" /></td>
    <td><span data-bind="text: Class" /></td>
</tr>

The above code shows a part of view, you can see the elements are bound to properties like text and value, these properties are provided by KO, and the right side of these properties are property key names which are bind in view-models with the help of observables like shown below:

C#
var self = this;
self.Batch = ko.observable();
self.Address = ko.observable();
self.Class = ko.observable();

So this would be the code in View model, anyways we’ll be discussing all this in detail.

NOTE: Data-bind is an HTML5 attribute.

VII) Setting up Environment in Visual Studio for KO

We go step by step to set up Knockout js environment in Visual Studio.

The pre-requisite is Visual Studio must be version greater than or equal to 12. I am using Visual Studio 2013 Express.

Step 1: Open Visual Studio and create a simple ASP.NET application, I have given it a name KOSetup.

Image 3

Step 2: Right click on project, and in context menu select manage Nuget packages to install JQuery and KO.

Image 4

Step 3: Type jQuery in search text box to get the latest compatible jQuery library. Click install to install the library.

Image 5

Step 4: In the similar fashion, search ‘knockout’ in search textbox and install knockoutjs library in your application.

Image 6

Step 5: Our solution will look like we have a folder created named Scripts and that contains jQuery and knockout libraries.

Image 7

Step 6: Now right click the project and add an aspx page, I named that page LearnKO.aspx.

Image 8

Step 7: Similarly create a JavaScript file and add that to the project , I named that file as LearnKO.js.

Image 9

Step 8: Open the learnKO.js file and drag the jQuery file and knockout.js library file to the LearKO.js file, we see in the below picture that reference of both the files is created on the js file. We did this because it will prove us intellisense support for jQuery and knockout on our LearnKO.js file.

Image 10

Step 9: Write document.ready function of jquery in our LearnKO.js file. Document.ready function is fired when our HTML document object model is loaded in browser.

Image 11

This is all we have to do to setup knockout, now we know how to setup initial environment to use knockout.js in our application.

Image 12

We proceed now to create the application, talk to database and create template and view model.

VIII) Creating Knockout Application

Step 10: For communication to database, add Entity Framework library in the same manner as we added JQuery and KO, installing Entity Framework library will add the EF DLL to the project. We’ll talk to database using Entity Framework of Microsoft. Alternatively, there are a number of ways to talk to database like ADO.NET, LINQ to SQL, etc. But first things first, create a database you have to use in SQL Server. I’ve provided the script for the same.

Image 13

Image 14

Step 11: Right click project and add ADO.NET Entity data Model, click Add, and follow these below steps:

Image 15

Step 12: Following is step two of Entity Data Model. You can choose model contents from database you already created. So select “Generate From database” option. Click Next.

Image 16

Step 13: Choose the table you want to add, i.e., Student table as shown below in the figure. Name the model as LearningKOModel. Click Finish.

Image 17

Step 14: We get certain files in our solution like context and tt files. We also get Student.CS file, that will act as our server side domain model. The context class contains the data communication methods of Entity Framework.

Image 18

Step 15: Write three methods with the help of Entity Framework in our aspx.cs page. One method to fetch all the Students and another method to save and delete a student to/from database, as shown below. Mark them as web method so that they could be called from client side.

Image 19

The code is as follows:

C#
#region Public Web Methods.
        /// <summary>
        /// Gets Student Details
        /// </summary>
        /// <returns></returns>
        [WebMethod]
        public static Student[] FetchStudents()
        {
            LearningKOEntities dbEntities = new LearningKOEntities();
            var data = (from item in dbEntities.Students
                        orderby item.StudentId
                        select item).Take(5);
            return data.ToArray();
        }

        /// <summary>
        /// Saves Student Details
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        [WebMethod]
        public static string SaveStudent(Student[] data)
        {
            try
            {
                var dbContext = new LearningKOEntities();
                var studentList = from dbStududent in dbContext.Students select dbStududent;
                foreach (Student userDetails in data)
                {
                    var student = new Student();
                    if (userDetails != null)
                    {
                        student.StudentId = userDetails.StudentId;
                        student.FirstName = userDetails.FirstName;
                        student.LastName = userDetails.LastName;
                        student.Address = userDetails.Address;
                        student.Age = userDetails.Age;
                        student.Gender = userDetails.Gender;
                        student.Batch = userDetails.Batch;
                        student.Class = userDetails.Class;
                        student.School = userDetails.School;
                        student.Domicile = userDetails.Domicile;
                    }
                    Student stud=(from st in studentList where 
                    st.StudentId==student.StudentId select st).FirstOrDefault();
                    if (stud == null)
                        dbContext.Students.Add(student);
                    dbContext.SaveChanges();
                }
                return "Data saved to database!";
            }
            catch (Exception ex)
            {
                return "Error: " + ex.Message;
            }
        }

        /// <summary>
        /// Deletes Student Details
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        [WebMethod]
        public static string DeleteStudent(Student data)
        {
            try
            {
                var dbContext = new LearningKOEntities();
                var student = dbContext.Students.FirstOrDefault
                (userId => userId.StudentId == data.StudentId);
                if (student != null)
                {
                    if (student != null)
                    {
                        dbContext.Students.Remove(student);
                        dbContext.SaveChanges();
                    }
                }
                return "Data deleted from database!";

            }
            catch (Exception ex)
            {
                return "Error: " + ex.Message;
            }
        }
        #endregion

Step 16: Open the aspx page we created and add the following code to it. The code provides templates in HTML bound to model properties, one to add Student and other displaying Student List.

HTML
<table style="width:100%;" >
            <tbody>
                <tr>
                    <th style="width:100px;">Property Name</th>
                    <th style="width:100px;">Enter Value</th>
                    <th style="width:100px;">Example of two Way Binding</th>
                </tr>
                </tbody>
            <tr>
                <td>Student ID (int):</td>
                <td>
                    <input data-bind="value: StudentId" />
                    </td> <!--,valueUpdate:'keypress'-->
                <td><span data-bind="text: StudentId" /></td>
            </tr>
            <tr>
                <td>First Name :</td>
                <td>
                    <input data-bind="value: FirstName" /></td>
                <td  ><span data-bind="text: FirstName" /></td>
            </tr>
            <tr>
                <td>Last Name :</td>
                <td>
                    <input data-bind="value: LastName" /></td>
                <td><span data-bind="text: LastName" /></td>
            </tr>
            
            <tr>
                <td>Student Age (int) :</td>
                <td>
                    <input data-bind="value: Age" /></td>
                <td><span data-bind="text: Age" /></td>
            </tr>
            <tr>
                <td>Gender :</td>
                <td>
                    <select data-bind="options: Genders, value: 
                    Gender, optionsCaption: 'Select Gender...'"></select></td>
                <td><span data-bind="text: Gender" /></td>
            </tr>
            <tr>
                <td>Batch :</td>
                <td>
                    <input data-bind="value: Batch" /></td>
                <td><span data-bind="text: Batch" /></td>
            </tr>
            <tr>
                <td>Address :</td>
                <td>
                    <input data-bind="value: Address" /></td>
                <td><span data-bind="text: Address" /></td>
            </tr>
            <tr>
                <td>Class :</td>
                <td>
                    <input data-bind="value: Class" /></td>
                <td><span data-bind="text: Class" /></td>
            </tr>
            <tr>
                <td>School :</td>
                <td>
                    <input data-bind="value: School" /></td>
                <td><span data-bind="text: School" /></td>
            </tr>
            <tr>
                <td>Domicile :</td>
                <td>
                    <select data-bind="options: Domiciles, value: 
                    Domicile, optionsCaption: 'Select Domicile...'"></select>
                </td>
                <td><span data-bind="text: Domicile" /></td>
            </tr>
            <tr>
                <td colspan="3">
                    <button type="button" data-bind="click: 
                    AddStudent">Add Student</button>
                    <button type="button" data-bind="click: 
                    SaveStudent">Save Student To Database</button>
                </td>
            </tr>

        </table>
            </div>

         <div style="width:70%;float:left;display:inline-block;">
             <h2>List of Students</h2>
        <table style="width:100%;" data-bind="visible: 
        Students().length > 0" border="0">
            <tr>
                <th>Student Id</th>
                <th>First Name</th>
                <th>Last Name</th>
                <th>Age</th>
                <th>Gender</th>
                <th>Batch</th>
                <th>Address</th>
                <th>Class</th>
                <th>School</th>
                <th>Domicile</th>
            </tr>
            <tbody data-bind="foreach: Students">
                <tr>
                    <td><span data-bind="text: StudentId" /></td>
                    <td>
                        <input data-bind="value: FirstName" /></td>
                    <td>
                        <input data-bind="value: LastName" /></td>
                    <td>
                        <input data-bind="value: Age" /></td>
                   
                    <td>
                        <select data-bind="options: $root.Genders, 
                        value: Gender"></select></td>
                    <td>
                        <input data-bind="value: Batch" /></td>
                    <td>
                        <input data-bind="value: Address" /></td>
                    <td>
                        <input data-bind="value: Class" /></td>
                    <td>
                        <input data-bind="value: School" /></td>
                    <td>
                        <select data-bind="options: $root.Domiciles, 
                        value: Domicile"></select></td>

                    <td><a href="#" data-bind="click: $root.
                    DeleteStudent">Delete</a></td>
                </tr>
            </tbody>
        </table>

There are two HTML tables, one for adding student to database and other showing all the students having delete anchor link to delete the student, these template properties will be bound in view model, where we write method to communicate with data base and call Web Methods we created in aspx.cs page. The Viewmodel also contains observables to be bound to these properties.

Step 17: Now it’s time to create ViewModel, Open the learnKO.js file and add codes to fetch, save and delete student, and observables bound to properties binded on controls of HTML page.

C#
/// <reference path="jquery-2.0.3.min.js" />
/// <reference path="knockout-3.0.0.js" />

function Student(data) {
    this.StudentId = ko.observable(data.StudentId);
    this.FirstName = ko.observable(data.FirstName);
    this.LastName = ko.observable(data.LastName);
    this.Age = ko.observable(data.Age);
    this.Gender = ko.observable(data.Gender);
    this.Batch = ko.observable(data.Batch);
    this.Address = ko.observable(data.Address);
    this.Class = ko.observable(data.Class);
    this.School = ko.observable(data.School);
    this.Domicile = ko.observable(data.Domicile);
}

function StudentViewModel() {
    var self = this;
    self.Domiciles = ko.observableArray(['Delhi', 'Outside Delhi']);
    self.Genders = ko.observableArray(['Male', 'Female']);
    self.Students = ko.observableArray([]);
    self.StudentId = ko.observable();
    self.FirstName = ko.observable();
    self.LastName = ko.observable();
    self.Age = ko.observable();
    self.Batch = ko.observable();
    self.Address = ko.observable();
    self.Class = ko.observable();
    self.School = ko.observable();
    self.Domicile = ko.observable();
    self.Gender = ko.observable();

   
    self.AddStudent = function () {
        self.Students.push(new Student({
            StudentId: self.StudentId(),
            FirstName: self.FirstName(),
            LastName: self.LastName(),
            Domicile: self.Domicile(),
            Age: self.Age(),
            Batch: self.Batch(),
            Address: self.Address(),
            Class: self.Class(),
            School: self.School(),
            Gender: self.Gender()
        }));
         self.StudentId(""),
         self.FirstName(""),
         self.LastName(""),
         self.Domicile(""),
         self.Age(""),
         self.Batch(""),
         self.Address(""),
         self.Class(""),
         self.School(""),
        self.Gender("")
    };

    self.DeleteStudent = function (student) {

        $.ajax({
            type: "POST",
            url: 'LearnKO.aspx/DeleteStudent',
            data: ko.toJSON({ data: student }),
            contentType: "application/json; charset=utf-8",
            success: function (result) {
                alert(result.d);
                self.Students.remove(student)
            },
            error: function (err) {
                alert(err.status + " - " + err.statusText);
            }
        });
    };

    self.SaveStudent = function () {
        $.ajax({
            type: "POST",
            url: 'LearnKO.aspx/SaveStudent',
            data: ko.toJSON({ data: self.Students }),
            contentType: "application/json; charset=utf-8",
            success: function (result) {
                alert(result.d);
            },
            error: function (err) {
            alert(err.status + " - " + err.statusText);
        }
        });
    };

    $.ajax({
        type: "POST",
        url: 'LearnKO.aspx/FetchStudents',
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: function (results) {
            var students = $.map(results.d, function (item) {
                return new Student(item)
            });
            self.Students(students);
        },
        error: function (err) {
            alert(err.status + " - " + err.statusText);
        }
    })
}

$(document).ready(function () {
    ko.applyBindings(new StudentViewModel());
});

We create StudentViewModel() as our primary view model JavaScript function, that contains all the business logic and operations.

We bind this View model on document ready function by the KO method named applyBindings. This initializes our view model, ko.applyBindings(new StudentViewModel());

Function function Student(data) contains observables bound to model properties.

We can create observable arrays of Domiciles and genders to bind to the dropdown list of our HTML. Ko provides these observables and other such properties to bind to model.

  • observable: Used to define model/entity properties. If these properties are bound with user interface and when value for these properties gets updated, automatically the UI elements bound with these properties will be updated with the new value instantaneously.

    E.g. this.StudentId = ko.observable(“1”); - => StudentId is the observable property. KO represent an object for the Knockout.js library.

    The value of the observable is read as var id= this. StudentId ();
  • observableArray: observableArray represents a collection of data elements which required notifications. It’s used to bind with the List kind of elements.
    E.g. this.Students = ko.observableArray([]);
  • applyBindings: This is used to activate knockout for the current HTML document or a specific UI element in HTML document. The parameter for this method is the view-model which is defined in JavaScript. This ViewModel contains the observable, observableArray and various methods.

Various other types of binding are used in this article:

  • click: Represents a click event handler added to the UI element so that JavaScript function is called.
  • value: This represents the value binding with the UI element’s value property to the property defined into the ViewModel.

The value binding should be used with <input>, <select>, <textarea>:

  • visible: This is used to hide or unhide the UI element based upon the value passed to its binding.
  • Text: This represents the text value of the parameter passed to the UI element.

Step 18: Include js files and stylesheet (you can create your own stylesheet file) to head section of aspx page.

Image 20

Step 19: Press F5 to run the application, and we’ll be shown a page having HTML controls as follows:

Image 21

List of Students shows the list of students from database, bound to viewmodel’s event.

Try to create a new student and add student, then save it to database, it will automatically be added to the right hand side list.

We can see that Domicile and Genders drop down lists are bound to our Viewmodel’s properties.

Note: Do not give string in StudentId and Age as no validation is put on those fields, code may break.

Create Student:

Image 22

Student added to list and database:

Image 23

Job done!

Image 24

Now you can say that you have become a knockout.js developer.

IX) Conclusion

We learnt a lot in this article about how to set up knockout.js in Visual Studio, lots of theory and also created a sample application just to hands-on the concept. There are numerous articles and blogs related to the same concept, you can explore more and more to learn. In the next article, I’ll explain creating a sample application and performing CRUD operations in MVC4 with knockout js and Entity Framework. Now pat your back to have done a great job by learning a new concept.

Image 25

Note: Few of the images in this article are taken via Google search.

You can follow my articles at csharppulse.blogspot.in.

Happy coding.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect https://codeteddy.com/
India India
Akhil Mittal is two times Microsoft MVP (Most Valuable Professional) firstly awarded in 2016 and continued in 2017 in Visual Studio and Technologies category, C# Corner MVP since 2013, Code Project MVP since 2014, a blogger, author and likes to write/read technical articles, blogs, and books. Akhil is a technical architect and loves to work on complex business problems and cutting-edge technologies. He has an experience of around 15 years in developing, designing, and architecting enterprises level applications primarily in Microsoft Technologies. He has diverse experience in working on cutting-edge technologies that include Microsoft Stack, AI, Machine Learning, and Cloud computing. Akhil is an MCP (Microsoft Certified Professional) in Web Applications and Dot Net Framework.
Visit Akhil Mittal’s personal blog CodeTeddy (CodeTeddy ) for some good and informative articles. Following are some tech certifications that Akhil cleared,
• AZ-304: Microsoft Azure Architect Design.
• AZ-303: Microsoft Azure Architect Technologies.
• AZ-900: Microsoft Azure Fundamentals.
• Microsoft MCTS (70-528) Certified Programmer.
• Microsoft MCTS (70-536) Certified Programmer.
• Microsoft MCTS (70-515) Certified Programmer.

LinkedIn: https://www.linkedin.com/in/akhilmittal/
This is a Collaborative Group

779 members

Comments and Discussions

 
QuestionUpdate Method For the same Pin
Member 1091918118-Nov-17 1:14
Member 1091918118-Nov-17 1:14 
Questiongetting error Pin
ketans10-Jun-16 3:23
ketans10-Jun-16 3:23 
GeneralMy vote of 5 Pin
Santhakumar M20-Jan-16 4:10
professionalSanthakumar M20-Jan-16 4:10 
GeneralRe: My vote of 5 Pin
Akhil Mittal26-Jan-16 23:53
professionalAkhil Mittal26-Jan-16 23:53 
GeneralRe: My vote of 5 Pin
Santhakumar M27-Jan-16 0:30
professionalSanthakumar M27-Jan-16 0:30 
Questionhow can we add a "load" function which will call automatically when we opening a new viewmodel? Pin
Member 938348622-Jan-15 22:06
Member 938348622-Jan-15 22:06 
GeneralNice article Pin
Member 1120859013-Dec-14 4:15
Member 1120859013-Dec-14 4:15 
GeneralRe: Nice article Pin
Akhil Mittal14-Dec-14 17:21
professionalAkhil Mittal14-Dec-14 17:21 
QuestionGet error if data type of StudentId is int Pin
sairfan13-Dec-14 9:47
sairfan13-Dec-14 9:47 
Questiontypo Pin
Johnny_Chu7-Aug-14 16:32
Johnny_Chu7-Aug-14 16:32 
QuestionUpdate Pin
indyaking24-Jun-14 21:54
indyaking24-Jun-14 21:54 
AnswerRe: Update Pin
Member 1106439421-Oct-14 21:50
Member 1106439421-Oct-14 21:50 
AnswerRe: Update Pin
Member 1106439421-Oct-14 21:52
Member 1106439421-Oct-14 21:52 
QuestionHow to handle File upload on same script Pin
Member 1041101319-Mar-14 20:29
professionalMember 1041101319-Mar-14 20:29 
QuestionNice example for starters Pin
Sreekanth Mothukuru20-Dec-13 2:49
Sreekanth Mothukuru20-Dec-13 2:49 
GeneralMy vote of 5 Pin
m_aks19-Dec-13 21:57
m_aks19-Dec-13 21:57 
BugExisting values inserting Pin
ojorma18-Dec-13 8:41
ojorma18-Dec-13 8:41 
GeneralRe: Existing values inserting Pin
Akhil Mittal18-Dec-13 16:53
professionalAkhil Mittal18-Dec-13 16:53 
This is just a concept explained.If you learnt the concept well.You can do that by yourself Smile | :)
For your particular issue, follow the second part of this series Complete End to End CRUD Operations Using Knockout.JS and EntityFramework 5 in MVC4 Application[^]
Thanks
Do not forget to comment and rate the article if it helped you by any means Smile | :) ...
For any technical requirement related to .net ,OOPS,C# and design patterns contact me on akhil.mittal20@gmail.com

Questiongood Pin
Kien Nguyen Ngoc18-Dec-13 6:40
Kien Nguyen Ngoc18-Dec-13 6:40 
AnswerRe: good Pin
Akhil Mittal18-Dec-13 16:51
professionalAkhil Mittal18-Dec-13 16:51 
GeneralMy vote of 5 Pin
Monjurul Habib17-Dec-13 5:21
professionalMonjurul Habib17-Dec-13 5:21 
GeneralRe: My vote of 5 Pin
Akhil Mittal17-Dec-13 17:05
professionalAkhil Mittal17-Dec-13 17:05 
Generalgood Pin
pank.gupta13-Dec-13 3:42
pank.gupta13-Dec-13 3:42 
GeneralRe: good Pin
Akhil Mittal13-Dec-13 3:52
professionalAkhil Mittal13-Dec-13 3:52 
GeneralMy vote of 5 Pin
swas.agr13-Dec-13 3:20
swas.agr13-Dec-13 3:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.