I am new in the development of Confluence Add-Ons. I want to use the example from the API. For instance look at this Page Dialog 2, there are the HTML-Code and the JS-Code separated.
I know how to define a JS-File and how to refer to the atlassian-plugin.xml.
Look here
<web-resource key="Confluence-resources" name="Confluence-Web-Resources">
<dependency>com.atlassian.auiplugin:ajs</dependency>
<resource type="download" name="confluence.js" location="/js/confluence.js"/>
</web-resource>
But where I must define the HTML-Code?
In essence, you need to render the HTML from execute method in your Macro class. But, practically speaking, there're a few "Atlassian-approved" approaches.
You might want to consider built-in Velocity templating support from VelocityUtils.getRenderedTemplate method.
Quoting Atlassian's Documentation, you can probably write something like:
public String execute(Map params, String body, RenderContext renderContext) throws MacroException {
// do something with params ...
Map context = MacroUtils.defaultVelocityContext();
context.put("page", page);
context.put("labels", labels);
return VelocityUtils.getRenderedTemplate("com/atlassian/confluence/example/sample-velocity.vm", context);
}
In recent years, they started to promote the usage Soy Template too.
Related
I want to use SCEditor in my Blazor page.
For example I create a new Blazor WASM project and I did these steps:
According to documentation I add this codes and references to index.html:
https://cdn.jsdelivr.net/npm/sceditor#3/minified/themes/default.min.css
https://cdn.jsdelivr.net/npm/sceditor#3/minified/sceditor.min.js
https://cdn.jsdelivr.net/npm/sceditor#3/minified/formats/bbcode.min.js
window.InitEditor = () => {
var textarea = document.getElementById('myTextArea');
sceditor.create(textarea, {
format: 'bbcode',
style: 'https://cdn.jsdelivr.net/npm/sceditor#3/minified/themes/content/default.min.css'
});
};
Add a textarea in counter page:
<textarea id="myTextArea" style="width:100%; height:200px;"></textarea>
add follow code to code section of counter page:
protected override Task OnAfterRenderAsync(bool firstRender)
{
JsRuntime.InvokeVoidAsync("InitEditor");
return base.OnAfterRenderAsync(firstRender);
}
Now I run the project and I see this editor:
then I click on fetch data menu and I see this:
Surprisingly, editor has shown in fetch data page. if I click on counter page again I see this:
There are 2 Editors O-O. and if I click on this menus then editors are increasing...
I changed the code this way:
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
JsRuntime.InvokeVoidAsync("InitEditor");
return base.OnAfterRenderAsync(firstRender);
}
the editor is shown once in counter page and fetch data page. Again I don't have any textarea in fetch data page.
How can I solve this problem?
Blazor documentation warns:
Only mutate the Document Object Model (DOM) with JavaScript (JS) when the object doesn't interact with Blazor. Blazor maintains representations of the DOM and interacts directly with DOM objects. If an element rendered by Blazor is modified externally using JS directly or via JS Interop, the DOM may no longer match Blazor's internal representation, which can result in undefined behavior. Undefined behavior may merely interfere with the presentation of elements or their functions but may also introduce security risks to the app or server.
This guidance not only applies to your own JS interop code but also to any JS libraries that the app uses, including anything provided by a third-party framework, such as Bootstrap JS and jQuery.
SCEditor is exactly one of those DOM-mutating libraries, and the effects of failure to observe that guidance you can see for yourself. (The ‘security risks’ bit is rather nonsensical: if your app can be made insecure merely by modifying client-side code, then it wasn’t very secure to begin with. But it’s otherwise good advice.)
Blazor does provide some interoperability with external DOM mutation in the form of element references. The documentation again warns:
Only use an element reference to mutate the contents of an empty element that doesn't interact with Blazor. This scenario is useful when a third-party API supplies content to the element. Because Blazor doesn't interact with the element, there's no possibility of a conflict between Blazor's representation of the element and the Document Object Model (DOM).
Heeding that warning, you should probably write something like below (not tested). In the component file (.razor):
<div #ref="sceditorContainer"></div>
#inject IJSRuntime js
#code {
private ElementReference sceditorContainer;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
await js.InvokeVoidAsync("initEditor", sceditorContainer);
}
}
}
And in JavaScript:
function initEditor(container) {
const textarea = document.createElement('textarea');
container.appendChild(textarea);
sceditor.create(textarea, {
format: 'bbcode',
style: 'https://cdn.jsdelivr.net/npm/sceditor#3/minified/themes/content/default.min.css'
});
}
If the SCEditor library is sufficiently well-behaved, it should only modify the DOM tree at most at the level of the parent of the textarea node you give it. You may think it would be enough to place a <textarea> in your component markup and capture a reference to that, but as it happens SCEditor adds siblings to that node, which may keep messing up rendering. For that reason, it is safer to put everything in an initially-empty wrapper element, which can act as a sandbox in which SCEditor has free rein.
Ideally, you would encapsulate everything SCEditor-related into a dedicated component that deals only with SCEditor, while the rest of your app would use that component like any other Blazor component.
Context: Azure, C#, ClearScript, JavaScript, HtmlAgilityPack, HtmlAgilityPack.CssSelectors
I do this a lot: add scripting to C# apps using ClearScript. Ordinarily this just works.
...
using Microsoft.ClearScript;
using Microsoft.ClearScript.Windows;
...
class Program
{
static JScriptEngine JSengine = null;
...
JSengine = new JScriptEngine(WindowsScriptEngineFlags.EnableDebugging | WindowsScriptEngineFlags.EnableJITDebugging);
....
JSengine.AddHostType("CSHtmlDocument", typeof(HtmlAgilityPack.HtmlDocument));
...
and then later, in the JavaScript code itself, there are things like
...
var hap = new CSHtmlDocument();
hap.LoadHtml(html);
...
So this is going really well until I add HtmlAgilityPack.CssSelectors into the mix. On the C# side, this adds extra methods to the HtmlDocument object, specifically QuerySelector and QuerySelectorAll. They're visible on the C# side. However, on the JavaScript side they're not and code such as
...
var selection = hap.QuerySelector(".reduced");
...
throws an error and looking at the object from a debugging session in Visual Studio 2015 shows no QuerySelector method in the hap var.
So what's the story? Is it a ClearScript issue or a C# issue? And what do I do about it? I'm quite happy to write a wrapper class, I was just expecting everything to work as before.
These new methods are most likely extension methods defined by a particular class. To make them accessible from script code, you must expose that class to the script engine.
EDIT: I'm not familiar with them, but it looks like the methods you're talking about are provided by the HapCssExtensionMethods class (or something very similar). To expose the methods, simply expose the class:
// C#
JSengine.AddHostType(typeof(HapCssExtensionMethods));
Once you've done that, your JavaScript sample above should work as is.
I'm not sure this is supported but wanted to see if any of you had come up with something creative to work around this.
Is there a way to call javascript from silverlight, without having to define any javascript functions on the aspx page/external js?
I'd like to be able to do something like:
HtmlPage.Window.Invoke("(function () { window.lastErrorMessage = 'foo'; })();")
Which, to forgo conversations about style, I'd agree goes against many best practices rules, but my current purpose is brainstorm some quick-and-dirty error reporting (could you even call it reporting?) before we implement the existing database-centric error logging in this solution.
Any thoughts? The idea is to generate something discreet that a user wouldn't feel intruded upon or something too technical (like IE's script error dialog), but something our app support could get a little more info from without access to code bases etc.
Thanks, Matthew
The method you are looking for is Eval not Invoke:-
HtmlPage.Window.Eval("(function () { window.lastErrorMessage = 'foo'; })();")
You could add your JavaScript dynamically to the hosting page DOM using HtmlPage.Document and then execute your added methods. Or are you trying not to modify the page at all?
HtmlElement head = HtmlPage.Document.GetElementsByTagName("head")[0] as HtmlElement;
HtmlDocument htmlDocument = HtmlPage.Document;
HtmlElement scriptElement = htmlDocument.CreateElement("script");
scriptElement.SetAttribute("type", #"text/javascript");
scriptElement.SetProperty("text", "function testMe(p) { alert(p); }");
head.AppendChild(scriptElement);
// Invoke like this
HtmlPage.Window.Invoke("testMe", "hello");
Most of the examples you find on the web, of using javascript from ASP.NET pages puts the javascript in the markup file (*.aspx). This is, of course, a really bad idea(tm), for all but the simplest uses of javascript.
What we want, of course, is to wrap the javascript up into a class, and to instantiate an instance of that class and tie it to the code-behind.
Microsoft provides a framework for doing this for user controls and server controls, in its IScriptControl interface. This allows a developer to create a javascript "component" - to define a javascript class in a *.js file, to include the *.js file on the page that contains the control, to instantiate an instance of the component, to set variables in the component from values in the code-behind, and to get a reference to the component in javascript on the client side.
The thing is - IScriptControl only works for user and server controls. It cannot be used to instantiate javascript objects at the page level.
So - how do people do this? We have some patterns we've been using, that seem to work. I was wondering what everyone thought of them, and what other people were using.
We start by defining a javascript class in a *.js file. In the code-behind, we create a loadJavascript() function, that we call from Page_Load on initial load or full postback (but not on partial postbacks).
In loadJavascript(), we include the *.js file with ScriptManager.RegisterClientScriptInclude(), and then construct a bit of javascript that instantiates an instance of the class, assigns a reference to a known name, and registers the object's initialize() and dispose() methods as handlers for window.load and window.unload.
E.g.:
string url = this.ResolveUrl("./FooBar.js");
ScriptManager.RegisterClientScriptInclude(this, this.GetType(), url, url);
string script = #"
if (typeof {0}_obj == 'undefined')
{0}_obj = {{}};
{0}_obj.fooBar = new FooBar();
Sys.UI.DomEvent.addHandler(window, 'load',
function()
{{
{0}_obj.fooBar.initialize('{1}', '{2}');
}}
);
Sys.UI.DomEvent.addHandler(window, 'unload', {0}_obj.fooBar.dispose);
";
script = String.Format(script,
new object[]
{
this.ClientID,
this.foo.ClientID,
this.bar.ClientID
});
ScriptManager.RegisterStartupScript(
this, this.GetType(), this.ClientID, script, true);
We construct an object name in the global namespace, based on the ClientID of the page, if we haven't already. We add an instance of our new class as a member of our global object. We add a window.load handler that calls our object's intialize() method, passing the clientIDs of the controls on the page that the object's methods need to access. And we add a window.unload handler that calls our object's dispose() method, that does whatever cleanup that is necessary.
This seems to be working, for us. We've used this pattern on a number of pages, some of which did significant amounts of partial-postbacks, without any problems.
I was wondering, first, what people thought of the pattern.
But more, I was wondering if we'd been reinventing the wheel, and if there were other approaches to dealing with the issues we were addressing, that we weren't aware of.
Anyone have any better ideas?
But more, I was wondering if we'd been reinventing the wheel, and if there were other approaches to dealing with the issues we were addressing, that we weren't aware of.
I think this the most good approaches, I use the same way some years now with out any problem in very complex javascript code. I do not see why you question your self :)
The idea is this you follow, now maybe there are some variations, maybe I not call the unload, nether create an object to keep the foobar and call the foobar rightway, but the idea is the same. I also check if the Javascript file have been loaded...
string script = #"
if (typeof (FooBar) != "undefined") {{
var {0}fooBar = new FooBar();
Sys.UI.DomEvent.addHandler(window, 'load',
function()
{{
{0}fooBar.initialize('{1}', '{2}');
}}
);
}}
I have a CDHTMLDialog, with which I have 2 HTML pages and a .js file with a few fairly simple functions.
I would like to be able to call one of the JS functions from my program with a simple data type passed with it. e.g. MyFunc(int). Nothing needs to be returned.
I would appreciate any guidance on how I go about this,
thanks.
Edit: Thanks to CR for his answer, and everyone else who submitted there ideas too.
Something a little like this worked in the end (stripped a little error handling from it for clarity):
void callJavaScriptFunc(int Fruit)
{
HRESULT hRes;
CString FuncStr;
CString LangStr = "javascript";
VARIANT vEmpty = {0};
CComPtr<IHTMLDocument2> HTML2Doc;
CComPtr<IHTMLWindow2> HTML2Wind;
hRes = GetDHtmlDocument(&HTML2Doc);
hRes = HTML2Doc->get_parentWindow(&HTML2Wind);
if( Fruit > 0 )
{
FuncStr = "myFunc(808)"; // Javascript parameters can be used
hRes = HTML2Wind->execScript(FuncStr.AllocSysString(), LangStr.AllocSysString(), &vEmpty);
}
}
Easiest approach would be to use the execScript() method in the IHTMLWindow2 interface.
So you could get the IHTMLDocument2 interface from your CDHTMLDialog by calling GetDHtmlDocument, then get the parentWindow from IHTMLDocument2. The parent window will have the IHTMLWindow2 interface that supports execScript().
There might be an easier way to get the IHTMLWindow2 interface from your CDHTMLDialog but I'm used to working at a lower level.
the SpiderMonkey library can "Call a JavaScript function from C++", please refer to
http://egachine.berlios.de/embedding-sm-best-practice/ar01s02.html#id2464522
but in your case, maybe this is not the answer.
To give you a hint - javascript injection in server-side-technologies is usually performed through bulk-load at startup (GWT) or injected when the HTML is generated and served each post-back (ASP.NET).
The important point of both approaches is that they inject the javascript calls somewhere in the page (or in a separated .js file linked in the HTML in case of GWT) when generating the HTML page.
Even if you're on win development (looks like it since you're on MFCs) it might be the case that you have to insert your js method call in the HTML and then load (or reload if you wish to interact with the html from your MFC app) the HTML file in your CHTMLDialog.
I don't see any other way of achieving this (maybe I am just not aware of some suitable out-of-the-box functionality) other than editing your HTML and (re)loading it - which is pretty convenient and workable if you have to call your js method once off or just inject some kind of event-handling logic.
Might be a bit of a pain if you have to interact with the page from your MFC app. In this case you have to re-generate your HTML and reload it in your CHTMLDialog.
Either way you can simply have some kind of placeholder in your HTML file, look for that and replace with your javascript code, then load the page in your CHTMLDialog:
onclick="__my_Javascript_Call_HERE__"