[{"data":1,"prerenderedAt":798},["ShallowReactive",2],{"en-tag-concrete5-1":3},{"count":4,"content":5},1,[6],{"id":7,"title":8,"body":9,"category":782,"createdAt":784,"description":785,"extension":786,"index":4,"meta":787,"navigation":64,"path":788,"publish":64,"seo":789,"series":790,"seriesTitle":785,"stem":791,"tag":792,"thumbnail":795,"updatedAt":796,"__hash__":797},"en_series\u002Fen\u002Fseries\u002Fconcrete5vue-1.md","Concrete5 + Vue CLI3： Making Rich UI in package. 1st： Set up vue project on Concrete5 package.",{"type":10,"value":11,"toc":769},"minimark",[12,25,28,33,36,118,121,126,129,132,145,148,153,156,160,164,167,175,179,182,188,191,197,203,210,213,218,232,236,242,249,393,399,409,415,600,606,609,616,684,698,704,715,720,747,750,755,758,762,765],[13,14,15,16,20,21,24],"p",{},"Hello everyone. In this post, I explain how to use ",[17,18,19],"strong",{},"Vue.js"," to make rich UI in ",[17,22,23],{},"Concrete5 package",". At there, I write the process that create vue project in C5 package directory and use compiled js file.",[13,26,27],{},"I assumed Readers know how to customize C5 packages and basic C5 knowledge.The C5’s version is 8.4+ in this post.",[29,30,32],"h2",{"id":31},"why-use-vuejs","Why use Vue.js?",[13,34,35],{},"Reason is simple. Because making rich UI with PHP(C5 system) and jquery is so hard.\nC5 prepares helper to print form as below.",[37,38,43],"pre",{"className":39,"code":40,"language":41,"meta":42,"style":42},"language-php shiki shiki-themes material-theme-ocean","\u002F\u002F Read Helper\n$form = Core::make('helper\u002Fform');\n\n\u002F\u002Finput type of text\necho $form->text($name, $default_value);\n\n\u002F\u002Ftext area\necho $form-> textarea($name, $default_value);\n\n\u002F\u002F File manager\n$file_selector = Core::make('helper\u002Fconcrete\u002Ffile_manager');\necho $file_selector->file('label', 'name_attr', 'Select Photo', $default_fileObj);\n","php","",[44,45,46,53,59,66,72,78,83,89,95,100,106,112],"code",{"__ignoreMap":42},[47,48,50],"span",{"class":49,"line":4},"line",[47,51,52],{},"\u002F\u002F Read Helper\n",[47,54,56],{"class":49,"line":55},2,[47,57,58],{},"$form = Core::make('helper\u002Fform');\n",[47,60,62],{"class":49,"line":61},3,[47,63,65],{"emptyLinePlaceholder":64},true,"\n",[47,67,69],{"class":49,"line":68},4,[47,70,71],{},"\u002F\u002Finput type of text\n",[47,73,75],{"class":49,"line":74},5,[47,76,77],{},"echo $form->text($name, $default_value);\n",[47,79,81],{"class":49,"line":80},6,[47,82,65],{"emptyLinePlaceholder":64},[47,84,86],{"class":49,"line":85},7,[47,87,88],{},"\u002F\u002Ftext area\n",[47,90,92],{"class":49,"line":91},8,[47,93,94],{},"echo $form-> textarea($name, $default_value);\n",[47,96,98],{"class":49,"line":97},9,[47,99,65],{"emptyLinePlaceholder":64},[47,101,103],{"class":49,"line":102},10,[47,104,105],{},"\u002F\u002F File manager\n",[47,107,109],{"class":49,"line":108},11,[47,110,111],{},"$file_selector = Core::make('helper\u002Fconcrete\u002Ffile_manager');\n",[47,113,115],{"class":49,"line":114},12,[47,116,117],{},"echo $file_selector->file('label', 'name_attr', 'Select Photo', $default_fileObj);\n",[13,119,120],{},"When I write them on page view.php, forms are shown as below.",[122,123],"image-render",{":src":124,":width":125},"'_mix\u002Fformhelpertest-768x272.png'","'100%'",[13,127,128],{},"Input text, textarea and C5’s file manager appeared. These has name property and can post data.",[13,130,131],{},"Simply forms are enough to use them. But,",[133,134,135,139,142],"ul",{},[136,137,138],"li",{},"Implication of front-end validation",[136,140,141],{},"Repeatable form",[136,143,144],{},"Dependency Forms (There are some patterns correspond to inputed value)",[13,146,147],{},"These implication is hard with only jquery and PHP.",[149,150,152],"h3",{"id":151},"lets-use-vuejs-power","Let’s use vue.js power",[13,154,155],{},"Many Rich Form’s UI are developed easily with Vue.js, React.js and backbone.js( which manage data state and HTML template by javascript.) Using those libraries, we can develop rich UI while reducing the number of development days.",[29,157,159],{"id":158},"preparation-of-creating-package","Preparation of creating package",[149,161,163],{"id":162},"create-a-dedicated-directory-for-package","Create a dedicated directory for package",[13,165,166],{},"Let’s make package has UI built by vue. Create custom package directory named ‘vuetest’ under \u002Fpackage . Directory structure as below.",[37,168,173],{"className":169,"code":171,"language":172},[170],"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",[44,174,171],{"__ignoreMap":42},[149,176,178],{"id":177},"create-vue-project","Create Vue project",[13,180,181],{},"Under package\u002Fvuetest, there are package controller file, single page file that shows UI. Then let’s create js directory and make vue project there, named as ‘packageui’.",[37,183,186],{"className":184,"code":185,"language":172},[170],"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",[44,187,185],{"__ignoreMap":42},[13,189,190],{},"Vue project has been created successfully! Next, create vue.config.js and write webpack setting for C5’s asset system.",[37,192,195],{"className":193,"code":194,"language":172},[170],"packageui $ touch vue.config.js\n",[44,196,194],{"__ignoreMap":42},[37,198,201],{"className":199,"code":200,"language":172},[170],"module.exports = {\n    configureWebpack: {\n      output: {\n        filename: '[name].js',\n        chunkFilename: '[name].js'\n      }\n    },\n  }\n",[44,202,200],{"__ignoreMap":42},[13,204,205,206,209],{},"This file configs that the name of compiled js files from vue file when running npm run build will be same name under the ",[44,207,208],{},"\u002Fdist"," directory.",[13,211,212],{},"When we build vue file on initial webpack config, built js file has been had hashed letters such as app384#34a.js . The hash will change at every build. Compiled js file name must be always same to be read by C5’s asset system. So I config that file.",[214,215,217],"h4",{"id":216},"if-you-want-use-vue-project-by-multiple-packages","If you want use vue project by multiple packages",[13,219,220,221,224,225,228,229],{},"In this time, I create dedicated vue project under the package called vuetest and its directory. But if you use it by multi packages, create ",[44,222,223],{},"\u002Fjs"," directory under ",[44,226,227],{},"\u002Fapplication"," directory and make vue project under ",[44,230,231],{},"\u002Fapplication\u002Fjs",[149,233,235],{"id":234},"combine-c5-and-compiled-js-file","Combine C5 and compiled js file",[13,237,238,239,241],{},"When running build with vue cli, compiled js file will be generated under ",[44,240,208],{}," directory. Let’s config as C5 system and page renders components can read that files!",[13,243,244,245,248],{},"By the way, build ",[44,246,247],{},"main.js"," . That file renders App component at page contains div element with id attribute named as ‘app’.",[37,250,255],{"className":251,"code":252,"filename":253,"language":254,"meta":42,"style":42},"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",[44,256,257,281,297,301,324,328,343,367],{"__ignoreMap":42},[47,258,259,263,267,270,274,278],{"class":49,"line":4},[47,260,262],{"class":261},"s6cf3","import",[47,264,266],{"class":265},"s0W1g"," Vue ",[47,268,269],{"class":261},"from",[47,271,273],{"class":272},"sAklC"," '",[47,275,277],{"class":276},"sfyAc","vue",[47,279,280],{"class":272},"'\n",[47,282,283,285,288,290,292,295],{"class":49,"line":55},[47,284,262],{"class":261},[47,286,287],{"class":265}," App ",[47,289,269],{"class":261},[47,291,273],{"class":272},[47,293,294],{"class":276},".\u002FApp.vue",[47,296,280],{"class":272},[47,298,299],{"class":49,"line":61},[47,300,65],{"emptyLinePlaceholder":64},[47,302,303,306,309,312,314,317,320],{"class":49,"line":68},[47,304,305],{"class":265},"Vue",[47,307,308],{"class":272},".",[47,310,311],{"class":265},"config",[47,313,308],{"class":272},[47,315,316],{"class":265},"productionTip ",[47,318,319],{"class":272},"=",[47,321,323],{"class":322},"sbqyR"," false\n",[47,325,326],{"class":49,"line":74},[47,327,65],{"emptyLinePlaceholder":64},[47,329,330,333,337,340],{"class":49,"line":80},[47,331,332],{"class":272},"new",[47,334,336],{"class":335},"sdLwU"," Vue",[47,338,339],{"class":265},"(",[47,341,342],{"class":272},"{\n",[47,344,345,348,351,355,359,361,364],{"class":49,"line":85},[47,346,347],{"class":335},"  render",[47,349,350],{"class":272},":",[47,352,354],{"class":353},"s7ZW3"," h",[47,356,358],{"class":357},"sJ14y"," =>",[47,360,354],{"class":335},[47,362,363],{"class":265},"(App)",[47,365,366],{"class":272},",\n",[47,368,369,372,375,377,380,382,385,388,390],{"class":49,"line":91},[47,370,371],{"class":272},"}",[47,373,374],{"class":265},")",[47,376,308],{"class":272},[47,378,379],{"class":335},"$mount",[47,381,339],{"class":265},[47,383,384],{"class":272},"'",[47,386,387],{"class":276},"#app",[47,389,384],{"class":272},[47,391,392],{"class":265},")\n",[37,394,397],{"className":395,"code":396,"language":172},[170],"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",[44,398,396],{"__ignoreMap":42},[13,400,401,402,405,406],{},"Build succeeded. Some files are not needed, but it is just ok to config as reading ",[44,403,404],{},"app.js"," and ",[44,407,408],{},"chunk-vendor.js",[13,410,411,412,374],{},"To read those js files, write some PHP script in package’s install controller php file (",[44,413,414],{},"package\u002Fvuetest\u002Fcontroller.php",[37,416,419],{"className":39,"code":417,"filename":418,"language":41,"meta":42,"style":42},"\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",[44,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":42},[47,422,423],{"class":49,"line":4},[47,424,425],{},"\u003C?php\n",[47,427,428],{"class":49,"line":55},[47,429,430],{},"namespace Concrete\\Package\\Vuetest;\n",[47,432,433],{"class":49,"line":61},[47,434,435],{},"defined('C5_EXECUTE') or die('Access Denied.');\n",[47,437,438],{"class":49,"line":68},[47,439,440],{},"use \\Concrete\\Core\\Asset\\AssetList;\n",[47,442,443],{"class":49,"line":74},[47,444,445],{},"use \\Concrete\\Core\\Asset\\Asset;\n",[47,447,448],{"class":49,"line":80},[47,449,65],{"emptyLinePlaceholder":64},[47,451,452],{"class":49,"line":85},[47,453,454],{},"class Controller extends \\Concrete\\Core\\Package\\Package {\n",[47,456,457],{"class":49,"line":91},[47,458,459],{},"    protected $pkgHandle = 'vuetest';\n",[47,461,462],{"class":49,"line":97},[47,463,464],{},"    protected $appVersionRequired = '5.7.4';\n",[47,466,467],{"class":49,"line":102},[47,468,469],{},"    protected $pkgVersion = '1.0.0';\n",[47,471,472],{"class":49,"line":108},[47,473,65],{"emptyLinePlaceholder":64},[47,475,476],{"class":49,"line":114},[47,477,478],{},"    public function on_start()\n",[47,480,482],{"class":49,"line":481},13,[47,483,484],{},"    {\n",[47,486,488],{"class":49,"line":487},14,[47,489,490],{},"        $al = AssetList::getInstance();\n",[47,492,494],{"class":49,"line":493},15,[47,495,496],{},"        $al->register(\n",[47,498,500],{"class":49,"line":499},16,[47,501,502],{},"            'javascript', 'package-vue-build', 'js\u002Fpackageui\u002Fdist\u002Fapp.js',\n",[47,504,506],{"class":49,"line":505},17,[47,507,508],{},"            array('version' => '1.0.0', 'position' => Asset::ASSET_POSITION_FOOTER, 'combine' => true),\n",[47,510,512],{"class":49,"line":511},18,[47,513,514],{},"            $this->pkgHandle\n",[47,516,518],{"class":49,"line":517},19,[47,519,520],{},"        );\n",[47,522,524],{"class":49,"line":523},20,[47,525,526],{},"        \n",[47,528,530],{"class":49,"line":529},21,[47,531,496],{},[47,533,535],{"class":49,"line":534},22,[47,536,537],{},"            'javascript', 'package-vue-chunk', 'js\u002Fpackageui\u002Fdist\u002Fchunk-vendors.js',\n",[47,539,541],{"class":49,"line":540},23,[47,542,508],{},[47,544,546],{"class":49,"line":545},24,[47,547,514],{},[47,549,551],{"class":49,"line":550},25,[47,552,520],{},[47,554,556],{"class":49,"line":555},26,[47,557,526],{},[47,559,561],{"class":49,"line":560},27,[47,562,563],{},"        $al->registerGroup('package-vue-production', array(\n",[47,565,567],{"class":49,"line":566},28,[47,568,569],{},"            array('javascript', 'package-vue-build'),\n",[47,571,573],{"class":49,"line":572},29,[47,574,575],{},"            array('javascript', 'package-vue-chunk'),\n",[47,577,579],{"class":49,"line":578},30,[47,580,581],{},"        )); \n",[47,583,585],{"class":49,"line":584},31,[47,586,587],{},"    }\n",[47,589,591],{"class":49,"line":590},32,[47,592,593],{},"...\n",[47,595,597],{"class":49,"line":596},33,[47,598,599],{},"}\n",[13,601,602,605],{},[44,603,604],{},"$al->register"," registers those two js files referred by path at controller of vuetest package. For simply asset reading, let’s group two files as name of package-vue-production .",[13,607,608],{},"C5 has asset registering\u002Freading system like this. Using this system, we can use registered files anywhere pages without file path problem. Now, we have registered js files, so next we set reading them on the page.",[13,610,611,612,615],{},"Write PHP script in ",[44,613,614],{},"vuetest\u002Fsingle_pages\u002Fdashboard\u002Fvuetest\u002Fview.php"," as below",[37,617,619],{"className":39,"code":618,"language":41,"meta":42,"style":42},"\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",[44,620,621,625,630,634,639,643,648,652,657,661,666,671,676,680],{"__ignoreMap":42},[47,622,623],{"class":49,"line":4},[47,624,425],{},[47,626,627],{"class":49,"line":55},[47,628,629],{},"namespace Concrete\\Package\\Vuetest\\Controller\\SinglePage\\Dashboard;\n",[47,631,632],{"class":49,"line":61},[47,633,435],{},[47,635,636],{"class":49,"line":68},[47,637,638],{},"use \\Concrete\\Core\\Page\\Controller\\DashboardPageController;\n",[47,640,641],{"class":49,"line":74},[47,642,65],{"emptyLinePlaceholder":64},[47,644,645],{"class":49,"line":80},[47,646,647],{},"class Vuetest extends DashboardPageController\n",[47,649,650],{"class":49,"line":85},[47,651,342],{},[47,653,654],{"class":49,"line":91},[47,655,656],{},"    public $packageHandle = 'vuetest';\n",[47,658,659],{"class":49,"line":97},[47,660,65],{"emptyLinePlaceholder":64},[47,662,663],{"class":49,"line":102},[47,664,665],{},"    public function view() {\n",[47,667,668],{"class":49,"line":108},[47,669,670],{},"        $this->requireAsset('package-vue-production');\n",[47,672,673],{"class":49,"line":114},[47,674,675],{},"        $this->set('success', 'My success message');\n",[47,677,678],{"class":49,"line":481},[47,679,587],{},[47,681,682],{"class":49,"line":487},[47,683,599],{},[13,685,686,689,690,693,694,697],{},[44,687,688],{},"$this->requireAsset('package-vue-production');"," orders page controller that ‘Read registered asset name as ",[44,691,692],{},"“package-vue-production”"," on this page(",[44,695,696],{},"view.php",")!‘",[13,699,700,701],{},"After setting, let’s search if the js files are read. Opening Chrome developer tool…\n",[122,702],{":src":703,":width":125},"'_mix\u002Fsc-2020-08-01-19.52.09-768x133.png'",[13,705,706,707,710,711,714],{},"Path ",[44,708,709],{},"\u002Fpackages\u002Fvuetest\u002Fjs\u002Fdist\u002F**"," are found! Next, create div element has ",[44,712,713],{},"id=\"app\""," and see the page again.",[13,716,717],{},[44,718,719],{},"Invuetest\u002Fsingle_pages\u002Fdashboard\u002Fvuetest\u002Fview.php",[37,721,723],{"className":39,"code":722,"filename":696,"language":41,"meta":42,"style":42},"\u003C?php\ndefined('C5_EXECUTE') or die('Access Denied.');\n?>\n\n\u003Cdiv id=\"app\">\u003C\u002Fdiv>\n",[44,724,725,729,733,738,742],{"__ignoreMap":42},[47,726,727],{"class":49,"line":4},[47,728,425],{},[47,730,731],{"class":49,"line":55},[47,732,435],{},[47,734,735],{"class":49,"line":61},[47,736,737],{},"?>\n",[47,739,740],{"class":49,"line":68},[47,741,65],{"emptyLinePlaceholder":64},[47,743,744],{"class":49,"line":74},[47,745,746],{},"\u003Cdiv id=\"app\">\u003C\u002Fdiv>\n",[13,748,749],{},"Reload page…",[122,751],{":src":752,":width":753,":center":754},"'_mix\u002Fsh-2020-08-01-19.56.01-768x812.png'","'500px'","true",[13,756,757],{},"Vue component (App) are rendered successfully!! Some image and layout dose not work because the paths for img and css are incorrect. But created vue component itself with vue CLI are read and appeared on target page.",[29,759,761],{"id":760},"next-step","Next Step",[13,763,764],{},"The above is the method to create a vue project on the concrete5 package and combine them. Setting to use vue has been readied, so next step, we create form to input data ,flow to register data in DB and registered data editing page .",[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":42,"searchDepth":61,"depth":61,"links":770},[771,774,781],{"id":31,"depth":55,"text":32,"children":772},[773],{"id":151,"depth":61,"text":152},{"id":158,"depth":55,"text":159,"children":775},[776,777,780],{"id":162,"depth":61,"text":163},{"id":177,"depth":61,"text":178,"children":778},[779],{"id":216,"depth":68,"text":217},{"id":234,"depth":61,"text":235},{"id":760,"depth":55,"text":761},[783],"devstack","2020-08-25","Making Rich UI in Concrete5 with VueCLI.","md",{},"\u002Fen\u002Fseries\u002Fconcrete5vue-1",{"title":8,"description":785},"enconcrete5vue","en\u002Fseries\u002Fconcrete5vue-1",[793,794,277],"concrete5","js","_mix\u002Fvuewithconcrete.png",null,"uCGiRmlnXldeIG8DikjaUPsIaa4G61Yrz5b1kwmPD2w",1780987140303]