Arka Plan

Geçenlerde ASP.NET MVC ile geliştirdiğim projemin çok büyüdüğünü ve javascript kodlarının artık kontrol edilemez bir hale geldiğini farkettim. Modüller ve bağlılıklar içinden çıkılmaz hale geldiğinde artık projenin Require.js ile söz kesmesinin zamanı geldiğine karar verdim. RequireJS gerçekten harika! Kullandıkça sizi düzene alıştırıyor ve gerçekten kod yazma alışkanlıklarınızı değiştiriyor. Aslında beklentim EcmaScript6 standart olarak tüm tarayıcılarda yerini aldığında çok daha iyi AMD ler görebilmek.

Proje backend olarak WebApi 2.0 ve frontend framework olarak da AngularJS kullanıyor. Ancak bazı yerlerde, bazı sorunları daha çabuk halletmek için, Razor’un html yardımcılarından Ajax.BeginForm() fonksiyonunu kullandım. Ajax formunu direkt backend e bağlayarak sızısız bir şekilde modal popuplardan backend’e veri kaydedebiliyordum. Bundan kolayca vazgeçme niyetim yoktu açıkçası. Aşama aşama başıma gelenleri paylaşmak istiyorum.

Peşinen söyleyeyim bu yazıda konu ile ilgili tutorial bulmayı bekleyenler bekleyenler burada sayfayı terketsinler :). Sadece bu örnekteki gibi birbirleriyle alakasız teknolojilerin kendi aralarında çatıştığı bir durumda, barış için bulduğum kendi çözümümü sunuyorum. Zaten benim gibi ASP.NET MVC, AngularJS ve RequireJS yi bir araya getirerek yılın abzürdlüğünü yapacak fazla da kişi olmayacağından bu konu hakkında tutorial yazma hakkımı gizli tutuyorum. :) Ancak ola ki yardım isteyen olursa seve seve ederim. Twitter’da @turhanco hesabım üzerinden ya da GitHub’da @turhancoskun hesaplarından bana ulaşabilirsiniz.

Ajax.BeginForm

Şimdi Ajax formunun tanımına bakarsak :

@using (Ajax.BeginForm(new AjaxOptions()
{
  HttpMethod = "POST",
  UpdateTargetId = "result",
  Url = "/api/customers",
  OnFailure = "onFailure",
  OnSuccess = "onSuccess",
  InsertionMode = InsertionMode.Replace
  }))

burada dikkatimizi çekmesi gereken üç satır var. Bunlar; UpdateTargetId, OnFailure ve OnSuccess.

İsimlerinden de anlaşılacağı üzere :

  • UpdateTargetId : Api gelen sonucun yazdırılacağı html elementinin Id’si
  • OnSuccess : Api işleminin başarılı olmasında çalışacak callback
  • OnFailure : Apiden bize html hatası döndüğünde çalışacak kısım

Burada apiden gelen sonuçları kullanıcıya gösterebilmek için şöyle birşey gerekebilir.

function onFailure(event) {
  $('#result').text(event.responseText);
  $('#result').parent().show().removeClass("alert-info").addClass("alert-danger");
}

function onSuccess() {
  $('#result').parent().show().removeClass("alert-danger").addClass("alert-info");
}

tabiki bu result nedir diye sorarsanız :

<div class="alert alert-info" style="display: none">
<i class="fa fa-info"></i>
<span id="result"></span>
</div>

Use Case

Bir drop down üzerinden müşteri seçmek istiyorsunuz ve aradığınız müşteri orada yok diyelim. Drop down üzerine iliştirilmiş ‘Yeni Müşteri Ekle’ yazısına tıkladığınızda yukarıdaki ajax formu bir bootstrap modal’ı içinde çıkıyor ve siz müşteri eklediğinizde söz konusu drop down kendiliğinden güncellenerek ana formdan hiç çıkmadan kayıt işleminize devam edebiliyorsunuz. İşte bütün bu dandikliklere katlanma sebebim bu. :) Meyvesinin bu kadar lezzetli olması.

Ana Form

Bahsettiğim Ajax.BeginForm() ‘ların bir bootstrap modal’ı üzerinde çalıştığını ve başka bir formdaki drop downlardaki bilgilere bilgi eklemek için yardımcı olarak kullanıldığını tekrar edeyim. Bu ana formdaki tüm veriler ve hatta drop down verileri angular.js ile kontrol ediliyor ve modal kapatıldığında angular ile kontrol edilen bu drop downlar update olmak zorunda.

Required.js’nin devreye girişi

Require.js nin bağlılıkları asenkron yüklemesi ve bizi global değişkenler kullanmamaya zorlaması, konsepte aykırı gibi duruyor. Demek istediğim :

function onSuccess() {
  $('#result').parent().show().removeClass("alert-danger").addClass("alert-info");
}

JQuery bağlılığını require.js bizzat yükleyeceği için sayfa yüklenirken $ tanımlayıcısını gören parser bize undefined not a function hatasını verecek. Çünkü JQuery henüz yüklenmedi!

Eller masaya

Angular.js güdümündeki formda tek bir drop down veya aynı drop down’ın kendini tekrar etmesi durumları olabilir. (Bende olduğu gibi). Bu durumda callback fonksiyonlarını require.js ile bir AMD modülü içerisinde tanımlayalım:

'use strict';
define(['jquery', 'angular'], function ($, angular) {
    var modalHelper = function(targetHtml, targetElement, multiple) {
        return {
            onSuccess: function() {
                $(targetHtml).parent().show().removeClass("alert-danger").addClass("alert-info");

                var isMultiple = multiple || false;

                if (isMultiple) {
                    $(targetElement).each(function() {
                        var innerScope = angular.element($(this)).scope();
                        innerScope.$apply(function() {
                            innerScope.refresh();
                        });
                    });
                } else {
                    var scope = angular.element($(targetElement)).scope();
                    scope.$apply(function () {
                        scope.refresh();
                    });
                }

            },
            onFailure: function(event) {
                $(targetHtml).text(event.responseText);
                $(targetHtml).parent().show().removeClass("alert-info").addClass("alert-danger");
            }
        }
    }

    return modalHelper;
});

Dosyanının modalhelper.js verip common dizinine kopyaladım.

Ayrıca bu modülün içerisine işlem başarılı olduğunda angular.js kontrolündeki drop downların tazelenmesi için gerekli kodları da ekledim.

Tanımladığımız bu modülü ana formun bağlı olduğu sayfadan sorumlu require.js kodundan çağırmamız gerekiyor.

require([
        'jquery',
        'ng-app',
        './common/modalhelper', // <- Yazdığım modül burada
        'bootstrap',
        'app/controllers/product',
        'app/controllers/currency',
        'app/controllers/customer',
        'app/controllers/tax'
],
function ($, ngapp, modalHelper) {
    require(['application/controllers/invoice-create'], function () {

        // Bind Ajax.BeginForm callbacks
        var customerHelper = new modalHelper('#result', '#customer');
        var productHelper = new modalHelper('#product_result', 'select[name="product"]', true); //<- multiple=true yaptık
        window.onFailure = customerHelper.onFailure;
        window.onSuccess = customerHelper.onSuccess;
        window.onProductSuccess = productHelper.onSuccess;
        window.onProductFailure = productHelper.onFailure;

        // ...
        // ...

      });
});

Modal tanımları aynı sayfada olduğu için farklı adlar veriyoruz. Aynı zamanda Ajax.BeginForm callback ler için global değişkenler istediğinden, window.onSuccess ve window.onFailure gibi global değişkenlere bağlıyoruz.

En iyi çözüm olmayabilir ama işleri aksatmadan yapılacak en hızlı çözüm bu.

“Fazla mükemmelliyetçi olmayın; en iyisine sahip olacağım derken, hiçbir şeye sahip olamazsınız.”