Personalizarea listei de clase de livrare în WooCommerce

Cel mai simplu lucru pe care ți l-ai dori să-l obții ar fi să adaugi o coloană nouă, dar până și simplitatea asta suportă câteva adnotări, adică prezintă o serie de chichițe și umflături. Mai dificil este să adaugi o acțiune conextuală nouă pentru fiecare rând din lista de clase de livrare.

TL;DR,

TL;DR,

Adăugarea unei noi coloane

În întreprinderea noastră, procesul tehnologic pentru adăugarea unei noi coloane presupune, după obținerea avizelor necesare de la autoritățile competente, două lucruri:

înregistrarea coloanei în capul de tabel (adică înștiințarea WooCommerce că, hei, am dori și noi o coloană nouă în acea tabelă, dacă se poate și nu deranjăm) și
furnizarea unei valori pentru fiecare celulă corespunzătoare acelei coloane, pentru înregistrarea (zi-i, bre, rând!) corespunzătoare fiecărei clase de livrare.

Pentru înregistrarea coloanei, se poate folosi filtrul woocommerce_shipping_classes_columns, în care primești ca parametru lista curentă de coloane (array asociativ în care cheile sunt identificatori de coloane, iar valorile sunt denumirile coloanelor, afișate-n capul de tabel) și din care dai mai departe lista de coloane modificată.

function s03_add_custom_shipping_class_columns(array $columns) {
	$columns['s03_custom_value'] = 'Custom value';
	return $columns;
}

add_filter('woocommerce_shipping_classes_columns', 
	's03_add_custom_shipping_class_columns', 
	10, 
	1);

Destul de simplu, nu? Mai merită menționat faptul că identificatorul coloanei, așa cum a fost adăugat în $columns este folosit și ca clasă CSS pentru celula din tabel. Mai jos, extras din codul WooCommerce (woocommerce/includes/admin/settings/views/html-admin-page-shipping-classes.php):

foreach ( $shipping_class_columns as $class => $heading ) {
	echo '<td class="' . esc_attr( $class ) . '">';
	...
}

Dar, după cum se poate observa, este insuficient, pentru că avem nevoie să și redăm valorile pentru fiecare clasă de livrare. Avem deci, nevoie de un alt hook, care nu are o denumire fixă, ci una compusă din prefixul woocommerce_shipping_classes_column_ și identificatorul fiecărei coloane care nu face parte din cele standard (woocommerce_shipping_classes_column_[identificator_coloana]).

Valoarea care se dorește afișată nu este returnată din funcție ci este redată direct (ex. echo). În cazul nostru:

function s03_render_shipping_class_column_custom_value() {
	echo '<div class="view">{{ data.custom_value }}</div>';
}

add_action('woocommerce_shipping_classes_column_s03_custom_value', 
	's03_render_shipping_class_column_custom_value', 
	10);

Un creier alimentat cu suficient de multă cafea va fi observat deja că nu este transmis niciun parametru către acest hook (spre deosebire de alte hook-uri similare în WordPress și WooCommerce care primesc, spre exemplu, înregistrarea curentă).

În cazul listei de clase de livrare, WooCommerce redă valorile folosind template-uri Backbone JS și un soi de data binding: Astfel, construcția {{ data.custom_value }} semnifică: din înregistrarea curentă (data) redă proprietatea custom_value.

Se ridică, așadar, întrebarea: cum facem ca proprietatea custom_value să fie adăugată pentru fiecare clasă de livrare? Sincer, mi-aș dori și eu să știu. O soluție ar fi (cu subtextul că afectează absolut toate locurile unde este nevoie de lista de clase de livrare) utilizarea filtrului woocommerce_get_shipping_classes, care primește ca parametru colecția curentă de clase de livrare (un array în care fiecare element este de tipul WP_Term) și din care dai mai departe o colecție modificată.

function s03_add_shipping_classes_custom_value(array $shpClasses) {
	foreach ($shpClasses as $sc) {
		/** @var WP_Term $sc */
		$sc->custom_value = 'Custom value for ' . $sc->slug;
	}
	return $shpClasses;
}

add_filter('woocommerce_get_shipping_classes', 
	's03_add_shipping_classes_custom_value', 
	10, 
	1);

S-ar putea să nu fie o soluție ideală, privind prin prisma faptului că se adaugă dinamic o proprietate nouă într-o clasă existentă, dar e mult mai bine decât dacă am schimba cu totul tipul fiecărui element din colecție (iar extinderea WP_Term nu este posibilă, căci este declarată drept final).

Așadar, toată suma de agerimi compusă până acum arată așa (am extras doar partea relevantă din tabelă):

Rezultatul primei etape

Rezultatul primei etape

Modificarea acțiunilor contextuale disponibile

Aici răspunsul la întrebare depinde simultan de cât de mult vreți să vă bateți capul și de cât sunteți plătiți, căci, pe de o parte, nu există vreo modalitate oficială (adică vreun hook sau vreo funcție, nici măcar vreo incantație) prin care să poată fi pusă-n operă.

Dar, îmi pare mie, am uitat să mă explic la ce mă refer mai exact prin acțiuni contextuale. Este vorba de opțiunile care apar pentru fiecare clasă de listare când te duci cu gherlanul peste ea (fiecare opțiune-i un link HTML ordinar cu un eveniment JavaScript atașat pe undeva):

Acțiunile contextuale

Acțiunile contextuale

Cu alte cuvinte, ne-am dori și noi să adăugam un link suplimentar acolo sau, la modul general, oricare alt element, cum ar fi și un simplu banner cu rol de informare (de genul Se rezolva, nu se poate, revino mai la noapte). După cum menționam mai sus, nu există o metodă oficială, deci trebuie improvizat, deci, mai departe, soluția este, prin definiție, fragilă.

Mai întâi, câteva considerente:

– vă aduceți aminte de la primul pas că lista este redată folosind template-uri BackboneJS;
– redarea se produce la un moment dat, nu știm exact când, căci nu există un eveniment declanșat anume-n JavaScript;
– fiindcă nu știm exact când, nu putem să pleznim un $(document).ready(…), pentru că nu va fi fost redată chiar în acel moment;
– se poate pune un timer – window.setTimeout(…) –  rezonabil de lung, dar… ce-nseamnă rezonabil de lung?

M-am gândit că poate ar fi bine să știu când exact este redat un rând din tabela de clase de livrare (deci când anume este atașat în DOM), astfel încât să pot reacționa punctual. Există o librărie pentru așa ceva, care face întreg procesul un pic mai neted.

Mai rămâne de stabilit ce selector anume să observăm, de preferat ceva ce ar fi sub controlul nostru. Păi dacă tot am adăugat o nouă coloană și dacă tot este redată de către WooCommerce cu identificatorul drept clasă CSS, putem monitoriza fix elementul cu acea clasă (s03_custom_value). Presupunând că am inclus fișierul .js necesar:

(function($) {
	"use strict";

	function _addCustomRowAction($parentRow, shippingClassId) {
		var linkHtml = [
			'<a href="#" ', 
					'class="s03_bau_link" ', 
					'data-id="' + shippingClassId + '">',
				'Fă bau',
			'</a>'
		].join('');
		
		$parentRow
			.find('.row-actions')
			.append(' | ' + linkHtml);
	}

	$.initialize('.s03_custom_value', function() {
		var $me = $(this);
		var $parentRow = $me
			.parent('tr');

		//Following check required, since css class 
		///	is also applied to heading cells
		var shippingClassId = parseInt($parentRow
			.attr('data-id'));

		if (!isNaN(shippingClassId)) {
			_addCustomRowAction($parentRow, shippingClassId);
		}
	});

	$(document).on('click', '.s03_bau_link', function() {
		alert('Bau (#id: ' + $(this).attr('data-id') + ')!');
	});
})(jQuery);

Prea multe nu sunt de explicat, dar, pentru a părea că articolul să pară mai util decât e (și eu mai doct decât sunt), menționez următoarele:

– apelul care ne interesează este și de la care pleacă totul este $.initialize;
– funcția înregistrată cu $.initialize este apelată pentru fiecare element creat conform selectorului dat (deci, dacă lista are K elemente, va fi declanșat de K+1 ori, deoarece WooCommerce adaugă clasa CSS și pe celula corespunzătoare capului de tabel);
– se pot face tot felul de artificii pentru a transporta date suplimentare, însă cel mai frecvent este nevoie de ID-ul clasei de livrare (pe care WooCommerce amabil îl adaugă pentru fiecare element tr din tabelă);
– evenimentele pentru link-urile adăugate sunt monitorizate separat, pentru a evita aberația cromatică numită onclick=”doarPentruPuturosi()”.

Iată și rezultatul obținut:

Fă bau

Fă bau