неділя, 14 лютого 2016 р.

Create table with dynamic rows and configurable row buttons with AngularJS

Hi all,
Today I want to write a short article how to create a table with dynamic configuration for rows. For example, I need to render simple table by data provided from a server and append to each row some extendable buttons, checkboxes etc. for making action on a row. For example, I will use this data:


  app.controller('MainCtrl', ['$scope', function($scope) {
  var vm = this;

  var tableData = [{
      firstName: 'Viktor',
      lastName: 'Dzundza',
      age: 25
    }, {
      firstName: 'Jony',
      lastName: 'Boyko',
      age: 24
    }, {
      firstName: 'John',
      lastName: 'Doe',
      age: 45
    },

    {
      firstName: 'Alisa',
      lastName: 'Doe',
      age: 2
    }
  ];

  var tableMeta = {
    columns: [{
      name: 'firstName',
      title: 'First Name'
    }, {
      name: 'lastName',
      title: 'Last Name'
    }, {
      name: 'age',
      title: 'Age'
    }]
  };
 }]);


After defining actual data and simple meta information for the table, I create a factory which represents Table constructor. I will use it for creating new table instance.

app.factory('Table', [function() {

  function TableConstructor(data, tableMeta) {
    this.rowActions = [];
    this.tableMeta = tableMeta;
    this.rows = [];
  }

  TableConstructor.prototype.getColumns = function() {
      return this.tableMeta.columns;
  };

  TableConstructor.prototype.getRows = function() {
      return this.rows;
  };

  TableConstructor.prototype.getRowActions = function() {
      return this.rowActions;
  };

  TableConstructor.prototype.addRowActions = function(actions) {

  };

  return TableConstructor;
}]);

As you can see the Table has a simple interface for getting rows and retrieving possible row actions.
Next step I create Row factory

app.factory('Row', function() {
    function Row(data, id) {
        this.id = id;
        this.state = {};
        this.data = data;
    }

    Row.prototype.toggleAsDeleted = function() {
        this.state.asDeleted = !this.state.asDeleted;
    };

    Row.prototype.deleteRow = function() {
        this.state.deleted = true;
    };

    Row.prototype.isDeletedMark = function() {
        return this.state.asDeleted;
    };

    Row.prototype.isDeleted = function() {
        return this.state.deleted;
    };

    return Row;
});

I add state object for Row which will keep some boolean properties as "deleted" an "asDeleted". After creating Row, I'll inject it into Table

this.rows = data.map(function(rowData, idx) {
      return new Row(rowData, idx + 1); //actually id should be provided from server
});

For adding buttons to the row, I create a plain object which describes the action. Thia object must have at least one property named 'html' which returns content for compiling. Also, this object could be extended by extra properties: 'handlers' and 'scope', The first contains methods for the element, another can extend the scope. See code below:

var deleteRow = {
      html: function() {
          return '';
      }
  };

  var markAsDeleted = {
      html: function() {
          return '';
      }
  };


Implement `addRowActions` method and simply add these actions to the table.

TableConstructor.prototype.addRowActions = function(actions) {
      actions.forEach((function(a) {
          this.rowActions.push(a)
      }).bind(this));
  };

  vm.table.addRowActions([deleteRow, markAsDeleted]);

At the end, I show html for all this stuff


   
Index
{{row.id}} {{v}}

You can see working example at Plunker: Plunker

Thanks for reading, have a nice day.

неділя, 27 липня 2014 р.

Using jquery templates for adding form element dynamically in ASP.NET MVC

Hello friends. Today I wanna tell about some obstucles with validation which I've found when I had working with form.
At first I've created two models for binding

using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using DataAnnotationsExtensions;

namespace DynamicForm.Models
{
    public class FormViewModel
    {
        public int? Id { get; set; }

        [Required]
        [StringLength(25, ErrorMessage = "Max firstname length is 25 symbols.")]
        [DisplayName("First name")]
        public string FirstName { get; set; }

        [Required]
        [StringLength(25, ErrorMessage = "Max lastname length is 25 symbols.")]
        [DisplayName("Last name")]
        public string LastName { get; set; }

        [Required]
        [Email(ErrorMessage = "Provide correct email address, please.")]
        [DisplayName("Email")]
        public string Email { get; set; }

        [Range(16, 150, ErrorMessage = "Age should be between 16 and 150.")]
        [DisplayName("Age")]
        public int? Age { get; set; }

        public IList Discounts { get; set; }
    }
}

and DiscountCode model

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using DataAnnotationsExtensions;

namespace DynamicForm.Models
{
    public class DiscountCode
    {
        [Required]
        [DisplayName("Code name")]
        [StringLength(10, ErrorMessage = "Max name length is 10 symbols.")]
        public string Code { get; set; }

        [Required]
        [DisplayName("Code discount")]
        [Integer(ErrorMessage = "The field Percent should be a positive non-decimal number")]
        [Range(1,60, ErrorMessage = "The field Percent should be between 1 and 60.")]
        public int Percent { get; set; }
    }
}

As you know HTML helpers for rendering inputs generate elements with additional data attributes (data-val, data-val-* and so on)
for unobtrusive client-side validation. I've created template without any data atributes at all. When data has been sent to server in action it will be validated if there is some problems action return back partial view with form to client, but now all inputs which had been added dynamically will be rendered with all data attributes which are needed for unobtrusive validation.

@{
    ViewBag.Title = "Dynamic form";
    Layout = "~/Views/Shared/Layout.cshtml";
}

<h2>Test dynamic form</h2>
<button id="showForm">Show form</button>
<div id="dialog" style="display: none;" title="Dynamic loaded form">
    <div id="formContainer">
        @{ Html.RenderAction("GetForm"); }
    </div>
    <div>
        <button id="AddInput">add element dynamically</button>
    </div>
</div>
@section scripts
{
    <script>
        (function ($) {
            $.fn.clearErrors = function () {
                $(this).each(function () {
                    $(this).find(".field-validation-error").empty();
                    $(this).trigger('reset.unobtrusiveValidation');
                });
            };

            $.fn.resetForm = function () {
                $(this).find('#Discounts > .row').remove();
                $(this).find('input').val('');
                $(this).find('.form-group').removeClass("has-success").removeClass("has-error");
            };

            $.fn.enableValidation = function () {
                $(this).removeData("validator").removeData("unobtrusiveValidation");
                $.validator.unobtrusive.parse($(this));
            };
            $.validator.setDefaults({
                highlight: function (element) {
                    $(element).closest(".form-group").removeClass("has-success").addClass("has-error");
                },
                unhighlight: function (element) {
                    $(element).closest(".form-group").removeClass("has-error").addClass('has-success');
                }
            });
            $.ajaxSetup({ cache: false });
            var dialog,
                dialogParams = {
                    dialogClass: "no-close",
                    minWidth: 800,
                    autoOpen: false,
                    modal: true
                };
            function View(params) {

                function initDialog() {
                    dialog = $(params.dialogSelector).dialog($.extend(dialogParams, {
                        open: function (event, ui) {
                            $('form').clearErrors();
                        },
                        buttons: {
                            "Close": function () {
                                dialog.dialog('close');
                            },
                            "Save": function () {
                                if ($('form').valid()) {
                                    $.ajax({
                                        url: $('form').attr('action'),
                                        type: "POST",
                                        data: $('form').serialize(),
                                        success: function (result) {
                                            if (result.success) {
                                                $('form').resetForm();
                                                $(dialog).dialog('close');
                                            } else {
                                                $("#formContainer").html(result);
                                                $('form').enableValidation();
                                            }
                                        }
                                    });
                                }
                            }
                        }
                    }));
                }

                function showDialog() {
                    initDialog();
                    dialog.dialog('open');
                }

                function bindEvents() {
                    $(params.showFormButtonSelector).on('click', function () {
                        showDialog();
                    });

                    $(params.dialogSelector).on('click', '#AddInput', function () {
                        var $discountsContainer = $("#Discounts"),
                             lastIndex = 0,
                            data, html;
                        if ($discountsContainer.find('.row:last').length > 0) {
                            //provide correct index for non-sequential binding
                            lastIndex = parseInt($discountsContainer.find('.row:last > input[name="Discounts.Index"]').val(), 10) + 1;
                        }
                        data = { index: lastIndex };
                        html = $.templates("#discountRow").render(data);
                        $(html).appendTo($discountsContainer);
                        $('form').enableValidation();
                    });

                    $(document).on('click', '.removeDiscountRow', function (e) {
                        $(e.target).parents('.row').remove();
                    });
                }

                return {
                    bindEvents: bindEvents
                }
            }

            $(function () {
                var view = new View({
                    dialogSelector: "#dialog",
                    showFormButtonSelector: "#showForm"
                });
                view.bindEvents();
            });
        })(jQuery);
    </script>
}
<script id="discountRow" type="text/x-jsrender">
    <div class="row">
        <input type="hidden" name="Discounts.Index" value="{{: index}}">
        <div class="col-md-4 form-group">
            <div class="input-group">
                <label class="control-label" for="Discounts_{{: index}}__Code">Code name</label>
                <input class="form-control" id="Discounts_{{: index}}__Code" name="Discounts[{{: index}}].Code" type="text" />
            </div>
        </div>
        <div class="col-md-6 form-group">
            <div class="input-group">
                <label class="control-label" for="Discounts_{{: index}}__Percent">Code discount</label>
                <input class="form-control" id="Discounts_{{: index}}__Percent" name="Discounts[{{: index}}].Percent" type="text" />
            </div>
        </div>
        <div class="col-md-2 form-group">
            <div class="input-group">
                <button type="button" class="btn btn-primary removeDiscountRow">Remove</button>
            </div>
        </div>
    </div>
</script>

<style>
    .removeDiscountRow {
        margin-top: 25px;
    }
</style>
I've written simple Controller for this example
using System.Web.Mvc;
using DynamicForm.Models;

namespace DynamicForm.Controllers
{
    public class IndexController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpGet]
        public ActionResult GetForm()
        {
            return PartialView("_Form", new FormViewModel());
        }

        [HttpPost]
        public ActionResult SaveForm(FormViewModel formviewModel)
        {
            if (!ModelState.IsValid)
            {
                return PartialView("_Form", formviewModel);
            }
            return Json(new { success = true });
        }
    }
}
The form you can see below
viewModel under debugger
All code you can find at github: GitHub

пʼятниця, 25 липня 2014 р.

Some notes about transportation problem

Hello guys. After work I remembered my studying at university. My first thoughts is about solving Monge–Kantorovich transportation problem using a modification of simplex method known as Method of Potentials. Transportation theory investigates methods for optimal allocation resources among consumers and transportation them with minimum cost. For example, suppose we have some factories which provide materials and shops which consume it.

(To be continued)

понеділок, 17 лютого 2014 р.

Improving ...

Short account of what I did:
- I signed up for Machine Learning Stanford course (Link);
- I registered on AI&BigData conference in Odessa Link;
- I am trying to study Clojure.

After studying Clojure I'll describe all troubles which I got during studying and I'll write about my impression of the conference too.

понеділок, 10 лютого 2014 р.

PHP Design Patterns - Strategy

Today I want to explain how to use strategy design pattern in PHP. I tried to use this pattern when I want decouple algorithms for calculating something at runtime. For example, we can dynamically changing methods which will be calculate price of some items using some logic.

Create car, for example:
namespace strategy;

abstract class Item{
    abstract function getPrice();
}

class Car extends Item{

    protected  $price;
    public $name;
    protected $isTuned;

    public function __construct($name, $price, $isTuned){
        $this->name = $name;
        $this->price = $price;
        $this->isTuned = $isTuned;
    }

    /**
     * @return bool
     */
    public function isTuned(){
        return $this->isTuned;
    }

    public function getPrice(){
        return $this->price;
    }
}

Suppose we want to separate method which will calculate price with different strategies. Create interface
which will be implemented in each strategy.
namespace strategy;

interface IPriceStrategy {
    /**
     * @param Item[] $items
     * @return mixed
     */
public function calculatePrice(array $items);
}


Now we can create simple discount strategy for calculating.

class DiscountStrategy implements IPriceStrategy
{

    private $discount = 0;

    /**
     * @param $discount - in percent
     * @throws \Exception
     */
    public function __construct($discount)
    {
        if ($discount < 0) throw new \Exception('Discount must be greater or equal 0');
        $this->discount = $discount;
    }

    /**
     * @param Item[] $items
     * @return mixed
     */
    public function calculatePrice(array $items)
    {
        $price = 0;
        foreach ($items as $item) {
            $price = $price + $item->getPrice();
        }

        return $price * ($this->discount / 100);
    }
}


And implement second strategy - if invoice has more than some quantity of cars we subtract percents from total bill.
class DiscountManyCars implements IPriceStrategy
{
    private $discount;
    private $discountQuantities;

    public function __construct($discount, $discountQuantities)
    {
        $this->discount = $discount;
        $this->discountQuantities = krsort($discountQuantities);
    }

    /**
     * @param Item[] $items
     * @return mixed
     */
    public function calculatePrice(array $items)
    {
        $count = count($items);
        $price = 0;
        foreach ($items as $item) {
            $price = $price + $item->getPrice();
        }

        if ($count >= $this->discountQuantities) {
            $price = $price * ($this->discount / 100);
        }

        return $price;
    }
}

And finally using strategy pattern:
namespace strategy;

require_once 'Car.php';
require_once 'Invoice.php';
require_once 'IPriceStrategy.php';

$invoice = new Invoice();
$car1 = new Car('BMV', 1200000, false);
$car2 = new Car('MERCEDES', 100000, true);
$car3 = new Car('AUDI', 300000, false);

$invoice->addCar($car1);
$invoice->addCar($car2);
$invoice->addCar($car2);
$invoice->addCar($car2);
$invoice->addCar($car2);
$invoice->addCar($car3);
$invoice->addCar($car3);
$invoice->addCar($car3);

$discountStrategy = new DiscountStrategy(20);
$manyDiscountStrategy = new DiscountManyCars(30, 6);

$priceOne = $invoice->calculatePrice($discountStrategy);
$priceTwo = $invoice->calculatePrice($manyDiscountStrategy);
setlocale(LC_MONETARY, 'en_US');
$fmt = new \NumberFormatter('en_US', \NumberFormatter::CURRENCY);
echo "first strategy: ", $fmt->formatCurrency($priceOne, "EUR"), "
"; echo "second strategy: ", $fmt->formatCurrency($priceTwo, "EUR"), "
";

Output:
first strategy: €500,000.00
second strategy: €750,000.00

середа, 8 січня 2014 р.

Найлегший крок.

Отже свята пройшли тому пора займатися. Сьогодні був тест підйому в 4.00 годині і просте згадування формул вивчених на вихідних. Завтра встаю в 4.00 і сідаю вчитися. Покищо повторяю це http://mmf.nsu.ru/sites/default/files/WH-GR.pdf і елементарну теорію ймовірності, прийшлося на вихідних повторики трохи комбінаторики. З фінансової математики трохи змістився. Цікавить курс Artificial Intelligence (бажання освоїти від Yandex'a). Якщо не сьогодні то ніколи. Я спати.

четвер, 26 грудня 2013 р.

Давно не писав

Я давно нічого нового не писав в блог. Дата публікації останньої статті аж за 9 вересня 2010 року а на вулиці скоро 2014 рік. Пройшло більше ніж 3 роки, а чи багато змінилося за цих три роки? Ну я закінчив університет, влаштувався на роботу. Зараз хочу почати писати деякі статті в блог, здебільшого заради вивчення нових трендів у програмуванні і структуризації своїх знань, думаю блог якраз для цього призначений. В мене трохи наполеонівські плани по вивченню всяких технологій, але я думаю що зможу. В принципі зараз я поставив собі вектор це js (як і front-end так і back-end) і фінансова математика.
Також я вирішив повністю змінити свій розпорядок дня. Тепер в мене буде підйом мінімум в 6.00, далі планую перенести на 5.00. Кожний день (буду старатися) писати в блог як ідуть успіхи. Наразі все.

четвер, 9 вересня 2010 р.

Практика

            Розкажу трохи про практики які я проходив за весь курс навчання в коледжі і університеті.
Перша моя практика була на 2 курсі в коледжі. Це була двохтижнева практика на засвоєння курсу Pascal i C. Pascal ми вчили в першому семестрі а С почали вчити в другому. Сама практика почалась в  другому семестрі і С повністю ми не знали, я маю на увазі  - умови, цикли, масиви і тд.
Перша програма на С  - звичайний ввід і вивід з обчисленням даних виглядала дуже складною. Але потім все пішло як по маслу. І в кінці практики я зробив висновок, що С легше вчити ніж Паскаль. Зараз, згадуючи ті завдання, я розумію, що вони були досить простенькими. Я не пам'ятаю чи була практика на 3 курсі.
              Потім на 4 курсі (випускному) у нас мала бути виробнича практика. Постало питання, де проходити практику? Викладач нам порадив піти в фірму Eleks. Якщо пам'ять мене не зраджує одне із питань було про нормальні схеми БД. Це були перші лекції про бд. Тільки в лекціях трошки не так  було все описано і  я заплутався. Але нічого страшного, згодом мені прийшлось робити одним людям курсовий, а там було завдання створити бд і нормалізувати її до форми Бойса-Кодда, ось тоді я  з цими питаннями розібрався. Отже, на практику нас взяли, я був не один. До речі, разом проходити практику досить цікаво. Нам видали завдання, пошук дублікатів файлів  і трьохрівнева програма (БД - Сервер - Клієнт). Так вийшло, що прийшлось мені одному робити щось, я вибрав пошук дублікатів, хоча і міг робити клієнт-сервер, тим більше, що у нас був похожий курсовий. Але все таки людина шукає легший варіант  і я звичайно як усі взявся за дублікати. Програму написав на С#. На моєму тодішньому диску С (110 000 файлів), саме так, це результат довгої неперестановки віндовса і встановлення різних непотрібних програм, програма находила  дублікати приблизно за 30-40 хвилин. Довго, дуже довго. У вас напевно виникне питання: ти що побайтно перевіряв файли? Ні. Я використовував хеш-суму. Далі розказувати не буду, буде нецікаво. Ось саме завдання.
            Потім я знов проходив практику в університеті. Але на будівельній фірмі. Ну я думаю ви зрозуміли про що мова. Завдання як такого немає, отже потрібно його придумати. Мій друг придумав зробити "базу". Я взявся за створення. Всі мої результати можна подивитись в цій темі. Можливо моя практика не проходила так як має бути, але я  час не гаяв. Пройшов 3 десятка відео уроків по Zend Framework, поставив Убунту як єдину ОС, відправив заявку на отримання Canon SDK, працював на городі і ще багато іншого.
            Висновок.
Не ходити на практику на будівельні фірми. Потім тільки незручності всякі, такі як "що робити?"
Ще одна рекомендація - ходити на практику як мінімум парами.
Дякую всім за увагу.

середа, 1 вересня 2010 р.

MySQL Workbench

MySQL Workbench Community Edition.
Мало того, що він сильно лагає так ще й пусті запити вміє виконувати.
В MySQL Query Browser показує помилку 1065 'Query was empty', в phpmyadmin
просто показує вікно "Не задано значення форми". Цікаво як себе поводить платна версія.