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!

 

Dev Session #1

By mofux

Dev Session #1

Drop a Day 2019

  • 1,166