[{"data":1,"prerenderedAt":3355},["ShallowReactive",2],{"series-concrete5vue-1":3},{"doc":4,"prev":796,"next":798},{"id":5,"title":6,"body":7,"category":782,"createdAt":784,"description":785,"extension":786,"index":39,"meta":787,"navigation":54,"path":788,"publish":54,"seo":789,"series":790,"seriesTitle":785,"stem":791,"tag":792,"thumbnail":795,"updatedAt":796,"__hash__":797},"series\u002Fseries\u002Fconcrete5vue-1.md","Concrete5にVueCLIを使ってUIを構築する。1【環境構築編】",{"type":8,"value":9,"toc":769},"minimark",[10,14,17,22,25,108,111,116,119,122,135,138,143,146,149,152,155,158,166,169,172,178,181,187,193,216,219,223,226,229,232,247,391,397,408,415,600,615,627,633,701,708,711,714,719,747,752,759,762,765],[11,12,13],"p",{},"こんにちはjunです。今回はconcrete5というCMSでVueを使ったパッケージのUIを作成していこうと思います。今回の記事ではコンポーネントを作成する前の、concrete5にvueプロジェクトを作成してCMSに結びつける環境構築まで行います。",[11,15,16],{},"またConcrete5とそのカスタマイズ方法についてある程度熟知している人向け、基準としてはカスタムパッケージを作成したことがある人向けの記事となります。対象とするConcrete5のバージョンは8.4以降となります。",[18,19,21],"h2",{"id":20},"なぜvue","なぜVue？",[11,23,24],{},"なぜVueを使うのか。それはPHPとjqueryによるUI構築が非常に面倒になったからです。Concrete5にはフォーム（テキストエリアとかCMSからのファイル選択フォーム）をPHPで出力してくれる以下の様なヘルパーが存在します。",[26,27,32],"pre",{"className":28,"code":29,"language":30,"meta":31,"style":31},"language-php shiki shiki-themes material-theme-ocean","\u002F\u002F ヘルパーを読み込む\n$form = Core::make('helper\u002Fform');\n\n\u002F\u002Fテキストinput\necho $form->text($name, $default_value);\n\n\u002F\u002Fテキストエリア\necho $form-> textarea($name, $default_value);\n\n\u002F\u002F ファイルマネージャーヘルパー\n$file_selector = Core::make('helper\u002Fconcrete\u002Ffile_manager');\necho $file_selector->file('label', 'name_attr', 'Select Photo', $default_fileObj);\n","php","",[33,34,35,43,49,56,62,68,73,79,85,90,96,102],"code",{"__ignoreMap":31},[36,37,40],"span",{"class":38,"line":39},"line",1,[36,41,42],{},"\u002F\u002F ヘルパーを読み込む\n",[36,44,46],{"class":38,"line":45},2,[36,47,48],{},"$form = Core::make('helper\u002Fform');\n",[36,50,52],{"class":38,"line":51},3,[36,53,55],{"emptyLinePlaceholder":54},true,"\n",[36,57,59],{"class":38,"line":58},4,[36,60,61],{},"\u002F\u002Fテキストinput\n",[36,63,65],{"class":38,"line":64},5,[36,66,67],{},"echo $form->text($name, $default_value);\n",[36,69,71],{"class":38,"line":70},6,[36,72,55],{"emptyLinePlaceholder":54},[36,74,76],{"class":38,"line":75},7,[36,77,78],{},"\u002F\u002Fテキストエリア\n",[36,80,82],{"class":38,"line":81},8,[36,83,84],{},"echo $form-> textarea($name, $default_value);\n",[36,86,88],{"class":38,"line":87},9,[36,89,55],{"emptyLinePlaceholder":54},[36,91,93],{"class":38,"line":92},10,[36,94,95],{},"\u002F\u002F ファイルマネージャーヘルパー\n",[36,97,99],{"class":38,"line":98},11,[36,100,101],{},"$file_selector = Core::make('helper\u002Fconcrete\u002Ffile_manager');\n",[36,103,105],{"class":38,"line":104},12,[36,106,107],{},"echo $file_selector->file('label', 'name_attr', 'Select Photo', $default_fileObj);\n",[11,109,110],{},"上記のコードをページ上で実行すれば以下の様にフォームが現れます。",[112,113],"image-render",{":src":114,":width":115},"'_mix\u002Fformhelpertest-768x272.png'","'100%'",[11,117,118],{},"上から順番にテキストインプット、テキストエリア、そしてconcrete５純正のファイルマネージャーが出現します。name属性が与えられ、postを通じてデータを送ることができます。",[11,120,121],{},"簡単なブロックやパッケージUIであれば問題ないのですが、",[123,124,125,129,132],"ul",{},[126,127,128],"li",{},"フロント側のバリデーションを実装する",[126,130,131],{},"決まったフォームを複数生成する",[126,133,134],{},"フォーム同士が入力値で依存させる場合（入力値でフォームのパターンが変わる）",[11,136,137],{},"上記の際にPHPとjqueryだけでは非常に苦労して工数もかかります。",[139,140,142],"h3",{"id":141},"vueを使えばリッチなuiが作成可能","Vueを使えばリッチなUIが作成可能",[11,144,145],{},"今流行りのリッチなUI、フォームはほとんどがVue、React、Backbone,jsなどHTMLテンプレート、jsによる状態管理を行うことで簡単に実装できます。工数を抑えつつも、これらのライブラリを用いることでリッチなUIを作成することもできます。",[11,147,148],{},"今回の記事ではVue CLIを用いて作成したUIをconcrete5のパッケージ上で表示させ、入力内容の追加・更新・削除まで行える様に実装していきます。原理がわかれば意外と簡単です。",[18,150,151],{"id":151},"パッケージの制作準備",[139,153,154],{"id":154},"パッケージの専用のディレクトリを作成",[11,156,157],{},"それではVueで構築したUIを持つパッケージを作成していきましょう。pckageディレクトリ配下にvuetestというカスタムパッケージディレクトリを作ります。またディレクトリ構成は以下の通りにします。",[26,159,164],{"className":160,"code":162,"language":163},[161],"language-text","documentroot $ cd .\u002Fpackage\npackage $ mkdir vuetest && touch ...\npackage $ tree vuetest\n.\u002F\n└── vuetest\n    ├── controller.php\n    ├── controllers\n    │   └── single_page\n    │       └── dashboard\n    │           └── vuetest.php\n    ├── db.xml\n    ├── icon.png\n    ├── js\n    │\n    └── single_pages\n        └── dashboard\n            └── vuetest\n                └── view.php\n","text",[33,165,162],{"__ignoreMap":31},[139,167,168],{"id":168},"vueプロジェクトを作成",[11,170,171],{},"パッケージに必要なコントローラーやUIを表示するダッシュボード上のシングルページとそのコントローラーも用意します。そしてjsディレクトリを作成してその中にvue cliを用いてプロジェクトを作成します。",[26,173,176],{"className":174,"code":175,"language":163},[161],"package $ cd vuetest\u002Fjs\njs $ vue create packageui\n\nVue CLI v4.4.6\n? Please pick a preset: default (babel, eslint) #default\n\nVue CLI v4.4.6\n✨  Creating project in documentroot\u002Fvuetest\u002Fjs\u002Fpackageui.\n🗃  Initializing git repository...\n⚙️  Installing CLI plugins. This might take a while... \n\n🎉  Successfully created project packageui.\n\njs $ cd packageui && tree -L 1\n.\n├── README.md\n├── babel.config.js\n├── node_modules\n├── package-lock.json\n├── package.json\n├── public\n",[33,177,175],{"__ignoreMap":31},[11,179,180],{},"無事にvueのプロジェクトが作成されました。concrete5との都合によりvue.config.jsファイルを作成してwebpackの設定をいじります。",[26,182,185],{"className":183,"code":184,"language":163},[161],"packageui $ touch vue.config.js\n",[33,186,184],{"__ignoreMap":31},[26,188,191],{"className":189,"code":190,"language":163},[161],"module.exports = {\n    configureWebpack: {\n      output: {\n        filename: '[name].js',\n        chunkFilename: '[name].js'\n      }\n    },\n  }\n",[33,192,190],{"__ignoreMap":31},[11,194,195,196,199,200,203,204,207,208,211,212,215],{},"これは ",[33,197,198],{},"npm run build",",",[33,201,202],{},"npm run build --mode development","で",[33,205,206],{},".vue","ファイルを",[33,209,210],{},".js","ファイルにコンパイルして",[33,213,214],{},"\u002Fdist","配下に配置される時に名前がいつも同じになる様に設定します。",[11,217,218],{},"buildをするとビルドされたファイルの名前にはハッシュされた英数字がつくのですが、これがビルドの度にころころ変わります。Concrete5でjsファイルをロードする場合は設定した名前が一致しないと読み込めませんので、ビルドの際に設定を変えなくて済む様に上記の設定をします。",[220,221,222],"h4",{"id":222},"もし複数のパッケージで共通のvueファイルを用いる場合",[11,224,225],{},"今回は vuetestというパッケージとそのディレクトリ配下に専用のvueプロジェクトを作成しますす。しかし複数のパッケージでvueファイルを使用したい場合、applicationディレクトリにjsディレクトリを作成し、vueプロジェクトを作成してください。",[139,227,228],{"id":228},"コンパイルしたjsファイルをconcrete5と結びつける",[11,230,231],{},"vue cliでビルドするとdist配下にコンパイルされたjsファイルが生成されます。そのファイルとレンダー先のconcrete5上のページ（シングルページ）で読み込める様に、このjsファイルをCMSが読み込める様に設定します。",[11,233,234,235,238,239,242,243,246],{},"まずとりあえず以下の",[33,236,237],{},"main.js","をビルドしてみます。レンダリング先に ",[33,240,241],{},"id=\"app\""," という要素があれば",[33,244,245],{},"Appコンポーネント","がレンダーされます。",[26,248,253],{"className":249,"code":250,"filename":251,"language":252,"meta":31,"style":31},"language-javascript shiki shiki-themes material-theme-ocean","import Vue from 'vue'\nimport App from '.\u002FApp.vue'\n\nVue.config.productionTip = false\n\nnew Vue({\n  render: h => h(App),\n}).$mount('#app')\n","src\u002Fmain.js","javascript",[33,254,255,279,295,299,322,326,341,365],{"__ignoreMap":31},[36,256,257,261,265,268,272,276],{"class":38,"line":39},[36,258,260],{"class":259},"s6cf3","import",[36,262,264],{"class":263},"s0W1g"," Vue ",[36,266,267],{"class":259},"from",[36,269,271],{"class":270},"sAklC"," '",[36,273,275],{"class":274},"sfyAc","vue",[36,277,278],{"class":270},"'\n",[36,280,281,283,286,288,290,293],{"class":38,"line":45},[36,282,260],{"class":259},[36,284,285],{"class":263}," App ",[36,287,267],{"class":259},[36,289,271],{"class":270},[36,291,292],{"class":274},".\u002FApp.vue",[36,294,278],{"class":270},[36,296,297],{"class":38,"line":51},[36,298,55],{"emptyLinePlaceholder":54},[36,300,301,304,307,310,312,315,318],{"class":38,"line":58},[36,302,303],{"class":263},"Vue",[36,305,306],{"class":270},".",[36,308,309],{"class":263},"config",[36,311,306],{"class":270},[36,313,314],{"class":263},"productionTip ",[36,316,317],{"class":270},"=",[36,319,321],{"class":320},"sbqyR"," false\n",[36,323,324],{"class":38,"line":64},[36,325,55],{"emptyLinePlaceholder":54},[36,327,328,331,335,338],{"class":38,"line":70},[36,329,330],{"class":270},"new",[36,332,334],{"class":333},"sdLwU"," Vue",[36,336,337],{"class":263},"(",[36,339,340],{"class":270},"{\n",[36,342,343,346,349,353,357,359,362],{"class":38,"line":75},[36,344,345],{"class":333},"  render",[36,347,348],{"class":270},":",[36,350,352],{"class":351},"s7ZW3"," h",[36,354,356],{"class":355},"sJ14y"," =>",[36,358,352],{"class":333},[36,360,361],{"class":263},"(App)",[36,363,364],{"class":270},",\n",[36,366,367,370,373,375,378,380,383,386,388],{"class":38,"line":81},[36,368,369],{"class":270},"}",[36,371,372],{"class":263},")",[36,374,306],{"class":270},[36,376,377],{"class":333},"$mount",[36,379,337],{"class":263},[36,381,382],{"class":270},"'",[36,384,385],{"class":274},"#app",[36,387,382],{"class":270},[36,389,390],{"class":263},")\n",[26,392,395],{"className":393,"code":394,"language":163},[161],"packageui ＄ npm run build\n.\n├── README.md\n├── babel.config.js\n├── dist\n│   ├── app.js\n│   ├── app.js.map\n│   ├── chunk-vendors.js\n│   ├── chunk-vendors.js.map\n│   ├── css\n│   ├── favicon.ico\n│   ├── img\n│   └── index.html\n├\n",[33,396,394],{"__ignoreMap":31},[11,398,399,400,403,404,407],{},"ビルド成功。いらないものもありますが、",[33,401,402],{},"app.js","と",[33,405,406],{},"chunk-vendors.js","が読み込まれる様にすればOKです。",[11,409,410,411,414],{},"パッケージ専用のjsファイルで作成する場合はパッケージのインストールコントローラ（",[33,412,413],{},"package\u002Fvuetest\u002Fcontroller.php","）に以下の様な記述をします。",[26,416,419],{"className":28,"code":417,"filename":418,"language":30,"meta":31,"style":31},"\u003C?php\nnamespace Concrete\\Package\\Vuetest;\ndefined('C5_EXECUTE') or die('Access Denied.');\nuse \\Concrete\\Core\\Asset\\AssetList;\nuse \\Concrete\\Core\\Asset\\Asset;\n\nclass Controller extends \\Concrete\\Core\\Package\\Package {\n    protected $pkgHandle = 'vuetest';\n    protected $appVersionRequired = '5.7.4';\n    protected $pkgVersion = '1.0.0';\n\n    public function on_start()\n    {\n        $al = AssetList::getInstance();\n        $al->register(\n            'javascript', 'package-vue-build', 'js\u002Fpackageui\u002Fdist\u002Fapp.js',\n            array('version' => '1.0.0', 'position' => Asset::ASSET_POSITION_FOOTER, 'combine' => true),\n            $this->pkgHandle\n        );\n        \n        $al->register(\n            'javascript', 'package-vue-chunk', 'js\u002Fpackageui\u002Fdist\u002Fchunk-vendors.js',\n            array('version' => '1.0.0', 'position' => Asset::ASSET_POSITION_FOOTER, 'combine' => true),\n            $this->pkgHandle\n        );\n        \n        $al->registerGroup('package-vue-production', array(\n            array('javascript', 'package-vue-build'),\n            array('javascript', 'package-vue-chunk'),\n        )); \n    }\n...\n}\n","controller.php",[33,420,421,426,431,436,441,446,450,455,460,465,470,474,479,485,491,497,503,509,515,521,527,532,538,543,548,553,558,564,570,576,582,588,594],{"__ignoreMap":31},[36,422,423],{"class":38,"line":39},[36,424,425],{},"\u003C?php\n",[36,427,428],{"class":38,"line":45},[36,429,430],{},"namespace Concrete\\Package\\Vuetest;\n",[36,432,433],{"class":38,"line":51},[36,434,435],{},"defined('C5_EXECUTE') or die('Access Denied.');\n",[36,437,438],{"class":38,"line":58},[36,439,440],{},"use \\Concrete\\Core\\Asset\\AssetList;\n",[36,442,443],{"class":38,"line":64},[36,444,445],{},"use \\Concrete\\Core\\Asset\\Asset;\n",[36,447,448],{"class":38,"line":70},[36,449,55],{"emptyLinePlaceholder":54},[36,451,452],{"class":38,"line":75},[36,453,454],{},"class Controller extends \\Concrete\\Core\\Package\\Package {\n",[36,456,457],{"class":38,"line":81},[36,458,459],{},"    protected $pkgHandle = 'vuetest';\n",[36,461,462],{"class":38,"line":87},[36,463,464],{},"    protected $appVersionRequired = '5.7.4';\n",[36,466,467],{"class":38,"line":92},[36,468,469],{},"    protected $pkgVersion = '1.0.0';\n",[36,471,472],{"class":38,"line":98},[36,473,55],{"emptyLinePlaceholder":54},[36,475,476],{"class":38,"line":104},[36,477,478],{},"    public function on_start()\n",[36,480,482],{"class":38,"line":481},13,[36,483,484],{},"    {\n",[36,486,488],{"class":38,"line":487},14,[36,489,490],{},"        $al = AssetList::getInstance();\n",[36,492,494],{"class":38,"line":493},15,[36,495,496],{},"        $al->register(\n",[36,498,500],{"class":38,"line":499},16,[36,501,502],{},"            'javascript', 'package-vue-build', 'js\u002Fpackageui\u002Fdist\u002Fapp.js',\n",[36,504,506],{"class":38,"line":505},17,[36,507,508],{},"            array('version' => '1.0.0', 'position' => Asset::ASSET_POSITION_FOOTER, 'combine' => true),\n",[36,510,512],{"class":38,"line":511},18,[36,513,514],{},"            $this->pkgHandle\n",[36,516,518],{"class":38,"line":517},19,[36,519,520],{},"        );\n",[36,522,524],{"class":38,"line":523},20,[36,525,526],{},"        \n",[36,528,530],{"class":38,"line":529},21,[36,531,496],{},[36,533,535],{"class":38,"line":534},22,[36,536,537],{},"            'javascript', 'package-vue-chunk', 'js\u002Fpackageui\u002Fdist\u002Fchunk-vendors.js',\n",[36,539,541],{"class":38,"line":540},23,[36,542,508],{},[36,544,546],{"class":38,"line":545},24,[36,547,514],{},[36,549,551],{"class":38,"line":550},25,[36,552,520],{},[36,554,556],{"class":38,"line":555},26,[36,557,526],{},[36,559,561],{"class":38,"line":560},27,[36,562,563],{},"        $al->registerGroup('package-vue-production', array(\n",[36,565,567],{"class":38,"line":566},28,[36,568,569],{},"            array('javascript', 'package-vue-build'),\n",[36,571,573],{"class":38,"line":572},29,[36,574,575],{},"            array('javascript', 'package-vue-chunk'),\n",[36,577,579],{"class":38,"line":578},30,[36,580,581],{},"        )); \n",[36,583,585],{"class":38,"line":584},31,[36,586,587],{},"    }\n",[36,589,591],{"class":38,"line":590},32,[36,592,593],{},"...\n",[36,595,597],{"class":38,"line":596},33,[36,598,599],{},"}\n",[11,601,602,603,606,607,610,611,614],{},"vuetestパッケージのコントローラーで",[33,604,605],{},"$al->register","を用いて、パスで指定したjsファイルを登録します。２つあるのでを",[33,608,609],{},"$al->registerGroup","用いてのp",[33,612,613],{},"ackage-vue-production","名前で２つのjsファイルを読み込む様にグルーピングします。",[11,616,617,618,622,623,626],{},"concrete5ではこの様なjs\u002Fcssのアセット登録システムがあり、登録をすれば適当にアセットが読み込むことができる様になります。",[619,620,621],"strong",{},"パッケージのコントローラーでは登録をした"," ので、次はjsファイルが必要な",[619,624,625],{},"ページのコントローラーで呼び出し"," を行います。",[11,628,629,632],{},[33,630,631],{},"vuetest\u002Fsingle_pages\u002Fdashboard\u002Fvuetest\u002Fview.php"," に以下の様に記述します。",[26,634,636],{"className":28,"code":635,"language":30,"meta":31,"style":31},"\u003C?php\nnamespace Concrete\\Package\\Vuetest\\Controller\\SinglePage\\Dashboard;\ndefined('C5_EXECUTE') or die('Access Denied.');\nuse \\Concrete\\Core\\Page\\Controller\\DashboardPageController;\n\nclass Vuetest extends DashboardPageController\n{\n    public $packageHandle = 'vuetest';\n\n    public function view() {\n        $this->requireAsset('package-vue-production');\n        $this->set('success', 'My success message');\n    }\n}\n",[33,637,638,642,647,651,656,660,665,669,674,678,683,688,693,697],{"__ignoreMap":31},[36,639,640],{"class":38,"line":39},[36,641,425],{},[36,643,644],{"class":38,"line":45},[36,645,646],{},"namespace Concrete\\Package\\Vuetest\\Controller\\SinglePage\\Dashboard;\n",[36,648,649],{"class":38,"line":51},[36,650,435],{},[36,652,653],{"class":38,"line":58},[36,654,655],{},"use \\Concrete\\Core\\Page\\Controller\\DashboardPageController;\n",[36,657,658],{"class":38,"line":64},[36,659,55],{"emptyLinePlaceholder":54},[36,661,662],{"class":38,"line":70},[36,663,664],{},"class Vuetest extends DashboardPageController\n",[36,666,667],{"class":38,"line":75},[36,668,340],{},[36,670,671],{"class":38,"line":81},[36,672,673],{},"    public $packageHandle = 'vuetest';\n",[36,675,676],{"class":38,"line":87},[36,677,55],{"emptyLinePlaceholder":54},[36,679,680],{"class":38,"line":92},[36,681,682],{},"    public function view() {\n",[36,684,685],{"class":38,"line":98},[36,686,687],{},"        $this->requireAsset('package-vue-production');\n",[36,689,690],{"class":38,"line":104},[36,691,692],{},"        $this->set('success', 'My success message');\n",[36,694,695],{"class":38,"line":481},[36,696,587],{},[36,698,699],{"class":38,"line":487},[36,700,599],{},[11,702,703,704,707],{},"この",[33,705,706],{},"$this->requireAsset('package-vue-production');","で「先ほど登録したjsアセットをこのページで読み込め！」と命令しています。設定した後、実際に該当ページで探してみましょう。",[112,709],{":src":710,":width":115},"'_mix\u002Fsc-2020-08-01-19.52.09-768x133.png'",[11,712,713],{},"いました。\u002Fpackages\u002Fvuetest\u002Fjs\u002Fdist\u002F** と指定したパスにて読み込まれています。それではid=\"app\"を持つ適当なdivを作成して再度みてみましょう。",[11,715,716,718],{},[33,717,631],{}," にて",[26,720,723],{"className":28,"code":721,"filename":722,"language":30,"meta":31,"style":31},"\u003C?php\ndefined('C5_EXECUTE') or die('Access Denied.');\n?>\n\n\u003Cdiv id=\"app\">\u003C\u002Fdiv>\n","view.php",[33,724,725,729,733,738,742],{"__ignoreMap":31},[36,726,727],{"class":38,"line":39},[36,728,425],{},[36,730,731],{"class":38,"line":45},[36,732,435],{},[36,734,735],{"class":38,"line":51},[36,736,737],{},"?>\n",[36,739,740],{"class":38,"line":58},[36,741,55],{"emptyLinePlaceholder":54},[36,743,744],{"class":38,"line":64},[36,745,746],{},"\u003Cdiv id=\"app\">\u003C\u002Fdiv>\n",[112,748],{":src":749,":width":750,":center":751},"'_mix\u002Fsh-2020-08-01-19.56.01-768x812.png'","'500px'","true",[11,753,754,755,758],{},"パスの関係上レイアウトが崩れ、画像が読み込めていませんがvue側で記述した内容が無事にレンダリングされています。これでvueとconcrete5のセッティングは完了です。vueプロジェクトでコンポーネントを作成しながら、適切な",[33,756,757],{},"id","を用いてページ上にレンダーします。",[18,760,761],{"id":761},"次はフォームコンポーネントの作成",[11,763,764],{},"以上がconcrete5のパッケージ上にvueプロジェクトを作成して、concrete5に結びつける方法でした。これでvueを使え、レンダーされる環境は整ったので次は情報を登録するための登録フォームと編集画面の作成を行っていきます。",[766,767,768],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}",{"title":31,"searchDepth":51,"depth":51,"links":770},[771,774,781],{"id":20,"depth":45,"text":21,"children":772},[773],{"id":141,"depth":51,"text":142},{"id":151,"depth":45,"text":151,"children":775},[776,777,780],{"id":154,"depth":51,"text":154},{"id":168,"depth":51,"text":168,"children":778},[779],{"id":222,"depth":58,"text":222},{"id":228,"depth":51,"text":228},{"id":761,"depth":45,"text":761},[783],"devstack","2020-08-25","Concrete5にVueCLIを使ってUIを構築する。","md",{},"\u002Fseries\u002Fconcrete5vue-1",{"title":6,"description":785},"concrete5vue","series\u002Fconcrete5vue-1",[793,794,275],"concrete5","js","_mix\u002Fvuewithconcrete.png",null,"SFdt3_VrU2GVgYjfNjC32zyOKCtr79IKO3992JqFQJo",{"id":799,"title":800,"body":801,"category":3347,"createdAt":3348,"description":3349,"extension":786,"index":45,"meta":3350,"navigation":54,"path":809,"publish":54,"seo":3351,"series":790,"seriesTitle":785,"stem":3352,"tag":3353,"thumbnail":795,"updatedAt":796,"__hash__":3354},"series\u002Fseries\u002Fconcrete5vue-2.md","Concrete5にVueCLIを使ってUIを構築する。2【コンポーネント作成・データ登録編】",{"type":8,"value":802,"toc":3326},[803,811,814,817,820,823,837,841,844,847,851,854,1077,1080,1084,1088,1091,1097,1104,1182,1185,1208,1215,1218,1240,1246,1249,1252,1258,1261,1264,1555,1558,1680,1688,1795,1802,1805,1817,1820,1823,1826,1830,1844,2268,2271,2274,2914,2929,2950,2953,2956,2961,2965,2971,2977,2987,2993,2999,3002,3005,3208,3221,3239,3243,3246,3265,3275,3278,3281,3284,3290,3293,3296,3299,3302,3306,3309,3320,3323],[11,804,805,806,810],{},"こんにちはjunです。",[807,808,6],"a",{"href":809},"\u002Fseries\u002Fconcrete5vue-2","の記事の続きを書いていきます。",[11,812,813],{},"今回の記事では",[11,815,816],{},"どんなフォームを作成するのか。\nテキストインプットを用いたフォームの簡易実装\nデータの保存のバックエンド実装\nを行いたいと思います。ファイルマネージャー、リッチテキストエディタをvueでレンダリングする方法は追加・編集・一覧化が終わった後に書きたいと思います。まずは簡単なフォーム（テキスト）とデータの保存をvueとともに実装していきましょう。",[18,818,819],{"id":819},"最終的に作成するフォーム",[11,821,822],{},"「アルバム管理」たるものを作成しようと思います。インスタグラム的な物で、機能は以下の通りです。",[123,824,825,828,831,834],{},[126,826,827],{},"1つのアルバムに複数枚の画像を自由に登録できる。数は基本的に無制限、最低１枚",[126,829,830],{},"画像と一緒にその画像に関する情報をリッチテキストエディタで編集できる。",[126,832,833],{},"画像は順番の並び替え、追加、削除が可能。",[126,835,836],{},"ページ内ではブロックを通じてアルバムの写真を出力できる。",[139,838,840],{"id":839},"ワイヤーフレーム超簡素","ワイヤーフレーム（超簡素）",[112,842],{":src":843,":width":115},"'_mix\u002Fvueconcrete2-1.jpeg'",[112,845],{":src":846,":width":115},"'_mix\u002Fslide-2.jpeg'",[18,848,850],{"id":849},"db構造","DB構造",[11,852,853],{},"db.xmlを用いて以下のようなテーブルを作成しておきます。",[26,855,860],{"className":856,"code":857,"filename":858,"language":859,"meta":31,"style":31},"language-xml shiki shiki-themes material-theme-ocean","\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003Cschema\n  xmlns=\"http:\u002F\u002Fwww.concrete5.org\u002Fdoctrine-xml\u002F0.5\"\n  xmlns:xsi=\"http:\u002F\u002Fwww.w3.org\u002F2001\u002FXMLSchema-instance\"\n  xsi:schemaLocation=\"http:\u002F\u002Fwww.concrete5.org\u002Fdoctrine-xml\u002F0.5 http:\u002F\u002Fconcrete5.github.io\u002Fdoctrine-xml\u002Fdoctrine-xml-0.5.xsd\">\n\n\u003Ctable name=\"album\">\n    \u003Cfield name=\"id\" type=\"integer\">\n        \u003Cautoincrement\u002F>\n        \u003Ckey\u002F>\n    \u003C\u002Ffield>\n    \u003Cfield name=\"title\" type=\"string\" size=\"255\"\u002F>\n    \u003Cfield name=\"created\" type=\"datetime\">\n      \u003Cdefault value=\"1000-01-01 00:00:00\"\u002F>\n      \u003Cnotnull\u002F>\n    \u003C\u002Ffield>\n    \u003Cfield name=\"modified\" type=\"timestamp\">\n      \u003Cdeftimestamp\u002F>\n      \u003Cnotnull\u002F>\n    \u003C\u002Ffield>\n\u003C\u002Ftable>\n\n\u003Ctable name=\"albumPics\">\n    \u003Cfield name=\"id\" type=\"integer\">\n        \u003Cautoincrement\u002F>\n        \u003Ckey\u002F>\n    \u003C\u002Ffield>\n    \u003Cfield name=\"albumID\" type=\"integer\">\n        \u003Cunsigned\u002F>\n    \u003C\u002Ffield>\n    \u003Cfield name=\"fID\" type=\"integer\">\n        \u003Cunsigned\u002F>\n    \u003C\u002Ffield>\n    \u003Cfield name=\"html\" type=\"text\" size=\"65535\"\u002F>\n    \u003Cfield name=\"created\" type=\"datetime\">\n      \u003Cdefault value=\"1000-01-01 00:00:00\"\u002F>\n      \u003Cnotnull\u002F>\n    \u003C\u002Ffield>\n    \u003Cfield name=\"modified\" type=\"timestamp\">\n      \u003Cdeftimestamp\u002F>\n      \u003Cnotnull\u002F>\n    \u003C\u002Ffield>\n\u003C\u002Ftable>\n\n\u003C\u002Fschema>\n","db.xml","xml",[33,861,862,867,872,877,882,887,891,896,901,906,911,916,921,926,931,936,940,945,950,954,958,963,967,972,976,980,984,988,993,998,1002,1007,1011,1015,1021,1026,1031,1036,1041,1046,1051,1056,1061,1066,1071],{"__ignoreMap":31},[36,863,864],{"class":38,"line":39},[36,865,866],{},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",[36,868,869],{"class":38,"line":45},[36,870,871],{},"\u003Cschema\n",[36,873,874],{"class":38,"line":51},[36,875,876],{},"  xmlns=\"http:\u002F\u002Fwww.concrete5.org\u002Fdoctrine-xml\u002F0.5\"\n",[36,878,879],{"class":38,"line":58},[36,880,881],{},"  xmlns:xsi=\"http:\u002F\u002Fwww.w3.org\u002F2001\u002FXMLSchema-instance\"\n",[36,883,884],{"class":38,"line":64},[36,885,886],{},"  xsi:schemaLocation=\"http:\u002F\u002Fwww.concrete5.org\u002Fdoctrine-xml\u002F0.5 http:\u002F\u002Fconcrete5.github.io\u002Fdoctrine-xml\u002Fdoctrine-xml-0.5.xsd\">\n",[36,888,889],{"class":38,"line":70},[36,890,55],{"emptyLinePlaceholder":54},[36,892,893],{"class":38,"line":75},[36,894,895],{},"\u003Ctable name=\"album\">\n",[36,897,898],{"class":38,"line":81},[36,899,900],{},"    \u003Cfield name=\"id\" type=\"integer\">\n",[36,902,903],{"class":38,"line":87},[36,904,905],{},"        \u003Cautoincrement\u002F>\n",[36,907,908],{"class":38,"line":92},[36,909,910],{},"        \u003Ckey\u002F>\n",[36,912,913],{"class":38,"line":98},[36,914,915],{},"    \u003C\u002Ffield>\n",[36,917,918],{"class":38,"line":104},[36,919,920],{},"    \u003Cfield name=\"title\" type=\"string\" size=\"255\"\u002F>\n",[36,922,923],{"class":38,"line":481},[36,924,925],{},"    \u003Cfield name=\"created\" type=\"datetime\">\n",[36,927,928],{"class":38,"line":487},[36,929,930],{},"      \u003Cdefault value=\"1000-01-01 00:00:00\"\u002F>\n",[36,932,933],{"class":38,"line":493},[36,934,935],{},"      \u003Cnotnull\u002F>\n",[36,937,938],{"class":38,"line":499},[36,939,915],{},[36,941,942],{"class":38,"line":505},[36,943,944],{},"    \u003Cfield name=\"modified\" type=\"timestamp\">\n",[36,946,947],{"class":38,"line":511},[36,948,949],{},"      \u003Cdeftimestamp\u002F>\n",[36,951,952],{"class":38,"line":517},[36,953,935],{},[36,955,956],{"class":38,"line":523},[36,957,915],{},[36,959,960],{"class":38,"line":529},[36,961,962],{},"\u003C\u002Ftable>\n",[36,964,965],{"class":38,"line":534},[36,966,55],{"emptyLinePlaceholder":54},[36,968,969],{"class":38,"line":540},[36,970,971],{},"\u003Ctable name=\"albumPics\">\n",[36,973,974],{"class":38,"line":545},[36,975,900],{},[36,977,978],{"class":38,"line":550},[36,979,905],{},[36,981,982],{"class":38,"line":555},[36,983,910],{},[36,985,986],{"class":38,"line":560},[36,987,915],{},[36,989,990],{"class":38,"line":566},[36,991,992],{},"    \u003Cfield name=\"albumID\" type=\"integer\">\n",[36,994,995],{"class":38,"line":572},[36,996,997],{},"        \u003Cunsigned\u002F>\n",[36,999,1000],{"class":38,"line":578},[36,1001,915],{},[36,1003,1004],{"class":38,"line":584},[36,1005,1006],{},"    \u003Cfield name=\"fID\" type=\"integer\">\n",[36,1008,1009],{"class":38,"line":590},[36,1010,997],{},[36,1012,1013],{"class":38,"line":596},[36,1014,915],{},[36,1016,1018],{"class":38,"line":1017},34,[36,1019,1020],{},"    \u003Cfield name=\"html\" type=\"text\" size=\"65535\"\u002F>\n",[36,1022,1024],{"class":38,"line":1023},35,[36,1025,925],{},[36,1027,1029],{"class":38,"line":1028},36,[36,1030,930],{},[36,1032,1034],{"class":38,"line":1033},37,[36,1035,935],{},[36,1037,1039],{"class":38,"line":1038},38,[36,1040,915],{},[36,1042,1044],{"class":38,"line":1043},39,[36,1045,944],{},[36,1047,1049],{"class":38,"line":1048},40,[36,1050,949],{},[36,1052,1054],{"class":38,"line":1053},41,[36,1055,935],{},[36,1057,1059],{"class":38,"line":1058},42,[36,1060,915],{},[36,1062,1064],{"class":38,"line":1063},43,[36,1065,962],{},[36,1067,1069],{"class":38,"line":1068},44,[36,1070,55],{"emptyLinePlaceholder":54},[36,1072,1074],{"class":38,"line":1073},45,[36,1075,1076],{},"\u003C\u002Fschema>\n",[11,1078,1079],{},"1アルバムに対して不定数の画像が登録されるので上記の様なDBになっています。",[18,1081,1083],{"id":1082},"パッケージのビューphpとvueのsrc構成","パッケージのビューPHPとvueのSrc構成",[139,1085,1087],{"id":1086},"パッケージ側phpの下準備","パッケージ側（PHP）の下準備",[11,1089,1090],{},"それではアルバムののフォームを作っていきましょう。前回インストールした「vuetest」にてフォーム用のシングルページを作成します。",[26,1092,1095],{"className":1093,"code":1094,"language":163},[161],"vuetest\n├── controller.php \u002F\u002Fこれはパッケージのcontroller\n├── controllers\n│   └── single_page\n│       └── dashboard\n│           └── vuetest.php\n└── single_pages\n    └── dashboard\n        └── vuetest\n            ├── add.php   \u002F\u002F追加画面シングルページ\n            ├── edit.php  \u002F\u002F編集画面シングルページ\n            └── view.php\n",[33,1096,1094],{"__ignoreMap":31},[11,1098,1099,1100,1103],{},"そしてシングルページのコントローラーである",[33,1101,1102],{},"vuetest.php","で以下の様に入力",[26,1105,1107],{"className":28,"code":1106,"filename":1102,"language":30,"meta":31,"style":31},"class Vuetest extends DashboardPageController\n{\n    public $packageHandle = 'vuetest';\n\n   \u002F\u002Fvuetest のシングルページでvueのjsファイルを読み込む様にする。\n　　public function on_start() {\n        $this->requireAsset('package-vue-production');\n    }\n\n    public function view() {\n    }\n\n    \u002F\u002F \u002Fdashboard\u002Fvuetest\u002F に \u002Fadd というURLを追加できる\n    public function add(){\n        $this->render('\u002Fdashboard\u002Fvuetest\u002Fadd');\n    }\n}\n",[33,1108,1109,1113,1117,1121,1125,1130,1135,1139,1143,1147,1151,1155,1159,1164,1169,1174,1178],{"__ignoreMap":31},[36,1110,1111],{"class":38,"line":39},[36,1112,664],{},[36,1114,1115],{"class":38,"line":45},[36,1116,340],{},[36,1118,1119],{"class":38,"line":51},[36,1120,673],{},[36,1122,1123],{"class":38,"line":58},[36,1124,55],{"emptyLinePlaceholder":54},[36,1126,1127],{"class":38,"line":64},[36,1128,1129],{},"   \u002F\u002Fvuetest のシングルページでvueのjsファイルを読み込む様にする。\n",[36,1131,1132],{"class":38,"line":70},[36,1133,1134],{},"　　public function on_start() {\n",[36,1136,1137],{"class":38,"line":75},[36,1138,687],{},[36,1140,1141],{"class":38,"line":81},[36,1142,587],{},[36,1144,1145],{"class":38,"line":87},[36,1146,55],{"emptyLinePlaceholder":54},[36,1148,1149],{"class":38,"line":92},[36,1150,682],{},[36,1152,1153],{"class":38,"line":98},[36,1154,587],{},[36,1156,1157],{"class":38,"line":104},[36,1158,55],{"emptyLinePlaceholder":54},[36,1160,1161],{"class":38,"line":481},[36,1162,1163],{},"    \u002F\u002F \u002Fdashboard\u002Fvuetest\u002F に \u002Fadd というURLを追加できる\n",[36,1165,1166],{"class":38,"line":487},[36,1167,1168],{},"    public function add(){\n",[36,1170,1171],{"class":38,"line":493},[36,1172,1173],{},"        $this->render('\u002Fdashboard\u002Fvuetest\u002Fadd');\n",[36,1175,1176],{"class":38,"line":499},[36,1177,587],{},[36,1179,1180],{"class":38,"line":505},[36,1181,599],{},[11,1183,1184],{},"すると\u002Fdashboard\u002Fvuetest\u002Fadd というURLを叩くとadd.phpが表示されます。",[26,1186,1189],{"className":28,"code":1187,"filename":1188,"language":30,"meta":31,"style":31},"\u003C?php\ndefined('C5_EXECUTE') or die('Access Denied.');\n?>\n\u003Cp>add用のページだよ\u003C\u002Fp>\n","add.php",[33,1190,1191,1195,1199,1203],{"__ignoreMap":31},[36,1192,1193],{"class":38,"line":39},[36,1194,425],{},[36,1196,1197],{"class":38,"line":45},[36,1198,435],{},[36,1200,1201],{"class":38,"line":51},[36,1202,737],{},[36,1204,1205],{"class":38,"line":58},[36,1206,1207],{},"\u003Cp>add用のページだよ\u003C\u002Fp>\n",[11,1209,1210,1211],{},"↓\n",[112,1212],{":src":1213,":width":1214,":center":751},"'_mix\u002Fscsh-2020-08-26-21.34.56-768x436.png'","'600px'",[11,1216,1217],{},"ページの表示が確認できたら、add.phpに以下の様に変更しておきます。",[26,1219,1221],{"className":28,"code":1220,"filename":1188,"language":30,"meta":31,"style":31},"\u003C?php\ndefined('C5_EXECUTE') or die('Access Denied.');\n?>\n\u003Cdiv id=\"add\">\u003C\u002Fdiv>\n",[33,1222,1223,1227,1231,1235],{"__ignoreMap":31},[36,1224,1225],{"class":38,"line":39},[36,1226,425],{},[36,1228,1229],{"class":38,"line":45},[36,1230,435],{},[36,1232,1233],{"class":38,"line":51},[36,1234,737],{},[36,1236,1237],{"class":38,"line":58},[36,1238,1239],{},"\u003Cdiv id=\"add\">\u003C\u002Fdiv>\n",[11,1241,703,1242,1245],{},[33,1243,1244],{},"\u003Cdiv id=”app>\u003C\u002Fdiv>","としたところがvueコンポーネントを流し込むエントリーになります。フォームのビューファイルであるadd.phpはこれだけでおしまいです。",[18,1247,1248],{"id":1248},"vue側の準備",[11,1250,1251],{},"ではvue側も作っていきましょう。src配下は以下の様に構成します。",[26,1253,1256],{"className":1254,"code":1255,"language":163},[161],"├── src\n   ├── add.vue\n   ├── components\n   │   └── form.vue\n   ├── edit.vue\n   ├── index.vue\n   └── main.js\n",[33,1257,1255],{"__ignoreMap":31},[11,1259,1260],{},"index.vue、add.vue、edit.vueそれぞれが一覧・追加・編集ページのUIを構築します。しかしedit.vueとadd.vueは入力項目が同じで、初期値があるかないかの違いだけです。そのため共通のコンポーネントform.vueを作成します。",[11,1262,1263],{},"form.vueはひとまず以下の様に設定しておきます。",[26,1265,1269],{"className":1266,"code":1267,"filename":1268,"language":275,"meta":31,"style":31},"language-vue shiki shiki-themes material-theme-ocean","\u003Ctemplate>\n    \u003Cdiv class=\"ccm-dashboard-content-inner\">\n        \u003Cp>vueレンダリングテスト\u003C\u002Fp>\n        \n        \u003Cdiv class=\"ccm-dashboard-form-actions-wrapper\">\n            \u003Cdiv class=\"ccm-dashboard-form-actions\">\n                \u003Cbutton class=\"pull-left btn btn-primary\">\n                    一覧へ戻る\n                \u003C\u002Fbutton>\n                \u003Ca href=\"#\" class=\"pull-right btn btn-primary\">\n                    登録\n                \u003C\u002Fa>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default {\n    name:'albumForm',\n    props:['isEdit']\n}\n\u003C\u002Fscript>\n","form.vue",[33,1270,1271,1283,1306,1326,1330,1349,1369,1390,1395,1404,1435,1440,1448,1457,1466,1475,1483,1487,1496,1507,1523,1543,1547],{"__ignoreMap":31},[36,1272,1273,1276,1280],{"class":38,"line":39},[36,1274,1275],{"class":270},"\u003C",[36,1277,1279],{"class":1278},"s-wAU","template",[36,1281,1282],{"class":270},">\n",[36,1284,1285,1288,1291,1294,1296,1299,1302,1304],{"class":38,"line":45},[36,1286,1287],{"class":270},"    \u003C",[36,1289,1290],{"class":1278},"div",[36,1292,1293],{"class":355}," class",[36,1295,317],{"class":270},[36,1297,1298],{"class":270},"\"",[36,1300,1301],{"class":274},"ccm-dashboard-content-inner",[36,1303,1298],{"class":270},[36,1305,1282],{"class":270},[36,1307,1308,1311,1313,1316,1319,1322,1324],{"class":38,"line":51},[36,1309,1310],{"class":270},"        \u003C",[36,1312,11],{"class":1278},[36,1314,1315],{"class":270},">",[36,1317,1318],{"class":263},"vueレンダリングテスト",[36,1320,1321],{"class":270},"\u003C\u002F",[36,1323,11],{"class":1278},[36,1325,1282],{"class":270},[36,1327,1328],{"class":38,"line":58},[36,1329,526],{"class":263},[36,1331,1332,1334,1336,1338,1340,1342,1345,1347],{"class":38,"line":64},[36,1333,1310],{"class":270},[36,1335,1290],{"class":1278},[36,1337,1293],{"class":355},[36,1339,317],{"class":270},[36,1341,1298],{"class":270},[36,1343,1344],{"class":274},"ccm-dashboard-form-actions-wrapper",[36,1346,1298],{"class":270},[36,1348,1282],{"class":270},[36,1350,1351,1354,1356,1358,1360,1362,1365,1367],{"class":38,"line":70},[36,1352,1353],{"class":270},"            \u003C",[36,1355,1290],{"class":1278},[36,1357,1293],{"class":355},[36,1359,317],{"class":270},[36,1361,1298],{"class":270},[36,1363,1364],{"class":274},"ccm-dashboard-form-actions",[36,1366,1298],{"class":270},[36,1368,1282],{"class":270},[36,1370,1371,1374,1377,1379,1381,1383,1386,1388],{"class":38,"line":75},[36,1372,1373],{"class":270},"                \u003C",[36,1375,1376],{"class":1278},"button",[36,1378,1293],{"class":355},[36,1380,317],{"class":270},[36,1382,1298],{"class":270},[36,1384,1385],{"class":274},"pull-left btn btn-primary",[36,1387,1298],{"class":270},[36,1389,1282],{"class":270},[36,1391,1392],{"class":38,"line":81},[36,1393,1394],{"class":263},"                    一覧へ戻る\n",[36,1396,1397,1400,1402],{"class":38,"line":87},[36,1398,1399],{"class":270},"                \u003C\u002F",[36,1401,1376],{"class":1278},[36,1403,1282],{"class":270},[36,1405,1406,1408,1410,1413,1415,1417,1420,1422,1424,1426,1428,1431,1433],{"class":38,"line":92},[36,1407,1373],{"class":270},[36,1409,807],{"class":1278},[36,1411,1412],{"class":355}," href",[36,1414,317],{"class":270},[36,1416,1298],{"class":270},[36,1418,1419],{"class":274},"#",[36,1421,1298],{"class":270},[36,1423,1293],{"class":355},[36,1425,317],{"class":270},[36,1427,1298],{"class":270},[36,1429,1430],{"class":274},"pull-right btn btn-primary",[36,1432,1298],{"class":270},[36,1434,1282],{"class":270},[36,1436,1437],{"class":38,"line":98},[36,1438,1439],{"class":263},"                    登録\n",[36,1441,1442,1444,1446],{"class":38,"line":104},[36,1443,1399],{"class":270},[36,1445,807],{"class":1278},[36,1447,1282],{"class":270},[36,1449,1450,1453,1455],{"class":38,"line":481},[36,1451,1452],{"class":270},"            \u003C\u002F",[36,1454,1290],{"class":1278},[36,1456,1282],{"class":270},[36,1458,1459,1462,1464],{"class":38,"line":487},[36,1460,1461],{"class":270},"        \u003C\u002F",[36,1463,1290],{"class":1278},[36,1465,1282],{"class":270},[36,1467,1468,1471,1473],{"class":38,"line":493},[36,1469,1470],{"class":270},"    \u003C\u002F",[36,1472,1290],{"class":1278},[36,1474,1282],{"class":270},[36,1476,1477,1479,1481],{"class":38,"line":499},[36,1478,1321],{"class":270},[36,1480,1279],{"class":1278},[36,1482,1282],{"class":270},[36,1484,1485],{"class":38,"line":505},[36,1486,55],{"emptyLinePlaceholder":54},[36,1488,1489,1491,1494],{"class":38,"line":511},[36,1490,1275],{"class":270},[36,1492,1493],{"class":1278},"script",[36,1495,1282],{"class":270},[36,1497,1498,1501,1504],{"class":38,"line":517},[36,1499,1500],{"class":259},"export",[36,1502,1503],{"class":259}," default",[36,1505,1506],{"class":270}," {\n",[36,1508,1509,1512,1514,1516,1519,1521],{"class":38,"line":523},[36,1510,1511],{"class":1278},"    name",[36,1513,348],{"class":270},[36,1515,382],{"class":270},[36,1517,1518],{"class":274},"albumForm",[36,1520,382],{"class":270},[36,1522,364],{"class":270},[36,1524,1525,1528,1530,1533,1535,1538,1540],{"class":38,"line":529},[36,1526,1527],{"class":1278},"    props",[36,1529,348],{"class":270},[36,1531,1532],{"class":263},"[",[36,1534,382],{"class":270},[36,1536,1537],{"class":274},"isEdit",[36,1539,382],{"class":270},[36,1541,1542],{"class":263},"]\n",[36,1544,1545],{"class":38,"line":534},[36,1546,599],{"class":270},[36,1548,1549,1551,1553],{"class":38,"line":540},[36,1550,1321],{"class":270},[36,1552,1493],{"class":1278},[36,1554,1282],{"class":270},[11,1556,1557],{},"そしてadd.vueでこのform.vueを読み込んで",[26,1559,1562],{"className":1266,"code":1560,"filename":1561,"language":275,"meta":31,"style":31},"\u003Ctemplate>\n    \u003CAlbumForm :isEdit=\"false\"\u002F>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport AlbumForm from '.\u002Fcomponents\u002Fform';\nexport default {\n    name:'add',\n    components:{AlbumForm}\n}\n\u003C\u002Fscript>\n","app.vue",[33,1563,1564,1572,1594,1602,1606,1614,1633,1641,1656,1668,1672],{"__ignoreMap":31},[36,1565,1566,1568,1570],{"class":38,"line":39},[36,1567,1275],{"class":270},[36,1569,1279],{"class":1278},[36,1571,1282],{"class":270},[36,1573,1574,1576,1579,1582,1584,1586,1589,1591],{"class":38,"line":45},[36,1575,1287],{"class":270},[36,1577,1578],{"class":1278},"AlbumForm",[36,1580,1581],{"class":355}," :isEdit",[36,1583,317],{"class":270},[36,1585,1298],{"class":270},[36,1587,1588],{"class":274},"false",[36,1590,1298],{"class":270},[36,1592,1593],{"class":270},"\u002F>\n",[36,1595,1596,1598,1600],{"class":38,"line":51},[36,1597,1321],{"class":270},[36,1599,1279],{"class":1278},[36,1601,1282],{"class":270},[36,1603,1604],{"class":38,"line":58},[36,1605,55],{"emptyLinePlaceholder":54},[36,1607,1608,1610,1612],{"class":38,"line":64},[36,1609,1275],{"class":270},[36,1611,1493],{"class":1278},[36,1613,1282],{"class":270},[36,1615,1616,1618,1621,1623,1625,1628,1630],{"class":38,"line":70},[36,1617,260],{"class":259},[36,1619,1620],{"class":263}," AlbumForm ",[36,1622,267],{"class":259},[36,1624,271],{"class":270},[36,1626,1627],{"class":274},".\u002Fcomponents\u002Fform",[36,1629,382],{"class":270},[36,1631,1632],{"class":270},";\n",[36,1634,1635,1637,1639],{"class":38,"line":75},[36,1636,1500],{"class":259},[36,1638,1503],{"class":259},[36,1640,1506],{"class":270},[36,1642,1643,1645,1647,1649,1652,1654],{"class":38,"line":81},[36,1644,1511],{"class":1278},[36,1646,348],{"class":270},[36,1648,382],{"class":270},[36,1650,1651],{"class":274},"add",[36,1653,382],{"class":270},[36,1655,364],{"class":270},[36,1657,1658,1661,1664,1666],{"class":38,"line":87},[36,1659,1660],{"class":1278},"    components",[36,1662,1663],{"class":270},":{",[36,1665,1578],{"class":263},[36,1667,599],{"class":270},[36,1669,1670],{"class":38,"line":92},[36,1671,599],{"class":270},[36,1673,1674,1676,1678],{"class":38,"line":98},[36,1675,1321],{"class":270},[36,1677,1493],{"class":1278},[36,1679,1282],{"class":270},[11,1681,1682,1684,1685,1687],{},[33,1683,237],{},"にて",[33,1686,241],{},"の要素にマウントする様にします。",[26,1689,1691],{"className":249,"code":1690,"filename":237,"language":252,"meta":31,"style":31},"import Vue from 'vue'\nimport Add from '.\u002Fadd.vue'\n\nVue.config.productionTip = false\n\nnew Vue({\n  render: h => h(Add),\n}).$mount('#add')\n",[33,1692,1693,1707,1723,1727,1743,1747,1757,1774],{"__ignoreMap":31},[36,1694,1695,1697,1699,1701,1703,1705],{"class":38,"line":39},[36,1696,260],{"class":259},[36,1698,264],{"class":263},[36,1700,267],{"class":259},[36,1702,271],{"class":270},[36,1704,275],{"class":274},[36,1706,278],{"class":270},[36,1708,1709,1711,1714,1716,1718,1721],{"class":38,"line":45},[36,1710,260],{"class":259},[36,1712,1713],{"class":263}," Add ",[36,1715,267],{"class":259},[36,1717,271],{"class":270},[36,1719,1720],{"class":274},".\u002Fadd.vue",[36,1722,278],{"class":270},[36,1724,1725],{"class":38,"line":51},[36,1726,55],{"emptyLinePlaceholder":54},[36,1728,1729,1731,1733,1735,1737,1739,1741],{"class":38,"line":58},[36,1730,303],{"class":263},[36,1732,306],{"class":270},[36,1734,309],{"class":263},[36,1736,306],{"class":270},[36,1738,314],{"class":263},[36,1740,317],{"class":270},[36,1742,321],{"class":320},[36,1744,1745],{"class":38,"line":64},[36,1746,55],{"emptyLinePlaceholder":54},[36,1748,1749,1751,1753,1755],{"class":38,"line":70},[36,1750,330],{"class":270},[36,1752,334],{"class":333},[36,1754,337],{"class":263},[36,1756,340],{"class":270},[36,1758,1759,1761,1763,1765,1767,1769,1772],{"class":38,"line":75},[36,1760,345],{"class":333},[36,1762,348],{"class":270},[36,1764,352],{"class":351},[36,1766,356],{"class":355},[36,1768,352],{"class":333},[36,1770,1771],{"class":263},"(Add)",[36,1773,364],{"class":270},[36,1775,1776,1778,1780,1782,1784,1786,1788,1791,1793],{"class":38,"line":81},[36,1777,369],{"class":270},[36,1779,372],{"class":263},[36,1781,306],{"class":270},[36,1783,377],{"class":333},[36,1785,337],{"class":263},[36,1787,382],{"class":270},[36,1789,1790],{"class":274},"#add",[36,1792,382],{"class":270},[36,1794,390],{"class":263},[11,1796,1797,1798,1801],{},"こうしてビルドをしてみましょう。そして",[33,1799,1800],{},"\u002Fdashboard\u002Fvuetest\u002Fadd","へ移動してみると",[112,1803],{":src":1804,":width":115},"'_mix\u002Fsch-2020-08-26-22.48.54-768x529.png'",[11,1806,1807,1808,1810,1811,1813,1814,1816],{},"しっかりと",[33,1809,241],{},"に",[33,1812,1268],{},"がレンダリングされました。ではこの",[33,1815,1268],{},"にガシガシとフォームUIを作っていきましょう！",[18,1818,1819],{"id":1819},"まずはタイトルを入力するだけのものを作成",[11,1821,1822],{},"最終的なフォームにはリッチテキストエディタとファイルセレクターなど、concrete5で用いられているフォームをvueで実装したいと思います。しかし、その２つはなかなか曲者で今回の記事で説明すると長くなるので次回に話します。",[11,1824,1825],{},"まずはvueとPHP(concrete5)でどう連携させてDBにデータを挿入するかの全体的な流れについて説明するとともに、タイトル部分（プレーンテキスト）を追加できる様にしていきましょう。",[139,1827,1829],{"id":1828},"postでバックに値を送る","POSTでバックに値を送る",[11,1831,1832,1833,1836,1837,1840,1841,1843],{},"vueで構築したUIで入力された値はどうやってバックに渡すか？簡単です。フォームを作る時の様に",[33,1834,1835],{},"input","を",[33,1838,1839],{},"\u003Cform method=\"post\">\u003C\u002Fform>","で囲んであげて、送信用のsubmitボタンを作るだけです。今回のパッケージではPOSTの値をAjaxとかで送るのでなく、普通にsubmitでサーバーに送信する様にしました。以下の様に",[33,1842,1268],{},"を変えます。",[26,1845,1847],{"className":1266,"code":1846,"filename":1268,"language":275,"meta":31,"style":31},"\u003Ctemplate>\n    \u003Cdiv class=\"ccm-dashboard-content-inner\">\n        \u003Ch3>アルバム新規追加\u003C\u002Fh3>\n        \u003Chr>\n        \u003Cform method=\"post\">\n            \u003Clabel for=\"title\" class=\"control-label\">アルバムタイトル\u003C\u002Flabel>\n            \u003Cinput type=\"text\" name=\"title\" class=\"form-control ccm-input-text\" v-model=\"title\">\n\n            \u003Cdiv class=\"ccm-dashboard-form-actions-wrapper\">\n                \u003Cdiv class=\"ccm-dashboard-form-actions\">\n                    \u003Cbutton class=\"pull-left btn btn-primary\">\n                        一覧へ戻る\n                    \u003C\u002Fbutton>\n                    \u003Cinput value=\"登録\" type=\"submit\" class=\"pull-right btn btn-primary\">\n                \u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n        \u003C\u002Fform>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default {\n    name:'albumForm',\n    props:['isEdit'],\n    data(){\n        return{\n            title:''\n        }\n    }\n}\n\u003C\u002Fscript>\n",[33,1848,1849,1857,1875,1892,1901,1922,1963,2015,2019,2037,2055,2074,2079,2088,2129,2137,2145,2153,2161,2169,2173,2181,2189,2203,2222,2230,2237,2247,2252,2256,2260],{"__ignoreMap":31},[36,1850,1851,1853,1855],{"class":38,"line":39},[36,1852,1275],{"class":270},[36,1854,1279],{"class":1278},[36,1856,1282],{"class":270},[36,1858,1859,1861,1863,1865,1867,1869,1871,1873],{"class":38,"line":45},[36,1860,1287],{"class":270},[36,1862,1290],{"class":1278},[36,1864,1293],{"class":355},[36,1866,317],{"class":270},[36,1868,1298],{"class":270},[36,1870,1301],{"class":274},[36,1872,1298],{"class":270},[36,1874,1282],{"class":270},[36,1876,1877,1879,1881,1883,1886,1888,1890],{"class":38,"line":51},[36,1878,1310],{"class":270},[36,1880,139],{"class":1278},[36,1882,1315],{"class":270},[36,1884,1885],{"class":263},"アルバム新規追加",[36,1887,1321],{"class":270},[36,1889,139],{"class":1278},[36,1891,1282],{"class":270},[36,1893,1894,1896,1899],{"class":38,"line":58},[36,1895,1310],{"class":270},[36,1897,1898],{"class":1278},"hr",[36,1900,1282],{"class":270},[36,1902,1903,1905,1908,1911,1913,1915,1918,1920],{"class":38,"line":64},[36,1904,1310],{"class":270},[36,1906,1907],{"class":1278},"form",[36,1909,1910],{"class":355}," method",[36,1912,317],{"class":270},[36,1914,1298],{"class":270},[36,1916,1917],{"class":274},"post",[36,1919,1298],{"class":270},[36,1921,1282],{"class":270},[36,1923,1924,1926,1929,1932,1934,1936,1939,1941,1943,1945,1947,1950,1952,1954,1957,1959,1961],{"class":38,"line":70},[36,1925,1353],{"class":270},[36,1927,1928],{"class":1278},"label",[36,1930,1931],{"class":355}," for",[36,1933,317],{"class":270},[36,1935,1298],{"class":270},[36,1937,1938],{"class":274},"title",[36,1940,1298],{"class":270},[36,1942,1293],{"class":355},[36,1944,317],{"class":270},[36,1946,1298],{"class":270},[36,1948,1949],{"class":274},"control-label",[36,1951,1298],{"class":270},[36,1953,1315],{"class":270},[36,1955,1956],{"class":263},"アルバムタイトル",[36,1958,1321],{"class":270},[36,1960,1928],{"class":1278},[36,1962,1282],{"class":270},[36,1964,1965,1967,1969,1972,1974,1976,1978,1980,1983,1985,1987,1989,1991,1993,1995,1997,2000,2002,2005,2007,2009,2011,2013],{"class":38,"line":75},[36,1966,1353],{"class":270},[36,1968,1835],{"class":1278},[36,1970,1971],{"class":355}," type",[36,1973,317],{"class":270},[36,1975,1298],{"class":270},[36,1977,163],{"class":274},[36,1979,1298],{"class":270},[36,1981,1982],{"class":355}," name",[36,1984,317],{"class":270},[36,1986,1298],{"class":270},[36,1988,1938],{"class":274},[36,1990,1298],{"class":270},[36,1992,1293],{"class":355},[36,1994,317],{"class":270},[36,1996,1298],{"class":270},[36,1998,1999],{"class":274},"form-control ccm-input-text",[36,2001,1298],{"class":270},[36,2003,2004],{"class":355}," v-model",[36,2006,317],{"class":270},[36,2008,1298],{"class":270},[36,2010,1938],{"class":274},[36,2012,1298],{"class":270},[36,2014,1282],{"class":270},[36,2016,2017],{"class":38,"line":81},[36,2018,55],{"emptyLinePlaceholder":54},[36,2020,2021,2023,2025,2027,2029,2031,2033,2035],{"class":38,"line":87},[36,2022,1353],{"class":270},[36,2024,1290],{"class":1278},[36,2026,1293],{"class":355},[36,2028,317],{"class":270},[36,2030,1298],{"class":270},[36,2032,1344],{"class":274},[36,2034,1298],{"class":270},[36,2036,1282],{"class":270},[36,2038,2039,2041,2043,2045,2047,2049,2051,2053],{"class":38,"line":92},[36,2040,1373],{"class":270},[36,2042,1290],{"class":1278},[36,2044,1293],{"class":355},[36,2046,317],{"class":270},[36,2048,1298],{"class":270},[36,2050,1364],{"class":274},[36,2052,1298],{"class":270},[36,2054,1282],{"class":270},[36,2056,2057,2060,2062,2064,2066,2068,2070,2072],{"class":38,"line":98},[36,2058,2059],{"class":270},"                    \u003C",[36,2061,1376],{"class":1278},[36,2063,1293],{"class":355},[36,2065,317],{"class":270},[36,2067,1298],{"class":270},[36,2069,1385],{"class":274},[36,2071,1298],{"class":270},[36,2073,1282],{"class":270},[36,2075,2076],{"class":38,"line":104},[36,2077,2078],{"class":263},"                        一覧へ戻る\n",[36,2080,2081,2084,2086],{"class":38,"line":481},[36,2082,2083],{"class":270},"                    \u003C\u002F",[36,2085,1376],{"class":1278},[36,2087,1282],{"class":270},[36,2089,2090,2092,2094,2097,2099,2101,2104,2106,2108,2110,2112,2115,2117,2119,2121,2123,2125,2127],{"class":38,"line":487},[36,2091,2059],{"class":270},[36,2093,1835],{"class":1278},[36,2095,2096],{"class":355}," value",[36,2098,317],{"class":270},[36,2100,1298],{"class":270},[36,2102,2103],{"class":274},"登録",[36,2105,1298],{"class":270},[36,2107,1971],{"class":355},[36,2109,317],{"class":270},[36,2111,1298],{"class":270},[36,2113,2114],{"class":274},"submit",[36,2116,1298],{"class":270},[36,2118,1293],{"class":355},[36,2120,317],{"class":270},[36,2122,1298],{"class":270},[36,2124,1430],{"class":274},[36,2126,1298],{"class":270},[36,2128,1282],{"class":270},[36,2130,2131,2133,2135],{"class":38,"line":493},[36,2132,1399],{"class":270},[36,2134,1290],{"class":1278},[36,2136,1282],{"class":270},[36,2138,2139,2141,2143],{"class":38,"line":499},[36,2140,1452],{"class":270},[36,2142,1290],{"class":1278},[36,2144,1282],{"class":270},[36,2146,2147,2149,2151],{"class":38,"line":505},[36,2148,1461],{"class":270},[36,2150,1907],{"class":1278},[36,2152,1282],{"class":270},[36,2154,2155,2157,2159],{"class":38,"line":511},[36,2156,1470],{"class":270},[36,2158,1290],{"class":1278},[36,2160,1282],{"class":270},[36,2162,2163,2165,2167],{"class":38,"line":517},[36,2164,1321],{"class":270},[36,2166,1279],{"class":1278},[36,2168,1282],{"class":270},[36,2170,2171],{"class":38,"line":523},[36,2172,55],{"emptyLinePlaceholder":54},[36,2174,2175,2177,2179],{"class":38,"line":529},[36,2176,1275],{"class":270},[36,2178,1493],{"class":1278},[36,2180,1282],{"class":270},[36,2182,2183,2185,2187],{"class":38,"line":534},[36,2184,1500],{"class":259},[36,2186,1503],{"class":259},[36,2188,1506],{"class":270},[36,2190,2191,2193,2195,2197,2199,2201],{"class":38,"line":540},[36,2192,1511],{"class":1278},[36,2194,348],{"class":270},[36,2196,382],{"class":270},[36,2198,1518],{"class":274},[36,2200,382],{"class":270},[36,2202,364],{"class":270},[36,2204,2205,2207,2209,2211,2213,2215,2217,2220],{"class":38,"line":545},[36,2206,1527],{"class":1278},[36,2208,348],{"class":270},[36,2210,1532],{"class":263},[36,2212,382],{"class":270},[36,2214,1537],{"class":274},[36,2216,382],{"class":270},[36,2218,2219],{"class":263},"]",[36,2221,364],{"class":270},[36,2223,2224,2227],{"class":38,"line":550},[36,2225,2226],{"class":1278},"    data",[36,2228,2229],{"class":270},"(){\n",[36,2231,2232,2235],{"class":38,"line":555},[36,2233,2234],{"class":259},"        return",[36,2236,340],{"class":270},[36,2238,2239,2242,2244],{"class":38,"line":560},[36,2240,2241],{"class":1278},"            title",[36,2243,348],{"class":270},[36,2245,2246],{"class":270},"''\n",[36,2248,2249],{"class":38,"line":566},[36,2250,2251],{"class":270},"        }\n",[36,2253,2254],{"class":38,"line":572},[36,2255,587],{"class":270},[36,2257,2258],{"class":38,"line":578},[36,2259,599],{"class":270},[36,2261,2262,2264,2266],{"class":38,"line":584},[36,2263,1321],{"class":270},[36,2265,1493],{"class":1278},[36,2267,1282],{"class":270},[139,2269,2270],{"id":2270},"フロントバリデーションを実装",[11,2272,2273],{},"フォームから入力された値をバリデーションチェックします。しかし今回はせっかくvueを使っているのでフロントでバリデーションをしてあげましょう。例えばtitleが空でないかをチェックする場合以下の様にします。",[26,2275,2277],{"className":1266,"code":2276,"filename":1268,"language":275,"meta":31,"style":31},"\u003Ctemplate>\n    \u003Cdiv class=\"ccm-dashboard-content-inner\">\n        \u003Ch3>アルバム新規追加\u003C\u002Fh3>\n        \u003Chr>\n        \u003Cform method=\"post\" @submit=\"checkForm\">\n            \u003Clabel for=\"title\" class=\"control-label\">アルバムタイトル\u003C\u002Flabel>\n            \u003Cinput type=\"text\" name=\"title\" class=\"form-control ccm-input-text\" v-model=\"title\">\n            \u003Cp class=\"text-danger\" v-if=\"errTitle.length>0\">{{errTitle}}\u003C\u002Fp>\n\n            \u003Cdiv class=\"ccm-dashboard-form-actions-wrapper\">\n                \u003Cdiv class=\"ccm-dashboard-form-actions\">\n                    \u003Cbutton class=\"pull-left btn btn-primary\">\n                        一覧へ戻る\n                    \u003C\u002Fbutton>\n                    \u003Cinput value=\"登録\" type=\"submit\" class=\"pull-right btn btn-primary\">\n                \u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n        \u003C\u002Fform>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript>\nexport default {\n    name:'albumForm',\n    props:['isEdit'],\n    data(){\n        return{\n            title:'',\n            errTitle:''\n        }\n    },\n    methods:{\n        checkForm($event){\n            if(this.title.length >0){\n                return true;\n            }else{\n                $event.preventDefault();\n                ConcreteAlert.error({\n                    title:'入力項目に誤りがあります。',\n                    message:'アルバムタイトルが入力されていません。',\n                    delay:5000\n                })\n                this.errTitle = \"タイトルを入力してください。\"\n            }\n        }\n    }\n}\n\u003C\u002Fscript>\n",[33,2278,2279,2287,2305,2321,2329,2359,2395,2443,2483,2487,2505,2523,2541,2545,2553,2591,2599,2607,2615,2623,2631,2639,2647,2661,2679,2685,2691,2702,2711,2715,2720,2728,2741,2769,2779,2789,2804,2818,2834,2850,2860,2867,2887,2892,2896,2900,2905],{"__ignoreMap":31},[36,2280,2281,2283,2285],{"class":38,"line":39},[36,2282,1275],{"class":270},[36,2284,1279],{"class":1278},[36,2286,1282],{"class":270},[36,2288,2289,2291,2293,2295,2297,2299,2301,2303],{"class":38,"line":45},[36,2290,1287],{"class":270},[36,2292,1290],{"class":1278},[36,2294,1293],{"class":355},[36,2296,317],{"class":270},[36,2298,1298],{"class":270},[36,2300,1301],{"class":274},[36,2302,1298],{"class":270},[36,2304,1282],{"class":270},[36,2306,2307,2309,2311,2313,2315,2317,2319],{"class":38,"line":51},[36,2308,1310],{"class":270},[36,2310,139],{"class":1278},[36,2312,1315],{"class":270},[36,2314,1885],{"class":263},[36,2316,1321],{"class":270},[36,2318,139],{"class":1278},[36,2320,1282],{"class":270},[36,2322,2323,2325,2327],{"class":38,"line":58},[36,2324,1310],{"class":270},[36,2326,1898],{"class":1278},[36,2328,1282],{"class":270},[36,2330,2331,2333,2335,2337,2339,2341,2343,2345,2348,2350,2352,2355,2357],{"class":38,"line":64},[36,2332,1310],{"class":270},[36,2334,1907],{"class":1278},[36,2336,1910],{"class":355},[36,2338,317],{"class":270},[36,2340,1298],{"class":270},[36,2342,1917],{"class":274},[36,2344,1298],{"class":270},[36,2346,2347],{"class":355}," @submit",[36,2349,317],{"class":270},[36,2351,1298],{"class":270},[36,2353,2354],{"class":274},"checkForm",[36,2356,1298],{"class":270},[36,2358,1282],{"class":270},[36,2360,2361,2363,2365,2367,2369,2371,2373,2375,2377,2379,2381,2383,2385,2387,2389,2391,2393],{"class":38,"line":70},[36,2362,1353],{"class":270},[36,2364,1928],{"class":1278},[36,2366,1931],{"class":355},[36,2368,317],{"class":270},[36,2370,1298],{"class":270},[36,2372,1938],{"class":274},[36,2374,1298],{"class":270},[36,2376,1293],{"class":355},[36,2378,317],{"class":270},[36,2380,1298],{"class":270},[36,2382,1949],{"class":274},[36,2384,1298],{"class":270},[36,2386,1315],{"class":270},[36,2388,1956],{"class":263},[36,2390,1321],{"class":270},[36,2392,1928],{"class":1278},[36,2394,1282],{"class":270},[36,2396,2397,2399,2401,2403,2405,2407,2409,2411,2413,2415,2417,2419,2421,2423,2425,2427,2429,2431,2433,2435,2437,2439,2441],{"class":38,"line":75},[36,2398,1353],{"class":270},[36,2400,1835],{"class":1278},[36,2402,1971],{"class":355},[36,2404,317],{"class":270},[36,2406,1298],{"class":270},[36,2408,163],{"class":274},[36,2410,1298],{"class":270},[36,2412,1982],{"class":355},[36,2414,317],{"class":270},[36,2416,1298],{"class":270},[36,2418,1938],{"class":274},[36,2420,1298],{"class":270},[36,2422,1293],{"class":355},[36,2424,317],{"class":270},[36,2426,1298],{"class":270},[36,2428,1999],{"class":274},[36,2430,1298],{"class":270},[36,2432,2004],{"class":355},[36,2434,317],{"class":270},[36,2436,1298],{"class":270},[36,2438,1938],{"class":274},[36,2440,1298],{"class":270},[36,2442,1282],{"class":270},[36,2444,2445,2447,2449,2451,2453,2455,2458,2460,2463,2465,2467,2470,2472,2474,2477,2479,2481],{"class":38,"line":81},[36,2446,1353],{"class":270},[36,2448,11],{"class":1278},[36,2450,1293],{"class":355},[36,2452,317],{"class":270},[36,2454,1298],{"class":270},[36,2456,2457],{"class":274},"text-danger",[36,2459,1298],{"class":270},[36,2461,2462],{"class":355}," v-if",[36,2464,317],{"class":270},[36,2466,1298],{"class":270},[36,2468,2469],{"class":274},"errTitle.length>0",[36,2471,1298],{"class":270},[36,2473,1315],{"class":270},[36,2475,2476],{"class":263},"{{errTitle}}",[36,2478,1321],{"class":270},[36,2480,11],{"class":1278},[36,2482,1282],{"class":270},[36,2484,2485],{"class":38,"line":87},[36,2486,55],{"emptyLinePlaceholder":54},[36,2488,2489,2491,2493,2495,2497,2499,2501,2503],{"class":38,"line":92},[36,2490,1353],{"class":270},[36,2492,1290],{"class":1278},[36,2494,1293],{"class":355},[36,2496,317],{"class":270},[36,2498,1298],{"class":270},[36,2500,1344],{"class":274},[36,2502,1298],{"class":270},[36,2504,1282],{"class":270},[36,2506,2507,2509,2511,2513,2515,2517,2519,2521],{"class":38,"line":98},[36,2508,1373],{"class":270},[36,2510,1290],{"class":1278},[36,2512,1293],{"class":355},[36,2514,317],{"class":270},[36,2516,1298],{"class":270},[36,2518,1364],{"class":274},[36,2520,1298],{"class":270},[36,2522,1282],{"class":270},[36,2524,2525,2527,2529,2531,2533,2535,2537,2539],{"class":38,"line":104},[36,2526,2059],{"class":270},[36,2528,1376],{"class":1278},[36,2530,1293],{"class":355},[36,2532,317],{"class":270},[36,2534,1298],{"class":270},[36,2536,1385],{"class":274},[36,2538,1298],{"class":270},[36,2540,1282],{"class":270},[36,2542,2543],{"class":38,"line":481},[36,2544,2078],{"class":263},[36,2546,2547,2549,2551],{"class":38,"line":487},[36,2548,2083],{"class":270},[36,2550,1376],{"class":1278},[36,2552,1282],{"class":270},[36,2554,2555,2557,2559,2561,2563,2565,2567,2569,2571,2573,2575,2577,2579,2581,2583,2585,2587,2589],{"class":38,"line":493},[36,2556,2059],{"class":270},[36,2558,1835],{"class":1278},[36,2560,2096],{"class":355},[36,2562,317],{"class":270},[36,2564,1298],{"class":270},[36,2566,2103],{"class":274},[36,2568,1298],{"class":270},[36,2570,1971],{"class":355},[36,2572,317],{"class":270},[36,2574,1298],{"class":270},[36,2576,2114],{"class":274},[36,2578,1298],{"class":270},[36,2580,1293],{"class":355},[36,2582,317],{"class":270},[36,2584,1298],{"class":270},[36,2586,1430],{"class":274},[36,2588,1298],{"class":270},[36,2590,1282],{"class":270},[36,2592,2593,2595,2597],{"class":38,"line":499},[36,2594,1399],{"class":270},[36,2596,1290],{"class":1278},[36,2598,1282],{"class":270},[36,2600,2601,2603,2605],{"class":38,"line":505},[36,2602,1452],{"class":270},[36,2604,1290],{"class":1278},[36,2606,1282],{"class":270},[36,2608,2609,2611,2613],{"class":38,"line":511},[36,2610,1461],{"class":270},[36,2612,1907],{"class":1278},[36,2614,1282],{"class":270},[36,2616,2617,2619,2621],{"class":38,"line":517},[36,2618,1470],{"class":270},[36,2620,1290],{"class":1278},[36,2622,1282],{"class":270},[36,2624,2625,2627,2629],{"class":38,"line":523},[36,2626,1321],{"class":270},[36,2628,1279],{"class":1278},[36,2630,1282],{"class":270},[36,2632,2633,2635,2637],{"class":38,"line":529},[36,2634,1275],{"class":270},[36,2636,1493],{"class":1278},[36,2638,1282],{"class":270},[36,2640,2641,2643,2645],{"class":38,"line":534},[36,2642,1500],{"class":259},[36,2644,1503],{"class":259},[36,2646,1506],{"class":270},[36,2648,2649,2651,2653,2655,2657,2659],{"class":38,"line":540},[36,2650,1511],{"class":1278},[36,2652,348],{"class":270},[36,2654,382],{"class":270},[36,2656,1518],{"class":274},[36,2658,382],{"class":270},[36,2660,364],{"class":270},[36,2662,2663,2665,2667,2669,2671,2673,2675,2677],{"class":38,"line":545},[36,2664,1527],{"class":1278},[36,2666,348],{"class":270},[36,2668,1532],{"class":263},[36,2670,382],{"class":270},[36,2672,1537],{"class":274},[36,2674,382],{"class":270},[36,2676,2219],{"class":263},[36,2678,364],{"class":270},[36,2680,2681,2683],{"class":38,"line":550},[36,2682,2226],{"class":1278},[36,2684,2229],{"class":270},[36,2686,2687,2689],{"class":38,"line":555},[36,2688,2234],{"class":259},[36,2690,340],{"class":270},[36,2692,2693,2695,2697,2700],{"class":38,"line":560},[36,2694,2241],{"class":1278},[36,2696,348],{"class":270},[36,2698,2699],{"class":270},"''",[36,2701,364],{"class":270},[36,2703,2704,2707,2709],{"class":38,"line":566},[36,2705,2706],{"class":1278},"            errTitle",[36,2708,348],{"class":270},[36,2710,2246],{"class":270},[36,2712,2713],{"class":38,"line":572},[36,2714,2251],{"class":270},[36,2716,2717],{"class":38,"line":578},[36,2718,2719],{"class":270},"    },\n",[36,2721,2722,2725],{"class":38,"line":584},[36,2723,2724],{"class":1278},"    methods",[36,2726,2727],{"class":270},":{\n",[36,2729,2730,2733,2735,2738],{"class":38,"line":590},[36,2731,2732],{"class":1278},"        checkForm",[36,2734,337],{"class":270},[36,2736,2737],{"class":351},"$event",[36,2739,2740],{"class":270},"){\n",[36,2742,2743,2746,2748,2751,2753,2755,2758,2761,2765,2767],{"class":38,"line":596},[36,2744,2745],{"class":259},"            if",[36,2747,337],{"class":1278},[36,2749,2750],{"class":270},"this.",[36,2752,1938],{"class":263},[36,2754,306],{"class":270},[36,2756,2757],{"class":263},"length",[36,2759,2760],{"class":270}," >",[36,2762,2764],{"class":2763},"sx098","0",[36,2766,372],{"class":1278},[36,2768,340],{"class":270},[36,2770,2771,2774,2777],{"class":38,"line":1017},[36,2772,2773],{"class":259},"                return",[36,2775,2776],{"class":320}," true",[36,2778,1632],{"class":270},[36,2780,2781,2784,2787],{"class":38,"line":1023},[36,2782,2783],{"class":270},"            }",[36,2785,2786],{"class":259},"else",[36,2788,340],{"class":270},[36,2790,2791,2794,2796,2799,2802],{"class":38,"line":1028},[36,2792,2793],{"class":263},"                $event",[36,2795,306],{"class":270},[36,2797,2798],{"class":333},"preventDefault",[36,2800,2801],{"class":1278},"()",[36,2803,1632],{"class":270},[36,2805,2806,2809,2811,2814,2816],{"class":38,"line":1033},[36,2807,2808],{"class":263},"                ConcreteAlert",[36,2810,306],{"class":270},[36,2812,2813],{"class":333},"error",[36,2815,337],{"class":1278},[36,2817,340],{"class":270},[36,2819,2820,2823,2825,2827,2830,2832],{"class":38,"line":1038},[36,2821,2822],{"class":1278},"                    title",[36,2824,348],{"class":270},[36,2826,382],{"class":270},[36,2828,2829],{"class":274},"入力項目に誤りがあります。",[36,2831,382],{"class":270},[36,2833,364],{"class":270},[36,2835,2836,2839,2841,2843,2846,2848],{"class":38,"line":1043},[36,2837,2838],{"class":1278},"                    message",[36,2840,348],{"class":270},[36,2842,382],{"class":270},[36,2844,2845],{"class":274},"アルバムタイトルが入力されていません。",[36,2847,382],{"class":270},[36,2849,364],{"class":270},[36,2851,2852,2855,2857],{"class":38,"line":1048},[36,2853,2854],{"class":1278},"                    delay",[36,2856,348],{"class":270},[36,2858,2859],{"class":2763},"5000\n",[36,2861,2862,2865],{"class":38,"line":1053},[36,2863,2864],{"class":270},"                }",[36,2866,390],{"class":1278},[36,2868,2869,2872,2875,2878,2881,2884],{"class":38,"line":1058},[36,2870,2871],{"class":270},"                this.",[36,2873,2874],{"class":263},"errTitle",[36,2876,2877],{"class":270}," =",[36,2879,2880],{"class":270}," \"",[36,2882,2883],{"class":274},"タイトルを入力してください。",[36,2885,2886],{"class":270},"\"\n",[36,2888,2889],{"class":38,"line":1063},[36,2890,2891],{"class":270},"            }\n",[36,2893,2894],{"class":38,"line":1068},[36,2895,2251],{"class":270},[36,2897,2898],{"class":38,"line":1073},[36,2899,587],{"class":270},[36,2901,2903],{"class":38,"line":2902},46,[36,2904,599],{"class":270},[36,2906,2908,2910,2912],{"class":38,"line":2907},47,[36,2909,1321],{"class":270},[36,2911,1493],{"class":1278},[36,2913,1282],{"class":270},[11,2915,2916,2918,2919,2922,2923,2925,2926,2928],{},[33,2917,1907],{},"の箇所に",[33,2920,2921],{},"@submit=\"checkForm\"","というイベントを作成。これはこのformがsubmitされた際に",[33,2924,2354],{},"というメソッドを発火させるという意味です。そして",[33,2927,2354],{},"ではtitleの値が空かどうかを判断しています。",[11,2930,2931,2932,2935,2936,2938,2939,2942,2943,2946,2947,2949],{},"もし空でない場合は",[33,2933,2934],{},"return true","となって",[33,2937,2114],{},"が通って、サーバーへ値が送信されます。からの場合は",[33,2940,2941],{},"$event.preventDefault();","が実行されてsubmitされません。そして",[33,2944,2945],{},"ConcreteAlert.error","という8.4系から使用できるconcrete5のフラッシュメッセージのjsを出しています。（",[33,2948,2945],{},"は特に何も読み込まないでも使える）",[11,2951,2952],{},"空で「登録」を押すと以下の様になります。",[112,2954],{":src":2955,":width":115},"'_mix\u002Fsch-2020-08-27-0.07.18-768x531.png'",[11,2957,2958,2960],{},[33,2959,2941],{},"によってサーバーにデータは送信されず、ユーザーに対してエラーを表示できました。",[220,2962,2964],{"id":2963},"eslintがある時のchips","ESLintがある時のchips",[11,2966,2967,2970],{},[33,2968,2969],{},"ConcreteAlert"," はconcrete5が用意してくれた便利なフラッシュメッセージです。しかし、ESLint付きのvueCLI内で使用ようとすると、ビルド時にこの様に怒られます。",[26,2972,2975],{"className":2973,"code":2974,"language":163},[161],"ERROR  Failed to compile with 1 errors                                                                                                       23:50:16\n\n error  in .\u002Fsrc\u002Fcomponents\u002Fform.vue\n\nModule Error (from .\u002Fnode_modules\u002Feslint-loader\u002Findex.js):\n\n\u002FApplications\u002FMAMP\u002Fhtdocs\u002Fc5test\u002Fpackages\u002Fvuetest\u002Fjs\u002Fpackageui\u002Fsrc\u002Fcomponents\u002Fform.vue\n  40:17  error  'ConcreteAlert' is not defined  no-undef\n\n✖ 1 problem (1 error, 0 warnings)\n",[33,2976,2974],{"__ignoreMap":31},[11,2978,2979,2980,2982,2983,2986],{},"そうです。vueプロジェクト内には",[33,2981,2969],{}," を定義したjsファイルがない、というかconcreteが用意したjsを読み込めないのでこの様に怒られます。これだとビルドできないので",[33,2984,2985],{},"package.json","のeslintの設定に以下の記述をします。",[26,2988,2991],{"className":2989,"code":2990,"language":163},[161]," \"eslintConfig\": {\n　　\"globals\":{\n      \"ConcreteAlert\": true,\n    }\n },\n",[33,2992,2990],{"__ignoreMap":31},[11,2994,2995,2996,2998],{},"こうするとESLintは「ConcreteAlertってのはグローバルな奴なんだな〜。」と認識してくれて、実際にvueプロジェクト外にある",[33,2997,2969],{},"に対して怒らなくなります。",[18,3000,3001],{"id":3001},"バックエンド実装",[11,3003,3004],{},"vueを用いてまずはアルバムのタイトルだけを入力できるフォームを作りました。そしてこのタイトルをDBに挿入するまで行います。と言ってもシングルページコントローラーを以下の様に記述します。",[26,3006,3008],{"className":28,"code":3007,"filename":1102,"language":30,"meta":31,"style":31},"\u003C?php\nnamespace Concrete\\Package\\Vuetest\\Controller\\SinglePage\\Dashboard;\ndefined('C5_EXECUTE') or die('Access Denied.');\nuse \\Concrete\\Core\\Page\\Controller\\DashboardPageController;\nuse Concrete\\Core\\Routing\\Redirect;\nuse Concrete\\Core\\Http\\Request;\n\n\nuse Core;\nuse Database;\n\nclass Vuetest extends DashboardPageController\n{\n    public $packageHandle = 'vuetest';\n\n    public function on_start()\n    {\n        $this->requireAsset('package-vue-production');\n    }\n\n    public function view() {\n    }\n\n    public function add(){\n        if(Request::isPost() == true){\n            $title = $this->post('title');\n\n            if(empty($title)==false){\n                $db = Database::connection();\n                $db->executeQuery(\"START TRANSACTION\");\n                $db->executeQuery(\n                    'INSERT album SET `title`=?, `created`=now(), `modified`=now()',\n                    array($title)\n                );\n                $db->executeQuery(\"COMMIT\");\n                Redirect::to('\u002Fdashboard\u002Fvuetest')->send();\n            }else{\n                Redirect::to('\u002Fdashboard\u002Fvuetest')->send();\n            }\n\n        }else{\n            $this->render('\u002Fdashboard\u002Fvuetest\u002Fadd');\n        }\n    }\n}\n",[33,3009,3010,3014,3018,3022,3026,3031,3036,3040,3044,3049,3054,3058,3062,3066,3070,3074,3078,3082,3086,3090,3094,3098,3102,3106,3110,3115,3120,3124,3129,3134,3139,3144,3149,3154,3159,3164,3169,3174,3178,3182,3186,3191,3196,3200,3204],{"__ignoreMap":31},[36,3011,3012],{"class":38,"line":39},[36,3013,425],{},[36,3015,3016],{"class":38,"line":45},[36,3017,646],{},[36,3019,3020],{"class":38,"line":51},[36,3021,435],{},[36,3023,3024],{"class":38,"line":58},[36,3025,655],{},[36,3027,3028],{"class":38,"line":64},[36,3029,3030],{},"use Concrete\\Core\\Routing\\Redirect;\n",[36,3032,3033],{"class":38,"line":70},[36,3034,3035],{},"use Concrete\\Core\\Http\\Request;\n",[36,3037,3038],{"class":38,"line":75},[36,3039,55],{"emptyLinePlaceholder":54},[36,3041,3042],{"class":38,"line":81},[36,3043,55],{"emptyLinePlaceholder":54},[36,3045,3046],{"class":38,"line":87},[36,3047,3048],{},"use Core;\n",[36,3050,3051],{"class":38,"line":92},[36,3052,3053],{},"use Database;\n",[36,3055,3056],{"class":38,"line":98},[36,3057,55],{"emptyLinePlaceholder":54},[36,3059,3060],{"class":38,"line":104},[36,3061,664],{},[36,3063,3064],{"class":38,"line":481},[36,3065,340],{},[36,3067,3068],{"class":38,"line":487},[36,3069,673],{},[36,3071,3072],{"class":38,"line":493},[36,3073,55],{"emptyLinePlaceholder":54},[36,3075,3076],{"class":38,"line":499},[36,3077,478],{},[36,3079,3080],{"class":38,"line":505},[36,3081,484],{},[36,3083,3084],{"class":38,"line":511},[36,3085,687],{},[36,3087,3088],{"class":38,"line":517},[36,3089,587],{},[36,3091,3092],{"class":38,"line":523},[36,3093,55],{"emptyLinePlaceholder":54},[36,3095,3096],{"class":38,"line":529},[36,3097,682],{},[36,3099,3100],{"class":38,"line":534},[36,3101,587],{},[36,3103,3104],{"class":38,"line":540},[36,3105,55],{"emptyLinePlaceholder":54},[36,3107,3108],{"class":38,"line":545},[36,3109,1168],{},[36,3111,3112],{"class":38,"line":550},[36,3113,3114],{},"        if(Request::isPost() == true){\n",[36,3116,3117],{"class":38,"line":555},[36,3118,3119],{},"            $title = $this->post('title');\n",[36,3121,3122],{"class":38,"line":560},[36,3123,55],{"emptyLinePlaceholder":54},[36,3125,3126],{"class":38,"line":566},[36,3127,3128],{},"            if(empty($title)==false){\n",[36,3130,3131],{"class":38,"line":572},[36,3132,3133],{},"                $db = Database::connection();\n",[36,3135,3136],{"class":38,"line":578},[36,3137,3138],{},"                $db->executeQuery(\"START TRANSACTION\");\n",[36,3140,3141],{"class":38,"line":584},[36,3142,3143],{},"                $db->executeQuery(\n",[36,3145,3146],{"class":38,"line":590},[36,3147,3148],{},"                    'INSERT album SET `title`=?, `created`=now(), `modified`=now()',\n",[36,3150,3151],{"class":38,"line":596},[36,3152,3153],{},"                    array($title)\n",[36,3155,3156],{"class":38,"line":1017},[36,3157,3158],{},"                );\n",[36,3160,3161],{"class":38,"line":1023},[36,3162,3163],{},"                $db->executeQuery(\"COMMIT\");\n",[36,3165,3166],{"class":38,"line":1028},[36,3167,3168],{},"                Redirect::to('\u002Fdashboard\u002Fvuetest')->send();\n",[36,3170,3171],{"class":38,"line":1033},[36,3172,3173],{},"            }else{\n",[36,3175,3176],{"class":38,"line":1038},[36,3177,3168],{},[36,3179,3180],{"class":38,"line":1043},[36,3181,2891],{},[36,3183,3184],{"class":38,"line":1048},[36,3185,55],{"emptyLinePlaceholder":54},[36,3187,3188],{"class":38,"line":1053},[36,3189,3190],{},"        }else{\n",[36,3192,3193],{"class":38,"line":1058},[36,3194,3195],{},"            $this->render('\u002Fdashboard\u002Fvuetest\u002Fadd');\n",[36,3197,3198],{"class":38,"line":1063},[36,3199,2251],{},[36,3201,3202],{"class":38,"line":1068},[36,3203,587],{},[36,3205,3206],{"class":38,"line":1073},[36,3207,599],{},[11,3209,3210,3212,3213,3216,3217,3220],{},[33,3211,1800],{}," でpostを送ると",[33,3214,3215],{},"add()","にて処理が行われます。",[33,3218,3219],{},"Request::isPost()","というメソッドを用いてリクエストがpostかどうかをチェックします。postであれば値をDBへ挿入するスクリプトを実行し、そうでなければ新規追加の画面を表示します。",[11,3222,3223,3226,3227,3230,3231,3234,3235,3238],{},[33,3224,3225],{},"DashboardPageController","配下では",[33,3228,3229],{},"$this->post('name')"," というメソッドで対応するname属性のinputの値を取得することができます！先ほどのフォームではタイトルの値を",[33,3232,3233],{},"name=\"title\"","としていたので",[33,3236,3237],{},"$title = $this->post('title');","で取得します",[139,3240,3242],{"id":3241},"chips-必ずバックエンドでもバリデーションを実装する","chips 必ずバックエンドでもバリデーションを実装する",[11,3244,3245],{},"よくみると下記の様にタイトルの値を検査しています。",[26,3247,3249],{"className":28,"code":3248,"filename":1102,"language":30,"meta":31,"style":31},"if(empty($title)==false){\n ...\n}\n",[33,3250,3251,3256,3261],{"__ignoreMap":31},[36,3252,3253],{"class":38,"line":39},[36,3254,3255],{},"if(empty($title)==false){\n",[36,3257,3258],{"class":38,"line":45},[36,3259,3260],{}," ...\n",[36,3262,3263],{"class":38,"line":51},[36,3264,599],{},[1290,3266,3270,3271],{"className":3267},[3268,3269],"alert","alert-danger","\n「フロントエンド でタイトルの値をバリデーションしたから別にやらなくても良くない？」というのは厳禁です。postで来た値は必ずバックエンドで同様にバリデーションをかけます。",[3272,3273,3274],"b",{},"なぜならブラウザのconsoleでフロントでの値は偽造することもでき、フロントエンドのバリデーションを不正にスルーできるからです。",[11,3276,3277],{},"vueで行っているバリデーションはあくまでユーザー補助、UX的な物でありセキュリティの観点からは言えばガバガバです。エンドユーザーが偽造ができないバックエンドであれば確実にバリデーションをすることができます。",[11,3279,3280],{},"dashbord配下は基本的にサイト管理者が触る物なので、不正な値を入れようとする人はいないと思いますが、フロントからpostされた値は基本的に信用しないスタイルを貫いた方が無難です。",[18,3282,3283],{"id":3283},"データを入れてみる",[11,3285,3286,3287,3289],{},"では早速使ってみましょう。",[33,3288,1800],{}," にアクセスするとタイトル入力フォームが出てきました。仮に「テスト」と入力。そして「登録」を押します。",[112,3291],{":src":3292,":width":115},"'_mix\u002Fsch-2020-08-27-0.48.59-768x203.png'",[11,3294,3295],{},"一覧のページにリダイレクトされました。ちゃんと挿入されたか、phpmyadminでみてみましょう。",[112,3297],{":src":3298,":width":750,":center":751},"'_mix\u002Fsch-2020-08-27-0.51.36-768x340.png'",[11,3300,3301],{},"いましたね。titleが「テスト」となっているので、正しくデータが入力されました。",[18,3303,3305],{"id":3304},"次回は","次回は…",[11,3307,3308],{},"以上がvueとバックエンド部分の一通りの実装でした。",[123,3310,3311,3314,3317],{},[126,3312,3313],{},"シングルページにvueのエントリーポイントを作る",[126,3315,3316],{},"エントリポイントにレンダリングされる様にコンポーネントを設定",[126,3318,3319],{},"jsをシングルページに読み込む",[11,3321,3322],{},"¥以上を意識すればconcrete5のシングルページに自由にvueを用いてUIを構築できます。あとはvueの使い方とバックエンドの設計を頑張るだけです。そして次回は編集画面と一覧画面の作成をしていきます。",[766,3324,3325],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}",{"title":31,"searchDepth":51,"depth":51,"links":3327},[3328,3331,3332,3335,3336,3342,3345,3346],{"id":819,"depth":45,"text":819,"children":3329},[3330],{"id":839,"depth":51,"text":840},{"id":849,"depth":45,"text":850},{"id":1082,"depth":45,"text":1083,"children":3333},[3334],{"id":1086,"depth":51,"text":1087},{"id":1248,"depth":45,"text":1248},{"id":1819,"depth":45,"text":1819,"children":3337},[3338,3339],{"id":1828,"depth":51,"text":1829},{"id":2270,"depth":51,"text":2270,"children":3340},[3341],{"id":2963,"depth":58,"text":2964},{"id":3001,"depth":45,"text":3001,"children":3343},[3344],{"id":3241,"depth":51,"text":3242},{"id":3283,"depth":45,"text":3283},{"id":3304,"depth":45,"text":3305},[783],"2020-08-27","oncrete5にVueCLIを使ってUIを構築する。データの登録。",{},{"title":800,"description":3349},"series\u002Fconcrete5vue-2",[793,794,275],"s28kXBmZtYl1vPtmgjcP0QiTjdZEL9Zwpvzi-377-0o",1780987144769]