Aug 09

В един от последните ми проекти ми се налагаше да генерирам много html елементи с javascript. Като за това основно използвах new Element() и Element#insert от Prototype. Като въпреки че като цяло двата метода са много изчистени и правят кода доста добре четим, винаги може и по-добре.

В началото написах един прост builder, но като малко го поизползвах тук и там, се оказа , че като код е доста по-малко, но не и като четимост. Затова написах и тези две малки добавки към Element#insert които ми спестиха доста писане и направиха кода още по-четим и добър. И същевременно показаха (за пореден път) няколко от добрите страни на Prototype.

Element.insert(element, { into: content });
Element.insert(element, [content, content, content]);

Element#insert(element, { into: content });

Тук просто добавих нов insert position, into. Общо взето е ясно какво прави той добавя елемента в друг елемент. Защото поне на 20 места имах нещо такова(или много подобно:

var element = new Element('div', {/* атрибут */});
document.body.appendChild(element);

// като с into това просто става
var element = new Element('div', {/* атрибут */}).insert({into: document.body });

За тези, които са запознати как работи точно от вътре Element#insert, няма да е никак трудно да разберат как съм добавил ‘into’. Prototype използва един object, който да съхранява всички insert функции – before, after, top, bottom. Той е
Element._insertionTranslations, и когато бива извикан Element#insert(element, {position: content}) се вика грубо казано Element._insertionTranslations[position](element, content). Заради това добавянето на нов insert метод е много лесно и става просто така:

Element._insertionTranslations.into = function(element, node){
	node.appendChild(element);
};

Внимание! Element._insertionTranslations не е част от Public Api-то на Prototype и затова може да се промени в бъдещи версии!

Страничен ефект

Element#insert(element, { into: content }) има много интересен ефект, за който не се бях сетил когато го писах. Но когато случайно “открих” бях много доволен.
Element#insert може да приема не само html елементи, но и нормални javascript обекти, които имат метод toElement ( тои трябва да връща html елемент). И така примерно имам клас подобен на Custom Select класа ми от ControlDepo 3 Widgets, който има един контейнер, към когото може да се добавят елементи. Чрез into могат да се правят такива неща:

var list = new (Class.create({
	initialize: function(ul){
		this.element = $(ul);
	},
	toElement: function(){
		return this.element;
	}
}))('some');

Тук, за декларацията на класа, ползвам прост номер описан, по-добре тук.

Element.insert(element, [content, content, content]);

Идеята тук е да може да се предава масив от елементи, които да се добавят в element.

var ul = new Element('ul');
ul.insert(li1);
ul.insert(li2);
ul.insert(li3);
// ... и т.н.

// като това понякога го пиша и така
[li1, li2, li3].each(Element.insert.curry(ul));

// с новата версия става просто
ul.insert([li1, li2, li3]);

Такава функционалност може да стане по 2 начина. Първо като пиша директно в кода на Element#insert ( или си направя мой си fork на Prototype). И втория вариянт е Function#wrap.

Колкото и да се изкушавам от първия вариант реших само да пусна един билен ( ticket ) на Prototype и да се надявам да го добавят като функционалност.  И след това си написах каквото ми трябваше чрез Function#wrap:

Element.addMethods({
    insert: Element.insert.wrap(function(insert, element, insertation){
        if (!Object.isArray(insertation)) return insert(element, insertation);

        element = $(element);
        insertation.each(insert.curry(element));
        return element;
    })
});

Тези двете добавки ги има в git.github – http://gist.github.com/164751. Там ще ги актуализирам ако правя промяна.

Ако някой има предложение или идея как може да се доразвият тези добавки или за други подобни, може да драсне някои коментар :)

Какво мислите по въпроса