Reaktif WordPress Eklentileri Oluşturma – Bölüm 3 – Elm

Reaktif WordPress Eklentileri Oluşturma serisinin bu son bölümünde, WordPress cron'unda ne olduğunu gösteren küçük WordPress pano widget'ı olan WP Cron Pixie'nin ön ucunu yazmak için Elm'i kullanmayı keşfedeceğim.
Neden Karaağaç?
Sanırım, bu makale dizisinin var olmasının tek sebebinin Elm hakkında yazabilmem olduğunu itiraf etmeliyim!
Elm, JavaScript'i derleyen işlevsel bir dildir. Elm, genel amaçlı bir dil olarak tasarlanmıştır, JavaScript yalnızca ilk derleme hedefidir. Bu nedenle, çok fazla “JavaScript geliştirilmiş” olan CoffeeScript veya TypeScript'ten çok zorunlu Dart programlama diline daha yakındır.
Bir önceki makalemde Vue.js'nin ne kadar harika olduğundan bahsetmiş olsam da, bu hala JavaScript için bir çerçeve ve JavaScript ile uzun yıllar çalışsam da, zevk aldığım bir dil olmaktan çok uzak olduğunu tamamen kabul ediyorum. ön uç web kodunu yazma.
Öte yandan, Elm'i kod yazmaktan keyif alıyorum. Elm Kılavuzundan ödünç aldığım bazı nedenler:
- Pratikte çalışma zamanı hatası yok. Sıfır yok. No undefined bir fonksiyon değildir.
- Özellikleri daha hızlı eklemenize yardımcı olan kolay hata mesajları.
- Uygulamanız büyüdükçe iyi mimarisini koruyan iyi tasarlanmış kod.
- Tüm Elm paketleri için otomatik olarak zorunlu semantik sürüm oluşturma.
Elm, reaktif stil geliştirmenin erken bir savunucusudur ve genellikle React uygulamalarının durumunu işlemek için kullanılan popüler Redux kitaplığının etkilerinden biri olarak kabul edilir.
Elm, 2012 yılında Evan Czaplicki tarafından tezi olarak tasarlandığından beri olgunlaştı. Evan'ın liderliğindeki proje, The Elm Architecture adlı uygulamaları yapılandırmanın bir yolunu buldu. Tamamını okumanıza izin vereceğim, ancak temel bilgiler, durum verilerini tutan bir Model Model bir update işlevine ve mevcut Model dayalı olarak uygulamayı düzenleyen bir view işlevine sahip olmanızdır. Tarayıcıda her şeyi hızlı tutmak için bir Sanal DOM uygulamasının verimli kullanımı ile her şey Elm çalışma zamanı tarafından birbirine bağlanır.
Daha ileri gitmeden önce, genel olarak Elm ve işlevsel kod yazmaya gelince yeni başlayan biri olduğumu belirtmek isterim. Bu yüzden deneyimli Elm ve işlevsel dil konusunda bilgili geliştiriciler, lütfen daha deyimsel Elm'de yapılabilecek her şeyi mazur görün ve yorumlarda ipuçlarını ve püf noktalarını paylaşmaktan çekinmeyin. Daha iyi Elm yazmayı öğrenmek için can atıyorum.
Plan
Şu anda WP Cron Pixie, tek bir build.js olarak derlenen birden çok *.vue dosyası kullanıyor. Tüm bileşen dosyalarından kurtulacağız ve uygulamayı önyüklemek için tek bir CronPixie.elm dosyasıyla tek bir main.js dosyasıyla başlayacağız ve daha verimli teslimat için ikisini tekrar tek bir build.js dosyasında derleyeceğiz. .
Kurulum
İlk önce Elm'i kurmamız gerekecek. Şahsen bugünlerde Mac'ime çoğu komut satırı yazılımını yüklemek için Homebrew kullanıyorum, bu yüzden benim için basit:
$ brew install elm
Ancak, Mac veya Windows yükleyicileri aracılığıyla veya Linux dahil olmak üzere çalıştığı her yerde npm aracılığıyla kurulum yapabilirsiniz.
Elm derleyicisinin çalıştığından ve çalıştığından emin olmak için bir "Merhaba Dünya" uygulaması oluşturalım. Aşağıdakileri bir HelloWorld.elm dosyasına ekleyin.
module HelloWorld exposing (..) import Html exposing (text) main = text "Hello World"
Daha sonra ile derleyin…
$ elm package install Some new packages are needed. Here is the upgrade plan. Install: elm-lang/core 4.0.5 elm-lang/html 1.1.0 elm-lang/virtual-dom 1.1.1 Do you approve of this plan? [Y/n] Starting downloads... ● elm-lang/html 1.1.0 ● elm-lang/virtual-dom 1.1.1 ● elm-lang/core 4.0.5 Packages configured successfully! $ elm make HelloWorld.elm Success! Compiled 31 modules. Successfully generated index.html
Gördüğünüz gibi, bu yeni proje, bu proje için bazı çekirdek kitaplıkların indirilmesini ve kurulmasını gerektiriyordu. elm make bu projede sonraki kullanımı, bu (çok küçük) kitaplıkları indirmeye gerek duymaz.
Artık, beklediğinizden çok daha büyük bir index.html sahip olacaksınız. Tam Elm çalışma zamanı kitaplığı ve uygulama kodu ile eksiksiz bir uygulama olduğu için büyüktür. Bir tarayıcıda açın ve “Merhaba Dünya” yı göreceksiniz.
Doğrulanmış çalışan bir Elm derleyicisi ile artık ön uç olarak Vue.js yerine Elm'i kullanmak için WP Cron Pixie'yi güncellemek için ihtiyacımız olan her şeye sahibiz.
Arka Uç Değişiklikleri
HTML'mizin büyük kısmı için Elm'in kendi HTML kitaplığını kullanacağımızdan, arka uçtan çıktı almamız gereken tek şey PHP, uygulamamızı kapatmak için bir div . Bu nedenle class-cron-pixie.php içindeki dashboard_widget_content() fonksiyonunu güncellememiz gerekiyor.
/** * Provides the initial content for the widget. */ public function dashboard_widget_content() { ?> <!-- Main content --> <div></div> <?php }
Bu, Vue.js sürümümüzün HTML'sinden biraz daha basit ve Backbone.js HTML'sinden çok daha basit.
Ayrıca, CSS'mizi artık düzenimiz ve kodumuzla karıştırmayacağımız için, Vue.js güncellemesinden önce sahip olduğumuz main.css dosyasını geri koyacağız. Bu, aşağıdakileri enqueue_scripts işlevine ekleyerek onu PHP arka ucundan tekrar kuyruğa almamız gerektiği anlamına gelir.
wp_enqueue_style( $script_handle, plugin_dir_url( $this->plugin_meta['file'] ) . 'css/main.css', array(), $this->plugin_meta['version'] );
Arka uçta yapmamız gereken tek bir değişiklik daha var, bu tamamen benim Elm ile olan göreceli deneyimsizliğime bağlı olabilir. Anlayabildiğim kadarıyla, Elm'den bir PHP arka ucuna iç içe geçmiş verileri kolayca POST yapamazsınız. En kolay çözüm, model verilerimizin yaptığı gibi çocukları olan yapıları JSON kodlamaktır. Bu, "şimdi" işini çalıştırmak için gönderilen bir Olayı işleyen ajax_events işlevinde, $_POST değişkeninden JSON kodunu çözmemiz gerektiği anlamına gelir.
$event = json_decode( stripcslashes( $_POST['model'] ), true );
Arka uç için bu kadar, hadi biraz JavaScript yazalım!
Ön uç JavaScript'i
Ne? Önyüzümüzü JavaScript yerine Elm'de yazdığımızı sanıyordum?
Elbette, tüm ön ucu Elm'de yazıyor olsaydık, sadece elm make index.html doğru yere yerleştirebilirdik. Ancak, bir Elm uygulamasını zaten var olan bir HTML ve JavaScript yüklü ön uca entegre edeceğiz, bu nedenle Elm uygulamamızı doğru yere eklemek için biraz JavaScript'e ihtiyacımız var. main.js , Elm uygulamamızın ön yüklemesini yapmak için ihtiyacımız olan temel bilgilere kadar indirelim.
var Elm = require( './CronPixie' ); var $mountPoint = document.getElementById( 'cron-pixie-main' ); var app = Elm.CronPixie.embed( $mountPoint, { strings: CronPixie.strings, nonce: CronPixie.nonce, timer_period: CronPixie.timer_period, schedules: CronPixie.data.schedules } );
Tüm JavaScript'imiz bu, artık yazmayacağız, yay!
Gördüğünüz gibi, uygulamamızın ana require( './CronPixie' ) ifadesiyle çekiyor, yani Elm kodumuzu main.js'nin yanında bir CronPixie.js dosyasına main.js . Buna birazdan geleceğiz.
Aksi takdirde, JS "cron-pixie-main" kimliğiyle oluşturduğumuz div'i buluyor ve ardından bunu gömülü Elm uygulamamız için bağlama noktası olarak kullanıyor.
Arka uç PHP'miz ayrıca, Elm uygulamamızda kullanmak istediğimiz çevrilebilir dizeler, güvenlik nonce (bir kez kullanılan sayı), veri için varsayılan yoklama aralığı ve aşağıdakileri içeren bir başlangıç veri seti gibi bazı yararlı JavaScript değişkenlerini açığa çıkarıyor. cron programları. Onları bir nesne olarak Elm programına geçiriyoruz.
ön uç karaağaç
Ön uç Elm kodunun tamamı CronPixie.elm . Bu nispeten küçük bir uygulama olduğundan, onu birden fazla dosyaya bölmeye gerek yoktu, ancak Elm Architecture, işlevlerin yeniden kullanılmasına yardımcı olabilecek sağlam bir modül sistemine sahiptir.
İlk adım
Yukarıda yazdığımız HelloWorld uygulamasına benzer bir şeyle başladım.
module CronPixie exposing (..) import Html exposing (text) main = text "Awesomeness Goes Here!"
Modülü benzersiz bir isme sahip olacak şekilde “CronPixie” olarak adlandırdığımı fark edeceksiniz. Yukarıdaki main.js dosyasında, Elm uygulamasını HTML'ye yerleştirirken bu modül adına başvuruyoruz. Bu şekilde, Elm'in diğer Elm uygulamalarıyla çakışmadan “WPMDBPro” adlı bir modülü de gömdüğünü hayal edebilirsiniz.
Benzersiz ve açıklayıcı bir ad kullanmak için yeniden kullanım için modüller yazarken de önemlidir. Örneğin, Karaağaç kaynağını modüllere bölersem, “Program” veya “Etkinlik” vb. adlı modüllere sahip olabilirim. Hatta bunlara “Cron.Schedule” ve “Cron.Event” demenin bir değeri olabilir. Ancak, tüm Elm kodumu tutan “CronPixie” ile çalışırken kesinlikle acı hissetmediğim için bölmemeye karar verdim.
Çekirdek HTML kitaplığını kurmak için Elm Paket Yöneticisini kullandım. Bu ayrıca elm-package.json dosyasını oluşturur ve genellikle Elm projesini başlatır.
$ elm package install elm-lang/html
JavaScript'e derleme, yalnızca çıktı dosya adının belirtilmesini gerektirir.
$ elm make src/elm/CronPixie.elm --output=src/js/CronPixie.js
Widget'ımız build.js'yi zaten kuyruğa aldığından, main.js build.js build.js (unutmayın, main.js , request kullanımıyla CronPixie.js otomatik olarak çeker) require önceki sürüm için ayarladığımız aynı browserify komutunu kullanarak.
$ browserify -e src/js/main.js -o src/js/build.js
Bu çalışmayla ve şimdi "Muhteşemlik Buraya Gidiyor!" ifadesini gösteren pencere öğesiyle. içeriği olarak, Vue.js bağımlılıklarını kaldırmak için package.json dosyasını temizledim.
$ npm uninstall vueify vue-resource vue --save-dev $ npm uninstall --save-dev babel-core babel-preset-es2015 babel-plugin-transform-runtime babel-runtime
package.json dosyası, tuş vuruşlarını azaltmak ve derlemeleri otomatikleştirmek için npm run aracılığıyla çalıştırılabilen build-js ve watch-js komut dosyaları girişleriyle zaten kurulduğundan, Elm kaynak dosyalarını oluşturmak için eşdeğerler ekledim.
Yukarıdaki build-elm betiğinin içeriğini gördünüz, peki ya watch-elm ? Bu durumda, *.elm dosyalarının ne zaman güncellendiğini izlemek için chokidar-cli kullanmamız ve ardından build-elm çalıştırmamız gerekir.
Chokidar, brunch, gulp, browserify ve webpack gibi şeyler tarafından dosya izleme mekanizmalarının çekirdeği olarak kullanılır.
$ npm install -g chokidar-cli
Bunu yükledikten sonra package.json dosyasının komut dosyaları bölümünü güncelledim.
{ "name": "wp-cron-pixie", "version": "1.2.0", "description": "A little dashboard widget to view the WordPress cron.", "main": "src/js/main.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build-js": "browserify -e src/js/main.js -o src/js/build.js", "watch-js": "watchify -v -e src/js/main.js -o src/js/build.js", "build-elm": "elm make src/elm/CronPixie.elm --output=src/js/CronPixie.js", "watch-elm": "chokidar '**/*.elm' -c 'npm run build-elm'", "watch": "npm run watch-js & npm run watch-elm" }, "repository": { "type": "git", "url": "git+https://[email protected]/ianmjones/wp-cron-pixie.git" }, "author": "Ian M. Jones <[email protected]>", "license": "GPL-2.0", "bugs": { "url": "https://github.com/ianmjones/wp-cron-pixie/issues" }, "homepage": "https://github.com/ianmjones/wp-cron-pixie#readme", "devDependencies": {} }
Değiştiğinde hem Elm hem de JavaScript kaynağını izleyip derleyecek şekilde gizlice girdiğimi fark ettiniz mi?
$ npm run watch
İzlenecek yol
Artık WP Cron Pixie için ön uç kaynağını nasıl derleyeceğinizi bildiğinize göre, CronPixie.elm kaynağındaki bölümlerin her birini inceleyeceğim.
Size Elm'i ve işlevsel stil geliştirmeyi öğretmek için yeterli alana sahip olmamın hiçbir yolu yok, bunu yapacak kadar uzaktan deneyimli olsam bile (hevesli bir acemiyim). Bu yüzden, ana özellikleri ve her bölümün diğerleriyle nasıl birleştiğini belirtmek için elimden gelenin en iyisini yapacağım.
Dosyanın 7 ana bölümü vardır:
- İthalatlar – Çekirdek ve topluluk modüllerini içe aktardığımız yer.
- Model – Durum verilerinin yapısını tanımlar.
- Mesajlar – Uygulamadan geçen mesaj türlerini tanımlar.
- Görünüm – HTML'yi mevcut duruma göre oluşturan işlevler.
- Güncelleme – Mesajların ulaştığı ve durum verilerinin güncellendiği yer.
- Abonelikler – Uygulamanın olaylara abone olduğu ve komut mesajları göndererek potansiyel olarak tepki verdiği yerler.
- Ana – Her şeyin başladığı yer.
ithalat
Kaynak dosyanın en üstünde modülün adını ve fonksiyonlarını kullanabilmemiz için içe aktarmak istediğimiz diğer modülleri tanımlarız.
module CronPixie exposing (..) import Html exposing (Html, div, text, h3, ul, li, span) import Html.Attributes exposing (class, title) import Html.Events exposing (..) import Html.App import Date import Date.Format import Time exposing (Time, second) import String import List exposing (head, tail, reverse) import Maybe exposing (withDefault) import Task import Http exposing (stringData, multipart) import Json.Decode exposing (..) import Json.Encode as Json
Bunun olabileceği kadar güzel olmadığı için özür dilerim, ancak geliştirme sırasında oluşturulduğu gibi bıraktım. Uygulamayı oluşturmaya çalıştığım sırayı, import ifadelerinin sırasına göre görebilirsiniz.
Gerçi oldukça basit şeyler. Bazı modüller basitçe isme göre içe aktarılır ve bu nedenle işlevlerini modül adları aracılığıyla kullanırdım, örneğin String.join .
Diğerlerinde oldukça sık kullandığım fonksiyonlar var, bu yüzden kodu biraz kısaltabilmem için bu fonksiyonları açığa çıkardım, örneğin, class 'ı kullanabilmem için Html.Attributes.class açığa çıktı.
Ve birkaç modül, iyi bir demet kullandığım için tüm işlevlerini doğrudan kullanıma açık hale getirdi. Örneğin, Json.Decode birçok işlev kullandım.
Json.Encode için, kodu biraz kısaltabilmem için ona yeni bir Json adı verdim.
Bu, CronPixie.elm başlangıcını kapsarken, main işlev olan mantıksal başlangıca geçelim.
Ana
Elm, Elm Mimarisi'nin farklı bölümlerini işlemek için hangi işlevleri kullanmak istediğimizi burada öğrenir.
main : Program Flags main = Html.App.programWithFlags { init = init , view = view , update = update , subscriptions = subscriptions }
Kullanılabilecek daha basit sürümler vardır, örneğin App.beginnerProgram gibi, yalnızca model ne adlandırdığınızı bilmek, işlevleri view ve update ister. Ancak, zaten var olan bazı JavaScript değişkenlerini iletmek istediğim için App.programWithFlags kullandım çünkü Program with Flags üretiyor. Ayrıca, daha sonra tartışacağımız subscriptions kullanabilmem gerekiyordu.
main iki kez yazıldığını fark edeceksiniz. Bunun nedeni, işlev için Tip Açıklaması vermeyi seçtim. Derleyiciye, bir işlevin hangi Türleri almasını ve döndürmesini beklediğinizi söylemenin bir yoludur, böylece bunları koddan ne çıkardığına karşı kontrol edebilir. Bu durumda main sadece bir Program Flags döndürür ve hiçbir parametresi yoktur. Sonraki kodda, parametreleri de belirten ek açıklamalar göreceksiniz.
Html.App.programWithFlags işlevi, tek bir bağımsız değişkenle, init , view , update ve subscriptions işlev adlarını belirten bir Record ile çağrılır. Benim uygulamamda bu işlev adları tam olarak aynı şekilde çağrılır, örneğin görünümü oluşturmak için kullanılan işleve "görünüm" denir.
JavaScript, PHP ve diğer birçok dilde dikey argüman listelerinin sonunda virgül görmeye alışkınsanız, Elm kodumda virgüllerin neden satırların başında olduğunu merak ediyor olabilirsiniz. Bu, Evan'ın kullanmaya başladığı bir kod stilidir ve Elm topluluğu, yeniden düzenleme sırasında virgül eklemeyi veya çıkarmayı unutma olasılığını azaltma eğiliminde olduğundan, Elm topluluğu tutmayı kabul etmiştir. Ben şahsen bu stili seviyorum ve yaklaşık 20 yıl önce Informix 4GL yazarken benimsedim!
Daha önce bahsetmediğimiz bir fonksiyon init , onu tanımlayan bölüme geçelim ve ne işe yaradığını görelim.
modeli
Elm, yeniden düzenleme sırasında çok fazla güvenlik sağlayan güçlü bir şekilde yazılmış bir dildir ve inan bana, eklentinin bu yinelemesi üzerinde çalışırken beni birçok baş ağrısından kurtardı. JavaScript tabanlı Backbone.js ve Vue.js yinelemelerinin aksine, Elm'de geliştirme yaparken bir kez bile “Tanımsız bir işlev değil” hatası almadım. Bunun nedeni, derleyicinin, işlevlerin kabul ettiği ve döndürdüğü tüm türleri kontrol etmesi ve hiçbir eşleşme olmamasını sağlamasıdır. Daha sonra if ifadelerinin yanlış işlenmiş verilerle ilgili olası sorunlara izin vermeyecek şekilde yapılandırıldığını da göreceksiniz.
Uygulamada işlemeyi umduğumuz verileri modelleyeceğiz.
type alias Model = { strings : Strings , nonce : String , timer_period : Float , schedules : List Schedule }
Bu, durum verilerimiz için en üst düzey kapsayıcıdır, buna Model diyoruz ve çeviri dizelerimizi, nonce dizesini, timer_period ve zamanlama koleksiyonlarımızı tutan bir kaydın takma adıdır.
type alias Strings = { no_events : String , due : String , now : String , passed : String , weeks_abrv : String , days_abrv : String , hours_abrv : String , minutes_abrv : String , seconds_abrv : String , run_now : String }
Strings ayrıca uygulamada kullanacağımız tüm çeviri dizeleri için bir tür takma adıdır.
type alias Schedule = { name : String , display : String , interval : Maybe Int , events : Maybe (List Event) }
Schedule , cron zamanlama bilgilerini içeren bir kayıt için bir tür diğer adıdır. Event kayıtlarının bir listesini içeren bir events öğesine sahip olabileceğini fark edeceksiniz.
type alias Event = { schedule : String , interval : Maybe Int , hook : String , args : List ( String, String ) , timestamp : Int , seconds_due : Int }
Event , bireysel bir cron olayının bilgilerini tutan bir kayıt için başka bir tür diğer adıdır.
type alias Divider = { name : String , val : Int }
Divider , daha sonra bir olayın ne kadar süreceği için görüntüleme aralıklarını oluşturmak için görünüm işlevlerinde kullanacağımız verileri işlemek için tanımladığım bir kolaylık türüdür (örn. “3h 40m 5s”).
type alias Flags = { strings : Strings , nonce : String , timer_period : String , schedules : Value }
Flags , main işlev tarafından döndürüldüğü şekliyle JavaScript'ten uygulamaya almayı beklediğimiz verilerin şeklini tanımlar.
init : Flags -> ( Model, Cmd Msg ) init flags = ( Model flags.strings flags.nonce (decodeTimerPeriod flags.timer_period) (decodeSchedules flags.schedules), Cmd.none )
init , Model herhangi bir varsayılan veriyle başlatan ve program başlangıcında bir şey olması gerektiğinde update işlevine potansiyel olarak bir Komut Mesajı gönderen bir işlevdir.
Bu durumda init , Flags adlı bir değişken olarak flags alır, öğelerin her birini yeni bir Model atar ve başlangıçta yapılacak başka bir eylem olmadığını söylemek için varsayılan Cmd.none ile bunu döndürür.
Burada iki şeyi fark ediyorsunuz:
-
Model, gerçektenModeltüründe bir kayıt döndüren bir işlevdir. -
initaslında hiçbir şeyreturngibi görünüyor.
Elm'de açık bir return ifadesi yoktur. Her işlev, sonucu otomatik olarak döndürülen bir ifadedir. Temel matematiğinize geri dönün.
z = x + y
x = 1 ve y = 2 ise, z = 3 olduğunu kolayca çıkarırsınız. Elm'de de durum farklı değil. İşlev, belirli parametrelerle çağırırsanız doğal olarak bir sonuç döndürecek bir ifadedir.
add xy = x + y
add 1 2 arayın ve cevap olarak 3 beklersiniz.
Elbette init , bir Model ve bir Cmd Msg olmak üzere iki sonuç döndürüyor gibi görünüyor, ancak gerçekten tek bir Tuple .
Bil diye söylüyorum, HER ŞEY Elm'de bir işlevdir. Bunu bir kez kavradığınızda, birçok şey mantıklı gelmeye başlar.
Mesajlar
Burada güncelleme işlevi tarafından işlenebilecek mesajların türünü tanımlıyoruz.
type Msg = Tick Time | FetchSucceed (List Schedule) | FetchFail Http.Error | RunNow Event | PostSucceed String | PostFail Http.Error
type Msg , yepyeni bir tür tanımlar. Bu sadece bir Record, String, Int veya her neyse, ayrı bir isim verilmiş değil, hayır, bu sizin tarafınızdan tanımlanan bir yapıya sahip yepyeni bir tür (bu durumda ben).
Tüm boruları ile bu özel tada Birlik tipi denir. Bir Msg , Time yüküne sahip Tick tipi dediğimiz bir şey olabileceğini tanımlar ( Time , çekirdek Time modülünde gerçekten bir Float olarak tanımlanır). Ancak Msg , bunun yerine FetchSucceeed dediğimiz ve ilişkili bir Schedule kayıtları List sahip olmasını bekleyecek bir şey içerebilir. Ve böylece birleşim tipinde tanımladığımız diğer tipler için.
Bu, belirttiğimiz birleşik türlerden herhangi biriyle eşleşen işlevlerde bir Msg kabul edebileceğimiz veya döndürebileceğimiz anlamına gelir, ancak başka bir şey değil. Bu şekilde derleyici, işlevler arasında hareket eden veri türlerini Msg s olarak kontrol edebilir, ancak belirli senaryolarda ne ürettiğimiz konusunda esnekliğe sahibiz.
Size göstereceğim kalan fonksiyonlarda Msg tipinin çok kullanıldığını göreceksiniz.
Görünüm
Tamam, HTML çıktımızı main için söz verdiğimiz view fonksiyonu ile tanımlayalım.
view : Model -> Html Msg view model = div [] [ h3 [] [ text "Schedules" ] , ul [ class "cron-pixie-schedules" ] (List.map (scheduleView model) model.schedules) ]
view işlevi, Model uyması gereken verileri alır ve ardından HTML'yi oluşturmak için onunla çalışır.
En üst düzeyde, çıktımızı sabitleyen bir <div></div> oluşturmak için div işlevini kullanırız. Köşeli ayraçlar, List tanımlamanın kısa bir yoludur. div işlevi ve HTML öğeleri oluşturmak için kullanılan işlevlerin hemen hemen tümü, öğenin öznitelikleri ve öğenin içeriği olmak üzere iki argüman alır. Bu nedenle, yukarıdaki div işlevine yapılan çağrıda, içeriği olarak boş bir öznitelik listesi ve bir h3 ve ul etiketi alır.
h3 etiketinin ayrıca hiçbir özelliği yoktur (bir kimliğe veya herhangi bir ekstra sınıfa ihtiyaç duymaz). Bununla birlikte, widget'a "Çizelgeler" başlığını vermek için bazı (HTML çıkışlı) text içerir.
ul , CSS tarafından kullanılan "cron-pixie-schedules" sınıfı verilir. <ul></ul> etiketinin içeriği olacak li öğelerinin listesini oluşturmak için, sağlanan model verilerinden gelen scheduleView listesi, ScheduleView adlı bir işlevle eşleştirilir.
scheduleView : Model -> Schedule -> Html Msg scheduleView model schedule = li [] [ span [ class "cron-pixie-schedule-display", title schedule.name ] [ text schedule.display ] , eventsView model schedule.events ]
scheduleView , tüm model verilerini artı tek bir Schedule 'u alır ve bir miktar HTML döndürür.
Muhtemelen beklediğiniz gibi, <li></li> etiketini oluşturmak için li adlı bir işlev kullanır; bu durumda, atanmış bir class ve title özniteliğine sahip bir span ve sağlanan programın display değerinden text içerir.
Bir çizelgenin bir grup alt cron olayı göstermesi gerekebileceğinden, li , model ve zamanlamanın events değeriyle birlikte olaylar eventsView işlevini de çağırır.
eventsView : Model -> Maybe (List Event) -> Html Msg eventsView model events = case events of Just events' -> ul [ class "cron-pixie-events" ] (List.map (eventView model) events') Nothing -> text ""
eventsView fonksiyonunda Maybe tipi ile gerçek anlamda karşılaşıyoruz. Ve ilk kez, bir case deyimi kullanarak bazı girdi verilerine dayanarak ne yapılacağına karar vermek için bazı dallanma mantığı da kullanıyoruz.
A Maybe , bir değerin mevcut olabileceğini veya hiçbir şey olmayabileceğini belirtmenize izin verir. olarak tanımlanır…
type Maybe a = Just a | Nothing
Yani sadece döndürülecek bir değer veya “Hiçbir şey” olacaktır.
case ifadesi, test ettiği değişkenin durumuyla eşleşen ilk ifadeyi bulana kadar bir eşleşme ifadeleri listesinden geçer. Bu durumda, events değişkeninin, olaylarla dolu bir ul oluşturmak için kullanılabilecek bir değere sahip olup olmadığını veya events hiçbir şey olmadığı için boş bir metin döndürüp döndürmediğini test ediyoruz.
eventView : Model -> Event -> Html Msg eventView model event = li [] [ span [ class "cron-pixie-event-run dashicons dashicons-controls-forward", title model.strings.run_now, onClick (RunNow event) ] [] , span [ class "cron-pixie-event-hook" ] [ text event.hook ] , div [ class "cron-pixie-event-timestamp dashicons-before dashicons-clock" ] [ text " " , span [ class "cron-pixie-event-due" ] [ text (model.strings.due ++ ": " ++ (due event.timestamp)) ] , text " " , span [ class "cron-pixie-event-seconds-due" ] [ text ("(" ++ (displayInterval model event.seconds_due) ++ ")") ] ] ]
Şimdiye kadar bu görünüm işlevlerinin nasıl olduğunu anlamış olmalısınız, bu yüzden eventView açıklamasını daha kısa tutacağım.
Model ve olay verilerini alır ve text doldurulmuş bir sürü span ve div öğesi tükürür.
Ancak dikkat edilmesi gereken iki şey var.
"İleri kontroller" simgesini gösteren yayılmanın niteliklerinden biri onClick (RunNow event) . Bu, yükü olarak geçerli Event birlikte RunNow tipi bir Msg gönderecek bir tıklama işleyicisi oluşturur. WordPress cron'un çalıştırması için bir olayı "son tarihi" olarak güncelleme sırası bu şekilde başlar.
İlk kez ++ operatörüyle birlikte dizeleri birleştiriyoruz. Bu önemli. Sadece matematik için olduğu için + kullanmayı denemeyin.
due : Int -> String due timestamp = timestamp * 1000 |> toFloat |> Date.fromTime |> Date.Format.format "%Y-%m-%d %H:%M:%S"
due işlevi, UNIX döneminden bu yana saniye cinsinden Tamsayı zaman damgası verildiğinde bir cron olayının bitiş tarihini içeren bir dize döndürür.
Saniyeler 1000 ile çarpılarak milisaniyeye dönüştürülür ve hesaplamanın sonucu, toFloat dönüştüren toFloat adlı bir işleve yönlendirilir. Bunun sonucu, istediğimiz son String üretmek için Date.Format modülünün format işlevine aktarılabilen iyi niyetli bir Date dönüştürmek için Date modülünün içinden fromTime işlevine aktarılır.
“> |> teknik olarak “İleri işlev uygulaması” olarak adlandırılır, ancak herkes buna “|” gibi “boru” der. UNIX komut satırındadır. Ayrıca bir <| bir işlevin sonuçlarını, bir milyon iç içe geçmiş () işlev kullanmak zorunda kalmadan bir işleve geri püskürtmek için. Bu boru işlevleri, kodu basitleştirmek ve işlevler aracılığıyla veri yolunu göstermek için harikadır.
Burada muhtemelen Date.Format topluluk tarafından yazılan bir paketten geldiğini belirtmeliyim. Elm için tüm paketler package.elm-lang.org aracılığıyla yayınlanır. Elm'in paket yöneticisi anlamsal sürüm oluşturmayı zorunlu kılar, yani bir yazarın paketin API'sinin küçük veya büyük bir sürüm değişikliğini gerektirecek kadar değiştiğini algılarsa, yalnızca bir yama düzeyinde sürüm çarpmasıyla bir paketi yayınlamasına izin vermez. Elm Paket Yöneticisi, Elm topluluğu için büyük bir varlıktır.
elm-date-format paketini projeme eklemek için tek yapmam gereken:
elm package install mgold/elm-date-format
Ardından, CronPixie.elm üstüne bir import ekleyin:
import Date.Format
Bununla Date.Format modülündeki tüm işlevlere tam erişimim oldu:
intervals : Model -> List Divider intervals model = [ { name = model.strings.weeks_abrv, val = 604800000 } , { name = model.strings.days_abrv, val = 86400000 } , { name = model.strings.hours_abrv, val = 3600000 } , { name = model.strings.minutes_abrv, val = 60000 } , { name = model.strings.seconds_abrv, val = 1000 } ]
intervals neredeyse sabittir. Çevrilebilir dizelerdeki kayıtların her birinin adını ayarlama ihtiyacı olmasaydı, yalnızca sabit bir sonuç üreten bir işlev olabilirdi.
Burada bilinen değerlerden kayıt oluşturmanın bir yolunu görüyorsunuz. Ancak, bu kayıtlar Divider türünün tanımına uyduğu için, değerlerini Divider model.strings.seconds_abrv 1000 gibi ifadelerle atayabilirdim.
Aralıklar işlevi, displayInterval işlevinde kullanılır.
displayInterval : Model -> Int -> String displayInterval model seconds = let -- Convert everything to milliseconds so we can handle seconds in map. milliseconds = seconds * 1000 in if 0 > (seconds + 60) then -- Cron runs max every 60 seconds. model.strings.passed else if 0 > (seconds - model.timer_period) then -- If due now or in next refresh period, show "now". model.strings.now else divideInterval [] milliseconds (intervals model) |> List.reverse |> String.join " "
Birkaç saniyeden "3h 40m 5s" gibi bir dize oluşturmak için displayInterval , milisaniyeye dönüştürülen saniyeleri tutmak için yerel bir değişken kullanır ve ardından kaç saniye olduğuna bağlı olarak "geçti" gibi bir metnin görüntülenip görüntülenmeyeceğini belirler. veya "şimdi" veya divideInterval işlevini kullanarak saniyeleri haftalara, günlere, saatlere, dakikalara ve saniyelere bölün.
divideInterval : List String -> Int -> List Divider -> List String divideInterval parts milliseconds dividers = case dividers of e1 :: rest -> divideInterval' parts milliseconds (head dividers) (withDefault [] (tail dividers)) _ -> parts divideInterval' : List String -> Int -> Maybe Divider -> List Divider -> List String divideInterval' parts milliseconds divider dividers = case divider of Just divider' -> let count = milliseconds // divider'.val in if 0 < count then divideInterval ((toString count ++ divider'.name) :: parts) (milliseconds % divider'.val) dividers else divideInterval parts milliseconds dividers Nothing -> parts
divideInterval ve alt işleme kuzeni divideInterval' işlevi, aralık dizesini oluşturan parçaları çıkarmak için birlikte çalışır.
Güncelleme
update işlevi, görünümden ve diğer yerlerden yayılan mesajları alarak, yanıt olarak Model güncelleyerek ve potansiyel olarak daha fazla mesaj göndererek uygulamanın sinir merkezi gibidir.
Özünde, update işlevim, Msg türünde tanımladığımız farklı mesaj türleriyle eşleşen bir case ifadesidir. Bu kurulumun güzelliği, case ifadesinden yanlışlıkla bir eşleşmeyi kaçırırsanız, Elm derleyicisinin yollarınızın hatasını kibarca açıklayacaktır.

update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Tick newTime -> ( model, getSchedules model.nonce ) FetchSucceed schedules -> ( { model | schedules = schedules }, Cmd.none ) FetchFail err -> ( model, Cmd.none ) RunNow event -> let dueEvent = { event | timestamp = (event.timestamp - event.seconds_due), seconds_due = 0 } in ( { model | schedules = List.map (updateScheduledEvent event dueEvent) model.schedules }, postEvent model.nonce dueEvent ) PostSucceed schedules -> ( model, Cmd.none ) PostFail err -> ( model, Cmd.none )
update init ile aynı kalıbı kullandığı göz önüne alındığında, fazla ayrıntıya girmeyeceğim. Muhtemelen belirtilmesi gereken bir şey, aşağıdaki gibi koddaki model neler olduğudur.
FetchSucceed schedules -> ( { model | schedules = schedules }, Cmd.none )
Burada model kaydından yeni bir kayıt oluşturuyoruz (Elm'de her şey değişmezdir), FetchSucceed mesajıyla birlikte geçirilen schedules değişkeninin içeriği için model schedules öğesini değiştirerek.
PHP gibi değişkenliğe izin veren dillerde şöyle bir şey kullanabilirsiniz…
$model->schedules = $schedules;
Ama sonra $model her yerde değiştirdiniz ve bunun beklemeyeceğiniz yan etkileri olabilir. Değişmezlik ile model , verileriyle oluşturulduğunda, o andan itibaren aynı olduğuna ve çağırdığınız hiçbir işlevin verileri değiştirmeyeceğine güvenirsiniz. Verileri değiştirmenin tek yolu, onu yeni bir değişken döndüren bir fonksiyona geçirmektir. Bu kadar çok hata, yalnızca değişmezlik ve dolayısıyla neredeyse hiçbir yan etki olmaksızın önlenir.
getSchedules : String -> Cmd Msg getSchedules nonce = let url = Http.url "/wp-admin/admin-ajax.php" [ ( "action", "cron_pixie_schedules" ), ( "nonce", nonce ) ] in Task.perform FetchFail FetchSucceed (Http.get schedulesDecoder url)
update bir Tick mesajı aldığında, geçerli nonce ile getSchedules çağırır. Bu işlev, arka uç kodumuzun JSON kodlu bir cron programları listesiyle yanıt vereceği "cron_pixie_schedules" action için bir URL oluşturur.
GET isteğini gerçekleştirmek için, Http.get işlevi çağrılır ve veri talep edilecek URL'nin yanı sıra bir JSON kod çözücüsü sağlayan bir işlev verilir. İstek başarısız veya başarılı olabileceğinden, sonuca bağlı olarak hangi mesajların gönderileceğini belirten bir Task içine sarılır.
schedulesDecoder : Decoder (List Schedule) schedulesDecoder = list scheduleDecoder scheduleDecoder : Decoder Schedule scheduleDecoder = object4 Schedule ("name" := string) ("display" := string) (maybe ("interval" := int)) (maybe ("events" := (list eventDecoder))) eventDecoder : Decoder Event eventDecoder = object6 Event (oneOf [ "schedule" := string, succeed "false" ]) (maybe ("interval" := int)) ("hook" := string) ("args" := eventArgsDecoder) ("timestamp" := int) ("seconds_due" := int) eventArgsDecoder : Decoder (List ( String, String )) eventArgsDecoder = oneOf [ keyValuePairs string , succeed [] ]
Yukarıdaki Decoder işlevleri, PHP arka ucundan aldığımız yapılandırılmış JSON'un kodunu çözmenin yollarını sağlar. Elm, JSON değerlerini Elm değerlerine dönüştürmek için bazı temel yeteneklere sahiptir, ancak benzersiz şekilde yapılandırılmış JSON'umuzu yapılandırılmış Elm değerlerimize dönüştürmek için doğal olarak eşlemeleri ve türlerini belirlememiz gerekir.
There's some finessing of the data in some cases, such as trying to get a “schedule” value from an Event , but falling back to a “false” String when one isn't supplied (eg for the “Once Only” schedule that is constructed for non-repeating cron events).
decodeSchedules : Value -> List Schedule decodeSchedules json = let result = decodeValue schedulesDecoder json in case result of Ok schedules -> schedules Err error -> [] decodeTimerPeriod : String -> Float decodeTimerPeriod string = let result = String.toFloat string in case result of Ok float -> float Err error -> 5.0
Related to the Decoder functions are the couple of functions above called decodeSchedules and decodeTimerPeriod . These simply decode passed in JSON data and return a list of schedules or properly cast timer period respectively. They are both used from the init function to handle the JSON data injected into the app at startup via the optional flags.
updateScheduledEvent : Event -> Event -> Schedule -> Schedule updateScheduledEvent oldEvent newEvent schedule = case schedule.events of Just events -> { schedule | events = Just <| List.map (updateMatchedEvent oldEvent newEvent) events } Nothing -> schedule updateMatchedEvent : Event -> Event -> Event -> Event updateMatchedEvent match newEvent event = if match == event then newEvent else event
The updateScheduledEvent and updateMatchedEvent work together to update a specific event record nestled in a schedule so that the displayed event properly reflects that it has just been set as “due”. This happens in response to a “RunNow” message having been sent after a click on a “Run Now” icon.
postEvent : String -> Event -> Cmd Msg postEvent nonce event = let url = "/wp-admin/admin-ajax.php" eventValue = Json.object [ ( "hook", Json.string event.hook ) , ( "args", Json.object (List.map (\( key, val ) -> ( key, Json.string val )) event.args) ) , ( "schedule", Json.string event.schedule ) , ( "timestamp", Json.int event.timestamp ) ] body = multipart [ stringData "action" "cron_pixie_events" , stringData "nonce" nonce , stringData "model" (Json.encode 0 eventValue) -- , stringData "model" (Json.encode 0 eventValue) ] in Task.perform PostFail PostSucceed (Http.post string url body)
When a RunNow message is sent to the update function it not only returns a new Model with the associated Event set as due to update the display, it also uses the postEvent function to POST the newly updated Event data to the backend.
The PHP backend expects an “action”, “nonce” and “model” to be sent in the body of the POST request, so postEvent constructs a multipart body with the required data. The model is encoded as a JSON string so that its structure can be preserved and unpacked on receipt.
abonelikler
The last bit of code we need to talk about is how we generate the Tick message that the update function responds to by calling the getSchedules function to request and and display updated data every 5 seconds.
subscriptions : Model -> Sub Msg subscriptions model = Time.every (model.timer_period * second) Tick
The Time.every function publishes the current time as a timestamp at whatever interval you ask of it. We ask it to tell us the current timestamp every model.timer_period seconds (which equates to 5 seconds), and send out a Tick message with the value in response. Bu kadar. Elm does the stringing together of all the things to ensure that subscriptions are processed and their emitted messages sent to the update function.
Hala Çalışıyor mu?
Elbette öyle!

The only discernable difference with the previous versions is that I'm now formatting the due dates in a slightly more compact and fully numeric SQL-like format.
You can find the full source to the Elm version of WP Cron Pixie on GitHub.
Sarmak
I thoroughly enjoyed writing this version of the plugin, more than either of the others. For me, Elm has brought functional development to the web in a way that is very approachable, practical, and relatively easy to grasp.
The simple flow of data through functions with no unplanned side effects reduces the likelihood of getting yourself into a tangled knot like you might with imperative languages. The language features coupled with the compiler is wonderful in the way that it picks up on potential bugs and politely refuses to complete (with helpful suggestions on ways to fix the problem).
Above all, it feels pretty fast to develop with and refactoring is a breeze due to the confidence installed by the type system and compiler.
If you'd like to learn more about Elm, I've already scattered this article with links to the excellent Elm Guide. I also thoroughly recommend the Elm In Action book by Richard Feldman, which is currently in “Early Access” and shaping up nicely. For a great free video course, check out James Moore's Elm For Beginners.
Who's up for developing their next frontend project in Elm? Yorumlarda bana bildirin.
ev borcu WordPress sitesi