Naučte HTML novým trikům (II)
Dnes si ukážeme, jak vytvořit nový HTML tag, reprezentující SVG prvek, který se zobrazí jako sedmisegmentovka (jedna číslice). Použijeme ho např. takhle: <svg-segment x=“20“ y=“30“ value=“6“ color=“blue“></svg-segment>
Článek volně navazuje na předchozí: Naučte HTML novým trikům.
Pozn.: Je mi jasné, že daný úkol lze realizovat mnoha způsoby, jistě jsou i elegantnější než moje řešení
Sedmisegmentovka
Jedná se hojně rozšířený způsob zobrazení čílic 0 – 9 (popřípadě 0 – F v šestnáctkové soustavě). Může vypadat např. takhle:
Každý segment má svoje označení (a – g), číslice zobrazíme tak, že příslušný segment buď rozsvítíme, nebo zhasneme.
Pro zobrazení číslice 3 rozsvítíme segmenty: a, b, c, d, g.
Direktiva v Angularu
Pomocí javascriptové knihovny AngularJS můžeme definovat nový tag, který potom využijeme v HTML kódu webové stránky. Naše direktiva se jmenuje svgSegment a v HTML kódu ji použijeme jako <svg-segment></svg-segment>.
Může vypadat takto:
app.directive('svgSegment', function(){
return {
restrict: 'E',
replace: true,
scope: {
x:'@',
y: '@',
min: '@',
max: '@',
value: '=value',
error: '@',
errorColor: '@',
color: '@'
},
templateNamespace: "svg",
template: '<g transform="translate({{x}}, {{y}})">\n\
<polyline id="segm_a" stroke-width="1" points="11, 14 17, 8 47, 8 53, 14 47, 20 17, 20 11, 14 17, 8" stroke-linejoin="miter" ng-attr-fill="{{a}}" />\n\
<polyline id="segm_g" stroke-width="1" points="11, 65 17, 59 47, 59 53, 65 47, 71 17, 71 10, 65 17, 59" stroke-linejoin="miter" ng-attr-fill="{{g}}" />\n\
<polyline id="segm_d" stroke-width="1" points="11, 114 17, 108 47, 108 53, 114 47, 120 17, 120 11, 114 17, 108" stroke-linejoin="miter" ng-attr-fill="{{d}}" />\n\
<polyline id="segm_f" stroke-width="1" points="8, 19 14, 25 14, 55 8, 61 2, 55 2, 25 8, 19 14, 25" stroke-linejoin="miter" ng-attr-fill="{{f}}" />\n\
<polyline id="segm_b" stroke-width="1" points="56, 19 62, 25 62, 55 56, 61 50, 55 50, 25 56, 19 62, 25" stroke-linejoin="miter" ng-attr-fill="{{b}}" />\n\
<polyline id="segm_c" stroke-width="1" points="56, 69 62, 75 62, 105 56, 111 50, 105 50, 75 56, 69 62, 75" stroke-linejoin="miter" ng-attr-fill="{{c}}" />\n\
<polyline id="segm_e" stroke-width="1" points="8, 69 14, 75 14, 105 8, 111 2, 105 2, 75 8, 69 14, 75" stroke-linejoin="miter" ng-attr-fill="{{e}}" />\n\
\n\</g>',
controller: function ($scope){
$scope.getColors = function(){
var val = $scope.value;
if (!$scope.isError) {
$scope.a = (val === 0 || val === 2 || val === 3 || val === 5 || val === 6 || val === 7 || val === 8 || val === 9) ? $scope.color : 'none';
$scope.b = (val === 0 || val === 1 || val === 2 || val === 3 || val === 4 || val === 7 || val === 8 || val === 9) ? $scope.color : 'none';
$scope.c = (val === 0 || val === 1 || val === 3 || val === 4 || val === 5 || val === 6|| val === 7 || val === 8 || val === 9) ? $scope.color : 'none';
$scope.d = (val === 0 || val === 2 || val === 3 || val === 5 || val === 6 || val === 8) ? $scope.color : 'none';
$scope.e = (val === 0 || val === 2 || val === 6 || val === 8 ) ? $scope.color : 'none';
$scope.f = (val === 0 || val === 4 || val === 5 || val === 6 || val === 8 || val === 9) ? $scope.color : 'none';
$scope.g = (val === 2 || val === 3 || val === 4 || val === 5 || val === 6 || val === 8 || val === 9) ? $scope.color : 'none';
}
else {
if ($scope.error === "E") {
$scope.a = $scope.f = $scope.g = $scope.e = $scope.d = $scope.errorColor;
$scope.b = $scope.c = 'none';
}
else {
$scope.g = $scope.errorColor;
$scope.a = $scope.b = $scope.c = $scope.d = $scope.e = $scope.f = 'none';
}
}
};
$scope.checkError = function(){
$scope.isError = false;
$scope.isError = !( ($scope.value === parseInt($scope.value)) && ($scope.value >= $scope.min) && ($scope.value <= $scope.max) );
};
$scope.min = ($scope.min === parseInt($scope.min)) ? $scope.min : 0;
$scope.max = ($scope.max === parseInt($scope.max)) ? $scope.max : 9;
$scope.color = $scope.color || 'black';
$scope.errorColor = $scope.errorColor || 'red';
$scope.error = ( ($scope.error === '-') || ($scope.error === 'E') ) ? $scope.error : 'E';
$scope.$watch('value', function(newVal){
$scope.value = newVal;
$scope.checkError();
$scope.getColors();
});
}
};
});
Direktiva vrací objekt, jehož vlastnost:
restrict: 'E' říká, že vytvoříme nový tag = element
replace: true značí, že (níže uvedená) vlastnost template nahradí náš tag <svg-element></svg-element> zápisem uvedeným v template
scope je další objekt, pomocí kterého ovlivníme chování tagu <svg-element></svg-element> z HTML kódu použitím atributů
x, y jsou souřadnice, kde se nám sedmisegmentovka vykreslí
min, max umožní definovat minimální a maximální číslici, kterou sedmisegmentovka zobrazí
value je hodnota, kterou chceme zobrazit
error je znak, který se má zobrazit v případě chyby (např. číslo mimo rozsah min-max), může být buď „-„ nebo „E“
errorColor je barva, kterou se zobrazí chyba
color je barva rozsvíceného segmentu
templateNamespace: "svg" je tu proto, aby direktiva fungovala uvnitř SVG
template obsahuje kód, který se vloží místo tagu <svg-element></svg-element>
na prvním řádku je případné posunutí segmentovky o x pixelů doprava a o y pixelů dolů (klasický způsob v SVG)
na dalších řádcích jsou pak definovány jednotlivé segmenty a až g pomocí tagu <polyline />, je to běžný zápis v SVG až na poslední atribut ng-attr-fill, který určuje barvu každého segmentu (podle toho, zda je rozsvícený nebo zhasnutý)
controller popisuje chování direktivy a provádí některé manipulace
funkce $scope.getColors() na základě hodnoty value nastaví barvu těm segmentům, které se mají rozsvítit, zhasnuté segmenty se vůbec nevykreslí; v případě chyby se zobrazí buď znak „-„ nebo znak „E“
funkce $scope.checkError() ověří, zda je zadána celočíselná hodnota value a zda je v intervalu min-max, v opačném případě nastaví proměnnou $scope.isError na true
poslední fukce $scope.$watch() by tu být nemusela v případě, že nepotřebujeme update sedmisegmentovky a stačilo by nám jen jednou nastavit její číselnou hodnotu, která se má zobrazit; pokud ale chceme, aby segmentovka reagovala na změnu atributu value, hodí se nám tato funkce
Použití tagu <svg-segment></svg-segment> v HTML
Direktiva definuje několik atributů pro tag (element) <svg-segment></svg-segment> zapsaný v HTML. Atributy jsou:
x, y, min, max, value, error, errorColor, color
Příklad použití:<svg>
<svg-segment x=“20“ y=“30“ value=“6“ color=“blue“></svg-segment>
</svg>
Co se stane v případě, že některý z atributů vynecháme? Každý atribut má svoji implicitní hodnotu (která se použije). Souřadnice x, y ji sice nemají implicitně uvedenu, ale pokud je vynecháme, zobrazí se sedmisegmentovka na pozici 0, 0. Ostatní implicitní hodnoty jsou definovány v controlleru, např. minimum:
$scope.min = ($scope.min === parseInt($scope.min)) ? $scope.min : 0;
Zdálo by se, že vynecháním nejpodstatnějšího atributu value nebude zřejmé, co se vlastně má zobrazit, nicméně zobrazí se chyba (znak „-„ nebo „E“), to zařídí funkce $scope.checkError(), v jejíž první části podmínky se ověří, zda platí $scope.value === parseInt($scope.value), tj. zda je atribut value celé číslo. Tím, že atribut value úplně vynecháme, podmínka splněna není a je nastaven příznak chyby a proměnná $scope.isError má hodnotu true.
Na závěr
Jsem si vědom toho, že daný úkol se dá řešit mnoha způsoby, z nichž některé jsou jistě elegantnější než moje řešení. Pro experty: Také vím, že v direktivách existují funkce link a compile…
Direktiva by šla ještě rozšířit o atribut type s hodnotami „bin“, „dec“, „hex“ a s příslušně upraveným controllorem by se pak dala použít na zobrazování dvojkových, desítkových a šestnáctkových číslic.
Pokud někomu nevyhovuje velikost sedmisegmentovky, nemusí nic upravovat v kódu, ale stačí obalit segmentovku tagy <g> a </g> a nastavit příslušnou hodnotu scale, např:<svg>
<g transform=“scale(0.5)“>
<svg-segment x=“20“ y=“30“ value=“6“ color=“blue“></svg-segment>
</g>
</svg>