Remove generic Tapestry JavaScript and CSS - javascript

I have created a new Tapestry 5.3 project using Maven. I noticed that Tapestry adds tons of different JS and CSS files to all pages:
<link type="text/css" rel="stylesheet" href="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/default.css"/>
<link type="text/css" rel="stylesheet" href="/tutorial1/assets/1.0-SNAPSHOT-DEV/ctx/layout/layout.css"/>
<link type="text/css" rel="stylesheet" href="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/tapestry-console.css"/>
<link type="text/css" rel="stylesheet" href="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/t5-alerts.css"/>
<link type="text/css" rel="stylesheet" href="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/tree.css"/>
<script src="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/underscore_1_3_3.js" type="text/javascript"></script>
<script src="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/scriptaculous_1_9_0/prototype.js" type="text/javascript"></script>
And many, MANY more...
Are these required for my site to work properly? If not, how can I remove them? I am pretty comfortable to write JS myself and I do not need Tapestry to add anything for me.

It's funny: Tapestry provides very rich functionality to override default service behavior but not in this case.
The main culprit JavaScriptSupport is created on the fly and can't be decorated.
MarkupRendererFilter javaScriptSupport = new MarkupRendererFilter() {
public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) {
DocumentLinker linker = environment.peekRequired(DocumentLinker.class);
//Surprise!;)
JavaScriptSupportImpl support = new JavaScriptSupportImpl(linker, javascriptStackSource, javascriptStackPathConstructor);
environment.push(JavaScriptSupport.class, support);
renderer.renderMarkup(writer);
environment.pop(JavaScriptSupport.class);
support.commit();
}
};
So the only way is to patch sources (as we did many times), or to try to override the javascriptStackSource which goes as param to JavaScriptSupportImpl:
Your AppModule.java
#Decorate(serviceInterface = JavaScriptStackSource.class)
public JavaScriptStackSource decorateJavaScriptStackSource(JavaScriptStackSource original) {
return new MyJavaScriptStackSource(original);
}
MyJavaScriptStackSource.java
public class MyJavaScriptStackSource implements JavaScriptStackSource {
// This bunch of stacks comes from got5
private Set<String> SKIP = new HashSet<String>(Arrays.asList("Slider", "AjaxUploadStack", "DataTableStack", "FormFragmentSupportStack", "FormSupportStack",
"SuperfishStack", "JQueryDateFieldStack", "GalleryStack"));
private class JavaScriptStackWraper implements JavaScriptStack {
private final JavaScriptStack original;
JavaScriptStackWraper(JavaScriptStack original) {
if (original != null) {
System.out.println("Wrap " + original.getClass().getName());
}
this.original = original;
}
#Override
public List<String> getStacks() {
return original != null ? original.getStacks() : Collections.<String>emptyList();
}
#Override
public List<Asset> getJavaScriptLibraries() {
return original != null ? original.getJavaScriptLibraries() : Collections.<Asset>emptyList();
}
// Always return empty list
#Override
public List<StylesheetLink> getStylesheets() {
return Collections.<StylesheetLink>emptyList();
}
#Override
public String getInitialization() {
return original != null ? original.getInitialization() : null;
}
}
private final JavaScriptStackSource original;
public MyJavaScriptStackSource(JavaScriptStackSource original) {
this.original = original;
}
#Override
public JavaScriptStack getStack(String name) {
JavaScriptStack stack = original.getStack(name);
if (!SKIP.contains(stack.getClass().getSimpleName())) {
return new JavaScriptStackWraper(stack);
}
return new JavaScriptStackWraper(null);
}
#Override
public List<String> getStackNames() {
return original.getStackNames();
}
}
It's a big piece of shitty code but it works.. I'd like to have more control of whats displaying in my pages in Tapestry.

Part of the advantage of Tapestry is the number of components that provide DHTML and Ajax behaviors out of the box without writing any JavaScript, but just configuring components.
It is possible to disable this, but that means many components you might like to use, such as Zone, will be broken. Likewise, all client-side input validation will be gone. I do have clients that have done this, but it is not a small undertaking.
Basically, you can override Tapestry's "core" JavaScriptStack. There isn't a FAQ for this, because it is not frequently asked. It is also not for an absolute beginner, more a journeyman thing (it is relatively easy to override things in Tapestry due to its inversion of control container, but knowing WHAT to override is trickier).
In any case, Tapestry 5.4 is well underway and is changing Tapestry's JavaScript to be much lighter and much more modular, as well as giving you the choice between Prototype (mostly for compatibility in existing Tapestry projects) or jQuery. Even then, however, there will be some amount of JavaScript built into the framework.

Related

Uncaught TypeError: Cannot read properties of null - using JavaScript in WebViewX

I am building widget in FlutterFlow using WebViewX, I use JavaScript inside WebViewX to hide part of the site I am showing. Below is my code:
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:webviewx/webviewx.dart';
class MyWebView extends StatefulWidget {
const MyWebView({
Key? key,
required this.url,
this.width,
this.height,
this.bypass = false,
this.horizontalScroll = false,
this.verticalScroll = false,
}) : super(key: key);
final bool bypass;
final bool horizontalScroll;
final bool verticalScroll;
final double? height;
final double? width;
final String url;
#override
_MyWebViewState createState() => _MyWebViewState();
}
class _MyWebViewState extends State<MyWebView> {
late WebViewXController webviewController;
#override
void dispose() {
webviewController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => WebViewX(
key: webviewKey,
width: widget.width ?? MediaQuery.of(context).size.width,
height: widget.height ?? MediaQuery.of(context).size.height,
ignoreAllGestures: false,
initialContent: widget.url,
initialMediaPlaybackPolicy:
AutoMediaPlaybackPolicy.requireUserActionForAllMediaTypes,
initialSourceType:
widget.bypass ? SourceType.urlBypass : SourceType.url,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) => webviewController = controller,
onPageFinished: (String url) {
webviewController.evalRawJavascript(
"document.getElementsByClassName('news-info')[0].style.display='none';"),
},
webSpecificParams: const WebSpecificParams(
webAllowFullscreenContent: true,
),
mobileSpecificParams: MobileSpecificParams(
debuggingEnabled: false,
gestureNavigationEnabled: true,
mobileGestureRecognizers: {
if (widget.verticalScroll)
Factory<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(),
),
if (widget.horizontalScroll)
Factory<HorizontalDragGestureRecognizer>(
() => HorizontalDragGestureRecognizer(),
),
},
androidEnableHybridComposition: true,
),
);
Key get webviewKey => Key(
[
widget.url,
widget.width,
widget.height,
widget.bypass,
widget.horizontalScroll,
widget.verticalScroll,
].map((s) => s?.toString() ?? '').join(),
);
}
Page loads just fine, but required div block is not hidden, I get Uncaught TypeError: Cannot read properties of null. I checked loaded site page in WebViewX frame and there is no original DOM structure so no div named "news-info" is present, so JS cannot find it. My questions are:
Is it normal WebView behaviour not to keep original DOM?
I have seen guides on using JS inside WebView to hide part of the site loaded, what is wrong with my implementation? Do I call JS code on the right time, after page is finished? Or should I do this earlier?
If there is no mistakes in the code could it be FlutterFlow or WebViewX issues, as guides I have seen are about WebView?
Thanks in advance for any advice.
I spent 2 days trying to find answer before posting here, but found in 5 minutes after posting. What a shame :)
So WebViewX implementation is able to inject some JS code if source type of URL you pass to WebViewX is defined as "urlBypass", while default is just "url". Added a line:
initialSourceType: SourceType.urlBypass,
and you are good to go.

Set style of html element by targeting it's id in Blazor

Say I have an element in Blazor. I want to set the elements style based on the value of the input field in Blazor. I am aware that you can do this by using if else blocks in the html code however I want to avoid this as it grows the code base extremely quick. A function would be better which evaluates the incoming object and styles it.
for instance if I have the element below which passes a value the value is passed to a function.
<th><InputText placeholder="First Name" id=FirstName #bind-Value="#filterModel.FirstName">First Name</InputText></th>
Function
private async void UpdateVisibleData(object sender, FieldChangedEventArgs e){
Console.WriteLine(e.FieldIdentifier.FieldName + " Change Detected");
var fieldChanged = e.FieldIdentifier.FieldName;
var IsEmptyString = FindEmptyStringInField(filterModel, fieldChanged);
if(IsEmptyString){
element.style.setProperty('--my-variable-name', 'hotpink');
}
}
I havent been able to get the style.setproperty part to work. not sure what other methods there are. Open to JSInterop. preferrable though if I can target the style.setproperty function and get this working.
Revised Answer
Or you can use JSInterop like this:
/wwwroot/js/example.js
export function examplefunction (Id, Color) {
document.getElementById(Id).style.backgroundColor = Color;
}
razor page :
#inject IJSRuntime JS
#code {
private IJSObjectReference? module;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import", "/js/example.js");
}
}
private async void UpdateVisibleData(object sender, FieldChangedEventArgs e){
var fieldChanged = e.FieldIdentifier.FieldName;
await module.InvokeVoidAsync("examplefunction", fieldChanged, "hotpink");
}
}
Original Answer
I would use ? operator to make simple HTML without additional code
<input type="text" placeholder="First Name" id=FirstName #bind=#textInput style='#(String.IsNullOrEmpty(textInput) ? "background-color: hotpink;" : "")' />
This isn't a exact answer by targeting the id. however I did find a workaround for this which isn't too bad for webassembly by passing a conditional function to the style tag on the element. It doesn't grow the code base as much as using if else blocks and declaring individual strings. although still targeting element id based on fieldeventchanges would be less computationally taxing as the conditional function is checked for every statechange on every field which uses it as opposed to only being called on the element id if the condition is met.
html
<th><InputText style="#StyleForEmptyString(filterModel.FirstName)" placeholder="First Name" id=FirstName #bind-Value="#filterModel.FirstName">First Name</InputText></th>
#code{
private string StyleForEmptyString(string fieldValue)
{
if (fieldValue == ""){
return "outline:none; box-shadow:none;";
}
return "";
}```

Determine if a WebView is invisible using only JS

I have an Android test app with a webView like so:
<WebView
android:alpha="0"
android:id="#+id/myWebView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
My Main activity loads a page that I have JS content running. JS is enabled using:
webSettings.setJavaScriptEnabled(true);
There is a button on my app that toggles the alpha value from 0 to 1 (show / hide webView).
Is there any "creative" way of detecting the change on the JS side?
Things I tried:
Checking requestAnimationFrame frame rate changes.
Visibility API
Update:
Clarification, I'm looking for a JS Only solution, the actual JS code is an SDK used inside a WebView environment.
I have no control over the Android app, full control over the WebView content.
You could subclass android's WebView, override its setAlpha method and add some logic to let the webpage know the current alpha value.
Here is a simplified example:
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
public class MyWebView extends WebView {
public MyWebView(Context context) {
super(context);
init();
}
public MyWebView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public MyWebView(Context context,
AttributeSet attrs,
int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
#SuppressLint("SetJavaScriptEnabled")
private void init() {
getSettings().setJavaScriptEnabled(true);
}
#Override
public void setAlpha(float alpha) {
super.setAlpha(alpha);
propagateAlphaToWebPage(alpha);
}
private void propagateAlphaToWebPage(float alpha) {
// setWebViewAlpha is a JS function that should be defined in your html's js
String jssnippet = "setWebViewAlpha("+alpha+");";
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
evaluateJavascript(jssnippet, null);
} else {
loadUrl("javascript:"+jssnippet);
}
}
}
Other related questions you may find helpful:
Android WebView - detect whether a JS function is defined
Android Calling JavaScript functions in WebView
Declaring a custom android UI element using XML
Below is complete code which will tell to JavaScript if WebView is visible or not.
Steps:
Create a layout containing a ToggleButton and WebView.
Load your html using htmlWebView.loadUrl("file:///android_asset/index.html"); and related code
Create a function onToggleButtonPressed() which will be called onClick of toggle button
In onToggleButtonPressed() show/hide WebView and at the same time pass the status to JavaScript using htmlWebView.evaluateJavascript() method. Pass visibilityStatusFromAndroid() to JavaScript
Get the status in JavaScript from visibilityStatusFromAndroid() function
Android Layout xml code:
<ToggleButton
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#color/red"
android:onClick="onToggleButtonPressed"
android:text="Button1"
android:textOn="Show"
android:textOff="Hide"
tools:ignore="MissingConstraints"
tools:layout_editor_absoluteX="148dp"
tools:layout_editor_absoluteY="0dp" />
<WebView
android:id="#+id/webView"
android:layout_width="368dp"
android:layout_height="447dp"
android:layout_alignParentStart="true"
android:layout_below="#+id/button"
tools:ignore="MissingConstraints"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="56dp"
android:layout_alignParentLeft="true" />
Java Code:
WebView htmlWebView; // put it outside onCreate() to make it accessible in onToggleButtonPressed()
htmlWebView = (WebView)findViewById(R.id.webView);
htmlWebView.setWebViewClient(new WebViewClient());
htmlWebView.loadUrl("file:///android_asset/index.html");
WebSettings webSetting = htmlWebView.getSettings();
webSetting.setJavaScriptEnabled(true);
public void onToggleButtonPressed(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
boolean on = ((ToggleButton) view).isChecked();
String visibility;
if (on) {
htmlWebView.setVisibility(View.GONE);
visibility = "NOT VISIBLE";
htmlWebView.evaluateJavascript("javascript: " +
"visibilityStatusFromAndroid(\"" + visibility + "\")", null);
} else {
htmlWebView.setVisibility(View.VISIBLE);
visibility = "VISIBLE NOW";
htmlWebView.evaluateJavascript("javascript: " +
"visibilityStatusFromAndroid(\"" + visibility + "\")", null);
}
}
}
JavaScript file:
function visibilityStatusFromAndroid(message) {
if (message === "NOT VISIBLE") {
console.log("webview is not visible");
// do your stuff here
} else if (message === "VISIBLE NOW") {
console.log("webview is visible now");
// do your stuff here
}
}
That's it. You are DONE!
Here is Video of working code.
Update:
There is one option that you can use to detect visibility only using JavaScript is document.hasFocus() but this will not work if there is any EditText on the screen and user will focus that EditText.
Just in case below is the code you can use in for document.hasFocus():
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>TEST</title>
<style>
#message { font-weight: bold; }
</style>
<script>
setInterval( checkPageFocus, 200 ); // you can change interval time to check more frequently
function checkPageFocus() {
var info = document.getElementById("message");
if ( document.hasFocus() ) {
info.innerHTML = "The document is VISIBLE.";
} else {
info.innerHTML = "The document is INVISIBLE.";
}
}
</script>
</head>
<body>
<h1>JavaScript hasFocus example</h1>
<div id="message">Waiting for user action</div>
</body>
</html>
There is (currently) no JS-only solution.
The content of the WebView isn't supposed to be able to get information about its environment.
The only exception is information which was volunteered by the environment itself.
This would contradict basic sandboxing and could lead to security issues.
As there is no standard API that would give you the information you want you would have to change the environment yourself to expose it to the WebView.
If you can't change the environment what you are requesting is (theoretically) impossible with the current browsers/android.
#Hack To find an actual loophole we could exploit to get the alpha value we would need more detailed information about the JS SDK, the environment and how much influence on the android / webview side of the app you have. And even then after investing much effort the result would most likely be the same.

What is the Vaadin 8 way of adding code to the html head tag?

Other SO answers suggest overriding ApplicationServlet.writeAjaxPageHtmlHeader, but I cannot find those classes and methods in Vaadin 8.
I cannot find anything similar in com.vaadin.server.VaadinServlet or com.vaadin.ui.UI.
There is the #JavaScript annotation, but if I put that on my UI class, the script would be loaded for every page of my application. I need it only on one specific page.
The initial HTML page is called bootstrap page in Vaadin. There is some documentation that hints you to right direction in Book of Vaadin.
In Vaadin 8, you need to add BootstrapListener to session. You can get created sessions by adding SessionInitListener in VaadinServlet.
Register Session
This example is using Vaadin with Spring Boot but the same principle applies when not using Spring Boot.
#Component("vaadinServlet")
#WebServlet(urlPatterns = "/*", name = "BootstrapVaadinServlet", asyncSupported = true)
#VaadinServletConfiguration(ui = BoostrapUi.class, productionMode = false)
public class BootstrapVaadinServlet extends SpringVaadinServlet {
private static final Logger logger = LoggerFactory.getLogger(BootstrapVaadinServlet.class);
#Override
protected void servletInitialized() throws ServletException {
super.servletInitialized();
getService().addSessionInitListener(this::addBoostrapListenerOnSessionInit);
}
private void addBoostrapListenerOnSessionInit(SessionInitEvent sessionInitEvent) {
sessionInitEvent.getSession().addBootstrapListener(new AppBootstrapListener());
}
}
Implement html head tag modification
public class AppBootstrapListener implements BootstrapListener {
#Override
public void modifyBootstrapFragment(BootstrapFragmentResponse bootstrapFragmentResponse) {
}
#Override
public void modifyBootstrapPage(BootstrapPageResponse res) {
Elements headTags = res.getDocument().getElementsByTag("head");
Element head = headTags.get(0);
head.appendChild(metaExample(res.getDocument()));
}
private Node metaExample(Document document) {
Element meta = document.createElement("meta");
meta.attr("author", "Me");
return meta;
}
}
If using add-on is ok, try HeaderTags
Overview
Using this add-on, you can define tags to add to the host page by
adding annotation to your UI class.
Sample from add ons usage example
#MetaTags({
// Replaces the Vaadin X-UA-Compatible header
#Meta(httpEquiv = "X-UA-Compatible", content = "hello"),
#Meta(name = "test", content = "test") })
// And showing how to create a link tag as well
#Link(rel = "foobar", href = "about:blank")
public class DemoUI extends UI {
...
}

Add JS to header despite JavascriptFilteredIntoFooterHeaderResponse

I order my imported CSS and JS with a JavascriptFilteredIntoFooterHeaderResponse. With this Class goes all my CSS in the header and all my JS to a separate container near the </body> tag. But now i need to add one JS to the header but wicket pushed every JS to the footer. Knows anybody a solution for this? JavascriptFilteredIntoFooterHeaderResponse is final and can't be overriden.
WicketApplication
#Override
public void init()
{
super.init();
setHeaderResponseDecorator( new IHeaderResponseDecorator()
{
#Override
public IHeaderResponse decorate( IHeaderResponse response )
{
return new JavascriptFilteredIntoFooterHeaderResponse( response, FOOTER_FILTER_NAME );
}
} );
}
BasePage.java
public BasePage()
{
add( new HeaderResponseFilteredResponseContainer( FOOTER_FILTER_NAME, FOOTER_FILTER_NAME ) );
}
BasePage.html
<body>
...
<div wicket:id="footerBucket" />
</body>
You don't need to override JavascriptFilteredIntoFooterHeaderResponse. Just use org.apache.wicket.resource.filtering.HeaderResponseContainerFilteringHeaderResponse with the following constructor:
HeaderResponseContainerFilteringHeaderResponse(IHeaderResponse response,
String headerFilterName, IHeaderResponseFilter[] filters)
For example, if you write:
IHeaderResponseFilter[] filters = new IHeaderResponseFilter[] {
new CssAcceptingHeaderResponseFilter(HEADER_FILTER_NAME),
new JavascriptAcceptingHeaderResponseFilter(FOOTER_FILTER_NAME) };
return new HeaderResponseContainerFilteringHeaderResponse(response,
HEADER_FILTER_NAME, filters);
it will be the same JavascriptFilteredIntoFooterHeaderResponse that you use in your code.
Here's an example of anonymous filter class that you can use instead of CssAcceptingHeaderResponseFilter to accomplish your task. File "script-for-the-header.js" is the script that you'd like to have in the header.
new CssAcceptingHeaderResponseFilter(HEADER_FILTER_NAME) {
#Override
public boolean acceptReference(ResourceReference ref) {
if (!Strings.isEmpty(ref.getName()) && ref.getName().equals("script-for-the-header.js")) {
return true;
} else {
return super.acceptReference(ref);
}
}
}
To better organize your code you may also consider overriding HeaderResponseContainerFilteringHeaderResponse (if you haven't guessed yet, it's also the parent of JavascriptFilteredIntoFooterHeaderResponse). See source code in JavascriptFilteredIntoFooterHeaderResponse.java for details.

Categories