Постове 1 - 5 от 18 с таг prototype.js

Sep 11

Наскоро прочетох – “A couple of interesting UI techniques at Flickr” от Signal vs Noise. И особенно ми хареса номера с използването на unicode за визуализирането на стрелки. Като винаги ме е дразнело това че трябва да се прибягва до картинки за елементарни неща. Напоследък гледам да избягвам колкото мога използването на каквито и да е било картинки. Малко съм улеснен от това че там където ми се налга да пиша CSS не се налага да се подържа нито едно IE, така че мога да се възползвам от неща като border-radius / box-shadow / text-shadow / gradients и други.

Но да се върна на темата. По принцип за това за което yahoo са ползвали стрелката аз ползвам този номер – “iPhone back button“.  Обаче веднага след като видях това за unicode – ▲ (буквално след 5 мин) вече му намерих първото приложение. Заменяйки цели 3 картинки (normal / hover / active ) за един бутон.

Пример

Малко по късно използвах тази техника и за нещо за което си мисля от доста време. Когато се отвори папка стрелкичката с ефект да се завърти от една до друга позиция. Това сега става изключтелно лесно само чрез

.arrow {
  /* цвят и размерите на бутона, както и вертикално как да се разположи */
  font-size: 10px;
  color: #000;
  vertical-align: 2px;

  /* без подчертаване и outline */
  text-decoration: none;
  outline: none;

  /* това е важно зада може да трансформираме елемента */
  display: inline-block;

  /* завърти стрелката на 90 градуса */
  -webkit-transform: rotate(90deg);
  -moz-transform: rotate(90deg);
  -o-transform: rotate(90deg);

  /*
     това мисля че не се потдъжа от никой още,
     но много скоро мисля че стане стандарт
  */
  transform: rotate(90deg);

  /* анимирай всички промени по стрелката за 0.3 секунди */
  -webkit-transition: all 0.3s ease-out;
  -moz-transition: all 0.3s ease-out;
  -o-transition: all 0.3s ease-out;
}

/*
  тъй като не искам да пиша в кода си unicode синволи,
  е по-добре да се добави автоматично от css
*/
.arrow:after {
  content:  "▲";
}

/*
  когато е отворена стрелката тя се завърта на 180 градуса
*/
.arrow.opened {
  -webkit-transform: rotate(180deg);
  -moz-transform: rotate(180deg);
  -o-transform: rotate(180deg);
  transform: rotate(180deg);
}

За задействането на ефекта е необходимо само да се сложи или премахне клас “opened” от “.аrrow” елемент. Това ще работи при Chrome, Safari, Opera и Firefox. Като само при Firefox за ефектите ще се почака малко.

Ето и 2 примерни JavaScript кода за стрелките с Prototype и jQuery (който са “доста” подобни):

// Prototype
document.on('click', '.arrow', function(e, element){
  e.preventDefault();
  element.toggleClassName('opened');
});

// jQuery
$('a.arrow').live('click', function(e){
  e.preventDefault();
  $(this).toggleClass('opened');
});

Ето и примерен arrows.html

Заключение

Тук може да се сложат :hover , :focus, :active където примерно да се сменя цвета на бутона или сянката. И това ще става анимирано по презумция. И няма да се налага да се ходи до Photoshop, после да се генерират sprite-тове (между другото за тяхното генериране, мисля скоро ще open-source-на едно мое tool-че за работа с тях) и да се позиционират при най-малките промени.

Комбинациятата от unicode синволи и CSS3 е страшно мощна. И може да елиминира 2 от най-големите причини за “бавни” сайтове – ненужни картинки и javascript за ефекти.
Аз имам късмет че там където правя такива неща не се подържа IE и просто мога да кажа че си е цял рай. Особенно хубавото на css ефектите е че не трябва да се грижите за конфликти в анимцията и други дребни промени. Още повече в много Java Script код съм виждал излишно много писаници за ефекти, вместо да Java Script да се  грижи само за логиката.

Ако някой има идей и предложение по темата ще се радвам да ги чуя, тъй като това е доста нова територия.

п.п. Ако ви трябва и за IE да работи, аз лично бих направил отделен файл ie-only.css и там бих ползвал картинки (няма как) и не бих правил ефектите. Който ползва IE едно че не е свикнал да вижда добре работещи ефекти, две че и не заслужава такива неща.

Jan 31

Както написах предишната седмица съм решил всяка седмица да събирам малко линкове и да ги описвам тук. За съжаление за поредна седмица нямах време да довърша някои от дълбоко замразените постове. Но мисля, че следващата ще ми е по-свободна.

Понеделник 25.01.2010

Yehuda’s – evented programming with jQuery – това се явява като (макар и само косвено) продължение на Creating UI Elements With Low-Coupling And Conditional Event Handling, за което писах миналата седмица. Което за пореден път ми показва, че май javascript компонентите на там са се насочили.

In praise of git’s index – много добра статия за git index-ите.

Вторник 26.01.2010

The Path to Rails 3: Approaching the upgrade и rails-upgrade: Automating a portion of the Rails 3 upgrade process. Rails 3 е почти готов след малко повече от година разработка. И от където и да го погледнеш си е огромна стъпка на пред, даже няколко. В постовете се обяснява за проблеми и съвети при преминаването към Rails 3.

JRuby за Аndroid – време беше. Как ви се струва едно ruby приложение за телефон през JRuby за Android, през MacRuby за iPhone и … (не че някой ще ползва windows mobile), но IronRuby за Windows Мobile. Преди време си бях говорил с познати за такава идея и че на теория е възможно (а според тях на практика не). Но това е още една стъпка в тази посока. Въпреки че анатомиите на iPhone / Android приложенията са доста различни, все повече си мисля, че е възможно да стане нещо такова.

Answering Baranovskiy’s JavaScript quiz – Явно Вторник е бил доста мързелив ден.  Nicholas C. Zakas показва отговорите на js теста на Dmitry  Baranovskiy. Аз с радост мога да се похваля, че само за единия въпрос се подвоумих малко (на този с arguments[2] = 10).

Сряда 27.01.2010

Put that data-* attribute away, son…You might hurt someoneDan Webb е страшно уважаван от мен дивелопър. Беше част от PrototypeJs Core и беше работил по един от първите Rails плъгини за Unobtrusive JavaScript. Да не говорим колко идеи почерпих от неговата LowPro за моя CD3.Behaviors.  За нещастие, доколкото разбрах, вече работи главно с jquery.

Но да се върнем на темата, че малко се отнесох. Тук той обяснява за data-атрибутите и че колкото и да са яки, не трябва да се използват прекомерно. Поста му се явява като отговор на Yehuda’s – evented programming with jQuery.

Четвъртък 28.01.2010

Emile.js talk (video & slides)Thomas Fuchs обяснява неговия mini css animation framework – Emile.js. Който е само 51 реда (+ празните редове и Copyright-а :) ). Много полезно и абсолютно задължително за тези, които не искат цял живот само да ползват неща, които не разбират.

Why Arel – ActiveRecord 3.0 ( който е част от Rails 3 ) е изграден върху Аrel. Като наскоро  излезе и Active Record Query Interface 3.0. Но какво е Arel, ами Relational Algebra (и моята първа реакция като го чух беше – ЪЪъъ). В поста се обясняват мотивите за написването на Аrel. Важно е да се каже, че Arel е напълно отделно от Rails и може се ползва независимо.

The HTML 5 sandbox Attribute Improves iFrame Security – Една от критиките към html5 е, че не помага на този етап за сигурността на мрежата. Това е една малка стъпка напред. Жалко, че май никой не е чувал за <module> тага предложен от Douglas Crockford още в далечната вече 2006.

New ActionMailer API in Rails 3.0 – Голямото пренаписване в Rails 3 върви с пълни сили и сега ActionMailer си намери майстора. (Все пак май ActionMailer си е като недоносеното дете в Rails пакета).

Петък 29.01.2010

По-рано през седмицата Apple обявиха така дългоочаквания iPad. И общо взето никой не го харесва. Като за мен си е просто играчка. На който ако и бяха сложили просто MacOsX щяха да разбият всички, но такъв е живота. Общо взето в Петък имах много работа и нищо интересно не ми попадна. Само 2-3-4… статии за iPad. Всичките еднакви от една страна Apple хейтърите го нареждат от всякъде, Аpple феновете леко разочаровани, поради големите очаквания и само неутралните наблюдатели с положителни оценки (леко резервирани, но положителни като цяло).

Бонус

Седмицата беше много изморителна и затова нямах много време да си прегледам Google Reader-a (все още имам към 200 не прочетени статии). Затова чак днес видях серия от 3 поста от Amix, създателят на Todoist и Plurk, и първият блог, който започнах да следя.

Накрая ще сложа едно футболно клипче, на което попаднах тази седмица, за голове вкарани чрез отборна игра:

Jan 16

Наскоро в проект използвах Sortable от Script.aculo.us и имах следната html структура:

  <ul id="category_products">
    <li id="product_1"><a href="/product/1/edit"><img src="/products/1/thumb.png" /></a></li>
    <li id="product_2"><a href="/product/2/edit"><img src="/products/2/thumb.png" /></a></li>
    <li id="product_3"><a href="/product/2/edit"><img src="/products/3/thumb.png" /></a></li>
    <!-- ... и така нататък ... -->
  </ul>

Това е списък със снимки на продукти, които може да се подреждат с провлачване. Също така при натискане на снимката на продукта, се отваря страница за промяна му. И тук се появява проблем, когато потребителя провлачи някоя снимка за да и смени реда, се отваря страницата за промяна на продукта. Това е меко казано е досадно.

Защо става така?

Много просто. Самото drag&drop работи така – на mousedown събитие върху li. При mouseup събитие li-то се пуска. Но в същият момент се получава и click събитие, което идва от картинката към връзката a, защото тя реално си е била натисната. И затова се получава този неприятен ефект.

Как може да се оправи това?

В Script.aculo.us има един обект Draggables, който е помощен обект грижещ се за Draggable обектите. Каквито са и нашите li-та. Той подъжа обзървъри. Като един обзървър може да има следните методи:

  • onStart – вика се, когато Draggable обект започне да се влачи
  • onDrag – вика се, докато Draggable се мести
  • onEnd – вика се след края на драгването

Вътрешно самия Sortable работи с един клас SortableObserver, който предава промените към Sortable инстанса.

И така, аз си направих един мой обзървър – DisableLinksOnDragObserver (който ползва моя Event.delegate метод).

Това, което DisableLinksOnDragObserver прави е, че когато започне да се драгва някой елемент, се слага маркер drag, и с него индикира, че в момента има влачене. След като се приключи влаченето, маркерът се прави false (тук е важно да се отбележи използването на Function#defer). Същевременно при всяко натискане на a елемент, ако drag е true, спираме събитието.

  // начин на ползване
  Draggables.addObserver(new DisableLinksOnDragObserver('category_products'));

Общо взето нищо сложно. Ако някой има идеи как може да се оптимизира или направи по-добре това, ще се радвам да ги чуя.

Sep 01

Преди малко по-малко от година в един пост озаглавен “Малко JavaScript refactoring“, бях описал как работи прост таб панел. Части от него използвам и до сега. Но е време за промяна.

Това което не ми допада в тогавашната версия е, че когато в страницата се добавя нов таб панел трябва да се вика отново TabPanel. А ако трябва да се трие нещата стават доста сложни. Също така и добавянето и/или изтриването на табчета става доста трудно. Затова реших да напиша нещо малко по-бързо и по-лесно.

В последните ми няколко проекта по които работя, ще поддържат само Firefox 3.5 и Safari 4, и  нагоре. Браузъри като IE (6/7/8) и даже Opera, въобще не влизат в картинката. Това ми дава възможност да се насладя на работата със html5 / css3. Затова структурата на един таб панел изглежда така:

<div class="tab_panel">
	<nav>
		<ul>
			<li class="tab selected">tab 1</li>
			<li class="tab">tab 2</li>
			<li class="tab">tab 3</li>
		</ul>
	</nav>
	<section></section>
	<section style="display: none;"></section>
	<section style="display: none;"></section>
</div>

А javascript, който задвижва нещата е:

function changeTab(){
	this.up('.tab_panel').select('section').invoke('hide')[this.previousSiblings('.tab').length].show();
	this.up().select('.selected').invoke('removeClassName', 'selected');
	this.addClassName('selected');
}
document.delegate('.tab_panel .tab', 'click', changeTab);

Как работи кода?

Под ‘this’ се има предвид избрания таб. На ред първи, се “качваме” до елемента, съдържащ таб панела и крием всички ‘section’ елементи. И след това показваме само избрания от нас елемент. Като this.previousSiblings(‘.tab’).length, ни връща колко таб бутна има преди избрания таб, което е и индекса на избрания елемент.
На втори ред.  Просто избирам всички елементи който имат клас “selected” (което отбелязва кое е избрания таб) и махам този клас от тях. Тук само искам да отбележа, че бих могъл да ползвам Element.down и после само Element.removeClassName на един елемент. Но съм предпочел да взема малко по “jquery” решение на въпроса.
И третия последен ред е просто слага ‘selected’ клас на избрания таб, маркирайки го като активен.

И след това моят Event.delegate, слуша за натискания на таб бутна и след това просто стартира changeTab.

На фокус

До преди няколко дни използвах този код при click събитие, но напоследък след по-голямото популяризиране на focus:in/focus:out и атрибута tabindex реших и да пробвам нещо малко “по-нестандартно” и така да направя таб панела достъпен и чрез “tab” бутона. :)

<div class="tab_panel">
	<nav>
		<ul>
			<li class="tab selected" tabindex="1">tab 1</li>
			<li class="tab" tabindex="2">tab 2</li>
			<li class="tab" tabindex="3">tab 3</li>
		</ul>
	</nav>
	<section></section>
	<section style="display: none;"></section>
	<section style="display: none;"></section>
</div>
function changeTab(){
	this.up('.tab_panel').select('section').invoke('hide')[this.previousSiblings('.tab').length].show();
	this.up().select('.selected').invoke('removeClassName', 'selected');
	this.addClassName('selected');
}
document.delegate('.tab_panel .tab', 'focus:in', changeTab);

Най-хубавото тук е че ‘click’ не ни трябва защото при натискане на елемент-а focus събитието се изстрелва така или иначе. Също така ако не искате се чудите за tabindex-а в какъв ред да е, просто може да се сложи навсякъде да е 1.

Примерен таб панел съм качил тук. Той изисква 2 неща (освен Prototype.js) – Event.delegate и focus:in.

За момента съм го тествал само в Firefox 3.5 / Safari4  и работи страхотно. За другите браузъри не би трябвало да има проблеми, но ако някои има проблеми с удоволствие бих помогнал.

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. Там ще ги актуализирам ако правя промяна.

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