În mod implicit, WooCommerce-ul nu impune un prag minim sub care o comandă să nu se poată realiza. Pe de altă parte, mi se pare nejustificată ideea de-a aduce în WP un plugin pentru un asemenea mărunțiș.
Practic, eu având în magazinul de bijuterii de bărbați produse sub 20 lei gen cercei de inox, am avut de multe ori surpriza unor comenzi de 7 lei. Prin urmare, am hotărât să caut o modalitate de-a inhiba aceste comenzi foarte mici. Am găsit astfel o soluție de-a adăuga în functions.php din tema de WooCommerce public_html/xxx/wp-content/themes/nume_tema/functions.php un action hook:
add_action( 'woocommerce_check_cart_items', 'required_min_cart_subtotal_amount' );
function required_min_cart_subtotal_amount() {
// hook-ul este executat doar in Cart si Checkout
if( is_cart() || is_checkout() ) {
// suma minima pentru care se poate finaliza comanda
$min_total = 20;
// Total (before taxes and shipping charges)
$total = WC()->cart->subtotal;
if( $total <= $min_total ) {
wc_add_notice( '<strong>' . sprintf( __("Pentru finalizarea comenzii, este necesara o suma minima in valoare de %s"), wc_price($min_total) ) . '<strong>', 'error' );
}
}
}
Delegation pattern reprezintă un mijloc elegant prin care se realiza comunicarea între instanțe, o situație foarte des întâlnită fiind aceea a transferului de informații între diferite viewcontroller-e. Pentru cei din Apple@DMT, situația este des întâlnită.
Pentru început să considerăm clasele următoare:
class LoginAPIService {
}
class LoginViewController {
private let callLoginService = LoginAPIService()
}
În acest moment avem două clase LoginAPIService – ce pune la dispoziție servicii specifice de login și LoginViewController – clasă care va acoperi un View Controller desenat în Storyboard, de exemplu.
Putem transmite mesaje către LoginAPIService foarte lejer prin instanța privată callLoginService. E o problemă, în schimb, transmiterea de informații de la LoginAPIService la LoginViewController.
Cineva ar putea spune: de ce să nu facem la fel, o instanță privată LoginViewController în LoginAPIService? Păi dacă LoginAPIService este un serviciu oferit ViewController-elor, e nevoie de câte o instanță a fiecărui ViewController în LoginAPIService. E puțin peste mână o astfel de abordare și, prin urmare, trebuie să găsim o soluție pentru această problemă a transmisiei de mesaje de la LoginAPIService către LoginViewController.
Delegation pattern reprezintă soluția pentru astfel de probleme. Apple ne ajută să implementăm astfel de pattern-uri prin intermediul protocoalelor.
Protocolul, pe scurt, reprezintă o clasă abstractă care, atunci când cineva aderă la el, trebuie obligatoriu implementat. Altfel spus, daca ader la un protocol, trebuie sa implementez toate metodele declarate de către acesta.
protocol LoginAPIDelegate: class {
func didRetrieveLoginUserDetailsFromServer(_ user: UserModel)
}
Cum folosim acest protocol în continuare?
class LoginAPIService {
weak var delegate: LoginAPIDelegate?
}
Ce este acest delegate declarat ca și proprietate în interiorul LoginAPIService?
Este o instanță de LoginAPIService care mă asigură de faptul că toate metodele definite în interiorul protocolului vor fi implementate de către această proprietate.
Cine anume “va dori” să fie de fapt acest delegat? Dacă vom extinde clasa LoginViewController astfel încât ea să fie conformă cu protocolul nostru LoginAPIDelegate, atunci acea proprietate delegate va putea fi chiar LoginViewController.
extension LoginViewController: LoginAPIDelegate {
func didRetrieveLoginUserDetailsFromServer(_ user: UserModel)
{
// prelucram datele care au fost furnizate de catre serviciu
}
}
Ceea ce este cel mai interesant, acum urmează. În interiorul clasei LoginViewController specificăm că aceasta este, de fapt cea care-și atribuie calitatea de delegat pentru instanța callLoginService de tip LoginAPIService. Astfel, callLoginService va putea comunica mesaje de la LoginViewController ->LoginAPIService, iar callLoginService.delegate va comunica mesaje de la LoginAPIService -> LoginViewController.
Comunicare LoginAPIService -> LoginViewController
Pentru a realiza transportul de mesaje de la LoginAPIService către LoginViewController vom face apel de didRetrieveLoginUserDetailsFromServer(_: ). Astfel, cine și-a asumat rolul de delegat (deci e conform cu protocolul LoginAPIDelegate), va executa apelul.
class LoginAPIService {
weak var delegate: LoginAPIDelegate?
func connectToServer() {
// facem request-ul de login și ambalăm datele în userModel
delegate?.didRetrieveLoginUserDetailsFromServer(userModel)
}
}
Astfel, am reușit să rezolvăm problema transferului de mesaje între cele două entități.
Principiul Inversării Dependenței sau Dependency Inversion Principle afirmă faptul că modulele de pe nivelul ierarhic superior trebuie să fie decuplate de cele de pe nivelurile ierarhice inferioare. Această decuplare între modulele superioare și cele de nivel inferior se va realiza prin introducerea unui nivel tampon de abstractizare între clasele care formează nivelul ierarhic superior și cele care formează nivelurile ierarhice inferioare.
În plus principiul consideră că abstractizarea nu trebuie să depindă de detalii, ci detaliile trebuie sa depindă de abstractizare. Acest principiu este foarte important pentru reutilizarea componentelor software. De asemenea, aplicarea corectă a acestui principiu face ca întreținerea codului să fie mult mai ușor de realizat.
Imediat când citim o astfel de definiție, gândul ne duce la protocoale și la ceea ce înseamnă abstractizarea în SWIFT.
Să intrăm mai mult în detalii.
Avem cazul concret în cadrul aplicației pe care-o dezvoltăm împreună cu elevii din grupul nostru Apple App@DMT.
Construcției ViewController-ul dedicat afisării detaliilor pentru ofertă -> aici
Avem clasele OfferDetailsTableViewCell1 și OfferDetailsTableViewCell2
import UIKit
class OfferDetailsTableViewCell2: UITableViewCell {
static let ReuseIdentifier = String(describing: OfferDetailsTableViewCell2.self)
static let NibName = String(describing: OfferDetailsTableViewCell2.self)
@IBOutlet weak var descriptionLabel: UILabel!
}
și
class OfferDetailsTableViewCell1: UITableViewCell {
static let ReuseIdentifier = String(describing: OfferDetailsTableViewCell1.self)
static let NibName = String(describing: OfferDetailsTableViewCell1.self)
@IBOutlet weak var headerLabel: UILabel!
@IBOutlet weak var dateLabel: UILabel!
@IBOutlet weak var priceLabel: UILabel!
}
clase acoperitoare pentru doua XIB-uri de UITableViewCell.
Ceea ce m-a făcut să scriu acest articol este modul în care este definit delegatul tableView(tableView:, cellForRowAt indexPath:)
Vedem că în fiecare case din switch se vor instanția cell-uri și, de asemenea, se vor returna aceste cell-uri.
Orice modificare facută în clasa inferioară, OfferDetailsTableViewCell2 sau OfferDetailsTableViewCell1, trebuie de asemenea sa se opereze în clasele de nivel superior, în cazul nostru OfferDtailsViewController care implementează UITableViewDelegate și UITableViewDataSource.
Cum rezolvăm această cuplare?
1. Introducem un protocol în care declarăm o functie config(withData:)
Prin intermediul protocolului OffersTableViewCellProtocol am reușit să punem în aplicare principiul inversării dependenței astfel încăt decuplarea dintre cele nivele să fie funcțională.
Atunci când adăugăm o clauză @objc unei metode în Swift, aceasta din urmă devine accesibilă din Objective C. Bun, teoretic sună foarte riguros, dar dacă eu nu am nici un fel de cod Objective C în aplicație? Ar trebui să nu am nici o problemă, nu?
Swift 4 folosește încă în fundal foarte multe protocoale ObjectiveC.
Ar trebui de fiecare dată când implementăm metodele din UITableViewDataSource să le specificăm fiecăreia un @objc. Swift 4 are grijă și nu ne forțează în acest sens.
Bun, și atunci unde și de ce am nevoie de @objc în cod Swift 4?
Cel mai bun exemplu în acest sens este o metodă care folosește un #selector. Avem nevoie să executăm o metodă loadRecordings după un delay de 0.5 secunde în thread-ul curent.
Deci selectorul din perform va trebui sa fie vizibil în runtime direct în ObjectiveC. Deci ar trebui ca metoda loadRecordings să aibe specificată o clauză @objc.
Deci prin deducție inversă, am ajuns la concluzia că trebuie să definesc astfel metoda loadRecordings
@objc func loadRecordings() {
.....
}
Lipsa acestei clauze @objc îmi va genera o eroare la compilare așa cum se vede și mai jos:
Vorbeam acum ceva timp despre closureîntr-un context mai didactic. De curând am avut nevoie pentru a oferi o explicație despre closure într-un context ceva mai amplu.
Cadrul în care se desfășoară activitatea noastră este următorul:
Sunt într-un ViewController – VC1 – și apăsând pe un Button (pasul 1) ajungem într-un al doilea ViewControoler – VC2. În acest al doilea VC trebuie sa completăm câteva detalii să zicem – nume și email și să le aruncăm peste rețea (pasul 2). Serverul procesează ceea ce i-am trimis noi și ne răspunde (pasul 3) cu un mesaj de “succes“.
În momentul în primim mesajul de “succes” deschidem un UIAlertController (pasul 4) care imi arată mesajul primit. La apăsarea butonului de OK (pasul 5) trebuie sa eliberăm VC2 și să ne întoarcem în VC1.
Ce problemă avem la pasul 5? Operațiunea de eliberare a VC2 trebuie făcută în momentul în care se face click pe OK, nu în momentul în care se afisează UIAlertController-ul.
Funcția care face afișarea UIAlertController-ului este definită într-un Alertmanager astfel:
Ce se observă? Am definit în okAction un UIAlertAction cu un handler care se va “activa” atunci când se apasă pe okAction. Acțiunea din handler, funcția anonimă prezentă acolo va fi implementată în momentul în care această funcție este apelată.
Prin urmare, avem un minunat closure în momentul în care vom apela funcția showGenericDialog: