
🛠 Kuinka luoda widget-liitännäinen WordPressille: vaiheittainen opas
💡 Kuinka rakentaa WordPress-vimpainlisäosa
- Luo
my-widget-plugin-kansio hakemistoonwp-content/plugins/jamy-widget-plugin.php-tiedosto, jossa on lisäosan otsikkotiedot, ja aktivoi se sitten hallintapaneelissa - Määrittele luokka, joka laajentaa
WP_Widget-luokkaa ja korvaa metodit__construct(),form(),update()jawidget() - Rekisteröi vimpain
register_widget()-funktiollawidgets_init-koukussa, jotta WordPress näkee sen listalla form()-metodissa renderöi asetuskentät;update()-metodissa siivoa ja tallenna datasanitize_text_field()-funktion avullawidget()-metodissa tulosta sisältö julkiselle puolelle ja escapoi arvot funktioillaesc_html()jawp_kses_post()
Vaihe 1: lisäosan rungon luominen
WordPress-vimpain ei ole vain rivi functions.php-tiedostossa. Jos haluat asetusten säilyvän, hallintalomakkeen toimivan ja itse vimpaimen selviävän seuraavasta teemapäivityksestä, sinun on paketoitava se lisäosaksi. Tämä eristää koodin ja tekee vimpaimesta riippumattoman teemanvaihdoksista.
Aloita tyhjällä kansiolla. Siirry hakemistoon wp-content/plugins/ ja luo kansio nimeltä my-widget-plugin. Luo sen sisään tiedosto nimeltä my-widget-plugin.php. Tämä on tiedosto, jonka WordPress lukee ensimmäisenä aktivoinnin yhteydessä. Avaa tiedosto ja lisää vakio-otsikkotiedot lisäosalle:
1 <?php 2 /* 3 Plugin Name: My Widget Plugin 4 Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/ 5 Description: Adds a customizable widget with text, textarea, checkbox, and dropdown. 6 Version: 1.0 7 Author: AJ Clarke 8 Author URI: https://www.wpexplorer.com/ 9 License: GPL2 10 */
Tallenna tiedosto. Siirry nyt WordPressin hallintapaneeliin → Lisäosat. Jos lisäosa näkyy listalla, runko on valmis. Klikkaa "Ota käyttöön". Se ei tee vielä mitään hyödyllistä; olemme vain ilmoittaneet sen olemassaolosta. Mutta WordPress tietää jo, että tämä lisäosa on olemassa ja on valmis suorittamaan sen koodin. Tämä on tärkeä periaate: ensin rekisteröinti, sitten logiikka.
Miten lisäosa toimii sisäisesti, mitkä koukut laukeavat aktivoinnissa ja miten WordPress paikantaa tiedostosi, käsitellään kaikki yllä olevalla videolla. Siirrytään nyt kiinnostavimpaan osaan: vimpainluokkaan.
Vaihe 2: vimpaimen rekisteröinti WP_Widgetin avulla
WordPress tarjoaa sisäänrakennetun luokan nimeltä WP_Widget; se on ollut osa ydintä versiosta 2.8 lähtien ja on edelleen kaikkien mukautettujen vimpainten perusta. Sinun ei tarvitse kirjoittaa tallennuslogiikkaa, kenttien luontia tai rekisteröintiä tyhjästä: laajenna vain luokkaa ja korvaa neljä metodia.
Lisää tämä koodi my-widget-plugin.php-tiedostoosi heti lisäosan otsikkotietojen jälkeen, ennen sulkevaa ?>-tagia:
1 // Widget class 2 class My_Custom_Widget extends WP_Widget { 3 4 public function __construct() { 5 parent::__construct( 6 'my_custom_widget', 7 __( 'My Custom Widget', 'text_domain' ), 8 array( 9 'customize_selective_refresh' => true, 10 ) 11 ); 12 } 13 14 public function form( $instance ) { 15 /* ... admin form ... */ 16 } 17 18 public function update( $new_instance, $old_instance ) { 19 /* ... saving settings ... */ 20 } 21 22 public function widget( $args, $instance ) { 23 /* ... frontend output ... */ 24 } 25 } 26 27 // Widget registration 28 function my_register_custom_widget() { 29 register_widget( 'My_Custom_Widget' ); 30 } 31 add_action( 'widgets_init', 'my_register_custom_widget' );
Puretaan, mitä tässä tapahtuu. Luokka My_Custom_Widget laajentaa WP_Widget-luokkaa, mikä antaa sinulle valmiit metodit, kuten get_field_id() ja get_field_name(), lomakekenttien attribuuttien luomiseen. Konstruktorissa välitämme kolme asiaa yläluokalle: yksilöllisen vimpaintunnisteen (pienet latinalaiset kirjaimet, ei välilyöntejä, my_custom_widget), sen ihmisluettavan nimen (__()-funktio tekee siitä käännettävän) ja asetustaulukon. Parametri customize_selective_refresh => true sallii vimpaimen päivittyä Mukauttajassa ilman koko sivun uudelleenlatausta, pieni yksityiskohta, joka säästää paljon turhautumista asennuksen aikana.
Funktio my_register_custom_widget() kutsuu register_widget()-funktiota widgets_init-koukussa. Näin WordPress saa tietää vimpaimestasi. Ilman tätä riviä hallintapaneelissa ei näy mitään.
Täytetään nyt form(), update() ja widget()-metodit oikealla logiikalla.
Vaihe 3: vimpainlomakkeen luominen hallintapaneelissa
Lomake on se, minkä ylläpitäjä näkee raahatessaan vimpaimen sivupalkkiin. Se koostuu kentistä: tekstikentistä, pudotusvalikoista, valintaruuduista. Jokaisen kentän on pystyttävä tallentamaan arvonsa ja näyttämään nykyinen arvo, kun se avataan uudelleen.
3.1. Form()-funktio ja syötekentät
Lisää tämä koodi luokkasi form()-metodiin. Se luo viisi kenttää: otsikon, tekstikentän, tekstialueen, valintaruudun ja pudotusvalikon.
1 public function form( $instance ) { 2 3 $defaults = array( 4 'title' => '', 5 'text' => '', 6 'textarea' => '', 7 'checkbox' => '', 8 'select' => '', 9 ); 10 11 $args = wp_parse_args( (array) $instance, $defaults ); 12 $title = $args['title']; 13 $text = $args['text']; 14 $textarea = $args['textarea']; 15 $checkbox = $args['checkbox']; 16 $select = $args['select']; 17 ?> 18 19 <p> 20 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"> 21 <?php _e( 'Widget Title', 'text_domain' ); ?> 22 </label> 23 <input class="widefat" 24 id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" 25 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" 26 type="text" 27 value="<?php echo esc_attr( $title ); ?>" /> 28 </p> 29 30 <p> 31 <label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"> 32 <?php _e( 'Text:', 'text_domain' ); ?> 33 </label> 34 <input class="widefat" 35 id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" 36 name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>" 37 type="text" 38 value="<?php echo esc_attr( $text ); ?>" /> 39 </p> 40 41 <p> 42 <label for="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>"> 43 <?php _e( 'Textarea:', 'text_domain' ); ?> 44 </label> 45 <textarea class="widefat" 46 id="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>" 47 name="<?php echo esc_attr( $this->get_field_name( 'textarea' ) ); ?>"><?php 48 echo wp_kses_post( $textarea ); 49 ?></textarea> 50 </p> 51 52 <p> 53 <input id="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>" 54 name="<?php echo esc_attr( $this->get_field_name( 'checkbox' ) ); ?>" 55 type="checkbox" 56 value="1" 57 <?php checked( '1', $checkbox ); ?> /> 58 <label for="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>"> 59 <?php _e( 'Show additional block', 'text_domain' ); ?> 60 </label> 61 </p> 62 63 <p> 64 <label for="<?php echo $this->get_field_id( 'select' ); ?>"> 65 <?php _e( 'Display variant', 'text_domain' ); ?> 66 </label> 67 <select name="<?php echo $this->get_field_name( 'select' ); ?>" 68 id="<?php echo $this->get_field_id( 'select' ); ?>" 69 class="widefat"> 70 <?php 71 $options = array( 72 '' => __( '— Select —', 'text_domain' ), 73 'option_1' => __( 'Option 1', 'text_domain' ), 74 'option_2' => __( 'Option 2', 'text_domain' ), 75 'option_3' => __( 'Option 3', 'text_domain' ), 76 ); 77 foreach ( $options as $key => $name ) { 78 printf( 79 '<option value="%s" %s>%s</option>', 80 esc_attr( $key ), 81 selected( $select, $key, false ), 82 esc_html( $name ) 83 ); 84 } 85 ?> 86 </select> 87 </p> 88 89 <?php }
Kiinnitä huomiota kahteen asiaan. Ensinnäkin korvasimme vanhentuneen extract()-funktion suoralla taulukon käsittelyllä. extract() on jo pitkään ollut poissuljettu WordPressin koodausstandardien ulkopuolelle; se luo muuttujia taulukon avainten nimillä, mikä on turvatonta ja vaikeuttaa virheenkorjausta. Toiseksi jokainen tulostearvo ajetaan funktioiden esc_attr(), wp_kses_post() tai esc_html() läpi. Tämä ei ole vainoharhaisuutta: tietokannasta tuleva data voi olla peräisin mistä tahansa, ja puhdistus on pakollista.
3.2. Update()-funktio tallentamista varten
update()-metodia kutsutaan, kun "Tallenna"-painiketta klikataan vimpainlomakkeessa. Sen tehtävänä on validoida jokainen kenttä ja palauttaa puhdistettu taulukko tietokantaan kirjoittamista varten.
1 public function update( $new_instance, $old_instance ) { 2 $instance = $old_instance; 3 4 $instance['title'] = isset( $new_instance['title'] ) 5 ? sanitize_text_field( $new_instance['title'] ) : ''; 6 $instance['text'] = isset( $new_instance['text'] ) 7 ? sanitize_text_field( $new_instance['text'] ) : ''; 8 $instance['textarea'] = isset( $new_instance['textarea'] ) 9 ? wp_kses_post( $new_instance['textarea'] ) : ''; 10 $instance['checkbox'] = isset( $new_instance['checkbox'] ) ? 1 : false; 11 $instance['select'] = isset( $new_instance['select'] ) 12 ? sanitize_text_field( $new_instance['select'] ) : ''; 13 14 return $instance; 15 }
Tässä käytämme sanitize_text_field()-funktiota wp_strip_all_tags()-funktion sijaan, koska se myös normalisoi tyhjät merkit, poistaa näkymättömät ohjausmerkit ja muuntaa merkkijonon turvalliseksi UTF-8:ksi. Tämä on perusteellisempi puhdistus. wp_strip_all_tags() jättää "raa'an" tekstin kaikkine tyhjien merkkien artefakteineen. Tekstimuotoisille lomakekentille valitse aina sanitize_text_field().
Tekstialue säilyttää wp_kses_post()-funktion: se sallii perus-HTML:n (linkit, lihavoidun tekstin, listat), mutta poistaa skriptit. Valintaruutu palauttaa arvon 1 tai false, mikä on luettavaa ja yksiselitteistä tietokannassa.
Vaihe 4: vimpaimen renderöinti julkisella puolella
widget()-funktio on se, minkä kävijä näkee. Se vastaanottaa kaksi parametria: $args (vimpaimen kääre, tagit ennen ja jälkeen otsikon ja sivupalkin) ja $instance (tallennetut asetukset tälle tietylle instanssille).
1 public function widget( $args, $instance ) { 2 3 $title = isset( $instance['title'] ) 4 ? apply_filters( 'widget_title', $instance['title'] ) : ''; 5 $text = isset( $instance['text'] ) ? $instance['text'] : ''; 6 $textarea = isset( $instance['textarea'] ) ? $instance['textarea'] : ''; 7 $select = isset( $instance['select'] ) ? $instance['select'] : ''; 8 $checkbox = ! empty( $instance['checkbox'] ) ? $instance['checkbox'] : false; 9 10 echo $args['before_widget']; 11 12 echo '<div class="widget-text wp_widget_plugin_box">'; 13 14 if ( $title ) { 15 echo $args['before_title'] . esc_html( $title ) . $args['after_title']; 16 } 17 18 if ( $text ) { 19 echo '<p>' . esc_html( $text ) . '</p>'; 20 } 21 22 if ( $textarea ) { 23 echo '<div class="widget-textarea">' . wp_kses_post( $textarea ) . '</div>'; 24 } 25 26 if ( $select ) { 27 echo '<p class="widget-select">' . esc_html( $select ) . '</p>'; 28 } 29 30 if ( $checkbox ) { 31 echo '<p class="widget-checkbox-result">' . esc_html__( 'Additional block activated', 'text_domain' ) . '</p>'; 32 } 33 34 echo '</div>'; 35 36 echo $args['after_widget']; 37 }
Avainkohta tässä: korvasimme extract( $args )-kutsun suoralla käsittelyllä $args['before_widget'] kautta. Syy on sama: extract() on vanhentunut ja turvaton. Käärimme myös tulosteen esc_html()-funktioon aina, kun odotetaan pelkkää tekstiä (otsikko, tekstimerkkijono, valinnan arvo). Tekstialue renderöidään wp_kses_post()-funktion läpi, joten jos ylläpitäjä lisäsi linkin tai lihavoidun tekstin, ne säilyvät.
CSS-luokka wp_widget_plugin_box antaa sinun tyylitellä lohkon teeman tyylitiedostosta. Voit vapaasti nimetä sen uudelleen, varmista vain, että luokka on yksilöllinen eikä ole ristiriidassa teeman luokkien kanssa.
Täydellinen lisäosan koodi
Kootaan kaikki yhteen. Tässä on täydellinen my-widget-plugin.php-tiedosto, valmiina kopioitavaksi ja aktivoitavaksi:
1 <?php 2 /* 3 Plugin Name: My Widget Plugin 4 Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/ 5 Description: Adds a customizable widget with text, textarea, checkbox, and dropdown. 6 Version: 1.0 7 Author: AJ Clarke 8 Author URI: https://www.wpexplorer.com/ 9 License: GPL2 10 */ 11 12 class My_Custom_Widget extends WP_Widget { 13 14 public function __construct() { 15 parent::__construct( 16 'my_custom_widget', 17 __( 'My Custom Widget', 'text_domain' ), 18 array( 'customize_selective_refresh' => true ) 19 ); 20 } 21 22 public function form( $instance ) { 23 $defaults = array( 24 'title' => '', 'text' => '', 'textarea' => '', 25 'checkbox' => '', 'select' => '' 26 ); 27 $args = wp_parse_args( (array) $instance, $defaults ); 28 $title = $args['title']; 29 $text = $args['text']; 30 $textarea = $args['textarea']; 31 $checkbox = $args['checkbox']; 32 $select = $args['select']; 33 ?> 34 <p> 35 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"> 36 <?php _e( 'Widget Title', 'text_domain' ); ?> 37 </label> 38 <input class="widefat" 39 id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" 40 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" 41 type="text" value="<?php echo esc_attr( $title ); ?>" /> 42 </p> 43 <p> 44 <label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"> 45 <?php _e( 'Text:', 'text_domain' ); ?> 46 </label> 47 <input class="widefat" 48 id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" 49 name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>" 50 type="text" value="<?php echo esc_attr( $text ); ?>" /> 51 </p> 52 <p> 53 <label for="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>"> 54 <?php _e( 'Textarea:', 'text_domain' ); ?> 55 </label> 56 <textarea class="widefat" 57 id="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>" 58 name="<?php echo esc_attr( $this->get_field_name( 'textarea' ) ); ?>"><?php 59 echo wp_kses_post( $textarea ); 60 ?></textarea> 61 </p> 62 <p> 63 <input id="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>" 64 name="<?php echo esc_attr( $this->get_field_name( 'checkbox' ) ); ?>" 65 type="checkbox" value="1" <?php checked( '1', $checkbox ); ?> /> 66 <label for="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>"> 67 <?php _e( 'Show additional block', 'text_domain' ); ?> 68 </label> 69 </p> 70 <p> 71 <label for="<?php echo $this->get_field_id( 'select' ); ?>"> 72 <?php _e( 'Display variant', 'text_domain' ); ?> 73 </label> 74 <select name="<?php echo $this->get_field_name( 'select' ); ?>" 75 id="<?php echo $this->get_field_id( 'select' ); ?>" class="widefat"> 76 <?php 77 $options = array( 78 '' => __( '— Select —', 'text_domain' ), 79 'option_1' => __( 'Option 1', 'text_domain' ), 80 'option_2' => __( 'Option 2', 'text_domain' ), 81 'option_3' => __( 'Option 3', 'text_domain' ), 82 ); 83 foreach ( $options as $key => $name ) { 84 printf( 85 '<option value="%s" %s>%s</option>', 86 esc_attr( $key ), 87 selected( $select, $key, false ), 88 esc_html( $name ) 89 ); 90 } 91 ?> 92 </select> 93 </p> 94 <?php 95 } 96 97 public function update( $new_instance, $old_instance ) { 98 $instance = $old_instance; 99 $instance['title'] = isset( $new_instance['title'] ) 100 ? sanitize_text_field( $new_instance['title'] ) : ''; 101 $instance['text'] = isset( $new_instance['text'] ) 102 ? sanitize_text_field( $new_instance['text'] ) : ''; 103 $instance['textarea'] = isset( $new_instance['textarea'] ) 104 ? wp_kses_post( $new_instance['textarea'] ) : ''; 105 $instance['checkbox'] = isset( $new_instance['checkbox'] ) ? 1 : false; 106 $instance['select'] = isset( $new_instance['select'] ) 107 ? sanitize_text_field( $new_instance['select'] ) : ''; 108 return $instance; 109 } 110 111 public function widget( $args, $instance ) { 112 $title = isset( $instance['title'] ) 113 ? apply_filters( 'widget_title', $instance['title'] ) : ''; 114 $text = isset( $instance['text'] ) ? $instance['text'] : ''; 115 $textarea = isset( $instance['textarea'] ) ? $instance['textarea'] : ''; 116 $select = isset( $instance['select'] ) ? $instance['select'] : ''; 117 $checkbox = ! empty( $instance['checkbox'] ) ? $instance['checkbox'] : false; 118 119 echo $args['before_widget']; 120 echo '<div class="widget-text wp_widget_plugin_box">'; 121 122 if ( $title ) { 123 echo $args['before_title'] . esc_html( $title ) . $args['after_title']; 124 } 125 if ( $text ) { 126 echo '<p>' . esc_html( $text ) . '</p>'; 127 } 128 if ( $textarea ) { 129 echo '<div class="widget-textarea">' . wp_kses_post( $textarea ) . '</div>'; 130 } 131 if ( $select ) { 132 echo '<p class="widget-select">' . esc_html( $select ) . '</p>'; 133 } 134 if ( $checkbox ) { 135 echo '<p class="widget-checkbox-result">' 136 . esc_html__( 'Additional block activated', 'text_domain' ) . '</p>'; 137 } 138 139 echo '</div>'; 140 echo $args['after_widget']; 141 } 142 } 143 144 function my_register_custom_widget() { 145 register_widget( 'My_Custom_Widget' ); 146 } 147 add_action( 'widgets_init', 'my_register_custom_widget' );
Sijoita tämä tiedosto hakemistoon wp-content/plugins/my-widget-plugin/, aktivoi lisäosa ja raahaa vimpain mihin tahansa sivupalkkiin kohdasta Ulkoasu → Vimpaimet. Täytä kentät, tallenna ja tarkista sivustosi.
Valmis koodi on saatavilla myös GitHubissa: wpexplorer/my-widget-plugin, jossa voit verrata sitä alkuperäiseen vuoden 2017 versioon ja nähdä tarkalleen, mitä muutimme.
⁉️🤔 Usein kysytyt kysymykset
Miksi laittaa vimpain lisäosaan, kun koodin voi vain lisätä teeman functions.php-tiedostoon?
Koodi functions.php-tiedostossa on sidottu aktiiviseen teemaan. Vaihda teemaa, ja vimpain katoaa. Lisäosa toimii teemasta riippumattomasti. Lisäksi lisäosan voi aktivoida valikoivasti eri sivustoilla, kun taas teemakoodia ei voi. Jos vimpain ratkaisee liiketoimintatarpeen (esimerkiksi tilauslomakkeen näyttäminen tietyllä asettelulla), se kuuluu lisäosaan.
Miksi sanitize_text_field() on parempi kuin wp_strip_all_tags()?
sanitize_text_field()ei ainoastaan poista HTML-tageja; se myös normalisoi tyhjät merkit, poistaa näkymättömät ohjausmerkit ja muuntaa merkkijonon UTF-8:ksi. Tämä on perusteellisempi puhdistus.wp_strip_all_tags()jättää "raa'an" tekstin kaikkine tyhjien merkkien artefakteineen. Tekstimuotoisille lomakekentille valitse ainasanitize_text_field().
Miksi korvasitte extract()-funktion suoralla taulukon käsittelyllä?
extract()-funktio on poistettu WordPressin koodausstandardeista versiosta 4.3 lähtien. Se luo muuttujia taulukon avainten nimillä paikallisessa näkyvyysalueessa; jos avain vastaa olemassa olevaa muuttujaa, ylikirjoitat sen ja päädyt vaikeasti löydettävään bugiin. Suora käsittely, kuten$args['before_widget'], on luettavaa ja turvallista.
Tarvitseeko minun tukea lohkovimpaimia (Gutenberg)?
Klassinen
WP_Widgettoimii lohkopohjaisten sivupalkkien kanssa taaksepäin yhteensopivuuden kautta: WordPress käärii sen automaattisesti Legacy Widget -lohkoon. Tämä riittää useimpiin skenaarioihin. Jos haluat luoda aidosti natiiveja lohkoja, katso Block Editor Handbook, jossa on erillinen rajapinta. MuttaWP_Widget-luokasta aloittaminen on helpompaa: koodi on lyhyempää, virheenkorjaus nopeampaa, ja se toimii kaikissa WordPress-versioissa ilman ylimääräisiä yhteensopivuuslisäosia.
Kuinka korjaan vian vimpaimessa, joka ei näy hallintapaneelissa?
Tarkista kolme asiaa. Ensinnäkin: laukesiko
widgets_init-koukku? Lisääerror_log( 'Widget registered' )rekisteröintifunktioon ja tarkista lokit. Toiseksi: onko konstruktorissa kriittinen virhe? OtaWP_DEBUGkäyttöön wp-config.php-tiedostossa. Kolmanneksi: vastaako luokan nimiregister_widget()-kutsussaWP_Widget-luokkaa laajentavan luokan nimeä? Yksi kirjoitusvirhe, eikä vimpain tule näkyviin.
Mitä seuraavaksi: mallipohjasta omaan vimpaimeesi
Olemme rakentaneet toimivan lisäosan, jossa on viisi kenttätyyppiä. Tämä ei ole valmis tuote; se on runko. Ota se perustaksesi ja mukauta se tarpeisiisi. Tarvitsetko tilauslomakevimpaimen? Korvaa tekstikentät sähköposti- ja nimikentillä ja lisää postituspalvelun API-kutsu widget()-metodin sisään. Tarvitsetko lohkon, jossa on tarjouksia ja bannereita? Lataa kuvia medialataajan kautta ja renderöi ne omassa merkinnässäsi. Mekaniikka on aina sama: form() piirtää kentät, update() tallentaa ne, widget() renderöi tulosteen.
Aloita pienestä: kopioi yllä oleva täydellinen koodi, aktivoi se testisivustolla ja kokeile asetuksia. Kun ymmärrät, miten data kulkee lomakkeesta julkiselle puolelle, ala lisätä omia kenttiäsi. Ja jos vimpain kaatuu valkoiseen ruutuun, palaa vaiheeseen 2 ja tarkista konstruktori.



