Saturday, January 23, 2021

Support Vector Machines (SVM) with Javascript Implementation for Beginners

I give an example of javascript implementation of support vector machines (SVM) and I explain SVM algorithm with hinge loss function in this article.

SVM algorithm aims to find a hyperplane that separates dataset in two classes:



For SVM, classes take values {1,-1}. Algorithm tries to find a hyperplane by maximizing margin between support vectors and hyperplane. We can describe support vectors as closest vectors to hyperplane. In 2 dimensional space, a hyperplane corresponds to a line.

So, to calculate a hyperplane, we must define hyperplane function:

f(x) = wx + b;

In this example, our space is 2 dimensional, so we have a coordinate point (x,y) and a class label (c).

As a result, we have a dataset that consists of javascript objects that includes {c,x,y} properties. Before going to optimization step, we must define SVM loss function. In this loss function, yi corresponds to c and xi corresponds to x,y for our case.


SVM algorithm uses hinge loss function. This loss function penalizes incorrect classifications. Max function is a convex function, so we can use gradient descent algorithm (GDA) as optimization algorithm. In 2 dimension, we have w1, w2 and b as optimization parameters. So we calculate partial derivatives:

d/dw1 Loss(w1,w2,c,x,y,b) = -1 * c * x

d/dw2 Loss(w1,w2,c,x,y,b) = -1 * c * y

d/b Loss(w1,w2,c,x,y,b) = -1 * c

While calculating loss, we calculate all losses for all points and take their mean value. As a result, we can calculate w1,w2 and b by using GDA. In our example, 1 labeled class and -1 labeled class are drawn red and blue respectively.


->alfa (learning parameter)

->maxIteration
Javascript codes:
<script>

        var alfa = 0.001;
        var maxIteration = 300000;

        var w1 = 1;
        var w2 = 1;
        var b = 1;
        var dw1 = 0;
        var dw2 = 0;
        var db = 0;

        var datas = [

            { c: 1, x:10, y: 100 },
            { c: 1, x:-10, y: 50 },
            { c: 1, x:50, y: 150 },
            { c: 1, x:100, y: 80 }, 
            { c: 1, x:30, y: 200 },
            { c: 1, x:200, y: 50 },
            { c: 1, x: 120, y: 30 },
            { c: 1, x: 100, y: 150 },
            { c: 1, x: 300, y: 50 },
            { c: 1, x: -150, y: 200 },
            { c: 1, x: -300, y: 400 },
            { c: 1, x: 233, y: 333 },
            { c: 1, x: 127, y: 190 },
            { c: 1, x: 100, y: -32},
            { c: 1, x: -50, y: -12 },
            { c: 1, x: -101, y: -50 },
            { c: 1, x: -301, y: -19 },
            { c: 1, x: 127, y: -22 },
            { c: -1, x:120, y: -100 },
            { c: -1, x:-50, y: -200 },
            { c: -1, x:800, y: 100 },
            { c: -1, x:150, y: -150 },
            { c: -1, x:70, y: -80 },
            { c: -1, x: 70, y: -500 },
            { c: -1, x: 200, y: -300 },
            { c: -1, x: -50, y: -150 },
            { c: -1, x: 111, y: -200 },
            { c: -1, x: 301, y: -310 },
            { c: -1, x: 200, y: -151 },
            { c: -1, x: -122, y: -99 },
            { c: -1, x: -301, y: -499 },
            { c: -1, x: 401, y: -1 },
        ]

        function getHingeLoss(data) {

            let loss = 1 - (w1 * data.x + w2 * data.y + b) * data.c;

            if (loss > 0)
                return loss;
            else
                return 0;
        }

        function setGradients() {

            let cost = 0;

            for (let i = 0; i < datas.length; i++) {

                let loss = getHingeLoss(datas[i]);
                cost += loss;

                if (loss > 0) {

                    dw1 += (-1 * datas[i].x * datas[i].c);
                    dw2 += (-1 * datas[i].y * datas[i].c);
                    db += (-1 * datas[i].c);
                }
            }

            cost /= datas.length;
            dw1 /= datas.length;
            dw2 /= datas.length;
            db /= datas.length;

            return cost;
        }

        function train() {

            setValues();
            drawPoints();

            for (var i = 0; i < maxIteration; i++) {

                setGradients();

                w1 -= alfa * dw1;
                w2 -= alfa * dw2;
                b -= alfa * db;             
            }

            drawLine();

            let resultDiv = document.getElementById("resultDiv");
            resultDiv.innerHTML = "w1:" + w1 + ", w2:" + w2 + ",b:" + b;
        }

        function drawPoints() {

            var canvas = document.getElementById("g");

            for (let i = 0; i < datas.length; i++) {

                let circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');;
                circle.setAttributeNS(null, "cx", datas[i].x);
                circle.setAttributeNS(null, "cy", datas[i].y);
                circle.setAttributeNS(null, "r", 1);

                if (datas[i].c == 1)
                    circle.setAttributeNS(null, "fill", "red");
                else
                    circle.setAttributeNS(null, "fill", "blue");

                canvas.appendChild(circle);
            }
        }

        function drawLine() {

            var canvas = document.getElementById("g");

            let point1x = 1000;
            let point1y = (w1 * point1x + b) / (-1 * w2);
            let point2x = -1000;
            let point2y = (w1 * point2x + b) / (-1 * w2);

            let line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
            line.setAttributeNS(null, "x1", point1x);
            line.setAttributeNS(null, "y1", point1y);
            line.setAttributeNS(null, "x2", point2x);
            line.setAttributeNS(null, "y2", point2y);
            line.setAttributeNS(null, "style", "stroke: rgb(0, 255, 0); stroke-width:1");
            canvas.appendChild(line);
        }

        function setValues() {

            alfa = parseFloat(document.getElementById("alfa").value);
            maxIteration = parseFloat(document.getElementById("maxIteration").value);
        }

    </script>
HTML codes:
<div>
        <input id="alfa" type="text" value="0.001" />->alfa
    </div>
    <div>
        <input id="maxIteration" type="text" value="300000" />->maxIteration
    </div>

    <div id="resultDiv" style="margin-top: 20px;"></div>
    <div style="margin-top: 20px;">
        <button onclick="train()" type="button">START</button>
    </div>

    <div style="margin-top: 50px;">
        <svg height="500" id="canvas" width="500">
            <g id="g" transform="translate(250 250) scale(1,-1)">
                <line style="stroke-width: 1; stroke: rgb(0, 0, 255);" x1="-1000" x2="1000" y1="0" y2="0"></line>
                <line style="stroke-width: 1; stroke: rgb(0, 0, 255);" x1="0" x2="0" y1="-1000" y2="1000"></line>

            </g>
        </svg>
    </div>

Sunday, January 17, 2021

Gradient Descent Algorithm for Beginners (Quadratic Function Example)

In this article, I explain how gradient descent algorithm (GDA) works. I use a quadratic function for explanation:

f(x) = ax^2+bx+c

As you know, gradient of a function is first derivative of function (f'(x)). First derivative of f(x):

f'(x) = 2ax + b;

GDA is a iterative algorithm that calculates gradient of function f(x) for x in each iteration, and calculates next point by substracting gradient from x. GDA uses a learning parameter (alfa) that regularizes changings of x. So in each iteration, GDA calculates next x value:

x(t+1) = x(t) - alfa*f'(x(t))

So, why does GDA use gradient of function?

Think yourself on a hill and you want to get down. If slope is steep, you take a big step, otherwise a small step. Because while coming to end of hill, slope will be smaller until it is zero.

If you think of gradient as a vector, gradient's direction always guide you to end of hill or top of hill.

Note that, we can use GDA to find minimum point of a convex function, and gradient ascent algorithm to find maximum point of a concave function.

I prepared a working javascript example. You can calculate minimum point of quadratic function and visualize calculated point and function with this code.

In each iteration, calculated point (x,y) is displayed as a red dot.


->x (starting value of x)
->alfa (learning parameter)
->maxIteration (maximum iteration count)
->a
->b
->c

Javascript Codes:

<script>

        var x = 200;
        var alfa = 0.01;
        var maxIteration = 5000;

        var a = 1;
        var b = -5;
        var c = 6;

        var fx = (x) => a * x * x + b * x + c;

        var dxfx = (x) => 2 * a * x + b;

        function start() {

            setValues();
            drawCurve();

            var canvas = document.getElementById("g");
            let y = 0;

            for (var i = 0; i < maxIteration; i++) {

                x = x - alfa * dxfx(x);
                y = fx(x);

                let circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');;
                circle.setAttributeNS(null, "cx", x);
                circle.setAttributeNS(null, "cy", y);
                circle.setAttributeNS(null, "r", 1);
                circle.setAttributeNS(null, "fill", "red");

                canvas.appendChild(circle);
            }

            let resultDiv = document.getElementById("resultDiv");
            resultDiv.innerHTML = "x:" + x + ", y:" + y;
        }

        function setValues() {

            x = parseFloat(document.getElementById("x").value);
            alfa = parseFloat(document.getElementById("alfa").value);
            maxIteration = parseFloat(document.getElementById("maxIteration").value);
            a = parseFloat(document.getElementById("a").value);
            b = parseFloat(document.getElementById("b").value);
            c = parseFloat(document.getElementById("c").value);
        }

        function drawCurve() {

            var canvas = document.getElementById("g");

            for (var i = 5000; i >= -5000; i -= 1) {

                let line = document.createElementNS('http://www.w3.org/2000/svg', 'line');     
                line.setAttributeNS(null, "x1", i);
                line.setAttributeNS(null, "y1", fx(i));
                line.setAttributeNS(null, "x2", i - 1);
                line.setAttributeNS(null, "y2", fx(i - 1));
                line.setAttributeNS(null, "style", "stroke: rgb(0, 255, 0); stroke-width:1");
                canvas.appendChild(line);
            }
        }

    </script>
HTML Codes:
<div>
        <input id="x" type="text" value="200" />->x
    </div>
    <div>
        <input id="alfa" type="text" value="0.01" />->alfa
    </div>
    <div>
        <input id="maxIteration" type="text" value="5000" />->maxIteration
    </div>
    <div>
        <input id="a" type="text" value="1" />->a
    </div>
    <div>
        <input id="b" type="text" value="-5" />->b
    </div>
    <div>
        <input id="c" type="text" value="6" />->c
    </div>

    <div style="margin-top:20px" id="resultDiv"></div>
    <div style="margin-top:20px">
        <button type="button" onclick="start()">START</button>
    </div>

    <div style="margin-top:50px">
        <svg id="canvas" width="500" height="500">
            <g id="g" transform="translate(250 250) scale(1,-1)">
                <line x1="-1000" y1="0" x2="1000" y2="0" style="stroke: rgb(0, 0, 255); stroke-width:1"></line>
                <line x1="0" y1="-1000" x2="0" y2="1000" style="stroke: rgb(0, 0, 255); stroke-width:1"></line>

            </g>
        </svg>
    </div>

Sunday, January 3, 2021

x-comment jquery plugin: Jquery plugin for commenting or discussion

With x-comment, users' comments can be displayed hierarchically and users can respond to each other. There is no level limit while answering. x-comment allows adding new comments, reply to comments, and runs entirely on json and javascript objects.

Github:

https://github.com/vyigity/x-comment

Dependency

  • Jquery
  • Bootstrap
  • Fontawesome
  • Globalize

Configuration- Plugin

  • mode: Can be set as "array" or "remote". With "array" value, data is processed locally by using the javascript array given to the plugin. This method can be used for batch input and output operations. The data is accessed by running the function that returns Jquery Deferred in the dataSource field with "remote" value. (required)
  • items: In "array" mode, data is given to the plugin here. Also, after the data is loaded in "remote" mode, it can be read as a javascript array.(required in array mode)
  • dataSource: Can be set as a function that returns a jquery Deferred object. Data is obtained by running this function by the plugin. Data must be sent as parameter to promise resolve function.(required in remote mode)
  • authorName: The name written here is used by the plugin when responding. (required)
  • width: It allows the size of the plugin to be set to px. (optional)
  • allowInsert: Can be set as "true/false". It allows to show the form that enables inserting. (optional - Default:true)
  • allowReply: Can be set as "true/false". Ensures that comments can be answered or not. (optional - Default:true)
  • allowDelete: Can be set as "true/false". Ensures that comments can be deleted or not. (optional - Default:true)
  • onInserting: Can be set as a function that returns a jquery Deferred object. Is triggered when adding a new record. 
    • Parameters: 
      • instance: Instance of plugin. For example, it can be used to access the items or the plugin can be refreshed. 
      • value: Value that is typed by user.
  • onReplied: Can be set as a function that returns a jquery Deferred object. Is triggered when replying to a comment. 
    • Parameters: 
      • instance: Instance of plugin. For example, it can be used to access the items or the plugin can be refreshed. 
      • value: Value that is typed by user. 
      • item: Comment that is answered to.
  • onDeleting: Can be set as a function that returns a jquery Deferred object. Is triggered when deleting a comment. 
    • Parameters: 
      • instance: Instance of plugin. For example, it can be used to access the items or the plugin can be refreshed. 
      • item: Comment about to be deleted.
  • onError: Is triggered on error that occurs in functions that return a jquery Deferred object. Deferred object must be used with reject function. 
    • Parameters: 
      • data: The value sent through the reject function of the Deferred object.
  • texts: Can be configured text in plugin. A javascript object. (optional)

Configuration - texts (optional)

  • sendButtonText: Determines the text on the button in the new comment creation section.
  • inputTextAreaPlaceHolder: Allows editing of placeholder that appears in comment text area.
  • sendReplyButtonText: Determines the text on the reply send button.
  • cancelButtonText: Determines the text on the button that enables the cancellation of the response..
  • replyButtonText: Determines the text on the button that enables the answering process.
  • deleteButtonText: Determines the text on the button that allows the comments to be deleted.

Configuration - items

  • id: A unique value of a comment item.
  • parent: Parent value of a comment item.
  • deletable: Can be set as "true/false". The value that indicates the comment that can be deleted or not.
  • content: Content of a comment item.
  • created: Creation date time of a comment item. Value must be formatted as JSON. For example, "2020-12-31T20:52:00Z"
  • fullName: User name of owner of comment item.




Example (data.js):
var comments = [
    {
        id: 1,
        parent: null,
        deletable: true,
        content: "1. yorum",
        created: "2020-12-31T20:52:00Z",
        fullName: "Veli Yigit Yolcu"
    },
    {
        id: 2,
        parent: null,
        deletable: true,
        content: "2. yorum",
        created: "2020-12-31T21:52:00Z",
        fullName: "Veli Yigit Yolcu"
    },
    {
        id: 3,
        parent: 2,
        deletable: true,
        content: "2. yoruma cevap",
        created: "2020-12-31T22:52:00Z",
        fullName: "Veli Yigit Yolcu"
    }
];
Example (index.html):
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <link href="css/bootstrap.min.css" rel="stylesheet" />
    <link href="css/comment.css" rel="stylesheet" />
    <link href="css/fontawesome.min.css" rel="stylesheet" />
    <link href="css/brands.css" rel="stylesheet" />
    <link href="css/solid.css" rel="stylesheet" />

    <script src="js/jquery-3.5.1.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
    <script src="js/x-comment.js"></script>

    <script src="cldr/cldr.js"></script>
    <script src="cldr/event.js"></script>
    <script src="cldr/supplemental.js"></script>

    <script src="globalize/globalize.js"></script>
    <script src="globalize/message.js"></script>
    <script src="globalize/number.js"></script>
    <script src="globalize/currency.js"></script>
    <script src="globalize/date.js"></script>

    <script src="cldr-data/supplemental.js"></script>
    <script src="cldr-data/tr.js"></script>

    <script src="data/data.js"></script>
</head>
<body>

    <script>

        Globalize.locale("tr");

        var currentId = 100;

        $(document).ready(function () {

            $("#xcomment").xcomment({

                mode: "array",
                items: comments,
                allowInsert: true,
                authorName: "Veli Yigit Yolcu",
                width: 600,
                onInserting: function (instance, value) {

                    let def = $.Deferred();

                    instance.items.splice(0, 0,
                        {
                            id: currentId,
                            parent: null,
                            deletable: true,
                            content: value,
                            created: new Date().toJSON(),
                            fullName: "Veli Yigit Yolcu"
                        });

                    currentId++;

                    def.resolve();

                    return def;
                },
                onReplied: function (instance, item, value) {

                    let def = $.Deferred();

                    instance.items.push(
                        {
                            id: currentId,
                            parent: item.id,
                            deletable: true,
                            content: value,
                            created: new Date().toJSON(),
                            fullName: "Veli Yigit Yolcu"
                        }
                    );

                    currentId++;

                    def.resolve();

                    return def;
                },
                onDeleting: function (instance, item) {

                    let def = $.Deferred();

                    for (var i = 0; i < instance.items.length; i++) {

                        if (instance.items[i].id === item.id) {

                            instance.items.splice(i, 1);
                        }
                    }

                    def.resolve();

                    return def;
                }
            });
        });

    </script>

    <div id="xcomment" ></div>

</body>
</html>

Friday, January 1, 2021

x-comment: Jquery için yorum-tartışma eklentisi - x-comment: Jquery plugin for commenting or discussion

For english:


Geçenlerde bir projede yorumlama eklentisi kullanma ihtiyacım oldu. İnternette araştırma yaptım ve ilginç bir şekilde iyi bir jquery eklentisi bulamadım. Bulduğum da (ismini burada vermeyeyim) kullanışlı olmadı ve hata verdi. İnternette bulduğum en iyi şey Semantic UI css dosyası oldu. Bende elimdekileri kullanarak bir jquery eklentisi olarak x-comment eklentisini yazdım. x-comment' i aslında sunucu odaklı çalışacak şekilde düşünmüştüm fakat sonradan lokal olarak da çalışabilecek şekilde geliştirdim. Bu yazıdaki örnek lokal kullanıma (array mod) örnektir.


x-comment ile kullanıcıların yorumları birbirlerine yanıt verecek şekilde hiyerarşik olarak gösterilebilmektedir. Yanıtlamada seviye sınırı yoktur. x-comment yeni yorum eklenmesine, yorumların yanıtlanmasına olanak verir ve tamamen json ve javascript nesneleri üzerinden çalışır. Aşağıdaki GitHub bağlantısından kodlara erişilebilir.

GitHub:


Bağımlılıklar
  • Jquery
  • Bootstrap
  • Fontawesome
  • Globalize
Ayarlar - Eklenti
  • mode: "array" veya "remote" değerlerini alabilir. "array" değerinde eklentiye verilen javascript array kullanılarak lokal olarak işlem yapılır. Toplu giriş çıkış işlemler için bu yöntem kullanılabilir. "remote" değerinde dataSource alanındaki Jquery Deferred döndüren fonksiyon çalıştırılarak veriye ulaşılır. (Zorunlu)
  • items: "array" modda eklentiye veri buradan verilir. Ayrıca "remote" modda veri yüklendikten sonra buradan javascript array olarak okunabilir.(Array modda zorunlu)
  • dataSource: Jquery Deffered nesnesi döndüren bir fonksiyon içerebilir. Eklenti tarafından bu fonksiyon çalıştırılarak veri elde edilir. Veri promise nesnesinin resolve fonksiyonuna parametre olarak gönderilmelidir. (Remote modda zorunlu)
  • authorName: Yanıtlama yapılırken eklenti tarafından burada yazan isim kullanılır. (Zorunlu)
  • width: Eklentinin boyutunun px olarak ayarlanması sağlar. (Zorunlu değil)
  • allowInsert: "true/false" değeri alabilir. Giriş yapılmasını sağlayan formun gösterilmesini sağlar. (Zorunlu değil - Default:true)
  • allowReply: "true/false" değeri alabilir. Yorumların yanıtlanabilir veya yanıtlamaz olmasını sağlar. (Zorunlu değil - Default:true)
  • allowDelete: "true/false" değeri alabilir. Yorumların silinebilir veya silinemez olmasını sağlar. (Zorunlu değil - Default:true)
  • onInserting: Jquery Deffered nesnesi döndüren bir fonksiyon içerebilir. Yeni kayıt eklenirken tetiklenir.
    • Parametreler: 
      • instance: Yorum eklentisinin instance değeridir. Örn. items alanına ulaşmak için kullanılabilir veya eklenti buradan yenilenebilir.
      • value: Kullanıcı tarafından girilen değerdir.
  • onReplied: Jquery Deffered nesnesi döndüren bir fonksiyon içerebilir. Bir yoruma cevap verildiğine tetiklenir.
    • Parametreler: 
      • instance: Yorum eklentisinin instance değeridir. Örn. items alanına ulaşmak için kullanılabilir veya eklenti buradan yenilenebilir.
      • value: Kullanıcı tarafından girilen değerdir.
      • item: Yanıt verilen yorumdur.
  • onDeleting: Jquery Deffered nesnesi döndüren bir fonksiyon içerebilir. Bir yorum silinirken tetiklenir.
    • Parametreler: 
      • instance: Yorum eklentisinin instance değeridir. Örn. items alanına ulaşmak için kullanılabilir veya eklenti buradan yenilenebilir.
      • item: Silinmek istenen yorumdur.
  • onError: Jquery Deffered nesnesi döndüren fonksiyonlar içinde hata olması durumunda tetiklenir. Deferred nesnesi reject fonksiyonu ile kullanılmalıdır.
    • Parametreler: 
      • data: Deferred nesnesinin reject fonksiyonu aracılığıyla gönderilen değerdir.
  • texts: Eklenti içindeki nesneler üzerinde yazıların ayarlanmasını sağlayan javascript nesnesini içerebilir. (Zorunlu değil)
Ayarlar - texts (Zorunlu değil)
  • sendButtonText: Yeni yorum oluşturma kısmında yer alan tuş üzerindeki yazıyı belirler.
  • inputTextAreaPlaceHolder: Yorum girilen yazı alanının üzerinde beliren yazının düzenlenmesini sağlar.
  • sendReplyButtonText: Yanıt gönderme tuşunun üzerindeki yazıyı belirler.
  • cancelButtonText: Yanıt verilme işleminin iptal edilmesini sağlayan tuş üzerindeki yazıyı belirler.
  • replyButtonText: Yanıtlama işleminin yapılmasını sağlayan tuş üzerindeki yazıyı belirler.
  • deleteButtonText: Yorumların silinmesini sağlayan tuş üzerindeki yazıyı belirler.
Ayarlar - items 
  • id: Yoruma ait eşsiz bir değerdir.
  • parent: Hangi yorumun alt yorumu (yanıtı) olduğunu belirten değerdir.
  • deletable: "true/false" değeri alabilir. Yorumun silinebilir olmasını veya olmamasını sağlayan değerdir.
  • content: Yorumun içeriğini belirtir.
  • created: Yorumun oluşturulma zamanını belirtir. JSON formatlı tarih değeri içermelidir. Örn. "2020-12-31T20:52:00Z"
  • fullName: Yorumu yapan kullanıcının adını içerir.

Örnek (data.js):
var comments = [
    {
        id: 1,
        parent: null,
        deletable: true,
        content: "1. yorum",
        created: "2020-12-31T20:52:00Z",
        fullName: "Veli Yigit Yolcu"
    },
    {
        id: 2,
        parent: null,
        deletable: true,
        content: "2. yorum",
        created: "2020-12-31T21:52:00Z",
        fullName: "Veli Yigit Yolcu"
    },
    {
        id: 3,
        parent: 2,
        deletable: true,
        content: "2. yoruma cevap",
        created: "2020-12-31T22:52:00Z",
        fullName: "Veli Yigit Yolcu"
    }
];
Örnek (index.html):
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <link href="css/bootstrap.min.css" rel="stylesheet" />
    <link href="css/comment.css" rel="stylesheet" />
    <link href="css/fontawesome.min.css" rel="stylesheet" />
    <link href="css/brands.css" rel="stylesheet" />
    <link href="css/solid.css" rel="stylesheet" />

    <script src="js/jquery-3.5.1.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
    <script src="js/x-comment.js"></script>

    <script src="cldr/cldr.js"></script>
    <script src="cldr/event.js"></script>
    <script src="cldr/supplemental.js"></script>

    <script src="globalize/globalize.js"></script>
    <script src="globalize/message.js"></script>
    <script src="globalize/number.js"></script>
    <script src="globalize/currency.js"></script>
    <script src="globalize/date.js"></script>

    <script src="cldr-data/supplemental.js"></script>
    <script src="cldr-data/tr.js"></script>

    <script src="data/data.js"></script>
</head>
<body>

    <script>

        Globalize.locale("tr");

        var currentId = 100;

        $(document).ready(function () {

            $("#xcomment").xcomment({

                mode: "array",
                items: comments,
                allowInsert: true,
                authorName: "Veli Yigit Yolcu",
                width: 600,
                onInserting: function (instance, value) {

                    let def = $.Deferred();

                    instance.items.splice(0, 0,
                        {
                            id: currentId,
                            parent: null,
                            deletable: true,
                            content: value,
                            created: new Date().toJSON(),
                            fullName: "Veli Yigit Yolcu"
                        });

                    currentId++;

                    def.resolve();

                    return def;
                },
                onReplied: function (instance, item, value) {

                    let def = $.Deferred();

                    instance.items.push(
                        {
                            id: currentId,
                            parent: item.id,
                            deletable: true,
                            content: value,
                            created: new Date().toJSON(),
                            fullName: "Veli Yigit Yolcu"
                        }
                    );

                    currentId++;

                    def.resolve();

                    return def;
                },
                onDeleting: function (instance, item) {

                    let def = $.Deferred();

                    for (var i = 0; i < instance.items.length; i++) {

                        if (instance.items[i].id === item.id) {

                            instance.items.splice(i, 1);
                        }
                    }

                    def.resolve();

                    return def;
                }
            });
        });

    </script>

    <div id="xcomment" ></div>

</body>
</html>