Bir vanilya JavaScript eklentisinin anatomisi

Bir eklenti (Plug-in) yazarken nelere dikkat etmeliyiz?

Bir javaScript eklentisi yazarken dikkat edilmesi gereken en önemli hususlardan birisi de o eklentisinin ANATOMİsidir.

Anatomi, Yunancada çıkarmak anlamına gelen ana ve kesmek anlamına gelen tomeden türetilmiş bir kelimedir.

wikipedia.org

The Anatomy

İşte tüm projelerime başladığım şablon. Merak etmeyin, adım adım ve inceleyerek ilerleyeceğiz.

(function (root, factory) {
	if ( typeof define === 'function' && define.amd ) {
		define(['boz'], factory(root));
	} else if ( typeof exports === 'object' ) {
		module.exports = factory(require('boz'));
	} else {
		root.myPlugin = factory(root, root.boz);
	}
})(typeof global !== "undefined" ? global : this.window || this.global, function (root) {

	'use strict';

	//
	// Variables
	//

	var myPlugin = {}; // Object for public APIs
	var supports = !!document.querySelector && !!root.addEventListener; // Feature test
	var settings; // Placeholder variables

	// Default settings
	var defaults = {
		someVar: 123,
		initClass: 'js-myplugin',
		callbackBefore: function () {},
		callbackAfter: function () {}
	};


	//
	// Methods
	//

	// @todo add plugin methods here

	/**
	 * Handle events
	 * @private
	 */
	var eventHandler = function (event) {
		// @todo Do something on event
	};

	/**
	 * Destroy the current initialization.
	 * @public
	 */
	myPlugin.destroy = function () {

		// If plugin isn't already initialized, stop
		if ( !settings ) return;

		// Remove init class for conditional CSS
		document.documentElement.classList.remove( settings.initClass );

		// @todo Undo any other init functions...

		// Remove event listeners
		document.removeEventListener('click', eventHandler, false);

		// Reset variables
		settings = null;

	};

	/**
	 * Initialize Plugin
	 * @public
	 * @param {Object} options User settings
	 */
	myPlugin.init = function ( options ) {

		// feature test
		if ( !supports ) return;

		// Destroy any existing initializations
		myPlugin.destroy();

		// Merge user options with defaults
		settings = boz.extend( defaults, options || {} );

		// Add class to HTML element to activate conditional CSS
		document.documentElement.classList.add( settings.initClass );

		// @todo Do stuff...

		// Listen for click events
		document.addEventListener('click', eventHandler, false);

	};


	//
	// Public APIs
	//

	return myPlugin;

});

Bağımlılıklar “Dependencies”

Söylemekte fayda var: Bu isteğe bağlıdır

Projelerimde genellikle iki ek dosya kullanırım. Bunlardan birincisi: ClassList.js[eklenecek] polyfill(destek)’i (desteğidir). Peki bu destek ne işe yarar? Diğer yazılarımızda da bahsettiğimiz gibi bu destek Internet Explorer için geliştirilmiştir. ClassList desteğini IE10+ dan IE8+ ‘ya yükseltmenize olanak sağlar. Tabi bu durum kişiye özeldir. Projelerinizin en düşük hangi Explorer sürümünü çalıştırması gerektiği ile doğru orantılıdır. O nedenle kullanmak pek tabi size (kodlayıcıya) kalmış bir durumdur.

Bir diğer ek dosyamız ise, boz.js[eklenecek] dir. Boz.js benim tarafımdan kodlanmış olan ufak bir kütüphanedir. Böylelikle her seferinde aynı kodları yazmaktan kurtuluyor ve bu kütüphanedeki hazır fonksiyonları çağırıp işin içinden çıkıyorum. Şunu belirtmeliyim ki bunu veya benzerlerini de kullanıp kullanmamak gene kodlayıcının arzusuna kalmış bir istisnai durumdur.

Biz yazımıza her iki durumu da dahil ederek devam edeceğiz. Böylelikle kullanmak istemediğiniz hususları es geçebilirsiniz.

Hadi başlayalım!

UMD Wrapper

Namı değer UMD Sarıcı. Bunu şöyle izah edelim, projelerinize (eklenti vs. fark etmez) UMD sarıcı – Universal Module Definition (UMD) sarmalayıcı– kullanarak yola çıkarsanız komut dosyalarınızın hem AMD hem de Common JS ile uyumlu olduğunu bildirmiş olursunuz. Bununla birlikte yazdığınız kodlamaların [önemli]“Hem değişken hem de fonksiyonların” geleneksel (GLOBAL) bir modül deseni olarak çalıştığı anlamına gelir. Yani yazdığınız bir eklenti bir başka eklenti veya kod’un etkisi altında kalmaz.

Bunu şöyle düşünün, ilk okulu bitiren bir çocuk senelerce uğraşıp bir takım bilgiler öğreniyor. Fakat Üniversite mezunu ağabeyi ile bir noktada bilgi çatışmasına giriyorlar. Birinin ak dediğine diğeri kara diyor. Şu halde 3. bir tekil şahıs hangisinin dediğine inanacaktır? Tabi ki hem yaşça büyük olduğu hem de daha üst mertebede eğitim aldığı için ağabeye inanılacaktır. Şimdi o küçük çocuğun düştüğü anlamsal çatışmaları düşünün. Kendisi bile neye inanacağını sapıtacak ve en sonunda ağabeyinin dediğine ayak uyduracak fakat hiç bir zaman kafasındaki çelişki sönümlenmeyecektir.

– vanillajs.wordpress.com

İşte sizin yazdığınız kod içerisinde bir değişkenin değeri “10” iken bir başkasının veya sizin yazdığınız herhangi bir başka eklenti vb. (etc.) kodlamalarda aynı isimdeki başka bir değişkenin değeri “0” kabul edildiğini düşünün. Mantıksal bir çelişki açığa çıkacaktır. Fakat MAKİNE DİLİ‘n de çelişkiye yer yoktur. O değişkenlerden hangisi Üniversite Mezunu ise veya referansı oraya dayanıyorsa onun dediği olur.

Pek tabi eşitlik olması durumunda akış şeması devreye girer ve SON SÖZÜ son konuşan söyler. Yani kısacası son gülen iyi güler ve kodlama sırasında en son olanın dediği olur.

Tabi biz şimdiye dek hep değişkenlerden bahsettik. Bu durumun aynısını fonksiyonlar içinde düşünebilirsiniz.

Neyse biz konumuza dönelim, biliyorum fazlaca saçmaladım 😛

İşte kodlamanın o kısmı:

(function (root, factory) {
	if ( typeof define === 'function' && define.amd ) {
		define(['boz'], factory(root));
	} else if ( typeof exports === 'object' ) {
		module.exports = factory(require('boz'));
	} else {
		root.myPlugin = factory(root, root.boz);
	}
})(typeof global !== 'undefined' ? global : this.window || this.global, function (root) {

	// Plugin stuff...

});

1myPlugin burada yazacağınız eklentinin adını tanımlar. Bunu kendi isteğinize ve arzunuza göre değiştirebilirsiniz.

2boz yukarıda da bahsettiğim üzere yardımcı bir eklenti ve bu eklentinin çalışması için gereken ön koşul. Eğer boz tanımlanmamışsa eklenti çalışmayacaktır.

3 Bir tarayıcıda her zaman için root = window kabul edilmeyebilir. Yani her şeyin kökü pencere olmalıdır. Ama böyle bir takım hatalar (bug) gerçekleşebilir. Bu hatayı engellemek için aşağıdaki satır kodlamayı ekiyoruz.

typeof global !== 'undefined' ? global : this.window

Use Strict use strict

use strict aslında tarayıcıya bir takım kurallar açısından daha katı olmasını söyler. Yani yapılan kural dışı kodlamalar, senaryolar ve geliştirilen fonksiyonlar açısından oluşabilecek hoş görü (istisnai durum) dışında davran ve kendi kurallarının (dil) dışına çıkma. Daha da kısaltacak olursak kabul edilebilir hata payını azaltıyor.

Kulağa saçma geliyor. Bunu neden kullanılır ki? Aslında bu durum sizi daha iyi, daha kaliteli, daha hatasız bir kod yazmaya zorlayacak. Eğer amacınız bu ise use-strict kullanmanızda fayda var diyelim ve devam edelim.

Variables

Namı değer değişkenler. Ama şunu ifade etmeliyim ki bu çeviri bana biraz saçma geliyor. Onun yerine yazılabilirler denilse daha iyi olurmuş. Tabi bu durum zaten güçlükle anlaşılan ifadeleri daha da karmaşıklaştırabilir. En iyisi bırakalım değişkenler olarak kalsın 🙂

//
// Variables
//

var myPlugin = {}; // Object for public APIs
var supports = !!document.querySelector && !!root.addEventListener; // Feature test
var settings; // Placeholder variables

// Default settings
var defaults = {
	someVar: 123,
	initClass: 'js-myplugin',
	callbackBefore: function () {},
	callbackAfter: function () {}
};

Tekrar tekrar söylemekte fayda var. Unutmayalım ki buradaki, myPlugin yazdığımız eklentinin adıdır. Bunu kendinize göre değiştirmeniz gerekir.

[önemli] myPlugin ‘i burada bir değişken olarak tanımladık fark ettiyseniz. Bunun sebebi yazdığımız eklenti deki toplanan tüm verilerin RETURN değeri ile SARMALAYICI yani UMD ‘nin dışına çıkarmasıdır.

Yani lafın özü, günün sonunda (sembolik olarak) yazdığımız { } bu süslü parantezlerin dışına hangi isimde ve hangi verilerin çıktığıdır ki, biz de o satırda tam olarak bunu yapıyoruz.

[önemli] bu tanımlamayı yapmamış olsaydık. Dışarıdan hiç bir değişkene veya fonksiyona ulaşamazdık.

[örnek] myPlugin.init(); fonksiyonunu dışarıdan erişilebilecek (PUBLIC) olarak kodladık. Fakat yukarıdaki tanımlamayı yapmamış olsaydık, bu fonksiyonunun dışarıdan kullanılması mümkün olmayacaktı.

Event Handler 

Namı değer etkinlik izleyiciler. “Bir eklentinin olmazsa olmaz fonksiyonlarıdır.” demek pekala mümkün:

Herhangi bir olay dinleyicisini –tıklamak, kaydırmak, pencereyi yeniden boyutlandırmak, zoom yapmak vb. etc.- eventHandler yönteminden geçiririz ki içinde bir takım mantıksal sorgulamalar yapabilelim.

Her türlü girişi buraya yazabilirsiniz. Örneğin, ben click olayı dinleyicilerimi document ögesine yerleştirmeyi seviyorum ve ardından tıklanan ögenin önemsediğim ögelerden biri olup olmadığını kontrol ediyorum. (Yani tıklanan öge benim aradığım öge mi onu sorguluyorum)

Örnek 1.0

var eventHandler = function (event) {
	var toggle = boz.getClosest(event.target, '[data-ornek]');
	if ( toggle ) {
		// Prevent default click event
		if ( toggle.tagName.toLowerCase() === 'a') {
			event.preventDefault();
		}
		// Eklenti deki diğer fonksiyonlar.
		myPlugin.someMethod();
	}
};

Burada mantıksal olarak şunları sağladık: document yani sayfadaki herhangi bir yere tıklandığı zaman eventHandler fonksiyonumuzu çalıştırdık. Bu fonksiyonun içinde ise, önce tıklanan elemanın ‘[data-ornek]’ etiketine sahip olup olmadığını kontrol ettik. Eğer bu etikete sahip bir eleman var ise deyip, o elemanın bir LİNK “a” olup olmadığını kontrol ettik. Eğer bir “LINK” ise preventDefault(); fonksiyonu ile normal tarayıcı davranışını reddetmesini emrettik. Kısacası link’e ait yeni sayfa açma işlemi iptal olacak. Ve hemen ardından someMethod(); isimli kendi fonksiyonumuzu çalıştırdık.

Eğer bu fonksiyon içerisine, bir sayfadan veri çekmeyi işlersek. Browser’da sayfa yenilemeden içerikleri değiştirebileceğimiz basit bir yol olacaktır 🙂

İsterseniz tüm olay türlerini tek bir işleyiciye aktarabilir ve olay türüne göre eylemin seyrini belirlemek için bazı mantıkları kullanabilirsiniz.

Örnek 1.1

var eventHandler = function (event) {
	if ( event.type === 'scroll' ) {
		myPlugin.scrollMethod();
	}
	if ( event.type === 'click' ) {
		myPlugin.clickMethod();
	}
};

Burada, gelen tüm eylemleri bir fonksiyonda topladık. Ve gelen olay türüne göre ayrıştırıp ona göre bir fonksiyon çalıştırdık. Örneğin, event.type === ‘scroll’ olayın bir scroll yani kaydırma olduğunu belirtiyor.

Destroy Method

Yani az çok anlaşılmıştır. Eklentiyi yok etmek. Çalışmaz hale getirmek.

Bu, aslında herhangi bir nedenle yeniden başlatmanız gerektiğinde veya başka bir komut dosyasından her şeyi durdurmanız gerektiğinde yararlıdır.

/**
 * Destroy the current initialization.
 * @public
 */
myPlugin.destroy = function () {

	// If plugin isn't already initialized, stop
	if ( !settings ) return;

	// Remove init class for conditional CSS
	document.documentElement.classList.remove( settings.initClass );

	// @todo Undo any other init functions...

	// Remove event listeners
	document.removeEventListener('click', eventHandler, false);

	// Reset variables
	settings = null;
};

Initialize

Namı değer başlatmak. “Yazdığımız eklentinin çalışmasını başlatmak” olarak tercüme edebiliriz.

Bir diğer tanım: Manuel (EL ile) başlatmak.

Bazen yazdığınız bir algoritmanın (eklenti vs. etc.) kendi kendine sayfanın yüklenmesiyle birlikte otomatikman çalışmasını istemeyebilirsiniz. Bunun kontrolünün elinizde olmasını isteyebilirsiniz. Sizin istediğiniz zamanda ve/veya istediğiniz şartlar sağlandığında çalışması veya en azından bunu destekliyor olabilme-si-niz, istendik bir durumdur. Böylesi durumlarda “manuel initialize” etmek gerekmektedir.

Bu durum projelerinizde (örneğin bir web sayfası hazırlamak) size çok büyük esneklikler sağlar. Çünkü her eklentinin her sayfada çalışması gerekmez. [önemli] *Hatta bazı sayfalarda çalışması gerekli iken bazı sayfalarda ise çalışmaması gerekebilir.

/**
 * Initialize Plugin
 * @public
 * @param {Object} options User settings
 */
myPlugin.init = function ( options ) {

	// feature test
	if ( !supports ) return;

	// Destroy any existing initializations
	myPlugin.destroy();

	// Merge user options with defaults
	settings = boz.extend( defaults, options || {} );

	// Add class to HTML element to activate conditional CSS
	document.documentElement.classList.add( settings.initClass );

	// @todo Do stuff...

	// Listen for click events
	document.addEventListener('click', eventHandler, false);

};

Burada dikkat edilmesi gereken öncelikle, gerekli web ve JavaScript API’lerinin desteklendiğinden emin olmak için bir kontrol yazılmış olması. Buradaki durumumda document.querySelectorve window.addEventListenerbizim için gerekli olan API’ler. Bunlar yukarıda (değişken olarak) yazdığımız, supports betiğinin başlangıcındaki değişkende tanımlanır.

Örnek 2.0

var supports = !!document.querySelector && !!root.addEventListener;

Eklentimizin başında yazdığımız bu değişkeni başlatma fonksiyonumuzun içinde kontrol ediyoruz.

Örnek 2.1

if ( !supports ) return;

(Çok basit bir tabirle hızlı geçmek istiyorum burayı) Eğer o ve o ‘ndan herhangi birisi yok ise, fonksiyondan boş çık! Doğal olarak eklentimizden de boş bir veri ile çıkış sağlanacaktır.

Ardından, iç –içe– çakışmaları önlemek veya olay dinleyicilerini çoğaltmak için betiğin /VARSA/ mevcut tüm başlatmalarını imha ediyoruz.

Örnek 2.2

myPlugin.destroy();

Sonra herhangi bir kullanıcı ayarını (eğer girilmiş ise) varsayılan değerler ile değiştiriyoruz: <abkz.> extend

settings = boz.extend( defaults, options || {} );

Yeri gelmişken ondan da bahsedelim,

Örnek 2.3

Bir kullanıcı ayarları şöyle yapar:

myPlugin.init({
	someVar: 456,
	initClass: 'js-change-me',
        anyVar: true,
});

Diğer Adımlar

Bu kısımda artık ana başlıklar altında değilde, kısa kısa püf noktaları vererek ilerleyeceğiz. Bu nedenle hepsini birleştirip, Diğer Adımlar adı altında sergiliyoruz.

1 Eklenti çalıştırıldıktan sonra DOM ögelerinden istediklerimize mesela (BODY) bir CSS sınıfı ekliyoruz. Bunun amacı ola ki eklentimiz herhangi bir nedenle hata verir ve/veya çalışmaz ise, bu durumda eklenecek olan sınıf o elemente eklenmaz ve bizde anlarız ki eklentimiz çalışmıyor. Buna bağlı olarak esnek bir CSS yazabiliriz. Böylelikle istenmedik CSS hatalarının da önüne geçmiş oluruz.

Örnek 3.0

document.documentElement.classList.add( settings.initClass );

2 bir sonraki olarak yaptığımız şey: olay dinleyicilerimizi kurarız (ayarlarız),

Örnek 3.1

document.addEventListener('click', eventHandler, false);

3 [önemli] Eklenti başlatılır başlatılmaz çalışması gereken (Bizim bu örneğimizde yer almayan) diğer tüm yöntemler de burada çağrılmalıdırlar.

Örnek 3.2

myPlugin.herhangiIslev();
myPlugin.moreFunc();
// ... daha fazla

4 [önemli] Eklenti dışında kullanılacak olan yöntemlerinizi dışarı çıkartın ( Return your public methods )!

Bu eklentideki dışarı çıkartacağımız son ve tek şey myPlugin‘ dir. Ki bu durumdan daha önce de bahsetmiştik. Bu değişkeni bu isimle dışarı çıkartmak aslında bize o isimi bir ÖN-EK olarak kullanacağımızı ifade ediyor. Bu ön-ek’i kullanarak eklentimizin içindeki –PUBLIC-, metodları dışarıdan çalıştırabiliriz.

return myPlugin;

Evet arkadaşlar böylelikle eklentimizin de yazımızın da sonuna geldik.

Yazım hatalarının ve (kimi yerde İngilizce, kimi yerde Türkçe) kullanmamın kusuruna bakmayın. Naçizane bazı terimleri tercüme etmek imkansız veya bazılarının da anlaşılabilmesi için orijinal hali ile kalması gerekir…

Sayfa başında eklediğimiz full sürümü bir de sonuna ekleyerek kapanışı yapalım:

Full Code: Anatomy of The JavaScript Plugin’s

(function (root, factory) {
	if ( typeof define === 'function' && define.amd ) {
		define(['boz'], factory(root));
	} else if ( typeof exports === 'object' ) {
		module.exports = factory(require('boz'));
	} else {
		root.myPlugin = factory(root, root.boz);
	}
})(typeof global !== "undefined" ? global : this.window || this.global, function (root) {

	'use strict';

	//
	// Variables
	//

	var myPlugin = {}; // Object for public APIs
	var supports = !!document.querySelector && !!root.addEventListener; // Feature test
	var settings; // Placeholder variables

	// Default settings
	var defaults = {
		someVar: 123,
		initClass: 'js-myplugin',
		callbackBefore: function () {},
		callbackAfter: function () {}
	};


	//
	// Methods
	//

	// @todo add plugin methods here

	/**
	 * Handle events
	 * @private
	 */
	var eventHandler = function (event) {
		// @todo Do something on event
	};

	/**
	 * Destroy the current initialization.
	 * @public
	 */
	myPlugin.destroy = function () {

		// If plugin isn't already initialized, stop
		if ( !settings ) return;

		// Remove init class for conditional CSS
		document.documentElement.classList.remove( settings.initClass );

		// @todo Undo any other init functions...

		// Remove event listeners
		document.removeEventListener('click', eventHandler, false);

		// Reset variables
		settings = null;

	};

	/**
	 * Initialize Plugin
	 * @public
	 * @param {Object} options User settings
	 */
	myPlugin.init = function ( options ) {

		// feature test
		if ( !supports ) return;

		// Destroy any existing initializations
		myPlugin.destroy();

		// Merge user options with defaults
		settings = boz.extend( defaults, options || {} );

		// Add class to HTML element to activate conditional CSS
		document.documentElement.classList.add( settings.initClass );

		// @todo Do stuff...

		// Listen for click events
		document.addEventListener('click', eventHandler, false);

	};


	//
	// Public APIs
	//

	return myPlugin;

});

Vanilya (Vanilla) JS nedir?

Aslına bakarsanız Vanilla JS, Pure JS ve Plain JS terimlerinin hepsi aynı şeyi ifade eder. Bu ifade “vanilla.js” isimli bir kütüphaneden gelmektedir ki onun da temelinde bir espri yatar.

Bu terimleri daha fazla açmak gerekirse, jQuery gibi bir takım seçici motorlar (Selector Engine) bizlere JavaScript yazmakta çok büyük kolaylıklar sağlar. Ancak bu gibi motorlar çok büyük kitlelere hitap ettiklerinden gittikçe büyüdüler. Böylelikle kütüphane havuzunda barındırdıkları yardımcı kodlar gittikçe arttı. Neticede toplam kod satırları ve dolayısıyla kapladıkları dosya boyutları da aynı oranda artış sergiledi ki, bu durum performansı da bir o kadar etkiledi.

JQuery gibi motorları kullandığımızda bir işlev yazmak için harcadığımız emek/iş gücü düşerken jQuery’in tüm kütüphanelerini çağırmak zorunda kaldığımızdan dolayı yazdığımız kod’un internet tarayıcıları, dolayısıyla insanlar üzerindeki performansı da oldukça düşük olmaya başladı.

Bu durumu şöyle ifade edebiliriz; bir pankek yapmak için koca bir fırın inşa ediyor ve o fırının içinde sadece kek üretimi yapıyoruz. Oldukça saçma değil mi? İşte kullanmadığımız bir yığın işlevleri içeren o motorları kullanmak da oldukça saçma.

İşte size jQuery vs. (saf) JavaScript performans diyagramı:

Vanilla JS vs. jQuery

Bu arada eminim Opera’nın JavaScript performansının Google Chrome’e dan daha iyi olduğu sizin de gözünüzden kaçmamıştır 😉

Nedir bu vanilla.js ?

Yazımızda daha önce de bahsettiğimiz üzere vanilla.js tamamen bir espriden ibaret. Asıl internet sitesi: http://vanilla-js.com/ olan ve her ne kadar şakacıktan da olsa bir framework’dur kendisi.

Yani: vanilla.js = Vanilla JS = Pure JS = Plain JS = (Saf) JavaScript

Zaten vanilla.js dosyasını indirmek isterseniz içinin boş olduğunu göreceksiniz. Seçici kütüphanelerinden hangisini tıklarsanız tıklayın Script’in dosya boyutunda bir değişiklik olmadığını ve 0KB olduğunu göreceksiniz.

Bu bir sitem midir? Yoksa espri mi? Bilinmez! Fakat bir amaca hizmet ettiği pekala ortada.

Ne yapmalı?

Artık bir seçici motor’u ve/veya framework kullanır mısınız? Yoksa projelerinizi biraz daha emek harcayıp saf JS ile mi kodlarsınız orası size kalmış. İşte pırasa, işte sapı!

Vanilla javaScript detect ‘click’ and ‘touch’ events in one boilerplate function.

        /**
         * Run a callback after a click or tap, without running duplicate callbacks for the same event
         * Hem dokunma hemde click eventlerinin aynı anda tetiklenmemesi için bir Callback ile geri besleme
         * Works in all modern browsers, and at least back to IE9.
         * @public
         * @polyfill    @required               None
         * @components  @required   @TODO       'util.queryElement()', 'util.on()'
         * @param       {Node}      elem        The element to listen for clicks and taps on
         * @param       {Function}  callback    The callback function to run on a click or tap
         */
        onClickOrTap: function (elem, callback) {

            elem = util.queryElement(elem);

            // Make sure a callback is provided
            if ( !callback || typeof(callback) !== 'function' ) return;

            // Variables
            var isTouch, startX, startY, distX, distY;

            /**
             * touchstart handler
             * @param  {event} event The touchstart event
             */
            var onTouchStartEvent = function (event) {
                // Disable click event
                isTouch = true;

                // Get the starting location and time when finger first touches surface
                startX = event.changedTouches[0].pageX;
                startY = event.changedTouches[0].pageY;
            };

            /**
             * touchend handler
             * @param  {event} event The touchend event
             */
            var onTouchEndEvent = function (event) {

                // Get the distance travelled and how long it took
                distX = event.changedTouches[0].pageX - startX;
                distY = event.changedTouches[0].pageY - startY;

                // If a swipe happened, do nothing
                if ( Math.abs(distX) >= 7 || Math.abs(distY) >= 10 ) return;

                // Run callback
                callback(event);

            };

            /**
             * click handler
             * @param  {event} event The click event
             */
            var onClickEvent = function (event) {
                // If touch is active, reset and bail
                if ( isTouch ) {
                    isTouch = false;
                    return;
                }

                // Run our callback
                callback(event);
            };

            // Event listeners
            util.on('touchstart', elem, onTouchStartEvent);
            util.on('touchend', elem, onTouchEndEvent);
            util.on('click', elem, onClickEvent);

        }

Function Components

on()

        /**
         * Add events or an event
         * Works in all modern browsers, and at least back to IE9.
         * @public
         * @polyfill    @required                   None
         * @components  @required       @TODO       'eventListenerHandler()'
         * @param       {String|Node}   types       The event type or types (space separated)
         * @param       {String}        selector    The selector to run the event on (, seperated)
         * @param       {Function}      callback    The function to run when the event fires
         */
        on: function (types, selector, callback) {

            // Make sure there's a selector and callback
            if (!selector || !callback) return;

            // Loop through each event type
            types.split(',').forEach((function (type) {

                // Remove whitespace
                type = type.trim();

                // If no event of this type yet, setup
                if (!activeEvents[type]) {
                    activeEvents[type] = [];
                    WIN.addEventListener(type, eventListenerHandler, true);
                }

                // Push to active events
                activeEvents[type].push({
                    selector: selector,
                    callback: callback
                });

            }));

        }

off()

        /**
         * Remove events or an event
         * Works in all modern browsers, and at least back to IE9.
         * @public
         * @polyfill    @required                   None
         * @components  @required       @TODO       'getListenerIndex()', 'eventListenerHandler()'
         * @param       {String|Node}   types       The event type or types (space separated)
         * @param       {String}        selector    The selector to remove the event from (, seperated)
         * @param       {Function}      callback    The function to remove
         */
        off: function (types, selector, callback) {

            // Loop through each event type
            types.split(',').forEach((function (type) {

                // Remove whitespace
                type = type.trim();

                // if event type doesn't exist, bail
                if (!activeEvents[type]) return;

                // If it's the last event of it's type, remove entirely
                if (activeEvents[type].length < 2 || !selector) {
                    delete activeEvents[type];
                    WIN.removeEventListener(type, eventListenerHandler, true);
                    return;
                }

                // Otherwise, remove event
                var index = getListenerIndex(activeEvents[type], selector, callback);
                if (index < 0) return;
                activeEvents[type].splice(index, 1);

            }));

        }

one()

        /**
         * Add events or an event
         * The handler is executed at most once per element per event type.
         * İşleyici, etkinlik türü başına öğe başına en fazla bir kez yürütülür ve sonra otomatik olarak silinir.
         * Works in all modern browsers, and at least back to IE9.
         * @public
         * @polyfill    @required                   None
         * @components  @required       @TODO       'util.on()', 'util.off()'
         * @param       {String|Node}   types       The event type or types (space separated)
         * @param       {String}        selector    The selector to remove the event from (, seperated)
         * @param       {Function}      callback    The function to remove
         */
        one: function (types, selector, callback) {
            util.on(types, selector, function callbackWrapper(e){
                callback(e);
                util.off(types, selector, callbackWrapper);
            });
        }

queryElement()

        /**
         * Return Query Element
         * Works in all modern browsers, and at least back to IE9/8.
         * @public
         * @polyfill    @required                   None
         * @components  @required                   None
         * @param       {String|Node}   selector    The selector to query element
         * @param       {Node}          elem        Parent Element
         * @returns     {Node}          elem        If has class return true
         */
        queryElement: function (selector, parent) {
            var lookUp = parent ? parent : DOC;
            return typeof selector === 'object' ? selector : lookUp.querySelector(selector);
        }

JavaScript “Object”, “Property”, “Prototype”, “Constructor” kullanımı

Zaten uzun uzun yazılar yazıyoruz. Bunu zaten bir çok blogger da yapıyor.

Fakat bu başlıktaki gibi kısa bir bilgiyi o yığınlarca kelime haznelerinden insanların bulması güçleşiyor. O nedenle ara ara bu gibi kısa bilgilendirmeleri yapacağız.

Property

JavaScript Property, objelere özellik eklemek olarak tercüme edilebilir.

var siteAccount = {}; // Object
siteAccount.number = 114521; // Property

Bir property eğer bir fonksiyonu işaret ediyorsa ona methoddiyebiliriz.

var siteAccount = {}; // Object
siteAccount.number = 114521; // Property
siteAccount.name = function() {
     return  "vanillajs.wordpress.com";
};

Constructor

Constructor (Kurucu metodu), ise;

var siteAccount = function(number, name){
   this.number = number;
   this.name = name;
   return this.number + " - " + this.name;
};

var newSiteAccout = new siteAccout("112473", "vanillaJS");

console.log(newSiteAccount); 
//log -> 112473 - vanillaJS

Prototype

Bir fonksiyonun içinde “this”‘i kullanarak aitliği belirleyebiliyoruz. Peki ya o fonksiyonun dışında yazmak istersek? İşte o zaman Prototiplerini oluşturmamız gerekir.

Javascript Object tipinde bulunan prototype adlı özel bir metod, tüm nesnelerde bulunur. Bu metod sayesinde nesnelere yeni property veya metodlar ekleyebilirsiniz. Constructor fonksiyonları ile kendi özel tiplerimizi oluşturmamız mümkündür. Oluşturduğumuz tipe ait iki farklı nesne örneği oluşturacağımız vakit her bir nesne örneği kendi property kümesini bünyesinde barındırır. Bu durum, veri tutucu property’ler için makul görünse de metodlar için bir sorun haline gelebilir. Metodlarımızı oluşturduğumuz tip içerisinden ayırabiliriz.

[1]

Örnek-1: Prototype kullanılmadan [1]

var Rectagle = function (x,y) {
     this.x = x;
     this.y = y;
     this.Area = function() { return x*y; };
};

Örnek-2: Prototype kullanarak [1]

var Rectangle = function (x, y) {
    this.x = x;
    this.y = y;
};
 
Rectangle.prototype = {
    Area: function() { return (this.x * this.y); }
};

Örnek-1 deki alan hesaplayıcı metod olan Area metodu Örnek-2 de tipin prototype metoduna tanımlanmıştır. [1]

Bu bize ne fayda sağlamıştır? Örnek-1 de constructor içinde tanımlanan fonksiyon için her Rectangle nesnesi oluşturulduğunda bellekte anonim bir fonksiyon oluşturulacak ve yer tutacak. Ancak Örnek-2 de durum farklı olacaktır. Prototype property’ler static olduklarından her Rectangle nesnesi prototype içine tanımlanan metotları referans olarak görecektir. [1]

Ayrıca Rectange tipine ait değer tutucu (x,y) üyeler ve fonksiyonlar ayrılmış oldu. Bu ayrımı farklı Javascript dosyalarına bile dağıtmak mümkündür. [1]

Prototype kullanımı öncelikli olarak performans artışı sağlamakla birlikte kodun okunabilirliğini de arttırmak açısından faydalı olmaktadır. [1]

Alıntı: [1]


Bayram Üçüncü | bayramucuncu.com/avascript-nesne-kavrami/

HTML etiketlerini vanilya JavaScript ile çözmek (decode)

Her videonun metni JSON’da kodlanmış bir dize olarak gönderilir. Bunun anlamı:

<p>In this course, you'll learn:</p>

Yukarıdaki gibi beklerken, tam olarak aşağıdaki gibi geliyor.

& lt;p& gt;In this course, you&rsquo;ll learn:& lt;/p& gt;

Sistem otomatik çözümlemesin diye bazı terimlerin arasına boşluk eklemek zorunda kaldık!

Bunu görüntüleyebilmek için, dizgeyi tekrar gerçek HTML olarak çözmek gerekiyor.

Getting Full Function Helper

var decodeHTML = function (html) {
	var txt = document.createElement('textarea');
	txt.innerHTML = html;
	return txt.value;
};

// Example
// Returns "<p>In this course, you'll learn:</p>"
var decoded = decodeHTML('<p>In this course, you&rsquo;ll learn:</p>');


İki dizinin eşit olup olmadığını kontrol etmek – Pure JS

Bu durumdan daha önce bahsetmiştik, ama genede bir köşeye ekleyelim:
– Pure JS,
– Vanilla JS,
– Plain JS

üçlemelerinin anlamları aynı kapıya dayanır. Her üçü de saf javaScript kullanarak bir takım kod blokları elde etme yöntemidir. Kısacası seçicileri içeren ek kütüphaneler bulunmaz. Buna bağlı olarak sadece kullandığınız yardımcı fonksiyonları kullanabilirsiniz.

Adım 1

Eşitliği kontrol etmek için önce dizilerin aynı uzunlukta olduğundan emin olmamız gerekir. Olmazsa, eşit değiller ve return false yapabiliriz (döndürebiliriz).

var arraysMatch = function (arr1, arr2) {

	// İlk önce dizilerin öge sayıları eşit mi onu kontrol et
	if (arr1.length !== arr2.length) return false;

};

Adım 2 [#1,#2]

İki dizinin tümü aynı ögelere sahipse bile, ancak farklı bir düzende dizilmişler ise kesinlikle eşit değillerdir.

#Adım 2:1

İlk dizideki her öge arasında döngü kuracağız ve dizinin (ideğişkeni sayesinde) ikinci dizideki aynı ögenin diziniyle aynı olup olmadığını kontrol edeceğiz. Değilse (ya da öge hiç mevcut değilse), geri göndereceğimiz değer: return false.

var arraysMatch = function (arr1, arr2) {

	// İlk önce dizilerin öge sayıları eşit mi onu kontrol et
	if (arr1.length !== arr2.length) return false;

	// Tüm ögelerin aynı olup olmadığını kontrol et
	for (var i = 0; arr1.length < i; i++) {
		if (arr1[i] !== arr2[i]) return false;
	}

};

#Adım 2:2

Her şey kontrol edildiğinde, eşitlik devam ediyorsa geri göndereceğimiz değer: return true.

Not: Burada dikkat edilmesi gereken durum EĞER(IF) koşulu gerçekleşirse fonksiyondan dışarı FALSE değeri ile döndüğünden ötürü, bunların hiç biri gerçekleşmezse (Koşulsuz + şartsız) direkt olarak TRUE değeri döndüre bilmemizdir.

Getting Full Function Helper

var arraysMatch = function (arr1, arr2) {

	// İlk önce dizilerin öge sayıları eşit mi onu kontrol et
	if (arr1.length !== arr2.length) return false;

	// Tüm ögelerin aynı olup olmadığını kontrol et
	for (var i = 0; i < arr1.length; i++) {
		if (arr1[i] !== arr2[i]) return false;
	}

	// True döndür
	return true;
};

Ayrıca Bkz.

Vanilya JavaScript ile bir diziden tekrar eden ögeleri çıkarmak

Bazen bir dizide (array) birden fazla olacak şekilde öge tekrarlanabilir.

var sehirler = ["Ankara", "İstanbul", "İzmir", "Ankara"];

Yukarıda görüldüğü üzere “Ankara” ögesi listemizde iki kere tekrarlanıyor. Biz bu listeyi müşterilerimize sunacağımız bir seçim menüsü haline getireceksek karmaşaya sebep verecektir. Bu nedenle tekrar eden ögeleri temizlememiz gerekir.

var sehirler = ["Ankara", "İstanbul", "İzmir", "Ankara"];
var sehirlerTekrarsiz = sehirler.filter(function(item, index){
	return sehirler.indexOf(item) >= index;
});

console.log(sehirlerTekrarsiz);
// Çıktı ["Ankara", "İstanbul", "İzmir"]

Yukarıdaki işlemleri her bir kod bloğunda tekrar tekrar kullanmak hem sizi hemde sitenizi (fazla kod satırı) yoracaktır. Bunun için bir yardımcı fonksiyon yazdık.

Getting Full Function Helper

var arrayUnique = function (arr) {
	return arr.filter(function(item, index){
		return arr.indexOf(item) >= index;
	});
};

var jobsUnique = arrayUnique(jobs);

Beğendiyseniz takip edebilirsiniz:

Diğer 2 aboneye katılın

Vanilla JS ile Animasyon Fonksiyonu Kullanımı

Hızlı bir özet

Buradaki yaklaşım, karmaşık JS kodlarına güvenmek yerine, öge’deki CSS animasyonlarını tetikleyen bir sınıf eklemeyi/çıkarmayı içerir. Ki bundan daha önce bahsetmiştik.

<h1 id="elem">Animate</h1>

<button class="show">Show</button>
<button class="hide">Hide</button>
var elem = document.querySelector('#elem');
document.addEventListener('click', function (event) {

	if (event.target.matches('.show')) {
		elem.removeAttribute('hidden');
		elem.classList.add('fadeInDown');
	}

	if (event.target.matches('.hide')) {
		elem.setAttribute('hidden', 'true');
		elem.classList.remove('fadeInDown');
	}

}, false);

Getting Full Setup

 * Apply a CSS animation to an element
 * @param  {Node}    elem      The element to animate
 * @param  {String}  animation The type of animation to apply
 * @param  {Boolean} hide      If true, apply the [hidden] attribute after the animation is done
 */
var animate = function (elem, animation, hide) {

	// If there's no element or animation, do nothing
	if (!elem || !animation) return;

	// Remove the [hidden] attribute
	elem.removeAttribute('hidden');

	// Apply the animation
	elem.classList.add(animation);

	// Detect when the animation ends
	elem.addEventListener('animationend', function endAnimation (event) {

		// Remove the animation class
		elem.classList.remove(animation);

		// If the element should be hidden, hide it
		if (hide) {
			elem.setAttribute('hidden', 'true');
		}

		// Remove this event listener
		elem.removeEventListener('animationend', endAnimation, false);

	}, false);

};