IONIC 3 InAppBrowser executeScript - ionic-framework

I am using this native plugin to open a InAppBrowser. Also I want to inject a css and/or JS in that page, as in that page mentioned, I should use below code for css inject but it doesn't work:
browser.on('loadstop').subscribe(event => {
browser.insertCSS({ code: "body{color: red;" });
});
Can anyone help me?

You can execute javascript with below syntax
let promise = browser.executeScript({
code: '(function() {if(functionname) { return functionname(); }}())'
});
This will return promise
promise.then((values) => {
// process values here
});
where browser is instance of your inappbrowser for example -
const browser = this.iab.create('url', "_blank", this.options);

Related

NEXTJS fix window is not defined on import [duplicate]

In my Next.js app I can't seem to access window:
Unhandled Rejection (ReferenceError): window is not defined
componentWillMount() {
console.log('window.innerHeight', window.innerHeight);
}
̶A̶n̶o̶t̶h̶e̶r̶ ̶s̶o̶l̶u̶t̶i̶o̶n̶ ̶i̶s̶ ̶b̶y̶ ̶u̶s̶i̶n̶g̶ ̶p̶r̶o̶c̶e̶s̶s̶.̶b̶r̶o̶w̶s̶e̶r ̶ ̶t̶o̶ ̶j̶u̶s̶t̶ ̶e̶x̶e̶c̶u̶t̶e̶ ̶ ̶y̶o̶u̶r̶ ̶c̶o̶m̶m̶a̶n̶d̶ ̶d̶u̶r̶i̶n̶g̶ ̶r̶e̶n̶d̶e̶r̶i̶n̶g̶ ̶o̶n̶ ̶t̶h̶e̶ ̶c̶l̶i̶e̶n̶t̶ ̶s̶i̶d̶e̶ ̶o̶n̶l̶y̶.
But process object has been deprecated in Webpack5 and also NextJS, because it is a NodeJS variable for backend side only.
So we have to use back window object from the browser.
if (typeof window !== "undefined") {
// Client-side-only code
}
Other solution is by using react hook to replace componentDidMount:
useEffect(() => {
// Client-side-only code
})
Move the code from componentWillMount() to componentDidMount():
componentDidMount() {
console.log('window.innerHeight', window.innerHeight);
}
In Next.js, componentDidMount() is executed only on the client where window and other browser specific APIs will be available. From the Next.js wiki:
Next.js is universal, which means it executes code first server-side,
then client-side. The window object is only present client-side, so if
you absolutely need to have access to it in some React component, you
should put that code in componentDidMount. This lifecycle method will
only be executed on the client. You may also want to check if there
isn't some alternative universal library which may suit your needs.
Along the same lines, componentWillMount() will be deprecated in v17 of React, so it effectively will be potentially unsafe to use in the very near future.
If you use React Hooks you can move the code into the Effect Hook:
import * as React from "react";
export const MyComp = () => {
React.useEffect(() => {
// window is accessible here.
console.log("window.innerHeight", window.innerHeight);
}, []);
return (<div></div>)
}
The code inside useEffect is only executed on the client (in the browser), thus it has access to window.
With No SSR
https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
The error occurs because window is not yet available, while component is still mounting. You can access window object after component is mounted.
You can create a very useful hook for getting dynamic window.innerHeight or window.innerWidth
const useDeviceSize = () => {
const [width, setWidth] = useState(0)
const [height, setHeight] = useState(0)
const handleWindowResize = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
}
useEffect(() => {
// component is mounted and window is available
handleWindowResize();
window.addEventListener('resize', handleWindowResize);
// unsubscribe from the event on component unmount
return () => window.removeEventListener('resize', handleWindowResize);
}, []);
return [width, height]
}
export default useDeviceSize
Use case:
const [width, height] = useDeviceSize();
componentWillMount() lifecycle hook works both on server as well as client side. In your case server would not know about window or document during page serving, the suggestion is to move the code to either
Solution 1:
componentDidMount()
Or, Solution 2
In case it is something that you only want to perform in then you could write something like:
componentWillMount() {
if (typeof window !== 'undefined') {
console.log('window.innerHeight', window.innerHeight);
}
}
In the constructor of your class Component you can add
if (typeof window === 'undefined') {
global.window = {}
}
Example:
import React, { Component } from 'react'
class MyClassName extends Component {
constructor(props){
super(props)
...
if (typeof window === 'undefined') {
global.window = {}
}
}
This will avoid the error (in my case, the error would occur after I would click reload of the page).
global?.window && window.innerHeight
It's important to use the operator ?., otherwise the build command might crash.
Best solution ever
import dynamic from 'next/dynamic';
const Chart = dynamic(()=> import('react-apexcharts'), {
ssr:false,
})
A bit late but you could also consider using Dynamic Imports from next turn off SSR for that component.
You can warp the import for your component inside a dynamic function and then, use the returned value as the actual component.
import dynamic from 'next/dynamic'
const BoardDynamic = dynamic(() => import('../components/Board.tsx'), {
ssr: false,
})
<>
<BoardDynamic />
</>
I have to access the hash from the URL so I come up with this
const hash = global.window && window.location.hash;
Here's an easy-to-use workaround that I did.
const runOnClient = (func: () => any) => {
if (typeof window !== "undefined") {
if (window.document.readyState == "loading") {
window.addEventListener("load", func);
} else {
func();
}
}
};
Usage:
runOnClient(() => {
// access window as you like
})
// or async
runOnClient(async () => {
// remember to catch errors that might be raised in promises, and use the `await` keyword wherever needed
})
This is better than just typeof window !== "undefined", because if you just check that the window is not undefined, it won't work if your page was redirected to, it just works once while loading. But this workaround works even if the page was redirected to, not just once while loading.
I was facing the same problem when i was developing a web application in next.js This fixed my problem, you have to refer to refer the window object in a life cycle method or a react Hook. For example lets say i want to create a store variable with redux and in this store i want to use a windows object i can do it as follows:
let store
useEffect(()=>{
store = createStore(rootReducers, window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__())
}, [])
....
So basically, when you are working with window's object always use a hook to play around or componentDidMount() life cycle method
I wrapped the general solution (if (typeof window === 'undefined') return;) in a custom hook, that I am very pleased with. It has a similiar interface to reacts useMemo hook which I really like.
import { useEffect, useMemo, useState } from "react";
const InitialState = Symbol("initial");
/**
*
* #param clientFactory Factory function similiar to `useMemo`. However, this function is only ever called on the client and will transform any returned promises into their resolved values.
* #param deps Factory function dependencies, just like in `useMemo`.
* #param serverFactory Factory function that may be called server side. Unlike the `clientFactory` function a resulting `Promise` will not be resolved, and will continue to be returned while the `clientFactory` is pending.
*/
export function useClientSideMemo<T = any, K = T>(
clientFactory: () => T | Promise<T>,
deps: Parameters<typeof useMemo>["1"],
serverFactory?: () => K
) {
const [memoized, setMemoized] = useState<T | typeof InitialState>(
InitialState
);
useEffect(() => {
(async () => {
setMemoized(await clientFactory());
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
return typeof window === "undefined" || memoized === InitialState
? serverFactory?.()
: memoized;
}
Usage Example:
I am using it to dynamically import libaries that are not compatible with SSR in next.js, since its own dynamic import is only compatible with components.
const renderer = useClientSideMemo(
async () =>
(await import("#/components/table/renderers/HighlightTextRenderer"))
.HighlightTextRendererAlias,
[],
() => "text"
);
As you can see I even implemented a fallback factory callback, so you may provide a result when initially rendering on the server aswell. In all other aspects this hook should behave similiar to reacts useMemo hook. Open to feedback.
For such cases, Next.js has Dynamic Import.
A module that includes a library that only works in the browser, it's suggested to use Dynamic Import. Refer
Date: 06/08/2021
Check if the window object exists or not and then follow the code along with it.
function getSelectedAddress() {
if (typeof window === 'undefined') return;
// Some other logic
}
For Next.js version 12.1.0, I find that we can use process.title to determine whether we are in browser or in node side. Hope it helps!
export default function Projects(props) {
console.log({ 'process?.title': process?.title });
return (
<div></div>
);
}
1. From the terminal, I receive { 'process?.title': 'node' }
2. From Chrome devtool, I revice { 'process?.title': 'browser' }
I had this same issue when refreshing the page (caused by an import that didn't work well with SSR).
What fixed it for me was going to pages where this was occurring and forcing the import to be dynamic:
import dynamic from 'next/dynamic';
const SomeComponent = dynamic(()=>{return import('../Components/SomeComponent')}, {ssr: false});
//import SomeComponent from '../Components/SomeComponent'
Commenting out the original import and importing the component dynamically forces the client-side rendering of the component.
The dynamic import is covered in Nextjs's documentation here:
https://nextjs.org/docs/advanced-features/dynamic-import
I got to this solution by watching the youtube video here:
https://www.youtube.com/watch?v=DA0ie1RPP6g
You can define a state var and use the window event handle to handle changes like so.
const [height, setHeight] = useState();
useEffect(() => {
if (!height) setHeight(window.innerHeight - 140);
window.addEventListener("resize", () => {
setHeight(window.innerHeight - 140);
});
}, []);
You can try the below code snippet for use-cases such as - to get current pathname (CurrentUrl Path)
import { useRouter } from "next/router";
const navigator = useRouter()
console.log(navigator.pathname);
For anyone who somehow cannot use hook (for example, function component):
Use setTimeout(() => yourFunctionWithWindow()); will allow it get the window instance. Guess it just need a little more time to load.
I want to leave this approach that I found interesting for future researchers. It's using a custom hook useEventListener that can be used in so many others needs.
Note that you will need to apply a little change in the originally posted one, like I suggest here.
So it will finish like this:
import { useRef, useEffect } from 'react'
export const useEventListener = (eventName, handler, element) => {
const savedHandler = useRef()
useEffect(() => {
savedHandler.current = handler
}, [handler])
useEffect(() => {
element = !element ? window : element
const isSupported = element && element.addEventListener
if (!isSupported) return
const eventListener = (event) => savedHandler.current(event)
element.addEventListener(eventName, eventListener)
return () => {
element.removeEventListener(eventName, eventListener)
}
}, [eventName, element])
}
If it is NextJS app and inside _document.js, use below:
<script dangerouslySetInnerHTML={{
__html: `
var innerHeight = window.innerHeight;
`
}} />

FILE_URI path for Camera not working on IONIC 4

When using the Cameara to take a picture with destinationType: this.camera.DestinationType.FILE_URI, the resulting URL will not work to display the image. For example, when attempting to take a photo like this:
this.camera.getPicture(options).then((url) => {
// Load Image
this.imagePath = url;
}, (err) => {
console.log(err);
});
Attempting to display it as <img [src]="imagePath" > will result in an error (file not found).
The problem here is that the URL is in the file:///storage... path instead of the correct one based on localhost.
In previous versions of Ionic, this would be solved by using normalizeURL. This will not work on Ionic 4 (or at least I could not make it work).
To solve this issue, you will need to use convertFileSrc():
import {WebView} from '#ionic-native/ionic-webview/ngx';
...
this.camera.getPicture(options).then((url) => {
// Load Image
this.imagePath = this.webview.convertFileSrc(url);
}, (err) => {
console.log(err);
});
Now the image URL will be in the appropriate http://localhost:8080/_file_/storage... format and will load correctly.
See WKWebView - Ionic Docs for more information.
In my case, the following code works with me
const downloadFileURL = 'file:///...';
// Convert a `file://` URL to a URL that is compatible with the local web server in the Web View plugin.
const displayedImg = (<any>window).Ionic.WebView.convertFileSrc(downloadFileURL);
In case some gots here looking for the answer on ionic4, check this out
"Not allowed to load local resource" for Local image from Remote page in PhoneGap Build
and look for the answer from #Alok Singh that's how I got it working on ionic4 and even works with livereload
UPDATE december 2021:
You have to install the new Ionic Webview
RUN:
ionic cordova plugin add cordova-plugin-ionic-webview
npm install #awesome-cordova-plugins/ionic-webview
Import it in app.module and your page where you wanna use it:
import { WebView } from '#awesome-cordova-plugins/ionic-webview/ngx';
image = "";
constructor(private webview: WebView){}
Then this will work:
this.camera.getPicture(options).then((imageData) => {
this.image = this.webview.convertFileSrc(imageData)
}, (err) => {
// Handle error
});
And show it in the HTML page:
<img [src]="image" alt="">

Angular could not be found on the page with angular application while running test for angular app in iFrame

I have an angular application running inside iFrame. I must need to launch parent application URL as it provide some flag which makes angular app working as expected. Now I need to write protractor tests for angular app in iFrame.
Here is the code.
describe('French page', function() {
var IFRAME = "iframe",
TITLE_FR = 'Découverte automatique',
PAGE_URL = '/SAAS/admin/app/page',
pagePaths = browser.params.paths;;
beforeEach(function (done) {
LOGIN_PAGE.goToPageAndLogin().then(function (){
browser.driver.ignoreSynchronization = true;
browser.get(PAGE_URL); // application has angular app in iFrame
browser.sleep(5000);
browser.waitForAngular();
done();
});
});
afterEach(function (done) {
demoPause();
LOGIN_PAGE.logout().then(done);
});
it('should be able to launch with fr-FR locale', function (done) {
browser.driver.switchTo().frame(IFRAME); //Switch to angular app in iFrame
// Check if element text is in french
browser.driver.findElement(by.css('.app-menu li:nth-child(1) p')).then(function (elem) {
elem.getText().then(function (text) {
expect(text).toBe(TITLE_FR); // I can see that both texts are same here while debugging
browser.driver.ignoreSynchronization = true;
done();
});
});
});
});
The test condition passed but it exit with below error.
Message:
Failed: Angular could not be found on the page
https://host/abcd/admin/app/page
retries looking for angular exceeded
The issue got fixed by putting
browser.ignoreSynchronization = true;
before
browser.get(PAGE_URL);
Few things:
The parameter IFRAME passed into:
browser.driver.switchTo().frame(IFRAME); needs to be the ID
property of the element, not the tag name of the element, example:
your iframe element
<iframe id="myIframeId" name="frame3">...</iframe>
you would in this case do
browser.diver.switchTo().frame("myIframeId");
Don't call browser.waitForAngular(); on a non-angular page. Since only your iframe element is Angular, I suggest doing the following to make sure a specific element is present before continuing:
browser.driver.wait(function() {
return browser.driver.isElementPresent(by.css("your_selector")).then(function(present) {
return present;
});
}, 20000);
This will wait for an element to be present for 20 seconds, regardless of the page being Angular or not and then continue.
After you switch to the iframe element, you should call browser.driver.ignoreSynchronization = false; to turn on Angular synchronization back on. Since your code inside your iframe is Angular.

Tracking url of external site launched in cordova-inappbrowser-plugin

I'm currently building an ionic app which is to be a wrapper for an external web application. What I want to do is to be able to track the url being redirected to when the user changes location in the external web app.
In my main controller I have the following code.
app.controller('MainCtrl', function ($rootScope) {
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
// Now safe to use the Codova API
var url = "https://external-site/";
var target = "_self";
var options = "location=no";
var ref = cordova.InAppBrowser.open(url, target, options);
ref.addEventListener('loadstart', function () {
console.log("loadstart");
});
}
});
When the page loads I don't get the event listener to fire or when the user changes locations in the external site. I have tried pointing the target to _system and _blank which makes no difference for me.
Can anybody help me?
Thanks in advance.
It's my experience that all the events not always fires on all platforms. Try subscribing to all the events and print some debug info. Then test on different devices (iOS, android) and see what events are fired.
$rootScope.$on('$cordovaInAppBrowser:loadstart', function(e, event){console.log('start')};
$rootScope.$on('$cordovaInAppBrowser:loadstop', function(e, event){console.log('stop')});
$rootScope.$on('$cordovaInAppBrowser:loaderror', function(e, event){console.log('err')});
$rootScope.$on('$cordovaInAppBrowser:exit', function(e, event){console.log('exit')});
btw: I'm using ngCordova here...
very strange.. all I did was update ionic, run 'ionic start test blank' add the plugin modify app.js to this
angular.module('starter', ['ionic'])
.run(function ($ionicPlatform) {
$ionicPlatform.ready(function () {
if (window.cordova && window.cordova.plugins.Keyboard) {
var inAppBrowserRef;
var target = "_self";
var options = "location=no";
inAppBrowserRef = cordova.InAppBrowser.open('https://onesignal.com/', target, options);
inAppBrowserRef.addEventListener('loadstart', function () { console.log('start') });
inAppBrowserRef.addEventListener('loadstop', function () { console.log('stop') });
inAppBrowserRef.addEventListener('loaderror', function () { console.log('err') });
}
});
})
and then run 'ionic run android' and all events fires perf.

Observe changes in TinyMCE from Dart

According to TinyMCE API, the following JavaScript code observe changes in TinyMCE editor:
tinyMCE.init({
...
setup : function(ed) {
ed.onChange.add(function(ed, l) {
console.debug('Editor contents was modified. Contents: ' + l.content);
});
}
});
However, I'm unable to run this code from Dart using the js Library. Help is appreciated.
UPDATE:
There is a problem in the JS code above. Alternatively, I found this working code in here:
var ed = new tinymce.Editor('textarea_id', {
init_setting_item: 1,
}, tinymce.EditorManager);
ed.on('change', function(e) {
var content = ed.getContent();
console.log(content);
});
ed.render();
I still need help running the code from Dart. And preferably storing its results in a Dart variable for subsequent processing.
Here's the same code called from Dart :
var ed = new js.Proxy(js.context.tinymce.Editor, 'textarea_id', js.map({
'init_setting_item': 1
}), js.context.tinymce.EditorManager);
js.retain(ed); // retain allows to use 'ed' in the following callback
ed.on('change', new js.Callback.many((e) {
var content = ed.getContent();
window.console.log(content);
}));
ed.render();