how to resolve Javascript issues when using 2sxc IRenderService to embed an App in a DNN Theme Layout - dotnetnuke

Im trying to embed my 2sxc App into a Theme layout page.
The App view html does render
<%-- This namespace provides this.GetScopedService<T>() --%>
<%# Import Namespace="ToSic.Sxc.Dnn" %>
<%-- This namespace provides all the common 2sxc services --%>
<%# Import Namespace="ToSic.Sxc.Services" %>
<%= this.GetScopedService<IRenderService>().Module(1041,3421) %>
, but its Javascript crashes.
The App uses its own API for searching.
1. // get the sxc-controller for this module
2. var sxc = $2sxc(3421);
3. // now get the data in the promise
4. sxc.webApi.get('app/auto/api/Forms/SearchForm')
5. .then(data => {
6. console.log(data)
7. });
originally line 2 crashed saying $2sxc is not recognised.
we resolved that by adding this script reference to our layout page
<script src="/desktopmodules/tosic_sexycontent/js/2sxc.api.min.js" type="text/javascript"></script>
And now it crashes on line 4 when trying to use sxc.webApi.get
Uncaught Can't find page - something went wrong, pls contact 2sxc.org
It seems I need to include another JS script.
I tried to also include
<script src="/desktopmodules/tosic_sexycontent/dist/inpage/inpage.min.js" type="text/javascript"></script>
but that made it worse

I resolved this by adding this
var page = GetService<ToSic.Sxc.Services.IPageService>();
page.Activate("2sxc.JsCore");

Update: to use the JS better in the theme, I've just pushed an update to 14.07.04.
Basically you can force a different context than the automatic one, using
var sxc = $2sxc({ pageId: 27, moduleId: 42, zoneId: 3, appId: 8});
sxc.webApi.fetchJson(...).then(...);
this uses the context identifier https://docs.2sxc.org/api/js/ContextIdentifier.html#Api_Js_SxcJs_ContextIdentifier which has existed for a few versions now, but there was a bug that was fixed in 14.07.04
See also https://docs.2sxc.org/api/js/SxcGlobal.html#Api_Js_SxcJs_SxcGlobal_get_3

Related

Multiple importmap.json for different environments in single-spa

In am converting the existing project into a single-spa. This application support different environments- dev,qa,prod. My requirement is to use the different import maps json based on the environment.
<% if (isLocal) { %>
<script type="systemjs-importmap">
{
"imports": {
"react": "https://cdn.jsdelivr.net/npm/react#16.13.1/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom#16.13.1/umd/react-dom.production.min.js",
"#ppm-canvas/root-config": "//localhost:9000/ppm-canvas-root-config.js",
"#ppm-canvas/match-upload": "//localhost:8081/ppm-canvas-match-upload.js"
}
}
</script>
<% } else { %>
<script type="systemjs-importmap" src="https://cdn.cloudfront.net/import-map.json"></script>
<% } %>
Here, I want to use something like multiple else if condition to be able to load different import-map.json from remote cdn.
Can someone pls help me in achieving this.
You can achieve this in your root-config since it is simply an ejs template, which is processed by Webpack. If you peek at your root-config's webpack.config.js you'll find isLocal derived there. You can add your own templateParameters there. Lastly, import-map-deployer is a useful tool for managing multiple import maps, which can be good for multiple deployment environments.

NW.js - webview - Angular is not defined error

I want control external website using NW.js and webview, but anytime I try this I have error - Angular is not defined.
My source:
index.html
html...head...body...
<webview id="webview" name="webview" src="https://google.com/" allownw></webview>
<script src="../js/script.js"></script>
...body...html
script.js
(function() {
var gui = require('nw.gui');
var win = gui.Window.get();
var webview = document.getElementById("webview");
var tray = new gui.Tray({
icon : 'assets/icon.png'
});
var menu = new gui.Menu();
menu.append(new gui.MenuItem({
type: 'normal',
label: '▶️ Play',
click: function() {
webview.executeScript({code:"var player=angular.element(document.body).injector().get('player'); player.play();"});
}
}));
tray.menu = menu;
}());
This code produce for me error: ReferenceError: angular is not defined
Note: Website google.com is only example.
From the nw.js docs:
loading local files in webview
Add the following permission to the manifest:
"webview": {
"partitions": [
{
"name": "trusted",
"accessible_resources": [ "<all_urls>" ]
}
]
}
and add 'partition="trusted"' attribute to the webview tag.
This is one reason why the <script tag cannot access your ../js/script.js file. I also suggest using a non-relative path to your local file example: chrome-extension://yourdomain/js/script.js (and that it be relative to the root not 'up' a directory).
From google's docs, you can set accessible_resources to be specific patterns or files.
The insertCss, setUserAgentOverride, permissionrequest event, and especially executeScript from google's documentation will likely be of interest to you.
NOTE: you may not want to give that webview access to your nwjs context (allownw) because it can access anything that your program has access to. This is especially true of both an external URL and http where MITM tampering or a third-party change could potentially cause someone to gain elevated access to the PC your application is running on.
For your example, I would keep the script.js outside of the webview and have it instrument the webview via script injection.

MiniProfiler: How do I profile an AngularJS + WebAPI app?

Cross-posted on the MiniProfiler community.
I'm trying to throw MiniProfiler into my current stack. I think I'm mostly setup, but am missing the UI approach and would like recommendations on the best way to proceed.
Current Stack
SQL for DB (including MiniProfiler tables)
EF 6
WebAPI 2 API app
Angular 1.x. app for the UI (separate app, no MVC backing it) -- I think it's 1.5.x at this point.
So, the current method of RenderIncludes() isn't available to me.
What's the best method to include the JS files and set them up to retrieve the information from the SQL Server storage? I know that the files are included in the UI repo, but I didn't see docs for explicit configuration.
What I've Tried So Far -- Web API App
Installed the MiniProfiler and MiniProfiler.EF6 packages.
Web.Config -- Added Handler
(not sure if this is necessary):
<add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified" preCondition="integratedMode" />
Added a CORS filter to expose the MiniProfiler IDs to my client app:
public class AddMiniProfilerCORSHeaderFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response.Headers.Add("Access-Control-Expose-Headers", "X-MiniProfiler-Ids");
}
}
Add that filter as part of startup:
config.Filters.Add(new AddMiniProfilerCORSHeaderFilter());`
In Global.asax, added to Application_Start():
var connectionString = ConfigurationReader.GetConnectionString(Constants.ConfigSettings.CONNECTION_STRING_NAME);
MiniProfiler.Settings.Storage = new SqlServerStorage(connectionString);
MiniProfilerEF6.Initialize();
Update the begin/end requests:
protected void Application_BeginRequest()
{
if (Request.IsLocal || ConfigurationReader.GetAppSetting(Constants.ConfigSettings.USE_PROFILER, false))
{
var sessionId = Guid.NewGuid().ToString();
MiniProfiler.Start(sessionId);
}
}
protected void Application_EndRequest()
{
MiniProfiler.Stop();
}
What I've tried so far -- client (Angular) App
Snagged the UI files from the Github Repo
Copied the UI directory to my project's output
Reference the CSS:
<link rel="stylesheet" href="js/lib/miniprofiler/includes.css" />
Call the JavaScript
<script async type="text/javascript"
id="mini-profiler"
src="js/lib/miniprofiler/includes.js?v=1.0.0.0"
data-current-id=""
data-path="https://localhost:44378/api/profiler/"
data-children="true"
data-ids=""
data-version="1.0.0.0"
data-controls="true"
data-start-hidden="false"
data-trivial-milliseconds="5">
</script>
Current Status
When I do these things, it looks like it just can't find the appropriate WebAPI controller to render the result. If I can figure out where that controller is or replicate it (as I'm attempting to do currently) I think I'll be in business.
The RenderIncludes function results in a <script> tag being output to the page. It is defined in the UI Repo as include.partial.html and currently looks like this:
<script async type="text/javascript" id="mini-profiler"
src="{path}includes.js?v={version}" data-version="{version}"
data-path="{path}" data-current-id="{currentId}"
data-ids="{ids}" data-position="{position}"
data-trivial="{showTrivial}" data-children="{showChildren}"
data-max-traces="{maxTracesToShow}" data-controls="{showControls}"
data-authorized="{authorized}" data-toggle-shortcut="{toggleShortcut}"
data-start-hidden="{startHidden}" data-trivial-milliseconds="{trivialMilliseconds}">
</script>
This is the piece of Javascript that runs the rendering.
The RenderIncludes function is defined here. It does the following:
Makes sure that you have storage set up
Checks that current request is authorized to view results
Gets the Ids of the unviewed profiles for the current user
Takes the Ids along with any other params that you passed into the function and inserts them into the placeholders in the script defined in include.partial.html
Outputs this <script>
So if you cannot call RenderIncludes, there should be no reason why you cannot just put the script file in place, retrieve the unviewed Ids, but them along with any other setup values you want into the <script> tag, and output the tag.
The key lines of code for retrieving the Id values are:
var ids = authorized
? MiniProfiler.Settings.Storage.GetUnviewedIds(profiler.User)
: new List<Guid>();
ids.Add(profiler.Id);
where profiler is the current instance of MiniProfiler (run on the current request.
You will also probably need to make sure that you can handle the call that the script will make to /mini-profiler-resources/results (passing in the id of the profiler as a param). The guts of this is located here in the GetSingleProfilerResult(HttpContext context) function

Backbone router working differently with utf-8 characters on Safari and Chrome

I have this example, using a route with an utf-8 non-ascii character:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Backbone Test</title>
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/underscore.js/1.3.1/underscore-min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/backbone.js/0.9.2/backbone-min.js"></script>
<script>
(function(){
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
})();
App.Router = Backbone.Router.extend({
routes: {
'charñ': 'charChrome',
'char%C3%B1': 'charSafari'
},
charChrome: function(){
$(document.body).append("Chrome-compatible route triggered.<br/>");
},
charSafari: function(){
$(document.body).append("Safari-compatible route triggered.<br/>");
},
});
new App.Router;
Backbone.history.start();
</script>
<h1>HELLO THERE</h1>
</body>
</html>
When the page got called with something like:
file://localhost/whatever.html#charñ
...it trigger a different function on Safari and Chrome.
It's REALLY not easy for me to change backbone version.
Is there a way to avoid that difference?
Note: interesting enough, removing the meta tag breaks Chrome routing.
This shouldn't be seen as an answer to that makes every browsers works the same but prevent this from happening. As far as I know, there is no much control on what backone will trigger. It's not like you can really filter the url before it gets handled by the Router.
That said, you can create routes for both style. To make this easier, you can create a special object that will get a utf8 string and create a urlencoded version of the route. Both routes will have the same callback.
On other possibility is to avoid utf-8 symbols in the url and report a bug to backbonejs. Having both routes created with urlencoded/urldecoded will make the site work for possibly every browsers supporting javascript. The downside is that you'll have to create n*2 routes.
Have you tried decoding the URL when it's passed to your function? It may be that Chrome decodes the URL for you while Safari does not. 'char%C3%B1' will decode to 'charñ' just fine when using a URI decoder, and it should have no effect on already-decoded strings (assuming the encoding used was the correct one, of course).
decodeURIComponent('char%C3%B1')
/*
charñ
*/
decodeURIComponent('charñ')
/*
charñ
*/
Going by http://backbonejs.org/docs/backbone.html#section-156, you may be able to substitute _extractParameters with a version that calls decodeURI or decodeURIComponent on fragment before executing the regular expression.
Another possibility to try, as I just noticed that routes can be regular expressions (see http://backbonejs.org/docs/backbone.html#section-152): if you don't have many of them, you could use something like /char(ñ|%C3%B1)/g, or write a function to produce such a regular expression for the unencoded value, such that makeRegex('charñ') would produce /char(ñ|%C3%B1)/g, or whatever the regular expression would be.
I solved it by adding an additional line in the while loop, inside _bindRoutes, to bind the encoded routes as well:
_bindRoutes: function() {
if (!this.routes) return;
this.routes = _.result(this, 'routes');
var route, routes = _.keys(this.routes);
while ((route = routes.pop()) != null) {
this.route(route, this.routes[route]);
//add this line:
this.route(encodeURIComponent(route).replace(/%2F/g,'/').replace(/%3A/g,':'), this.routes[route]);
}
}

Google Analytics don't seem to be firing

I've tried to look around many threads here around GA, I feel like I've done what's required. My site consist of HTML5 and angular.js, the page is structured roughly as follow (through ng-include)
index.html
|-header
--|-navigation
|-content
|-footer
when a link from navigation bar is clicked, the content will change (partials page in angular), header, footer stays the same.
The last script tag in my < head > is as follow (inside index.html)
<script type="text/javascript">
var globalLanguage = 'en';
// GA tracking variable
var _gaq = [['_setAccount', 'UA-XXXXXXXX-X'], ['_trackPageview']];
</script>
and at the bottom part of index.html, before the closing < / body > tag:
<script type="text/javascript">
(function(d, t) {
var g = d.createElement(t), s = d.getElementsByTagName(t)[0];
g.async = 1;
g.src = '//www.google-analytics.com/ga.js';
g.type = 'text/javascript';
s.parentNode.insertBefore(g, s);
}(document, 'script'));
</script>
All my angular controller function, call a common function as described in Tracking Google Analytics Page Views with Angular.js
function gaqPageView($scope, $location, $window) {
console.log('triggering google analytics');
$scope.$on('$viewContentLoaded', function(event) {
console.log('event triggered, tracking: ' + $location.path());
$window._gaq.push([ '_trackPageview', $location.path() ]);
});
}
I do see the console log statements, there's no error in the console either.
When I print out the content of _gaq, I do get an array that grows as I navigate around the page (which mean my _gaq.push call is working just fine).
However, in my the network call (in chrome dev tool), I don't see any _utm.gif call to Google Analytics. (Basics of Debugging Google Analytics Code: GA Chrome Debugger and other tools).
What am I missing here? seems like the google analytics is not firing off the event and reporting it?
Edit: I am pretty sure I am silly here, the _gaq variable itself is just a normal javascript array, so of course _gaq.push work just fine. But what am I missing to get Google Analytics to kick in and start sending the content of that _gaq?
Are you currently running your server on localhost, or an intranet name without a "." -- the tracking GIF request doesn't get made for localhost servers by default.
See Google Analytics GIF request not sent.
Another idea: Usually _gaq is defined as an array only if it's not already defined. If ga.js has already executed, you might be overwriting the _gaq object. It doesn't seem likely from your code organization, but...
Try replacing
var _gaq = [['_setAccount', 'UA-XXXXXXXX-X'], ['_trackPageview']];
with
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXXXXX-X'], ['_trackPageview']);

Resources