Custom Drops
Dev Session #1
Custom Drops
Warum?
Ein JTL Shop Plugin schreiben ist mächtig, aber aufwändig
-
SQL
-
Backend UI
-
Backend Logik
-
Datenquellen
-
Mehrsprachigkeit
-
Persistierung
-
Frontend Logik
-
Frontend Rendering
-
Dokumentation
TODO
MEIN PLUGIN
Lösung daher häufig
Templateanpassung
-
Updatefähigkeit geht verloren
-
HTML/Smarty/CSS/JS Kenntnisse beim Kunden nötig
-
Keine Medienverwaltung
-
FTP Datenpflege
-
Mehrsprachigkeit nur über Sprachvariablen
-
Kein Überblick für Dritte
-
"Hidden Conventions"
-
Fehlende Dokumentation
-
Templateanpassung
Der Spaß geht verloren
Der gute Weg
Custom Drops
-
SQL -
Backend UI -
Backend
Logik -
Datenquellen -
Mehrsprachigkeit -
Persistierung -
Frontend Logik -
Frontend
Rendering -
Dokumentation
TODO
MEIN DROP
+ Backups
+ Zentrale Verwaltung
+ Darstellungsfilter
+ Selector Finder
+ Multiple Instanzen
+ Effiziente Ausführung
TODO
MEIN DROP
Features
On Top
+ Ressourcenverwaltung
+ Zentrale Mediengalerie
+ Drops verlinken
+ Basisdokumentation
+ Entwicklerframework
+ Weiterentwicklung
Custom Drops
Wie?
LET'S GO
CUSTOM DROPS
Live Entwicklungsumgegbung
Adresse
Drop a Day 2019 / dday2019
W-LAN
Aufbau
CUSTOM DROPS
DROP ID
CUSTOM DROPS
-
Vendor-Prefix UPPERCASE
-
Name ohne Leerzeichen und Umlaute, UpperCamelCase
-
ACME_DEMO
-
ACME_MeinTestDrop
-
KK_Plain
-
CL_Nosto
-
S360_ItemSlider
-
ZUNFT_Berater
Aufbau
CUSTOM DROPS
config.json
Metadaten und Einstellungen des Drops
template.tpl
Setzt die Darstellung des Drops um
drop.php
Interaktion eines Drops mit PHP
Aufbau
CUSTOM DROPS
upgrade.js
Datenmigration zwischen Versionen
js/css Dateien
Ressourcen die zur Funktion und Optik des
Drops beitragen
Readme.md
Dokumentation des Drops
icon.png
Icon des Drops für das Dropper Backend
config.json
CUSTOM DROPS
{
"name": "Demo Drop",
"description": "Ein erstes eigenes Drop",
"version": "1.0.0"
}
Metadaten
config.json
CUSTOM DROPS
{
"name": "Demo Drop",
"description": "Ein erstes eigenes Drop",
"version": "1.0.0",
"scripts": ["js/slider.js", "js/whatever.js"],
"stylesheets": ["css/acme-demo.css"]
}
Ressourcen einbinden
config.json
CUSTOM DROPS
{
...
// Einstellungen hinter dem Gear-Menu (optional)
"config": [
setting,
setting
],
// Haupteinstellungen (optional)
"settings": [
setting,
setting
],
// Voreingestellte Werte für ein neues Drop
"defaults": {
...
}
}
Einstellungen hinzufügen
config.json
CUSTOM DROPS
setting
{
"name": "Name der Einstellung",
"description": "Hilfe und Beschreibung zur Einstellung",
"type": "<CONTROL-TYP>",
"property": "<VARIABLEN-NAME>",
"multilanguage": true|false, // optional
"section": "<TAB-NAME>", // optional
"condition": "<JS-AUSDRUCK>", // optional
... // Control-spezifische Parameter
}
Aufbau einer Einstellung
text
CONTROLS
{
...
"type": "text",
"append": "px" // optional
}
ERZEUGT
"Hallo Welt"
html/code
CONTROLS
{
...
"type": "html|code",
"height": 300, // optional
"lang": "html" // für "code"
"langInline": true|false // für "code"
}
ERZEUGT
"<h1>Hallo Welt!</h1>"
checkbox
CONTROLS
{
...
"type": "checkbox"
}
ERZEUGT
true
upload
CONTROLS
{
...
"type": "upload"
}
ERZEUGT
"/bilder/kk_dropper_uploads/meinBild.png"
date
CONTROLS
{
...
"type": "date",
"format": "yyyy/mm/dd" // optional
}
ERZEUGT
"2015/04/16"
time
CONTROLS
{
...
"type": "time"
}
ERZEUGT
"13:37"
color
CONTROLS
{
...
"type": "color"
}
ERZEUGT
"rgba(122,176,11,0.61)"
link
CONTROLS
{
...
"type": "link"
}
ERZEUGT
{
"url": "Plugins",
"title": "JTL Shop Plugins",
"openExternal": true,
"attrs": "href='Plugins' title='JTL Shop Plugins' target='_blank'"
}
...mit Auswahl
CONTROLS
{
...
"type": "select|multiselect|switch|imagepicker",
"options": "DATASOURCE" | [OPTION, OPTION, ...]
}
OPTION
{ "label": "Eins", "value": 1 }
Datasources
CONTROLS
switch
CONTROLS
{
...
"type": "switch",
"options": [
{ "label": "an", "value": true },
{ "label": "aus", "value": false }
]
}
ERZEUGT
true
select
CONTROLS
{
...
"type": "select",
"options": [
{ "label": "0 (INT)", "value": 0 },
{ "label": "1 (INT)", "value": 1 },
{ "label": "true (BOOL)", "value": true },
{ "label": "false (BOOL)", "value": false },
{ "label": "null", "value": null },
{ "label": "Hallo Welt (STRING)", "value": "Hallo Welt" }
]
}
ERZEUGT
false
multilselect
CONTROLS
{
...
"type": "multiselect",
"options": [
{ "label": "0 (INT)", "value": 0 },
{ "label": "1 (INT)", "value": 1 },
{ "label": "true (BOOL)", "value": true },
{ "label": "false (BOOL)", "value": false },
{ "label": "null", "value": null },
{ "label": "Hallo Welt (STRING)", "value": "Hallo Welt" }
]
}
ERZEUGT
[true, 1, "Hallo Welt"]
imagepicker
CONTROLS
{
...
"type": "imagepicker",
"options": [
{ "label": "Vorstellen", "value": "before", "image": "img/before.png" },
{ "label": "Anstellen", "value": "after", "image": "img/after" },
{ "label": "Vorhängen", "value": "prepend", "image": "img/prepend.png" },
{ "label": "Anhängen", "value": "append", "image": "img/append.png" },
...
],
"width": 600 // Breite des Popovers, optional
}
ERZEUGT
"append"
list
CONTROLS
list
CONTROLS
{
...
"type": "list",
"itemSettings": [setting1, setting2, ...],
// Voreinstellungen bei neuen Listitems
"itemDefaults": { "prop1": "value1", "prop2": "value2", ...},
// Der Name des Listitems ergibt sich aus dem
// konfigurierten Wert einer Einstellung, optional
"itemNameBinding": "prop1",
// Name für ein neues Listenelement
"newItemName": "Neuer Eintrag",
// Darstellungsfilter für Listenelemente deaktivieren
"itemConditionals": false
}
ERZEUGT
[
{ "prop1": "<value>", "prop2": "<value>", ... },
{ "prop1": "<value>", "prop2": "<value>", ... }
]
BEISPIEL
{
"name": "Bilder",
"description": "Füge neue Bilder hinzu oder bearbeite sie",
"type": "list",
"property": "images",
"itemSettings": [
{
"name": "Bildtitel",
"type": "text",
"property": "title"
},
{
"name": "Bild",
"type": "upload",
"property": "image"
},
{
"name": "Rahmen anzeigen?",
"type": "switch",
"property": "showBorder",
"options": [
{ "label": "Ja", "value": true },
{ "label": "Nein", "value": false }
]
}
],
"itemDefaults": {
"showBorder": true
},
"itemNameBinding": "title",
"newItemName": "Neues Bild"
}
template.tpl
CUSTOM DROPS
<!-- Smarty Variablen des Shops ausgeben -->
<h1>Hallo Welt! Ich bin auf Seitentyp {$nSeitenTyp}</h1>
<!-- Drop Variable ausgeben -->
<h1>{$drop.greeting}</h1>
<!-- Listenelemente -->
{foreach $drop.images as $image}
<img
src="{$image.image}"
alt="{$image.title}"
class="img-responsive {if $image.showBorder}img-bordered{/if}"
/>
{/foreach}
Drop-Daten: $drop
Tutorial #1
CUSTOM DROPS
Erstellung eines Custom-Drops, das eine Galerie von Bildern mit Titeln und Links erzeugt.
Javascript
CUSTOM DROPS
<div class="acme-demo">Hallo Welt!</div>
<script type="text/javascript">
DropperFramework.init('.acme-demo', function(drop) {
drop.$.css('background', 'red');
});
</script>
DropperFramework
Kümmert sich um die Laufzeit und Initialisierung eines Drops.
Javascript
CUSTOM DROPS
<div class="acme-demo acme-demo-{$drop._internal.guid}">Hallo Welt!</div>
<script type="text/javascript">
DropperFramework.init('.acme-demo-{$drop._internal.guid}', function(drop) {
drop.$.css('background', '{$drop.bgColor}');
});
</script>
Bei mehreren Instanzen eines Drops, die per Einstellungen gesteuert werden, bietet es sich an einen eindeutigen Selektor zu erstellen und darauf zu initialisieren.
Achtung: Nutze das id-Attribut mit Sorgfalt. Es kann immer mehrere Instanzen eines Drops auf der Seite geben!
Tutorial #2
CUSTOM DROPS
Die Bildergalerie soll jetzt zu einem Slider gemacht werden. Hierbei wird der im JTL-Shop enthaltene Slick-Slider benutzt.
// Slick Konfiguration:
{
centerMode: true,
variableWidth: true,
infinite: true,
dots: true,
swipeToSlide: true,
waitForAnimate: false,
speed: 200
}
drop.php
CUSTOM DROPS
<?php
class ACME_DemoDrop extends DropActions {
// init() wird automatisch vor dem Rendern des
// Drop-Templates aufgerufen und dient als Konstrukor.
function init() {
// Erweiterung der Daten möglich
$this->drop['message'] = 'Hello World!';
// Manipulation möglich
foreach ($this->drop['images'] as &$image) {
$image['title'] = strtoupper($image['title']);
}
}
// Weitere Methoden die im Template verwendet werden können
function greet($name) {
return 'Hallo ' . $name;
}
}
drop.php
CUSTOM DROPS
<!-- Neues Drop Property ausgeben -->
<div>{$drop.message}</div> // Hello World!
<!-- Funktion aus der PHP-Klasse aufrufen -->
<div>{$drop.actions->greet('Martin')}</div> // Hallo Martin
Im Template des Drops kannst du via {$drop.actions} auf die Instanz der Drop-Klasse zugreifen.
template.tpl
Tutorial #3
CUSTOM DROPS
Thumbnails für die Galeriebilder generieren und nutzen.
-
Neue Einstellung für die Thumbnail-Größe hinzufügen
-
Einstellung Nutzen und Thumb-Urls generieren
kkDropperHelper::createThumbnail($imageUrl, $width, $height, $crop=true, $upscale=false)
upgrade.js
CUSTOM DROPS
angular.element(document).ready(function() {
// register ACME_Demo upgrades
angular.injector(['app']).invoke(['upgrade', function(upgrade) {
upgrade.addDropUpgrade("ACME_Demo", "1.0.0", "1.0.1", function(drop) {
drop.demo = true;
return drop;
});
upgrade.addDropUpgrade("ACME_Demo", "1.0.1", "1.0.2", function(drop) { ... });
upgrade.addDropUpgrade("ACME_Demo", "1.0.2", "1.0.3", function(drop) { ... });
}]);
});
-
"version" in der config.json erhöhen
-
"upgrade.js" anlegen (falls noch nicht vorhanden)
-
Upgrade-Funktion erstellen und Datenmigration ausführen
Tutorial #4
CUSTOM DROPS
-
Neue Einstellung 'overlayColor' für Bilder anlegen, um Bilder hervorheben zu können
-
upgrade.js nutzen, um alte Drop-Instanzen mit der 'overlayColor' Einstellung nachzurüsten
DropStorage
CUSTOM DROPS
$this->storage->set($key, $value, $scope);
$this->storage->get($key, $scope);
$this->storage->exists($key, $scope);
Innerhalb deiner drop.php kannst du serialisierbare Daten jeglicher Art speichern und laden!
Du kannst Daten zwischen Drop-Instanzen teilen. Drop-Storage hat hierfür ein "scope"-Konzept
// nur für diese Instanz: 'instance'
$this->storage->set('readable-by-my-instance', 'hello', 'instance');
// für alle Instanzen des selben Drops: 'drop'
$this->storage->set('readable-by-other-instances', 'hello', 'drop');
// für alle Instanzen, egal welchen Drops: 'global'
$this->storage->set('readable-by-all-instances', 'hello', 'global');
DropStorage
CUSTOM DROPS
Persistierung im Dateisystem
k_dropper
|_ storage
|_ Shared // Scope 'global'
|_ ACME_Demo
|_ Shared // Scope 'drop'
|_ 1231-afas-f123-sdff // Scope 'instance'
|_ 9afb-212a-fa26-54d1 // Scope 'instance'
|_ ... // Scope 'instance'
{
"name": "Demo Drop",
...
"export": {
"storage": ["instance", "drop"]
}
}
Export-Einstellungen in der config.json
DropIO
CUSTOM DROPS
Ajax-RPC mit der drop.php
class ACME_DemoDrop extends DropActions {
// Drop IO Funktion müssen mit 'io_' beginnen!
function io_sayHello($params) {
return $this->drop['greeting'] . ' ' . $params['name'];
}
}
Javascript
var io = DropperFramework.io(dropGuid);
io.call('sayHello', { name: 'Martin' }, function(err, result) {
console.log(err, result); // null, 'Hallo Martin'
});
Tutorial #5
CUSTOM DROPS
Galeriebilder sollen durch Klick auf einen Button geliked oder disliked werden können. Bei jedem Like zählt ein globaler Counter für das Bild hoch bzw. runter.
Die Likes pro Bild sollen via DropperStorage gespeichert werden und der Klick selbst via DropIO getrackt werden.
HOT OR NOT
Pause
Immer noch Bock?
Schau dir das entstandene Fiddle an, klone es und mach damit was du willst!