Hlavní obsah
Programování
Typ objektu tlačítko
Jedním z nejlepších způsobů, jak udělat kód znovu použitelný a výkonný, je použít objektově orientovaného programování, zejména pro vytváření ovládacích prvků uživatelského rozhraní (UI) jako jsou tlačítka. V objektově orientovaném programování pohlížíme na programovací svět z hlediska abstraktních objektových typů s určitým typem chování a pak vytváříme konkrétní instance těchto objektových typů s konkrétními parametry. Pokud si nepamatuješ, jak se to v JavaScriptu dělá, zopakuj si to zde.
Chceme-li použít OOP k vytváření tlačítek, budeme muset definovat typ objektu
Button
a pak na něm definovat metody jako jeho nakreslení a práce s kliknutím myši. Rádi bychom byli schopni napsat kód, který vypadá takto.var btn1 = new Button(...);
btn1.draw();
mouseClicked = function() {
if (btn1.isMouseInside()) {
println("Hele, kliknutí!");
}
}
Pojďme kód porovnat s tím, který jsme napsali v posledním článku:
var btn1 = {...};
drawButton(btn1);
mouseClicked = function() {
if (isMouseInside(btn1)) {
println("Hele, kliknutí!");
}
}
Jsou si velmi podobné, že ano? Ale je zde jeden velký rozdíl - všechny funkce jsou definovány na typu objektu
Button
a ve skutečnosti patří k tlačítkům. Existuje užší spojení vlastností a chování, které má tendenci vést k přehlednějšímu a znovu použitelnějšímu kódu.Pro definování typu objektu
Button
musíme začít u konstruktoru: je to speciální funkce, která obsahuje konfigurační parametry a nastavuje počáteční vlastnosti instance objektu.Jako první pokus je zde konstruktor, který pracuje s x, y, šířkou a výškou:
var Button = function(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
};
var btn1 = new Button(100, 100, 150, 150);
Jistě to funguje, ale my bychom rádi doporučili jiný postup. Namísto jednotlivých parametrů by mohl konstruktor pracovat s konfiguračním objektem.
var Button = function(config) {
this.x = config.x;
this.y = config.y;
this.width = config.width;
this.height = config.height;
this.label = config.label;
};
Výhodou konfiguračního objektu je, že můžeme do konstruktoru přidávat stále další parametry (například
label
), a je pro nás tedy snadné rozumět tomu, co každý parametr dělá, když tvoříme tlačítko:var btn1 = new Button({
x: 100, y: 100,
width: 150, height: 50,
label: "Prosím klikni!"});
Můžeme však jít o krok dál. Co když bude mít většina tlačítek stejnou šířku nebo výšku? Neměli bychom nadále určovat parametry šířky a výšky pro každé tlačítko zvlášť, měli bychom je specifikovat jenom v případě potřeby. Můžeme pomocí konstruktoru zkontrolovat, zda je vlastnost skutečně definována v konfiguračním objektu, a pokud ne, nastavit výchozí hodnotu. Nějak takhle:
var Button = function(config) {
this.x = config.x || 0;
this.y = config.y || 0;
this.width = config.width || 150;
this.height = config.height || 50;
this.label = config.label || "Klik";
};
Nyní můžeme kód zavolat s podmnožinou vlastností, protože ostatní budou nastaveny na výchozí hodnotu:
var btn1 = new Button({x: 100, y: 100, label: "Prosím klikni!"});
To je ale práce kvůli konstruktoru, že? Ale vyplatí se, uvidíš.
Teď, když máme konstruktor hotov, pojďme definovat chování: metodu
draw
. Bude se jednat o stejný kód jako pro funkci drawButton
, ale všechny vlastnosti se budou získávat od this
, protože je kód definován na samotném prototypu objektu:Button.prototype.draw = function() {
fill(0, 234, 255);
rect(this.x, this.y, this.width, this.height, 5);
fill(0, 0, 0);
textSize(19);
textAlign(LEFT, TOP);
text(this.label, this.x+10, this.y+this.height/4);
};
Jakmile je tlačítko definováno, můžeme jej zavolat:
btn1.draw();
Zde je program, který používá objekt
Button
k vytvoření dvou tlačítek - všimni si, jak snadné je vytvořit a nakreslit více tlačítek:Nicméně jsme přeskočili tu těžší část: práci s kliknutími. Můžeme začít tím, že definujeme funkci na prototypu
Button
, která vrátí "true", pokud uživatel klikne uvnitř rámečku konkrétního tlačítka. Jak již bylo řečeno, kód je podobný naší předchozí funkci, ale všechny vlastnosti získává z this
:Button.prototype.isMouseInside = function() {
return mouseX > this.x &&
mouseX < (this.x + this.width) &&
mouseY > this.y &&
mouseY < (this.y + this.height);
};
Teď můžeme použít vnitřek funkce
mouseClicked
:mouseClicked = function() {
if (btn1.isMouseInside()) {
println("Rozhodl ses dobře!");
} else if (btn2.isMouseInside()) {
println("Jupí, vybral sis mě!");
}
};
Vyzkoušej si kód níže a klikni na každé z tlačítek:
Pořád mi něco úplně nesedí, týká se to našeho způsobu, jak pracujeme s kliknutími. Celým smyslem objektově orientovaného programování je spojit veškeré chování související s objektem uvnitř objektu, a použít vlastnosti pro přizpůsobení chování. Některé vlastnosti jsme ale nechali mimo objekt,
println
uvnitř mouseClicked
:mouseClicked = function() {
if (btn1.isMouseInside()) {
println("Rozhodl ses dobře!");
} else if (btn2.isMouseInside()) {
println("Jupí, vybral sis mě!");
}
};
Textová prohlášení by měla být nějakým způsobem lépe svázána s každým tlačítkem, jako něco, co předáváme do konstruktoru. Podle toho, jak kód vypadá nyní, můžeme předat prohlášení do konfigurace konstruktoru a následně definovat funkci
handleMouseClick
pro zobrazení:var Button = function(config) {
...
this.message = config.message || "Klik!";
};
Button.prototype.handleMouseClick = function() {
if (this.isMouseInside()) {
println(this.message);
}
};
var btn1 = new Button({
x: 100,
y: 100,
label: "Prosím klikni!",
message: "Rozhodl ses správně!"
});
mouseClicked = function() {
btn1.handleMouseClick();
};
Teď je to mnohem lepší, protože je nyní vše spojené s konkrétním chováním každého tlačítka zabaleno do konstruktoru. Zároveň je to ale příliš jednoduché. Co kdybychom chtěli udělat něco jiného než zobrazit prohlášení, jako nakreslit několik tvarů nebo změnit scény, něco, co by obsahovalo několik řádků kódu? V takovém případě chceme konstruktorovi poskytnout více než jen řetězec - chceme mu ve skutečnosti dodat spoustu kódu. Jak můžeme předávat spoustu kódu?
...Za pomocí funkcí! V JavaScriptu (ale ne ve všech jazycích) můžeme předávat funkce jako parametry funkcí. V mnoha situacích je to velmi užitečné, ale obzvláště užitečné to je při definování chování částí uživatelského rozhraní (UI) jako jsou tlačítka. Tlačítku můžeme říci: "tady máš tuto funkci, je to blok kódu, který chci zavolat, když uživatel klikne na tlačítko." Tyto funkce označujeme jako "callback" funkce, protože se nebudou volat okamžitě, budou "zavolány" až později ve vhodný okamžik.
Můžeme začít tak, že předáme parametr
onClick
, který je funkcí:var btn1 = new Button({
x: 100,
y: 100,
label: "Prosím klikni!",
onClick: function() {
text("Rozhodl ses správně!", 100, 300);
}
});
Následně se musíme ujistit, že náš konstruktor nastaví vlastnost
onClick
podle toho, co bylo předáno. Ve výchozím nastavení, v případě, že není předána funkce onClick
, pouze vytvoříme funkci "no-op" - funkci, která neprovádí žádné operace. Je to jen proto, abychom ji mohli zavolat a nedostali jsme chybu:var Button = function(config) {
// ...
this.onClick = config.onClick || function() {};
};
A na závěr je třeba zavolat zpět callback funkci poté, co uživatel klikne na tlačítko. Je to docela jednoduché- můžeme ji zavolat napsáním názvu vlastnosti, do které jsme ji uložili, a zakončit to prázdnými závorkami:
Button.prototype.handleMouseClick = function() {
if (this.isMouseInside()) {
this.onClick();
}
};
A máme hotovo - máme objekt
Button
, pomocí kterého můžeme snadno vytvořit nová tlačítka, každé tlačítko přitom může vypadat jinak a reagovat jinak na kliknutí. Zkus si pohrát s kliknutím na příkladu níže a zjisti, co se stane, když změníš parametry tlačítka:Nyní, když máš tuto hezkou šablonu, můžeš přizpůsobit svá vlastní tlačítka, přidat jim různé barvy nebo je nechat reagovat na jiné události, jako je přejetí myší. Vyzkoušej si to při tvorbě svých programů.
Chceš se zapojit do diskuze?
Zatím žádné příspěvky.