Extending with JavaScript
Having you component in PHP is awesome, but that does not limit you to it.
It makes perfect sense that wou would like to use some extra JavaScript with so many libraries available out there. Like UI tools for drag-and-drop, animations, charts, etc.
That stuff is not important for server-side rendering since SEO does not care, but you user will certainly appreciate some extra features.
Autogenerated JavaScript
As it was already mentioned in a introduction section, your PHP components are being converted into their JavaScript counterparts. These files are located at
/viewi-app/js/app
folder. This folder is getting overridden each time you build your Viewi project.
But there is also another folder that is created specifically for your custom code:
/viewi-app/js/modules/your_name
.
your_name
is your application name from configuration $viewiConfig = (new AppConfig('your_name'))
.
Keep/move your custom JavaScript implementation relative to this folder.
The name of your application should be ([a-zA-Z-_]*). 'default' is default app name.
By default the source code looks like this:
index.ts
export const modules = {};
It is a typescript but it is optional and you can use a simple JavaScript.
Standardizing purpose and Viewi package support.
For example, code from some package /vendor/package/src/viewi-app/js/modules/your_app_name
will get exported to
viewi-app/js/exports/your_app_name
if you use that package.
Extending your component
Mark your component with ExtendWithJs
attribute.
Imagine you that have this component:
<?php

namespace Components\Views\CustomJs;

use Viewi\Components\Attributes\LazyLoad;
use Viewi\Components\BaseComponent;
use Viewi\Builder\Attributes\ExtendWithJs;

#[ExtendWithJs]
class CustomJsPage extends BaseComponent
{
 public string $title = 'Custom JS page with lazy loading';
 public string $markText = "some text \n\n# Marked in browser\n\nRendered by **marked**.";

 public function getMarkedHtml($text)
 {
 return "";
 }
}
And you want your front-end to render markdown
content with marked
js library.
You got to your JavaScript source code:
cd ./viewi-app/js
Then install NPM package:
npm install marked
And now you want to use that.
Open your /viewi-app/js/modules/your_name/index.ts
file and modify it. You can create folders and other files in the modules
folder, it is up to you.
To extend CustomJsPage
page we need to import it from our app
folder:
import { CustomJsPage } from '../../app/main/components/Components/Views/CustomJs/CustomJsPage';
Then we need our marked
library:
import { marked } from 'marked';
Now, the extending itself. You can change a JavaScript class by adding or modifying methods or properties within a prototype of the class. For example, to modify getMarkedHtml
method you need to reassign it in prototype:
CustomJsPage.prototype.getMarkedHtml = function (this: CustomJsPage) {
 // new logic
};
And last, you need to export these changes by adding CustomJsPage
to modules
export:
// Add CustomJsPage to this
export const modules = { /** here **/ };
export const modules = { CustomJsPage };
Final result:
import { marked } from 'marked';
import { CustomJsPage } from '../../app/main/components/Views/CustomJs/CustomJsPage';

CustomJsPage.prototype.getMarkedHtml = function (this: CustomJsPage) {
 return marked(this.markText);
};

export const modules = { CustomJsPage };
Now run the build and you can see marked
library working in the browser.
Server-side rendering consideration
Couple of the important things to remember while working with custom JavScript.
Always wrap custom JavaScript output in the template to avoid hydration failure:
Bad
{{getMarkedHtml($markText)}}
will break the page on client-side since there will be a mismatch during hydration process while trying to match server-side content.
Server renders an empty string (or could be worse, renders something different). While client-side is trying to match newly generated markdown with the server variant.
<Layout title="$title">
 <h1>$title</h1>
 <h3>Input</h3>
 <label for="markdown-text">
 Enter some markdown
 </label>
 <div>
 <textarea id="markdown-text" rows="10" model="$markText" style="width: 100%;"></textarea>
 </div>
 <h3>Input:</h3>
 <div>
 <pre>$markText</pre>
 </div>
 <h3>Output:</h3>
 {{getMarkedHtml($markText)}}
</Layout>
Good
Always wrap you custom output into another tag.
<div>{{getMarkedHtml($markText)}}</div>
<Layout title="$title">
 <h1>$title</h1>
 <h3>Input</h3>
 <label for="markdown-text">
 Enter some markdown
 </label>
 <div>
 <textarea id="markdown-text" rows="10" model="$markText" style="width: 100%;"></textarea>
 </div>
 <h3>Input:</h3>
 <div>
 <pre>$markText</pre>
 </div>
 <h3>Output:</h3>
 <div>{{getMarkedHtml($markText)}}</div>
</Layout>
Complete JavaScript rewrite
If you want to implement JavaScript version completely from scratch, mark your component with CustomJs
attribute.
Export your JavaScript implementation in your index.ts
file.