More Related Content
Similar to 第142回Smalltalk勉強会 - PharoJSで作るWebアプリケーション (20)
More from Masashi Umezawa (20)
第142回Smalltalk勉強会 - PharoJSで作るWebアプリケーション
- 8. DOMの要素にアクセス
● document, window などにアクセス可能
○ テキストインプットに何か入力
■ #nameTextInput のidが
振られているので...
○ Playgroundで
(document getElementById: 'nameTextInput') value. を"print it"すると
- 12. PjHelloWorldApp の構成要素
● index.html
○ 基本的にはidを付与した要素が並んでいるだけ
○ 生成されたindex.js を読み込むようになっている
<div style="position:relative;width:100%;max-width:471px;">
<img src="pharoJsLogo.png" alt="PharoJS Logo" style="width:100%;" />
</div>
<input type="text" id="nameTextInput"> <button id="sayHelloButton">Say
Hello</button>
<p><strong><span id="greetingMessageContainer"></span></strong></p>
<script language="javascript" type="text/javascript" src="index.js">
</script>
- 13. PjHelloWorldApp クラスを見る - start メソッド
● アプリのエントリポイント
○ getElementById: でDOM要素を取得
○ addEventListener:block: でイベントハンドラを登録
| nameInput sayHelloButton greetingMessageContainer |
super start.
user := PjUser new.
nameInput := document getElementById: #nameTextInput.
sayHelloButton := document getElementById: #sayHelloButton.
nameInput addEventListener: #change block: [ user name: nameInput value ].
greetingMessageContainer := document getElementById:
#greetingMessageContainer.
sayHelloButton addEventListener: #click block: [ greetingMessageContainer
innerHTML: 'Hello ' , user name ]
- 14. PjHelloWorldAppクラスを見る - appClasses クラスメソッド
● 生成対象となるクラス群を示す
○ 自身に加え、PJHelloWorldAppが使用しているPjUserクラスも追加
● トランスパイル時に内部的に呼び出されるメソッドのため
<pharoJsSkip> プラグマがついている
■ メソッドが生成対象にならない
appClasses
<pharoJsSkip>
^ super appClasses, { PjUser }
- 15. PjHelloWorldAppクラスを見る - PjUserの使用
● インスタンス変数userを定義
● PjUser自身はnameのgetter, setterを持つだけの単純なModelクラス
● startメソッド内でnewして普通に使用している
PjFileBasedWebApp subclass: #PjHelloWorldApp
instanceVariableNames: 'user'
classVariableNames: ''
package: 'PharoJs-Examples-HelloWorld'
user := PjUser new.
greetingMessageContainer innerHTML: 'Hello ' , user name
- 16. Smalltalk から JavaScript への変換ルール
● 大体は想像通り
● SmalltalkのメソッドはJavaScript側では pj_ の接頭辞がつく
○ 既存のJavaScriptの関数名と衝突しないようにするため
○ MNUもシミュレートされる
● Smalltalk側で js_ の接頭辞をつけておくと js_ が取れた形でJavaScript
の関数になる
○ MNUにならない
- 18. newメッセージの変換ルール
● ClassA new → pj_new() となる
○ 内部でpj_basicNew()を呼び出し、
最終的にコンストラクタを呼び出している
● コンストラクタに引数を渡したい場合
○ Class new: param
○ Class new: paramA with: paramB
- 19. Globalオブジェクト
● window, document などは最初から参照できる
● グローバルオブジェクト群は PjUniversalGlobals, PjDomGlobals
などのプール辞書に格納されている
○ 直接参照できない場合、メッセージ送信で取得
■ window Event
■ window localStorage
など
- 20. インラインJavaScript
● メソッド定義時に javascript: プラグマを指定することでJavaScriptをその
まま書ける
● 例: log: anObject メソッドの実体を console.dir(anObject) にする
log: anObject
<javascript: 'console.dir(anObject)'>
● これで利用時は self log: someObj と書ける
- 21. playground / export 再び
● Appクラスに playground メッセージを送ると
○ index.js にbridgeの機能が入る
■ 内部的にWebSocketを利用
■ デバッグ機能なのでデプロイ時には不要
● Appクラスに exportApp メッセージを送ると
○ index.js にbridge は入らない
■ Smalltalkの基本クラス群は入ったまま
■ デプロイ時にはこちらを使う
■ terserなどでminifyする
- 24. ライブラリの指定方法
● index.html にCDNのリンクを書く
○ index.htmlは自動生成されない
○ 既存のものをコピーするなどして自作
…
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css"
>
…
<script src="https://unpkg.com/mithril/mithril.js"></script>
<script language="javascript" type="text/javascript"
src="index.js"></script>
</body>
- 27. index.html
…
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
<title>Counter</title>
</head>
<body>
<div id="app" class="hero">
<div class="container">
<p class="title">Counter</p>
<button id="resetButton" class="button is-danger">Reset</button>
<button id="incrementButton" class="button is-primary">Increment</button>
</div>
</div>
<script src="https://unpkg.com/mithril/mithril.js"></script>
<script language="javascript" type="text/javascript" src="index.js"></script>
</body>
- 29. htmlのアプリ表示部分
<div id="app" class="hero">
<div class="container">
<p class="title">Counter</p>
<button id="resetButton" class="button
is-danger">Reset</button>
<button id="incrementButton" class="button
is-primary">Increment</button>
</div>
</div>
- 34. m("main", [
m("h1", {class: "title"}, "My first app"),
m("button", "A button"),
])
<main>
<h1 class="title">My first app</h1>
<button>A button</button>
</main>
m(selector, attributes, children) の利用
● m関数の入れ子で仮想DOMのツリーが作られる
● HTMLだと...
- 35. var vnodes = m("main", [
m("h1", {class: "title"}, "My first app"),
m("button", "A button"),
])
m.render(document.getElementById("app"), vnodes)
render(element, vnodes) によるHTML生成
● render関数でDOMにHTMLが適用される
● これで id="app"の子要素が、mainタグによる要素に置きかわる
- 36. インラインJavaScriptメソッド作成 - m:attrs:children:
● mやrenderを楽に使うためPharoJS側にメソッドを用意しておく
_m: selector attrs: attrs children: children
<javascript: 'return m(selector, attrs, children)'>
m: selector attrs: attrs children: children
^ self _m: selector attrs: attrs asDictionary children: children
SsMithrilApp Mithril API
- 37. インラインJavaScriptメソッド作成 - render:vnodes:
● 便利な renderAt:vnodes: も定義した
render: element vnodes: vnodes
<javascript: 'm.render(element, vnodes)'>
renderAt: elementId vnodes: vnodes
^ self render: (self elementAt: elementId) vnodes: vnodes
SsMithrilApp Mithril API
- 38. html側にcounter表示箇所を追加
<div id="app" class="hero">
<div class="container">
<p class="title">Counter</p>
<p id="counter" class="subtitle">0</p>
<button id="resetButton" class="button
is-danger">Reset</button>
<button id="incrementButton" class="button
is-primary">Increment</button>
</div>
</div>
- 42. TraitにAPIメソッドをまとめる - SsTMithril作成
● Mithril.js 関係のメソッド群をTraitにまとめておくと便利
Trait named: #SsTMithril
instanceVariableNames: ''
package: 'StStudy-Pjs-Mithril'
○ Mithril API のメソッドカテゴリごとSsTMithrilに移動
- 43. TraitにAPIメソッドをまとめる - SsTMithril利用
● SsMithrilAppでSsTMithrilをuse:
○ SsMithrilApp がすっきり
○ TraitsがJavaScriptで使えてハッピー
PjFileBasedWebApp subclass: #SsMithrilApp
uses: SsTMithril
instanceVariableNames: 'counter'
classVariableNames: ''
package: 'StStudy-Pjs-Mithril'
- 47. PjApplication class >> inAppFolderRunCommandLine: aBlock のパッチ
inAppFolderRunCommandLine: aBlock
<pharoJsSkip>
| commandLine |
commandLine := String streamContents: [ :str |
str
<< 'cd ';
<< self appFullJsFolderPath pathString;
<< $;.
aBlock value: str ].
OSPlatform current isWindows ifTrue:[
^ WBWindowsWebBrowser
shellExecute: 'Open' file: 'pwsh' parameters:'-Command "',commandLine,'"'
directory: '' show: 5.
].
LibC system: commandLine
- 48. 簡単な Express.js アプリを作ってみる
● 既存のJavaScriptやCSSのライブラリも利用
● JavaScript
○ EJS (サーバ側)
■ 軽量なテンプレートエンジン
○ htmx (クライアント側)
■ HTMLの属性指定でAjaxやDOM置き換えを可能にするライブラリ
○ hyperscript (クライアント側)
■ HTMLの属性指定で簡易なスクリプトを書けるライブラリ
● CSS
○ Bulma
- 52. TIPS: nodemonの導入
● export時にアプリのリロードを自動的に行わせるため nodemon を
入れておくと良い
$ npm install - D nodemon
● package.jsonのscriptsセクションを編集
"scripts": {
"start": "nodemon index.js",
"debug": "nodemon --inspect index.js"
}
$ npm run start
● 以下でアプリを開始すると、exportの度にリロードが行われる
- 59. partials/main.ejs
● MithrilAppのindex.htmlからapp部分を拝借
○ 後でhtmxを使うため hx-get, hx-target の指定が入っている
<div id="app" class="hero">
<div class="container">
<p class="title">Counter</p>
<p id="counter" class="subtitle is-size-1">0</p>
<button hx-get="/reset" hx-target="#counter" class="button is-danger">
Reset
</button>
<button hx-get="/increment" hx-target="#counter" class="button is-primary">
Increment
</button>
</div>
</div>
- 60. EJSの利用
● initRoutes のhandler: 内をejsを使う形に変更
initRoutes
server set: 'view engine' to: 'ejs'.
server get: '/' handler: [ :req :res | self renderIndex: res ]
initialization
SsExpressCounterApp
renderIndex: res
res render: 'pages/index'
rendering
SsExpressCounterApp
○ index.ejs の内容が表示されるようになる
- 61. Counter部分のレンダリング
● ボタンを押すと404になる
● htmxの hx-get で/increment, /reset に対してGET は飛ぶようになって
いる
● hx-targetの指定で、GETの結果のHTMLはid="counter"のタグに反映さ
れる
○ ルーティングを追加し、counter部分のHTMLを返すようにする
<button hx-get="/increment" hx-target="#counter" class="button is-primary">
Increment
</button>
- 62. partials/counter.ejs
● <%= counter %> でcounterの値を適用してレンダリング
○ ついでにstyleもcounterの値に応じて変わるようにした
<div style="<%= counter % 3 == 0 ?
'color:red' : 'color:gray' %>">
<%= counter %>
</div>
- 63. initRoutesの修正
● increment, reset のルーティングを追加
○ GETリクエストがあったらモデルを更新し renderCounter: でレンダリング
initRoutes
server set: 'view engine' to: 'ejs'.
server get: '/' handler: [ :req :res | self renderIndex: res ].
server get: '/increment' handler: [ :req :res |
counter increment.
self renderCounter: res ].
server get: '/reset' handler: [ :req :res |
counter reset.
self renderCounter: res ]
initialization
SsExpressCounterApp
- 65. hyperscriptで連打対策
● ちょっとしたロジックをクライアント側に入れたい時
○ Incrementボタン連打の間隔をhyperscriptで調整
<div id="app" class="hero">
<div class="container">
<p class="title">Counter</p>
<p id="counter" class="subtitle is-size-1">0</p>
<button hx-get="/reset" hx-target="#counter" class="button is-danger">
Reset
</button>
<button hx-get="/increment" hx-target="#counter" class="button is-primary"
_="on click toggle [@disabled='true'] for 0.1s">
Increment
</button>
</div>
</div>