From 3bc59d7638a3f51ea865e45712ea6ba43f2a03c5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 08:57:14 +0000 Subject: [PATCH] Add a Tutorials section with an eight-step Walkthrough A prototype of what ui5.github.io/tutorials does for OpenUI5: a short, curated learning path, and a tutorial whose every step is a complete app rather than a fragment. The step-per-runnable-app shape is theirs; the mechanics are ours and are simpler. A step there is a built app plus a ZIP download, produced by a build orchestrator over an npm workspace per step. A step here is one class in a fenced block, so the playground rule engine already gives it the live preview - the reader runs the text on the page, and nothing needs hosting or a second copy that can drift. Eight steps, each the whole app as it stands: Hello World, a first view, events, data binding, list binding, row events, popups, and the dispatcher-and-methods structure real apps use. Measured rather than assumed, per AGENTS.md: - all 8 fences carry a Run button (checked against playground.mjs), - all 8 were opened in a playground built from source: every one reaches `running` and renders, - the behaviour each page PROMISES was driven in that playground - the toast, the greeting from the bound input, the five rows, the row-specific event, the dialog editing the clicked row and the list picking the change up, and Cancel leaving it alone, - chain-house-layout passes on all eight views, verified by breaking one chain and watching the rule fire. The preview image is that app running, captured from the playground, not a mockup. The pages are linked from the nav dropdown and sit in a sidebar section of their own between Getting Started and the Cookbook - a tutorial is read after the install and before the reference chapters. Directory-index links are avoided: generate-llms.mjs maps a sidebar link to docs/.md, so `/tutorials/` is not a page and the section follows the repository's own `/cookbook/overview` convention instead. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PwxPfiy5WE8fVxfnToFeWk --- docs/.vitepress/config.mjs | 28 +++ docs/public/tutorials/walkthrough-preview.png | Bin 0 -> 37760 bytes docs/tutorials/overview.md | 32 ++++ docs/tutorials/walkthrough/overview.md | 42 +++++ docs/tutorials/walkthrough/step-1.md | 43 +++++ docs/tutorials/walkthrough/step-2.md | 62 +++++++ docs/tutorials/walkthrough/step-3.md | 77 ++++++++ docs/tutorials/walkthrough/step-4.md | 67 +++++++ docs/tutorials/walkthrough/step-5.md | 82 +++++++++ docs/tutorials/walkthrough/step-6.md | 92 ++++++++++ docs/tutorials/walkthrough/step-7.md | 130 ++++++++++++++ docs/tutorials/walkthrough/step-8.md | 164 ++++++++++++++++++ 12 files changed, 819 insertions(+) create mode 100644 docs/public/tutorials/walkthrough-preview.png create mode 100644 docs/tutorials/overview.md create mode 100644 docs/tutorials/walkthrough/overview.md create mode 100644 docs/tutorials/walkthrough/step-1.md create mode 100644 docs/tutorials/walkthrough/step-2.md create mode 100644 docs/tutorials/walkthrough/step-3.md create mode 100644 docs/tutorials/walkthrough/step-4.md create mode 100644 docs/tutorials/walkthrough/step-5.md create mode 100644 docs/tutorials/walkthrough/step-6.md create mode 100644 docs/tutorials/walkthrough/step-7.md create mode 100644 docs/tutorials/walkthrough/step-8.md diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index aa0861a2..1fde2bc1 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -123,6 +123,7 @@ export default defineConfig({ // The section is called Getting Started in the sidebar this opens; // "Introduction" was the first PAGE in it, one level down. { text: "Getting Started", link: "/get_started/about" }, // nav + { text: "Tutorials", link: "/tutorials/overview" }, // nav { text: "Cookbook", link: "/cookbook/overview" }, { text: "Configuration", link: "/configuration/setup" }, // Technical Insight is not a line of its own here any more: it is a @@ -192,6 +193,33 @@ export default defineConfig({ { text: `What's Next?`, link: "/get_started/next" }, ], }, + { + // Between Getting Started and the Cookbook on purpose: a tutorial is + // read after the framework is installed and before the reference + // chapters, and that is the order the three sections stand in. + text: "Tutorials", + link: "/tutorials/overview", + collapsed: true, + items: [ + { text: "Overview", link: "/tutorials/overview" }, // sidebar + { + text: "Walkthrough", + link: "/tutorials/walkthrough/overview", + collapsed: true, + items: [ + { text: "Introduction", link: "/tutorials/walkthrough/overview" }, // sidebar + { text: "1 — Hello World", link: "/tutorials/walkthrough/step-1" }, + { text: "2 — A First View", link: "/tutorials/walkthrough/step-2" }, + { text: "3 — Events", link: "/tutorials/walkthrough/step-3" }, + { text: "4 — Data Binding", link: "/tutorials/walkthrough/step-4" }, + { text: "5 — List Binding", link: "/tutorials/walkthrough/step-5" }, + { text: "6 — Row Events", link: "/tutorials/walkthrough/step-6" }, + { text: "7 — Popups", link: "/tutorials/walkthrough/step-7" }, + { text: "8 — App Structure", link: "/tutorials/walkthrough/step-8" }, + ], + }, + ], + }, { text: "Cookbook", link: "/cookbook/overview", diff --git a/docs/public/tutorials/walkthrough-preview.png b/docs/public/tutorials/walkthrough-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..eb2f4d917beb2e691af7d94e4984b6916ce72be5 GIT binary patch literal 37760 zcmb??cUY6pw}dM}}aiXb)8K}wL`BfW&8AiZ}8 zgwT5uYC=o0;rqMyd3N`?d!KuE_pg)p&CEMz&di+iIiHyc*HBZWzH#>k85tS1(pv>B zGBSz{GO|m0*DjIXD0oSc1Ra@@!b=_R^c}?Y_qUG_#64sjf^COO>E8X&2O(sOq1`;9 zkFvf!%6>l*aiqD!UE9H76A3D*wG%jY-t$S2qn*BC^i{Da`}6C|ibp za=*vg-9xZ;M?V#ju2PIsnulu5WtVgv1q>TB)e$~-9X$TTnEMvbpXpK$~ zYr+i?YhnHFIFZ@j5GfS*D}Do*rf&5CYCQ-W@0Zb9VThZ@bsq=^9j|Wk(@W_VfP{k9 zra}B+AT&4-~))iIWKwbaG(5AqZ;FN3hHyQJMPOhx3dX3ObAINzVrf{;Y zs~#E=<9TOOj=jl&0Us~{TV+9|CI{#==O!1T;Bwf!g!O5+0euAL95AJ-M5FN!IUvAw zH7&@vVXeVF{%fIR^}!4J*B7JN?!N5`-b{E$OQwEr-POgHvPY6JVUXuyP%7 ztw2Q%`VF?)(18QHI6hY zo_q*Z4PrRIxUo&B8AZQXo8q13?%c+3Rkei}-gC6QuP0kOjG_!$Y>-r9luSdOb`?lA zghjlDl5;DnW{UWYexKgWhe-KuopZvyN)h`LJKoHX=f`RJc z$`z`0Gp5WVD0wptyDN5Cp^I8g1!YBllySf)oeZeS`zfw?d)>*PwpxFS2osf+H)zYC zul8OZwHg%NYfJ6WmpSYH;cqRUbj!~3fe*CqMf!@AdjND_syf}6#FK;Br zAgZi6ZZ610dyiKI;^S3yNCjqhu2cxBq^p!FvDD4s+bb$AT-2NONq%kz*AHXa zdENnsCE8Z6k2;Q0#$&bkj3T;MM<@0fd`_&lmb0q<$a2}j+d7<^DwoO9hTD z3qKCP)T}c_foey^>k#6~KdN`Xu5uDkpJEw}&HU7rQIi5|!hskf(6Gw)gfssr3G3PS z*TsU#Sb%Kb+v_r}4+` zZ6yjY0cv)`TKxkDTKht#XfrpbPX-GH>y&T=^F2tW_fj6w*T$}iw3?4{+LAcirR>ne zmKL`GV%affrf4+D+RIsT!PVbo;$*BVQhkb3os|JKw-`|wen??AdX?|n#j zM5np;-oUZT0$oex4Z>(u!^0UUa`e|3Ii(Yj+a>X%SqYj&N7U#E-j-gYnvVgK^Givk z#@c0b>ZTR;14ipX%U$zsx@Hqe3@V)ib*8d!ePML6ZU)&_U9k}nHP*R*K)C-pqXx>&?w-04;EH{3Rl8XgY%#>Lx%auWz24Qj&E}rP2(K(&l8A zpjf3Xh*75o98PA7D%y=Z58TRN(*|l(dv2zs;sd=5X**mO#Uk@Iv-{x&_VvcC5zh^3 zB4L}&3enANR+NtIPOUh&=_vRYXj^)4@{UTYz`9V1xF}DyWPJ(d4PHxS%Ah1~aUjCu z50`ZBB&hA(=8!Co*}<&Erl+y#yp;XKeM@~|IXgcvrd}XFTE(_!dgM!d}>5ETfx}C zn$#>tXrSz@PWW45?{66hioQORB=)?cv82?Wm?Zl(bub@WiHEF7;Q09h-a=h8*`3@o zc@$4e7ZL&#>?Z!0at7$mKU36QT*o*I(lt8=p=W#uOt}mX<(e$A+Zv0rQ3AwsBlA-f z2#5eh;8Kp9Sr8=IPW-Dan8oO z%S#aNU+sdSyy_?TRgJa1AMg&%2YoMF@g5|%o{c)ha!XjFFO%>Q>;CDQj`33WC)bjL zhzy@+jZg2sIdt<%WP9WxRF!X)E8+g$`<=f@RJU-S@maU9SBDhF8r1 zvhAqCv0Y%!!|3RFoX<&1>$xx5ek3U(6r`o}p@0TVf3axlg@7s!)Yk6F263TS=WqVC z^IDgj3`~(S%T2={4jpko?Vx#Fc6{E`R6quR&rpJ@d$5Jxcq?iqbuj)LGrOdQ!D668 zZW-|iHzzyHb)!xwz}-6h8z*S2KX$M-HsW)31iz^j?sO(}?(`V4W{s_isBH7+t1*sy zh7Q=;JVCl=y1r3(pqeR;Nz!cI1|dc&8 zo|UvU!s1@TvP8*E61d(=t*okgUEtw6djK$Q67YvF_XT>Ts_G=)Ug133Lhz`7GQv5| z4ufQ6e&Rz+&*qbZ@Z-{7Cwr)pWuC^uB}sFW*NQ1ar>438q%2R=Z?dK7F|VFZa#DsA zc0z8wM`%n)7Y;R|uF~$0-v4;ho_Z|I2@}Rl8FtSL+yZ-gXCvBwHp3u{C08O>s_p+-_ z(KpdrBh4zz@wcZlN(Kh$gfGfeQ)o#+%gRWNdQa#eImdV&_gk#p6e0cdLfWE2g4@^B zG~=+YDj6}Jr&9lQ{37qEme%r0UTU0;id1b(?TTRgm_PSV zNqh6b>G`j27m3wyx##9+$K2O{EL!cXRg)3lb5Z-hlvBYs8}5~;VGx-a*31``(hn(d zqrwp4$d0{J`aySo8$bPBY%^5mXgpqBw#L#p_M*_RngE)~9%>W$OQGwp2jj&nxGmUE z0XY+7B5Hi`Xmi`Zr7q}X-Ao2n{s^5^z;P!Phu->MaXX*M!Ae`}PMK26=3X)y*1yJP zitRa|%EXs)t@U0Zp6!NkWIGV^Yf8jc*(5wFAItde{{ERdh4k&i#c`#Np#x5))SIeqJ0kW=Z#XaARk3#A5kx8~yg7SUPMv~+>rzcSKgd;2#Nq%w2_EIhBIha9M z?8m74e=f}V`}3{2c1I?aie(1Zl*k!)4eeq&1;if zdjt2h<~j6vuKY(;)tUp$KcJ{WR5*$weO&i1(2JB0{M{Fqo7Ohrw!49K!1w*Mn56=% zt6)7pkj(JHj}czgJAKvaBNl(0(Uh@GNQD>+l`a>keFNoN8^echZ;VjwC|O@RpuP+69mr2jWRrj_43C!%owMne!s1$TkAg2$C_h3Fa1dL|I1bj4iRK+q{)RtZ%=IFvb4S0hHMnQec|13kI^g-R zYbr&ExCB>>w)zke*tC9`UTKU;h|u0V1$_FXvGED9dLy8tx`BvkID@LGW$X=WQ#tp( zt-V3wPTa*6Jc?pXEA$1m)#FHVqMiKN0>_w zbadG!_2R|b6MfgZ2WIX==k~YSOZvd*9(bc|4|i7|$q7bRtcJORR;Ig#9OD0pXddqg zS8y)IqrtJ6@T-(1ljXCn_2Y2AIo&#hsI^JN zZQ|>r@Sx_3!+|nMA4bv1`dIz>+V}G-z}MRx0sG6-nnihj?pO%@_4cknVhrcto!i>F z_gd#Z6x?>YM$+L)t`OG?Y&&Pu$;oEQA)(m_{(-*Innj5;GhbvUzX=BzHVB$S_qv zTah>9vlQM*4NaH)pgl+#7EuzP`^|}68Z>YEAuiYGWE|$)cY4HXOq&?3Ys7(cTjq%n ze{i7!_l=ze9eR$mgdevAK#%O|MLXfc@(dj4oxGxQrU^#Ib=2w^6Vk71Y0Z4E88smv zasCfUwnpOUB3RpkT3=GvjfqOiHU4bpX=6h4>AB~Zwy|tjCD&aBt+HOk-U)R4*dIG#@0D4rpM2Z@4 z)0zDRW11)aDXsyZTWY>+5cL_Ko`r(EniOtABzaLYJgw<41%tRQ^}8}pP7P`bT|-am zqjIzJ0L{w=U)^^kVyvga} z-T77|yiPX!sm7_}f;0~{`?ob+jnrcyJ(CR-{@*g}{|!HKPJ`C%lJf=m44$-TJD-eL zjW8uMimqNUvqa4V=GHg$j*8?Bg_LwgV-OP!+56*^LBuB|GEQrAo6CM;NGHcl{WQXM zds}xDYoL#*g-(0Qttr;ZoArxHM7DX=i(a78@C%*E`b}w`h4u0VXVEQQgmY;T3)V>; z$Y7v-&uES0Q}<+9&M&}tS}lo8Dj;kwUccVf64X)Eitv!jOBS8uI$yJP z0f3fvpSo@)^g9`gb4Z&!d(@(jm#IMp?kPFBTC52>vCPq}iiFbMsaPIULIX+5l4Qj4^Of;xC8cle65rXa* zDYVs}aXP(4#o76pfBp4lHhp3vB`US59Boa_OukpL1SAY(I=7vwMq`**lH}@VvCtT1 z>%(_Zw-c=+QEf?X;cy{C-$ND_;?ZO?PrNVhn~5Y0z(SqvIX}AlY4Uy1f>wjGG~plR zhG%nv|mN6f?KCdZa9(z2Xl{^?4~y;*DtlZ-5`l7lP7gA(^+zim!r@l|=Mi;sgrd#9D6 zS6Lq*u=rVp88|nJ!><-C7s&5uV6ptSrl4gG`zQP_%NQlE*ueV=Q>kZ?I|zhiBBLjt zei!J#F!!I-Icw{1FfURs{f-xrbM3NZ4He+C-`>avq{71imISF^MPKqdeX)@ge#C$+ z|JLfvWHVU@GOJy@k^ohwbNR_2(=&1K~hcTyG*mP3h%pVmzo zYCy`HuFi~gfz52>*mE1~!E&-+j{iwP=`Q8@nHit23v#&=j?1rvVVT~lw3YP)oMFHH zf=Hy88JTLk9p~{;EyJqS`ztCniHY1(cp|6kZ1t|hg+s3NS zHl+umE_^0wh{$Y^=OhPx@vH3H|04^wWJaXj*Y11y6lLL$XkSo$mJWUs;+cha36Qz9 zME;!Q_cNH4*q>Ymhw)2tY*b%EzgU+ypp1&KkQuh6pZlctsfotp?EtOgEtz2!y17p= zQKaWv54^KPNHy6t6bH7OXkR|^_?4^jnNsS>XX#Lr=s3nRR;<2^}9 zp((TvGYeNH=XfIYS^E7G$IQg*+b{0Hdkm+;F~Rhn^u9B+!o-su`@r25$}1D)k?PKV zgnU-(Aycn<YeRG@SWh0oaZCzK?ml*M6#?x&q@dTKrM{M!(gvcYD{C<`4ji$ zHZV9$8 zm4MFX%MTZ3BaukHJnYw8uNP|Dw|5n;Rd)!;LN?n0_i0-=R6frKEnN#Rj{Gvnl7-(R zdVGIM!5e*BpqB0N&7oOa^)yk5H!G*`+{UGqPJOi-Ds$0K^|YiH1jlQn=ymST8Uwyu z5|w|nrwoB(KKyCW_L-CXk{HVV6Z0k6>GKXvHD_RL?^_<+w`ubDu*f;wRO^B5z=8nf z;c(nv!I608#{}e=Hb~HH%~ec^>}Oxb%ku?kZ#>z8U2@-mabDOqy* z9RkTsvF&__PrmnkN2v9lU6ZhU6v{EMc-=-9j!ylfbnvoih|-=TFZD`rEz?^U=2{X5 z6JN@gBXTyoLaFfA`kk6U(0av*dZ6Bh)_ZX36VSY;#dh54th&1qi-#`?t1A{vmnE7* z^*i`5*9TzD^y#{(KS{-Za3PrucMNt4gm%`V^^X&iKP#`evysU&cDLLQLxB;d^$n8} z#E`ZghzmMopKP3CO^Iucw+@sv#TJsVHxW?&`g!eYSRZPnetzqt2_^F5??NnY*#)Wag#5R$P<+~0oi z4USKfP@B|X`;@W5vtj-E(A|xlGN*Pf=`6PNWemm=^qY>Pu1!s#-4w@+=ev@F)q2Lj zt+n5(`)Hj@_;s)6ME#?A#@~F*N2=KcH-+_sX^s4OVHBaHGcfXnN3C7sQ{EY|rb8+& zbn5SB=my`eTw~|N2`RRlk&lmC4c=MygP1rY*8A@+!^(GrE)g_H6F%hjANFgBdTJ=g zTQd|vX7^VMvMzi4So=8$e$MAUs#n#^1Y81OP-31jX{R3W4WDU-Sr5fsgb>olfL%nDUDzdn51471u1}`A~|&(h2b|) zEkN}EbU3!xmkw-v<xYr?<^J< zJLl+bw2=u;@=eZnN{>oKYr0(N*wJQ9zk|jd@Ax@!-a=mQ)@4}IAWQU5ncq8Vhm@Zq z7icc4`n^ScQoxcO_+%46(+5ZIJQub7U`p!Gsc+-m7TkN2Q8a^ zqEj`z_nlflk@Njk{0fW6tdKX3(O2I!y0N%5xAP)1 zo|HY=%+@ybwxqV!Te?|Z8$=~7eBp6nw>kx%0YA7?U&8C**hKd;yM^zJLsHsQuG z^w;d=uASOg?Xe|eF%;Z+vMV6 zS3W(`R-}%4?2E%d(n)+CEEL30gLE=IB7{a%`>mzLLi2B?d+7k7`PoH!zydYWMAq&1`VomG*r7+X!m#gwR2C=g&(U4-P|L zQ!C~dzwuce-Vp__8(}VEg8XrszWNSn_l+rjS|W)<;+3V^%qC`g%D|Rg73QDXjvZf> zs{|#*-UZf3zSGGZIO~-0So&+&o-K$y2k8ej<&-1S8vrQ)(XzD-tau%yabeE=WRmyq$93ph`ne2JG0ng5~qw0QB~y3Xi!(@ zx`*<8!Mc)MFGbmtfp`0^hPDif44A54XLAaLJHh=<&xI0e?N4*0z;lAd48f7_5soHZ zV&}1~kplAvvg?{gt8%FadxiE4bqJ05Px&vO*?$$LQM?A(3#KXEnx51bi`+)n*9O(^ z8VBQf{zV_eG!rVV{+1eJz9r{Z9}e`V-)F4)Dgj#PUhF;Q)2H5IaAX>3FBiu9&dft2 zJ9#^qx`i>z>AcSz`@7AX8*k7jN}lhwq-R&c4-KkXrCp8}ZLG{U~XWwP{j7dsQTOS+JN58nseG>%n4F5=X(+U60pAkV?UZjSIAXtr+t*mc|09>{okm3XJ7nZ=Zx z!A8AinYk$j^Zg!;r!VF{Tz#`pZvOrlEjRNL`@S(BkM|6LMeqy4N&n}kf}<+ftGsr~ zjbSx^Y$t&K*&6bB|3fS9MF8TDEWezi4VVH}1-8zY08>XtpeKYIvExOu*hVR+;eh7k zqqLf#YH8@eH?Opn^fot5A^%Yr#hS8T>g`6y!Pkk%jGD;ZlydVnug&k%a|p#7k;?7b zoeEtFCU8NROAydB^=P!3gI?2G7X)1eeMBdTq06gXnS0e&KUz5+TY`+1azBZY7uwSn znbr=M3s(xPcpm*Km({rV$34=3BO=2J0D>7nzi~Q@(+VIh-E8g;U$EWpi)WGJZ?A z{wi0iS(2Ye(5^kExnnX{QkLuSTD^k%AlAX0)ypvI@Z*ay^aN8t4eAR+xb{Pu4F&{v zo_<}o^RZmXAdkn(Ck92bzq`v%YW;6d1rV~&k5zk}EVN{A>IDei9QxU!g{H}=niTTS z;Cy3a7hrq$u|AbP|8Ktso{6vYCzLVwyU~REdpQh^r{RhE;-=+N$2c>=p4QbOJt;8k z+g64vGGoNhutB;u==7`3T5YGPoM_cYkW(3Mxcjed)k90iKKv}vfe*TMD8Wv5UtIhWcV5ZJ=H8lP!QbxcjYnlL z>zJ+i65~zus<5?Eojp49xz$$vr`Ssx>&_9nYA7ix6hYA|mDR5Be3NGTZhWILhg+*p zkd}nx&kx$o>ODwv8Tl%WK4kQVl{5G%W%nDqMa?f$+QIq}7$X(T4N_3D1aV0W_r9B% znYk3KMo64B=B=@RTun(gqk1ZatLgm^JgK2(^x|H%?B}$xrXla*XD_@G-*Gm5M+!^~ z7<-MbKXI(fKFhf-Nb$Lzakyt>mb^NyRuv^jg?h%yzvf9jTP(!v_S94kN_pOkMjAi9 zlRPO=_^h{#8?jQM!PjeFULzpr@q8{5C-+DfNx|Q&4Wf(+U!bYB{*>EJxn@$+lHJ_o^DQtNHQQ zwkVVild&K=4sQ-?cs^9v37Q#&CE;lQl)V57khRwy^Tw-(fkh`33%-)SX4lD1Y%g}^PD_>+l2Pg>s(PI)}>qQA? z`olhaf8*twrmiQE6)9T??P>hTP5y}xQ zf@Q_!MWv<1FmKIcxH`nz`B3_DeCCMWc}!eOtE0}zc=SM9qepmD)VGM6U<^GgQe^E` z+Js1p{UBQYTW}RjjvxvnA>L>bNG?1^Jm7v6WQQ}90I_UG?I34RXSD04g{6e~l-Mct zR1YO_V@pfRWFgbb!IPeQOC7zvy;aDJ*`VEx)=Kx>%YX;XP-B06SsUFCeXyh*A=u|o zmMrSMEAN8!e5roZAgtr|T9w|I*@1+sRT7WdeXgwY3(fiCMJY~?RvDRX0J)Ty*qkop z?5>iyy^b%^vgj5ja|UtMPo{i9fwc4dZ+8S_TXL1G%61&k?QA#tRyPCgF4|HKlSna} zTc_hfO=o>+OlQ+`$Z>R&B~*gA+S2~zYA(VXA!=@Je(A-Tw!S`Yu~U$ehLc^^!X-fj zMzy#ftx+zYgb2Xlw~bhjy|di2b+6j+o;$>i#KN1U;P&?0x#M!EEdfOuPJXg#{n4~7 zmML{5<((Q=`^n?J(9{8Q<@Lxz*ZSgpqmk!tKeXQr7JEq{Kn4IcoNayzMSdBN@8ZPGBLX*)TBn4`vp znjOT;PuKeEdYXlsKU(CIy{C(Zo&zB(3Y&ifwm6rgdA;oD{_db(^O07N)n(AV?(;%< zTU*DeOgOtLbRBq5-~66air~4nnzJcz<)(t~??C6B!dE`LKa%Sc=Mhz~t}*$(2Xa-g zxvI}G>q3t9DXpWgmlR5JqvapcndVTeo0~V|F4!#Kq<2{UECcF9CclyoiH_Eh& zYdp#`rjG!U$H@pQ{gC}l`e5GA(hFb8lNkU&zv{!3tyz?Gb4;s~J;{2x ziB}$y5^t>sle98uT^i}<$ z+dWU*J+c16dy^C|NfaYIYW^V$c+#t*XhjQVKcU*F;o4HE!puPkzOO_J+`M0UxH0LL znfmb|1afvI$NcJxU%S23m%|3D1Z;}+dzktHO0^1W{BD+)l`Q)|3x4@eoyX&YhfCZr z`v5sj1XUi(ZLjMu=u#phBd?H2{*fwGUhc7ElShfz=efaV1)TbB?n8?PnECk!BPTug z$Hif-=Q!0dD8r}glQO|Sm1q~8Tk*AvZhfE}-xgM9+i+XpgR>Y@->qE(w2yAfHIu2VYnw4tFur`S37^>nRd1?2Z*@-bjUM)AP--Tn2~qVJ7xUY)O6Dt-1Z;n}#c; z{99*DyiRZc7&YR*E=ioJf%c6YVHr$l~ikY$Q4r3K3k$>w@ zXjxp640fC|mxuR;T5*4BdV=>s^q?0O4u{8$x=s1y_HlQb37XEwA7B7@9MMGbXl*fB zJ@B}0NGILd0SPTB@jJqlI780jTiQEK#oY%ngk$1hCcfN!qR3f|=<%n0R43pnxB;8b z`Pj5&X{jTUy5w1T3fDk``B)Nc4e#HbQqrm3^78a2X$0*Jw_jRO24{We7^yVNeryJs za>=q=@N|t>eeqV)mGV;F`qy5N6E;&7qc|;pax}Ygbczat;J2P`svk_45%&wgGRN+l zZNkiQBi{?cZYm?|ZMVD9>Ux~yEH=J3oip&{Sy7x8nEn_R;Np^Bt#xaG*X=k5B@20M zf83^LMOBD#$(Wp;F;hV#ypL_#<66u8MfOHR%m|0T^%z;g;daU7-%f=fV*Vy1pk=w0 zus>xAa|d{ziP}yuv<4CIN1R7H^l5TgW`nJ`z|^zEY8OIN!zgl*De|!H_I6%R4$RE$ z(7WZZpt#s~M5J}EVe(5a4cW_Kot>nzAAXa42yxAnp_PDdO`%@yWv@YwRpc74I!Jue zZ_@k}?Rh@Hg&dr(jJ~F9r|)+9PVlIzoayJbP0HeZJBaqyUa$TVk<84f>HWn&va^6dBI&+I7g9-u zbhKkZ>HyanP1uSQ98KwBCTKODV&Sy8ajl&-)Z-wsYcZW&)kbnAo{6iIh6WSZbGV(q5?Et) z`3-*rxWQrN&{kWgaSPLY7S1`oC)(m zQ<-zg1RT`NGH7NFrUeX~^qfwgsd`Mhf0E{$j@?;e9?Ga%4={a_Wo9O5&svBHCcCuk zCB=tpgxQe;wMh|&dSsKtbCY7#y`R#$pD`3ly4v2~v|vju3zJ3)vU-h5*gCEt(!`-Qy+vDEcZ)yOfd#o}N=V4|y zkZsII!at6HC6yGHu7xJOMcnCD-3oYiMD_Z>o2UF>V>bFd?bU_o1)A>RJC0vG(IOaC z|Kxd7waK}2u2QeNsT%oSJ-_Z#yv8r4umWbh+Q;p8uo0q|z^s-YA6$<97yE_aKFjO=ZmndpNg4(3(K5tCSOKx+*l*EFs!F5v zwfXsa0hGb-N8%8ODz~w*uO>lF=ly|sj)|(z2jBdqw4|RSSN|Da`_L4DYFkawnOO6T zcQC-EKPW6hEHCINwNKs0FC?WAFd4XYz;1V86un{$2N4Z(R~C7zq<`wSS*edSu3H>v z6{PM^n$eE)BX*_FK%qwNv!*y|Weg`LCvS7|1|;cpA42$^K5l&X``HA+-ud)XbM{i& zn03gM^V$JBtUTS<-C`*ZVuku%PFO0yg*NyHXH|nWs!elx!)# zabM%Y?4X;K^UdElg|1P}D;72G@lW@uu}V}*FGPRQ{IvvBB@~Y{JnVda;KsgFfjo zZ<=X2f)|hdHMa3TED=N3dm${v!k6i;(WqUKS4H*}%xk}}C!og@|N!dPw`G9E5-LpKB}p%6$h@e*$4j=^{k_UhO&U4KAhmCPQ2t3JY-5HPXeYP zF-ptN03Y|EN}=O49&Bzi&7ZKOVl(Hnl+h~Wmj{QcdUY}g`(H%ur%==S8s;eR^9W;f zy(2v9&Pd}9rCETBCLyYT6B5gYt~}h`SL8I#nDQRD#V&3+T+g~b=j#bbBYS==*CH2W z3<-EfNSIyyn04c-hc`8E>YhydyFR0%Rhqvl{*bCub^()Uf4_HI z-JkUQYM_W3NIydRwzPw62R*d8kg)AnE?JXp#+zqy+ zoBY)&`gxPVrO=Q25|<{rG>eP%b=8D(iY_md*u7vUzs3ZhRbv#r(dW47p6Q*vx1T6t z?a+I&py8@(wyv4YB0iKpEd1u%2FsLx`q9w27VItEexKm~@z^9zs@hZk=)0e_I>#*m zg|-#g$MqfG1`{XVAAWQ8JFj`&L8WHZ-TQwTKv=C5uWy+FYMT?%wNe6pU4ses4^``r ztQjuTT`tpf0x6-W)7a$v4alB6u0u0JEqoqDpf9~J9%G+#+$*ZESpFo!IA$`mI$B!% z*!sYjIGyfJ(%BIIIq=ICk-;gt!sxEh*Dw|4V%9Vh%Orh$o%Fh7jIlq4Juv|OqGsHk^>+0pl zGapV4K3ZdoA-F-Rz0$c{Z{a`1-2NuUeo|@2p!G+ zgjSCptDn9q_I-aJ#?P>=xrAINd&B+OBG2Hlu?9|}If`^cx+WU`+%Bx})K4CiEkk#k za9BTkb>Os}s))1H@C=*`5qwnl5Pa8<=nhtA*F-*j=D(I+ZJm7XhQ4L4!~w9~{k`#F zA_BkLEB<7s{3|Z#mmP{5U^xDSh?-KA<^{8Zh@bs{K*Hn~oKQ(czFLc$L#yoiYAuv+ zx@l)*AX?wzs+!U6@!Tmt?7W&@3M)DHBY^|9iJ-(7J*58iAiCL);FvJ`Q|TJ;*WFjG zL#fTZDC%UG$6{nWd5&G+Jpo3TEH8RzAmMF^P;{@r<|>o`IXo}aID4kDy3QPI0LGlWL9EKT;{mj$^tidC@9 zcwiugwLC51_ZSV`<$`rLknya}>Y zxuYvLG7f8A!;pUdo4k-@__Ok6a{}!f+D#+E$yrHeV1QWznjm&X6zODA>(E29J_E69 z5$Kn6*?DW5w=GZh()`v#a9C5X2K{sc`|O0s{oxsn(@XN|?MjmgVk2(9iYyvTeNx6w z+Hmu;W+KO-uW`|6Q!@*Q$Lilr<9YwBW=H#jvjKKGzDxSrKE;25n%V-U)!1gIy`}9f z=ej1J`@#Q%us4r~I{M#+Efo<`mP$xM$SC_xgzWnghRROJHiN-bw#t@lW#45RWoIm* zkbNJ9A^S3$eK3~$)AxCPulx6Vp8NH>|DV^)oO9miea`zj*LA&Bdd$Z#H22vsVCUCu zR&cGxb;Nrq=$$U#~oQ5mjPSz|EkyuCNGw8XR$1vpZUy?so75ohPo^u#dH4l zd+6j?`gyFcSuzuL8=5-_6K~Utu0p`9}9JiQ|T5yX;TlIX4f|s4XUPrB3-o-b6jgBul z7Orul_C$eVvV=;-5J>#tU)|n>_%2c2ZwTfE z_TDxS^GFFMKfI$oUvfouroLi+dp{x3nWI&M=TYsMvXk!~U+jTJ z3RceN4tJtfmRjLsvpiZY`K{pdMR)FKl_y1U+-TTzEBGkkNMPXRyg}g{qYEo830FOk z$?u;t0F1tdp{3Z~7w@od`E9RIrvt07t(Q11ic9mulLU&`JcHNcy+HF{ki>V>#y;x< z6%pGg{mdi>Mk1Pb#JCk(9TL@6_CUD`c6wGVk}0~{^Q$0y&L}ym9)A2HG}&TzFm zYgxIu<&8O=p!4T;=kevdM$`gDmzGSWRGde4z+PfQOrY+c4N9^NY*gc|njPH?*XQ5X z0VTt``|m}a_-pO^YPLf&51!-GkEVEiJ@H`RDV{8P7BYmYePLWmxW&4mFwoCeqTGDC z^Xnu&E8x}ksk=TC=ez*oY`1Z}-C{uZ7_!36K7a5N-qN7_QiVusWL-8jFju?QV>*Mf zwm8L+h&WK{vxc%Kv^suWDW^1iAPLUZWOCfaukLhTrehTyDXl=z`HOsUk4m3(ONPDNuMD&-;&%~lT-BT2Q z)E1Ecmc-A0a$}{~+m(AVvGjI6sV4$aGrPlD3QBzc>GJ-GN))h+S2}Z7=uPxK&PY`< z?x$ZJ=-;SbYXii!TWTg#9)eAMS4Lm2-c%KTe&==uU5yk&<1{JS1$lX%B1UuO`g_3} zi4?T_z%#;lZ>5S-o48qsJ=0u>*j!YhTjuRI3k`5o6}EFeq;WCQil1aPTWWB>S&QP< zFtDd_e2;grX_31V68+Aj4Y@-zZimS41)D#h`d&(wA)E5HEN>4$+tEb?Lk+cKJ^YvE_Bw;zHAZ8F^`NL z{Am-o8P*2>VmA7^FuFoBzGl2Vk-tEoG&DhH;A@7X7dN*Ywn(dGKysy9%7e@g@;&*I z)f~GK^DY(b-HEeXo&L!R=y1s&>BxpQ<<6d-xgM( zaTe$yJd_~+z?V&y521Yva?oiCuW|U@Reh}Ub-APKW}+GWfp|QQ%DUryO#dF2|3yv4 zSC;!{A2!CE7~<;QOO5aa4L3aNL`Dn2w*PeCTCza{qD^ZDmv6y2YFX8zcIbqoEUwRv z==Ws3&=^Z7@v)Y)C875cB#R5%oa5^hLrjKvJ}dxlWp(GK`-wz2(y9J*q28byR_?BZ zZR*qFsZCU5FX;Z@NO7USk64=QZcw0?-L_S?xf5cIDu+8x497$yUcc)%u&DXE^7J6u zz$f~;INZDM@8NO9e;18Bu}1SBN2)^%p6Xx)TRd*a1Z;(GUnC;Vmcuw*m9swID15Q> zZEoKB`(GFpt);u)3n7UH7%sB^!<}?<$@9|&!HIerJ#3w%6TrzpSk5x+XM_n5a>Z&LvCse6?a zODr)**MsFs;q|lmcI;;S2OpBMhw%cX*$Uf5=}rafCo7bp+h#JTW7W-&t*@1?7?u>f zq%(eCfTLS){FMTdwFz`KW7S#rDx^GwxwGl~SPrgF0~S2W%8;pk7EGqt@sN;7VBHkx zm0;@q=BXL_{|~#WaOk2Gmtbk~+?{YO>(vl@E;;YT@)slE!gU+#G}sdxC2UF*A>(1q z!4}#eE>*h|cLXd*fF`(2)))Q|?q)SGg7@-N_J17UNhss#zCBY`);MZgB*rt`L?zmV zY}M!;>Tq$dutG71Da>WHH)() zVPu?Qk&V3D^C85K7J%;56n`|nu(VP;_|2p4AD7DenOj~(?jq+`DW~P{wwRZ&Vn@b! zX}HC`6`U^+`4(LUO>TzS>z*GrIuS$fK~T2Ah}l&)H#ZKbv8!s%KrI@3Tl5={Fstc? z2i5m7kB5DkM4IKr3tyj#_tj@3t_5v)0$dm_=j$+oB=h)G*`(?NRdq8D2hx|GF0+J5 zPbnj-;8uN`yBO0aW<+RJdsD&Z#*65hcZyfr^dar-!CrDbMtTt@4?;)*swmB*%X`i{Y3d z721hBYin_t#W{p9lr@DR3pI^3;uWo-+{(#P4}N%K(I$4DZH&&+;=Ag^VQ- zmC+WV&`D5-eN&_>Qqng`veyYOjvsUv&%>aS+^3t32I{+|@ruqIlLAVkHf0ZA=tWRa zji+66*^UW3SdZEBbQ%$H+Ah2$Bq@pHh`Q6-4I zcv|ypN^(HNLuvf@&!lJ^pFZxo|3UgJE~MDFYv>lCx}*iMgWFi@7L}1-E7NXWN>a-D zFDzgxCFEWeG1GoAvpC7QT}r{`5T}Zy^5sm?`@gW^0%p zb){i>2AX}|ITR|r-jeG1lzHCqe7hAnnZfaCT;v^fBjvXzuRrd+iAvkKjzqytot{0E z?=%Nz)UNK1JxYw+8x0Aig&Vt@61_>7o7u_9$kY7z-m(WHf+0m1x#OK0&%prBuQDF1 z6RYL3Z7oTT@iBP$=N<9MMR*}N`DelXfP_3N=1H&_`%Vp&O|v`KH2A|7xNzz z?$EsBBR=7*;Qr}WPVv{H?=IFej==p&|8x1t{L*#l8u5j#j;=P(jQ?xj-x&W7SboN+ z8Gg_CRtL9dD^-DGSBg@O((B!<9DOpicSa^f&uk=S0{sJ<40+9_*XC;NJI*UYUkBLR z>rPm}Gj_v7om=Za1Jx+QmsQS`v(P)C1OAC1=?PzuM6E(d11KqrE6w~vk(u6ZgROVn zxQtD)o9)D*oo1gog57Er`fNhMxlbuwVcs;LjdJ)fgPD zXGufs%7>6WCg;y1Zx6JHVQ7Dus8Z@kwJ_;nuE6G;;}JIQL!F)Jkjeohai#R8=mVr= z>@4Gr_uPsiTn1u(zbWtl8Gqd{>252*iaY{^pYEm(4F)XKY9era6gBSJvgxAI7v*jM z0wJDn871IzdZ=UjYB&6>n|CRj+;*`5@>6D3{!en8%Ejk40Cv87W%8*NV1O|M-$yho zOqXEdLK}C#)!fUoKIaL8sfR)xhT8K=OL-&a&Dgm6=YCYLe~w(CqF6^zT2TV*_>a$9 zY-&TE;rdf6E6%Ep<$oNkL8U{MUjKT}u4E%i`V1C5J6S8E=U|(tjJ>hlj}(TBB_%dd zb6S4SO8Rz0{TN{9%Q42z6AF}yQ2wI$b1TSzq^Khvu0`R(E^$z4LQ5le4H(Kz?^`lI zhXZJ-Gl9Va3a*C~t#2+rl%n&ZkXYQHdMF2cM2k;dp8JfZwSCJ(Y^Uli!@{1)pJ0UNxY+pOL)Ei*~70kj~;}F(1!X^UwY6{oYx*tRkQee5x6N z49?lp1pXOs&LZOt4!a5}72YfmMSlL`KLz7I>&^9;f{vMm&ZvtN_IVISKYc*ZvGlq% z#r-gpRr54wZ_khN=l24dDy_pf;*;PU-rVkglN-X#Gw)8D177&U;G)_8`}c3E^O`&%K0ZE%3BS~~%%Qso`pnJqnJsUdG1Vxtx#!Ts0gfgHHZFP&*|IbKBQx9V zOAU&AL2$;L?LCZcQ0D4tsaybR5FuB>*W9a zcQ^WPoPHdz@6^2S2XhUSaSp!~RQ$;)@r{D>pP(eZSvj6fE@AwweqVf4Tc?HkZ50?0 zI9Iy%FPahEg+;qmX!1#q@p2gg7k<8$;k-AT@Ku+eNC;k2RZ^kZ%&cS^@?!*-(MKW}TQJnCCE)!rxg_ud5hye=OCv&E`In2K9{Gma62y!_s4 z{GQyb!|p$fhhh+M4U^?XCXF;F8UUi=+0fQ=OhWUYyEg1-E~p6jwOXAVR`llcRgB)w z-^7Y_W4vs?9Y8f5h-Dix(yY(uX)o&YO@&?Qf+?eGe&LB7m zg-^*`gH-}SsW&-}Av{^H-`vUT=5zrvUS|0@nHf{RxFr_epAxpFc?vqOg@;H#aHB%;kJGUYB8Sqr;Ks4J6{D zyd3gx@*jUv-i0>R%HW;DV~#xQOYIgW0u@^qDlcCx@vBzd*35Mwga>~9R*v?L&1Mwc4($71i_U) zUFJKsmz(bdI~8$xi2VUM-qwq7;^}O=@>i$Rt|=dNSo75NMA{h}N@}k^KPm07^?$@G zyVJJ%i%IK>a!4KFL+yNPwtcEot78&=BRwds^T~Y)g_OG@FJD)J8jC^ZrqSu=LAinW z@wYGF4Uc@D2d7rE-{$5&GwJq-SQX(IvU-yP@FItM zMK6D}i^*HLIOML|*$TTIJUP3dU4KP&6Km}J{M5GXKHvvQ?iG!*hnEw_H{gHTy-JD4Sn1Kvi@yG;!151>4k`wR;7p|V70hg5-@pC82cobqk!g?z-$S=mP$>9~K$4|S((gtg z1>NSo)1Gk8BKY!T#_=GJNGd|R&jRm#c3AIZdCRH6^-J~b1LbEEsZZX+MZOPmI!~Ke z3KSkZ6N83r5k`OQ$POQ>xy%Qk$9SD^-ymirjw(wCnClveShEsx5h z54TcVI6!vN<4u0JorXFYFyE2}DJ#PYR#q2QW9Gnw+x7`( z3V!~FUzE*kzA>AmWQkVE3%RQ=@)ac-%!rL;IG$UyeLL%nWkxYc>HKMh4}sLKHwAC| zW9G)#3mxVKx@tV_@FY9`N!gABmy0zNVdZo)1vXyjI1`->aP6!d{$X127M>ilq{gj?$c6g0-QU^}K-b7>f-J7(Hd>tCb{9 zX@Qc}Pm)3Y=`PS{8r-VDG`44~%s++SO7UZ0THv(oU4hSoc~28S^Pyjl8aEQut~K@F z^0ZB~Ca)*G|8UxQsSCPc3@0z_x+@Fw293FWSU@27!n@!Uxjr?u8QCI`=5Q zi}!FIFeEuL-FlurApwWPSVsd`OIOZGA9 z>z(k=u77&V(oZ)d2u4K~Yc4;#~|m|L!$nzdPf$am^=fk zLB2-p6WU(9SiDcY`o|El(#?9G20I4E>5_n53YTSr*OhWDr^yzRP9e$gB2~*J7}HHS z_C%h)V2b4u_kXh`TCn1;=MLU)S3eTZ3Jun%D%w_=p?mn+J)YO@!}oZ*g*_e(sv zp??yIsVB?(;wx*|Aj{6IXLdW}uZqPC)mEK)mBElYcyIve8|b_qJNtc(`TAIX-h)Bp z_F6Y62FtN=YmfSooa`$b?J1hwyw(TJmcAp*Uf9pK+~sT+14#3d^p%jgCk4}fW^Lbv zkL85114h25XXxvWajszl#)MSWX4A0L@)-;3SI`(O#&o4$uPS}6v`Oe5Z@s`<)ZXo} z>cwj_5D>F*FmH*Ii2#Svc}-;L5=)}W>kVa@+ESN3VPPe?F<2LdJ}dO(42jULICDFt zq3OFo;RX7DCR;w@A⩰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 RK59S%Id{eeQF8iv)UTH*dHetX literal 0 HcmV?d00001 diff --git a/docs/tutorials/overview.md b/docs/tutorials/overview.md new file mode 100644 index 00000000..ba43f701 --- /dev/null +++ b/docs/tutorials/overview.md @@ -0,0 +1,32 @@ +--- +outline: [2, 4] +description: Learn abap2UI5 by building — step-by-step tutorials where every step is a complete class you can run in the browser. +--- +# Tutorials + +Learn abap2UI5 by building something. A tutorial is a sequence of steps, and +every step is one **complete class**: run it directly under its code with the +Run button, or copy it into your system and start it like any abap2UI5 app +(see the [Quickstart](/get_started/quickstart)). + +## Learning Path + +| Tutorial | Type | Content | +|---|---|---| +| [Walkthrough](/tutorials/walkthrough/overview) | Foundation | Builds a small invoice app from scratch, one step at a time — the app class and its lifecycle, views built in ABAP, events, data binding, lists, popups, and the structure real apps use. | + +More tutorials — deep dives into single topics — will follow. Until then, the +[Cookbook](/cookbook/overview) covers each topic as a reference chapter. + +## After the Tutorials + +Three catalogues of complete, tested apps continue where the tutorials stop: + +- [Samples](https://abap2ui5.github.io/samples/) — one app per pattern, along a guided learning path +- [Controls](https://abap2ui5.github.io/samples-controls/) — UI5 demo kit samples rebuilt as abap2UI5 apps, searchable by control +- [Stack](https://abap2ui5.github.io/samples-stack/) — integration samples per technology, from RAP to WebSocket + +And two pages of this site compress the walkthrough for readers in a hurry: +[Hello World](/get_started/hello_world) covers the first steps on a single +page, the [Full Example](/get_started/full_example) builds a realistic +selection-screen app on top of them. diff --git a/docs/tutorials/walkthrough/overview.md b/docs/tutorials/walkthrough/overview.md new file mode 100644 index 00000000..e4e20302 --- /dev/null +++ b/docs/tutorials/walkthrough/overview.md @@ -0,0 +1,42 @@ +--- +outline: [2, 4] +description: Build a small invoice app from scratch in eight steps — every step a complete, runnable abap2UI5 class. +--- +# Walkthrough + +In this tutorial we build a small invoice app from scratch and meet every +paradigm an abap2UI5 app is made of along the way: the app class and its +lifecycle, views written in ABAP, events, data binding, lists and popups. + +## Preview + +![The finished walkthrough app: a list of invoices with supplier and quantity, and a dialog editing the quantity of one row](/tutorials/walkthrough-preview.png) + +This is the app after the last step — about a hundred lines of ABAP, no +frontend project, no OData service. + +Each step is a **complete, runnable class** — the whole app as it stands at +that point, not a fragment. Press the Run button under the code to start it in +the browser, or copy the class into your system and launch it like any +abap2UI5 app (see the [Quickstart](/get_started/quickstart)). No step depends +on anything outside its own code, so you can also jump straight to the step +you are interested in. + +## Steps + +- **[Step 1: Hello World](/tutorials/walkthrough/step-1)** — the smallest possible app: one class, one method, one message. +- **[Step 2: A First View](/tutorials/walkthrough/step-2)** — render a UI5 view built entirely in ABAP. +- **[Step 3: Events](/tutorials/walkthrough/step-3)** — a button, a press event, and the lifecycle behind them. +- **[Step 4: Data Binding](/tutorials/walkthrough/step-4)** — an input field whose value reaches the server by itself. +- **[Step 5: List Binding](/tutorials/walkthrough/step-5)** — show an internal table as a UI5 list. +- **[Step 6: Row Events](/tutorials/walkthrough/step-6)** — react to a click on a row, and know which row it was. +- **[Step 7: Popups](/tutorials/walkthrough/step-7)** — edit a row in a dialog. +- **[Step 8: App Structure](/tutorials/walkthrough/step-8)** — refactor into the structure real apps use. + +## What You Should Know + +No prior abap2UI5 or UI5 experience is needed — the tutorial introduces every +concept as it appears. Basic ABAP (classes, methods, internal tables) is +assumed. If you want the framework installed in your own system first, do the +[Quickstart](/get_started/quickstart) — but the Run button works without any +installation at all. diff --git a/docs/tutorials/walkthrough/step-1.md b/docs/tutorials/walkthrough/step-1.md new file mode 100644 index 00000000..505340e6 --- /dev/null +++ b/docs/tutorials/walkthrough/step-1.md @@ -0,0 +1,43 @@ +--- +outline: [2, 4] +description: The smallest possible abap2UI5 app — one class, one method, one message. +--- +# Step 1: Hello World + +Every abap2UI5 app is one ABAP class implementing the interface +`z2ui5_if_app`. That interface has a single method, `main`, and the framework +calls it with one parameter: `client`, your only API. This is the smallest app +that can exist: + +```abap +CLASS zcl_app_walkthrough DEFINITION PUBLIC. + PUBLIC SECTION. + INTERFACES z2ui5_if_app. +ENDCLASS. + +CLASS zcl_app_walkthrough IMPLEMENTATION. + METHOD z2ui5_if_app~main. + client->message_box_display( `Hello World` ). + ENDMETHOD. +ENDCLASS. +``` + +Press **Run this example** under the code — the class starts right here in +the browser. To run it in your own system instead, copy it in, open the +abap2UI5 startup page (see the [Quickstart](/get_started/quickstart)), and +enter the class name. + +## What Just Happened + +- **No app project, no OData service, no frontend artifact.** The class *is* + the app. abap2UI5 follows a thin-frontend model: the browser only renders, + while all logic, state and data stay in ABAP on the server. +- **`main` runs on every roundtrip.** The framework calls it when the app + starts and again after every user interaction. Right now every call shows + the same message box; from [Step 3](/tutorials/walkthrough/step-3) on we + will tell the calls apart. +- **`client` is the whole API.** Displaying views and messages, reacting to + events, binding data — everything in this tutorial goes through this one + object. + +Next, we replace the message box with a real UI5 view. diff --git a/docs/tutorials/walkthrough/step-2.md b/docs/tutorials/walkthrough/step-2.md new file mode 100644 index 00000000..0888e5d0 --- /dev/null +++ b/docs/tutorials/walkthrough/step-2.md @@ -0,0 +1,62 @@ +--- +outline: [2, 4] +description: Render a UI5 XML view built entirely in ABAP with the view builder. +--- +# Step 2: A First View + +A message box is not much of a UI. UI5 apps describe their screens as XML +views — and in abap2UI5 you write that view in ABAP, with +`z2ui5_cl_ui5_view_builder`: + +```abap +CLASS zcl_app_walkthrough DEFINITION PUBLIC. + PUBLIC SECTION. + INTERFACES z2ui5_if_app. +ENDCLASS. + +CLASS zcl_app_walkthrough IMPLEMENTATION. + METHOD z2ui5_if_app~main. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `Walkthrough - Step 2` + + )->tag( `Text` + )->a( n = `text` v = `Hello World` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. +ENDCLASS. +``` + +## The Four Verbs + +You are writing a UI5 XML view, one control per call. The builder has four +verbs and no list of controls to look up — every UI5 control, property and +aggregation is available, because the builder never knew any of them by name: + +| | | +| --- | --- | +| `ele( )` | add a control and **descend** into it — for a container | +| `tag( )` | add a control and **stay** — for a leaf | +| `a( )` | set **one** attribute on the control the chain points at | +| `end( )` | ascend to the parent | + +The single rule: `a( )` applies to the control the chain currently points at, +so attributes follow their control — and a control gets them *before* its +first child. The root `mvc:View` and its `xmlns` declarations are written by +hand, exactly as in a real UI5 view. `stringify( )` renders the XML from the +root, and `view_display( )` sends it to the browser. + +The indentation mirrors the XML tree — `Text` sits inside `Page` inside +`Shell` — which is what makes the chain readable as the view it builds. The +full layout rules are on the [View → Definition](/cookbook/view/definition) +page. + +Next, the app gets its first button — and with it, the app lifecycle. diff --git a/docs/tutorials/walkthrough/step-3.md b/docs/tutorials/walkthrough/step-3.md new file mode 100644 index 00000000..c6d1f947 --- /dev/null +++ b/docs/tutorials/walkthrough/step-3.md @@ -0,0 +1,77 @@ +--- +outline: [2, 4] +description: A button, a press event, and the abap2UI5 lifecycle behind them. +--- +# Step 3: Events + +The framework calls `main` on **every** roundtrip — on the initial start and +again after each user interaction. As soon as an app reacts to input, `main` +has to tell those calls apart. That job falls to the lifecycle checks, and +`main` becomes a dispatcher: + +```abap +CLASS zcl_app_walkthrough DEFINITION PUBLIC. + PUBLIC SECTION. + INTERFACES z2ui5_if_app. +ENDCLASS. + +CLASS zcl_app_walkthrough IMPLEMENTATION. + METHOD z2ui5_if_app~main. + + IF client->check_on_navigated( ). + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `Walkthrough - Step 3` + + )->tag( `Text` + )->a( n = `text` v = `Hello World` + )->tag( `Button` + )->a( n = `text` v = `Say Hello` + )->a( n = `press` v = client->_event( `SAY_HELLO` ) ). + + client->view_display( view->stringify( ) ). + + ELSEIF client->check_on_event( `SAY_HELLO` ). + + client->message_toast_display( `Hello from abap2UI5!` ). + + ENDIF. + + ENDMETHOD. +ENDCLASS. +``` + +## The Lifecycle + +```text +┌──────────┐ ┌──────────┐ ┌──────────┐ +│ Browser │──────>│ main() │──────>│ Browser │ +│ (Start) │ HTTP │ view │ HTTP │ (View) │ +└──────────┘ └──────────┘ └────┬─────┘ + │ user clicks +┌──────────┐ ┌──────────┐ ┌────┴─────┐ +│ Browser │<──────│ main() │<──────│ Browser │ +│ (Toast) │ HTTP │ event │ HTTP │ (Event) │ +└──────────┘ └──────────┘ └──────────┘ +``` + +- **`check_on_navigated( )`** is true when the app has to draw its screen — + on the first start, and again whenever the user navigates back to it later. + This branch displays the view. +- **`check_on_event( )`** is true when the user triggered an event. The + argument names which one. +- ``client->_event( `SAY_HELLO` )`` wires the button: it returns the press + handler that sends the event — with the name you chose — back to `main`. + +Each `check_*` method is true only for its own phase, so the `IF`/`ELSEIF` +chain cleanly dispatches every roundtrip. The full picture is on the +[Life Cycle](/cookbook/event_navigation/life_cycle) page. + +Next: data leaves the browser and reaches your class — without a single line +of transfer code. diff --git a/docs/tutorials/walkthrough/step-4.md b/docs/tutorials/walkthrough/step-4.md new file mode 100644 index 00000000..876cc83c --- /dev/null +++ b/docs/tutorials/walkthrough/step-4.md @@ -0,0 +1,67 @@ +--- +outline: [2, 4] +description: Bind an input field to a class attribute — data reaches the server by itself. +--- +# Step 4: Data Binding + +So far the app only talks. Now the user answers: an input field replaces the +text, and whatever is typed into it arrives in your class — without a single +line of transfer code: + +```abap +CLASS zcl_app_walkthrough DEFINITION PUBLIC. + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + DATA recipient TYPE string. +ENDCLASS. + +CLASS zcl_app_walkthrough IMPLEMENTATION. + METHOD z2ui5_if_app~main. + + IF client->check_on_navigated( ). + + recipient = `World`. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `Walkthrough - Step 4` + + )->tag( `Input` + )->a( n = `value` v = client->_bind( recipient ) + )->tag( `Button` + )->a( n = `text` v = `Say Hello` + )->a( n = `press` v = client->_event( `SAY_HELLO` ) ). + + client->view_display( view->stringify( ) ). + + ELSEIF client->check_on_event( `SAY_HELLO` ). + + client->message_toast_display( |Hello { recipient }!| ). + + ENDIF. + + ENDMETHOD. +ENDCLASS. +``` + +## How the Value Travels + +- **`DATA recipient TYPE string`** — a public attribute is the model. The + framework serializes public attributes after every roundtrip and restores + them before the next one, so your class keeps its state without any session + handling. The attribute must be in the `PUBLIC SECTION`: the framework + reads it dynamically and silently ignores private or protected ones (full + rules on the [Binding](/cookbook/model/binding) page). +- **`client->_bind( recipient )`** connects the attribute to the `value` + property of the input. When the button fires, the browser sends the current + screen state along with the event — by the time your `ELSEIF` branch runs, + `recipient` already holds what the user typed. +- **Type it, press the button** — the toast greets whatever name is in the + field. Two-way, and you wrote no transfer code. + +Next, the same binding moves a whole internal table into a list. diff --git a/docs/tutorials/walkthrough/step-5.md b/docs/tutorials/walkthrough/step-5.md new file mode 100644 index 00000000..dd86327c --- /dev/null +++ b/docs/tutorials/walkthrough/step-5.md @@ -0,0 +1,82 @@ +--- +outline: [2, 4] +description: Show an internal table as a UI5 list with aggregation binding. +--- +# Step 5: List Binding + +An app that greets is nice; an app that shows data is useful. The invoice +app starts here: an internal table of invoices, bound to a UI5 list — +one row template, repeated per line of the table: + +```abap +CLASS zcl_app_walkthrough DEFINITION PUBLIC. + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_invoice, + product TYPE string, + supplier TYPE string, + quantity TYPE string, + END OF ty_s_invoice. + + DATA t_invoices TYPE STANDARD TABLE OF ty_s_invoice WITH EMPTY KEY. +ENDCLASS. + +CLASS zcl_app_walkthrough IMPLEMENTATION. + METHOD z2ui5_if_app~main. + + IF client->check_on_navigated( ). + + t_invoices = VALUE #( + ( product = `Pineapple` supplier = `ACME` quantity = `21` ) + ( product = `Milk` supplier = `Green Growers` quantity = `4` ) + ( product = `Canned Beans` supplier = `Corner Deli` quantity = `3` ) + ( product = `Salad` supplier = `Green Growers` quantity = `2` ) + ( product = `Bread` supplier = `Corner Deli` quantity = `1` ) ). + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `Walkthrough - Step 5` + + )->ele( `List` + )->a( n = `headerText` v = `Invoices` + )->a( n = `items` v = client->_bind( t_invoices ) + + )->ele( `items` + )->tag( `StandardListItem` + )->a( n = `title` v = `{PRODUCT}` + )->a( n = `description` v = `{SUPPLIER}` + )->a( n = `info` v = `{QUANTITY}` ). + + client->view_display( view->stringify( ) ). + + ENDIF. + + ENDMETHOD. +ENDCLASS. +``` + +## One Template, Many Rows + +- **The table binds like the string did.** ``client->_bind( t_invoices )`` + on the list's `items` aggregation — the same call as in + [Step 4](/tutorials/walkthrough/step-4), just with an internal table behind + it. +- **The `StandardListItem` is a template, not a row.** UI5 clones it once per + line of the table. Inside the template, `{PRODUCT}` is a plain UI5 binding + path relative to the row — the framework maps your ABAP field names, in + uppercase. +- **The data is initialized in the `check_on_navigated` branch**, right + before the view. In a real app this is where your `SELECT` runs; the + tutorial keeps demo data so the class stays self-contained. + +Tables (`sap.m.Table` with columns and cells) bind exactly the same way — +the [Tables](/cookbook/model/tables) page has that variant. + +Next, the rows learn to react to a click. diff --git a/docs/tutorials/walkthrough/step-6.md b/docs/tutorials/walkthrough/step-6.md new file mode 100644 index 00000000..9d32b013 --- /dev/null +++ b/docs/tutorials/walkthrough/step-6.md @@ -0,0 +1,92 @@ +--- +outline: [2, 4] +description: React to a click on a list row and know which row it was — events with arguments. +--- +# Step 6: Row Events + +A list of five rows raises a question [Step 3](/tutorials/walkthrough/step-3) +did not have: *which* row was clicked? The event stays the same — what is new +is the argument it carries: + +```abap +CLASS zcl_app_walkthrough DEFINITION PUBLIC. + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_invoice, + product TYPE string, + supplier TYPE string, + quantity TYPE string, + END OF ty_s_invoice. + + DATA t_invoices TYPE STANDARD TABLE OF ty_s_invoice WITH EMPTY KEY. +ENDCLASS. + +CLASS zcl_app_walkthrough IMPLEMENTATION. + METHOD z2ui5_if_app~main. + + IF client->check_on_navigated( ). + + t_invoices = VALUE #( + ( product = `Pineapple` supplier = `ACME` quantity = `21` ) + ( product = `Milk` supplier = `Green Growers` quantity = `4` ) + ( product = `Canned Beans` supplier = `Corner Deli` quantity = `3` ) + ( product = `Salad` supplier = `Green Growers` quantity = `2` ) + ( product = `Bread` supplier = `Corner Deli` quantity = `1` ) ). + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `Walkthrough - Step 6` + + )->ele( `List` + )->a( n = `headerText` v = `Invoices` + )->a( n = `items` v = client->_bind( t_invoices ) + + )->ele( `items` + )->tag( `StandardListItem` + )->a( n = `title` v = `{PRODUCT}` + )->a( n = `description` v = `{SUPPLIER}` + )->a( n = `info` v = `{QUANTITY}` + )->a( n = `type` v = `Active` + )->a( n = `press` v = client->_event( val = `SHOW_INVOICE` + t_arg = VALUE #( ( `${PRODUCT}` ) ) ) ). + + client->view_display( view->stringify( ) ). + + ELSEIF client->check_on_event( `SHOW_INVOICE` ). + + DATA(s_invoice) = VALUE #( t_invoices[ product = client->get_event_arg( ) ] OPTIONAL ). + client->message_toast_display( |{ s_invoice-quantity } x { s_invoice-product } from { s_invoice-supplier }| ). + + ENDIF. + + ENDMETHOD. +ENDCLASS. +``` + +## Events With Arguments + +- **`type = Active`** makes the whole row clickable and gives it press + feedback; the `press` handler sits on the row template, so every row fires + the same event. +- **The argument rides with the event:** + ``t_arg = VALUE #( ( `${PRODUCT}` ) )``. The `${...}` syntax is resolved + *per row* in the browser — each clone of the template carries its own + product name. On the server, `client->get_event_arg( )` returns it, and a + table read finds the row. +- **The state is still there.** `t_invoices` was filled in the + `check_on_navigated` branch of an earlier roundtrip — the framework + restored it before this one, so the event handler can read it. That is the + serialization from [Step 4](/tutorials/walkthrough/step-4) doing its job. + +More on both event directions — server events like this one, and events +handled purely in the browser — under +[Event → Backend](/cookbook/event_navigation/backend). + +A toast is a modest way to show an invoice. Next, a real dialog. diff --git a/docs/tutorials/walkthrough/step-7.md b/docs/tutorials/walkthrough/step-7.md new file mode 100644 index 00000000..1fc44130 --- /dev/null +++ b/docs/tutorials/walkthrough/step-7.md @@ -0,0 +1,130 @@ +--- +outline: [2, 4] +description: Edit a list row in a dialog — popups with the same builder, displayed over the running view. +--- +# Step 7: Popups + +Clicking a row now opens a dialog to edit the invoice's quantity. A popup is +built with the same builder and the same verbs as the view — only the root +element changes (`core:FragmentDefinition` instead of `mvc:View`), and it is +handed to `popup_display( )` while the main view stays untouched behind it: + +```abap +CLASS zcl_app_walkthrough DEFINITION PUBLIC. + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_invoice, + product TYPE string, + supplier TYPE string, + quantity TYPE string, + END OF ty_s_invoice. + + DATA t_invoices TYPE STANDARD TABLE OF ty_s_invoice WITH EMPTY KEY. + DATA s_edit TYPE ty_s_invoice. +ENDCLASS. + +CLASS zcl_app_walkthrough IMPLEMENTATION. + METHOD z2ui5_if_app~main. + + IF client->check_on_navigated( ). + + t_invoices = VALUE #( + ( product = `Pineapple` supplier = `ACME` quantity = `21` ) + ( product = `Milk` supplier = `Green Growers` quantity = `4` ) + ( product = `Canned Beans` supplier = `Corner Deli` quantity = `3` ) + ( product = `Salad` supplier = `Green Growers` quantity = `2` ) + ( product = `Bread` supplier = `Corner Deli` quantity = `1` ) ). + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `Walkthrough - Step 7` + + )->ele( `List` + )->a( n = `headerText` v = `Invoices` + )->a( n = `items` v = client->_bind( t_invoices ) + + )->ele( `items` + )->tag( `StandardListItem` + )->a( n = `title` v = `{PRODUCT}` + )->a( n = `description` v = `{SUPPLIER}` + )->a( n = `info` v = `{QUANTITY}` + )->a( n = `type` v = `Active` + )->a( n = `press` v = client->_event( val = `EDIT` + t_arg = VALUE #( ( `${PRODUCT}` ) ) ) ). + + client->view_display( view->stringify( ) ). + + ELSEIF client->check_on_event( `EDIT` ). + + s_edit = VALUE #( t_invoices[ product = client->get_event_arg( ) ] OPTIONAL ). + + DATA(popup) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `FragmentDefinition` ns = `core` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:core` v = `sap.ui.core` + + )->ele( `Dialog` + )->a( n = `title` v = |Edit { s_edit-product }| + + )->ele( `content` + + )->tag( `Label` + )->a( n = `text` v = `Quantity` + )->tag( `Input` + )->a( n = `value` v = client->_bind( s_edit-quantity ) + + )->end( + + )->ele( `buttons` + + )->tag( `Button` + )->a( n = `text` v = `Cancel` + )->a( n = `press` v = client->_event( `CANCEL` ) + )->tag( `Button` + )->a( n = `text` v = `Save` + )->a( n = `press` v = client->_event( `SAVE` ) + )->a( n = `type` v = `Emphasized` ). + + client->popup_display( popup->stringify( ) ). + + ELSEIF client->check_on_event( `SAVE` ). + + t_invoices[ product = s_edit-product ]-quantity = s_edit-quantity. + client->popup_destroy( ). + client->message_toast_display( |{ s_edit-product } updated.| ). + + ELSEIF client->check_on_event( `CANCEL` ). + + client->popup_destroy( ). + + ENDIF. + + ENDMETHOD. +ENDCLASS. +``` + +## What Is New + +- **`s_edit` is the popup's model.** The `EDIT` handler copies the clicked + row into it, and the dialog's input binds to `s_edit-quantity` — by the + time `SAVE` arrives, the attribute already holds the new value. +- **`popup_display( )` / `popup_destroy( )`** show and close the dialog. The + main view is never rebuilt — it waits behind the popup. +- **No refresh call after `SAVE`.** The handler changes `t_invoices` and + does nothing else: every roundtrip that changed bound data pushes the new + model to the browser by itself, and the list updates. (You may see + `view_model_update( )` in older code — it is deliberately empty and does + nothing.) + +Popovers, message boxes and the built-in popup classes are covered under +[Popup, Popover](/cookbook/popup_popover/popup). + +The app is complete — one last step separates it into the structure real +apps use. diff --git a/docs/tutorials/walkthrough/step-8.md b/docs/tutorials/walkthrough/step-8.md new file mode 100644 index 00000000..3e8d6eb5 --- /dev/null +++ b/docs/tutorials/walkthrough/step-8.md @@ -0,0 +1,164 @@ +--- +outline: [2, 4] +description: Refactor the walkthrough app into the structure real apps use — a dispatcher, named methods, protected state. +--- +# Step 8: App Structure + +The app is complete, but everything lives in one `main` method. Real apps — +the framework's own, and the sample catalogues' — separate the phases into +methods, so this last step changes no behavior at all: it puts the code where +a reader expects it. + +```abap +CLASS zcl_app_walkthrough DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_invoice, + product TYPE string, + supplier TYPE string, + quantity TYPE string, + END OF ty_s_invoice. + + DATA t_invoices TYPE STANDARD TABLE OF ty_s_invoice WITH EMPTY KEY. + DATA s_edit TYPE ty_s_invoice. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS on_event. + METHODS view_display. + METHODS popup_edit_display. + +ENDCLASS. + +CLASS zcl_app_walkthrough IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get( )-event. + WHEN `EDIT`. + s_edit = VALUE #( t_invoices[ product = client->get_event_arg( ) ] OPTIONAL ). + popup_edit_display( ). + WHEN `SAVE`. + t_invoices[ product = s_edit-product ]-quantity = s_edit-quantity. + client->popup_destroy( ). + client->message_toast_display( |{ s_edit-product } updated.| ). + WHEN `CANCEL`. + client->popup_destroy( ). + ENDCASE. + + ENDMETHOD. + + + METHOD view_display. + + t_invoices = VALUE #( + ( product = `Pineapple` supplier = `ACME` quantity = `21` ) + ( product = `Milk` supplier = `Green Growers` quantity = `4` ) + ( product = `Canned Beans` supplier = `Corner Deli` quantity = `3` ) + ( product = `Salad` supplier = `Green Growers` quantity = `2` ) + ( product = `Bread` supplier = `Corner Deli` quantity = `1` ) ). + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `Walkthrough - Step 8` + + )->ele( `List` + )->a( n = `headerText` v = `Invoices` + )->a( n = `items` v = client->_bind( t_invoices ) + + )->ele( `items` + )->tag( `StandardListItem` + )->a( n = `title` v = `{PRODUCT}` + )->a( n = `description` v = `{SUPPLIER}` + )->a( n = `info` v = `{QUANTITY}` + )->a( n = `type` v = `Active` + )->a( n = `press` v = client->_event( val = `EDIT` + t_arg = VALUE #( ( `${PRODUCT}` ) ) ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + + + METHOD popup_edit_display. + + DATA(popup) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `FragmentDefinition` ns = `core` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:core` v = `sap.ui.core` + + )->ele( `Dialog` + )->a( n = `title` v = |Edit { s_edit-product }| + + )->ele( `content` + + )->tag( `Label` + )->a( n = `text` v = `Quantity` + )->tag( `Input` + )->a( n = `value` v = client->_bind( s_edit-quantity ) + + )->end( + + )->ele( `buttons` + + )->tag( `Button` + )->a( n = `text` v = `Cancel` + )->a( n = `press` v = client->_event( `CANCEL` ) + )->tag( `Button` + )->a( n = `text` v = `Save` + )->a( n = `press` v = client->_event( `SAVE` ) + )->a( n = `type` v = `Emphasized` ). + + client->popup_display( popup->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. +``` + +## The Structure + +- **`main` is a pure dispatcher.** It stashes `client` in a protected + attribute — so the handler methods can use it without passing it around — + and routes each roundtrip to the method for its phase. `check_on_event( )` + without an argument is true for *any* event; the `CASE` in `on_event` + decides which one. +- **State stays public, everything else protected.** Public attributes are + the serialized, browser-visible model — bound data and nothing more. The + `client` reference and the methods are implementation. +- **One method per screen.** `view_display` for the page, `popup_edit_display` + for the dialog. When an app grows, this is the seam it grows along. + +## Where to Go From Here + +You have seen everything an abap2UI5 app is made of. Three places continue +from here: + +- the [Full Example](/get_started/full_example) — the same structure applied + to a realistic selection-screen app with a table and database access, +- the [Cookbook](/cookbook/overview) — every topic of this walkthrough as a + reference chapter, from [value helps](/cookbook/expert_more/value_help) to + [navigation between apps](/cookbook/event_navigation/navigation), +- the [sample catalogues](https://abap2ui5.github.io/samples/) — complete, + tested apps for nearly every pattern, each one class like here.