In my Windows 8.1 app I used MessageBox.Show() to popup a message. That is gone in UWP. How can I show a message?
Yup, indeed something like that, the new method is to use the MessageDialog class. You have to create an object of that type. You can also add buttons. It's a bit more complex I think. But you can also use some shortcuts here. To just show a message, use this:
await new MessageDialog("Your message here", "Title of the message dialog").ShowAsync();
To show an simple Yes/No message, you can do it like this:
MessageDialog dialog = new MessageDialog("Yes or no?");
dialog.Commands.Add(new UICommand("Yes", null));
dialog.Commands.Add(new UICommand("No", null));
dialog.DefaultCommandIndex = 0;
dialog.CancelCommandIndex = 1;
var cmd = await dialog.ShowAsync();
if (cmd.Label == "Yes")
{
// do something
}
Take a look at the Windows.UI.Popups.MessageDialog class and try this:
// Create a MessageDialog
var dialog = new MessageDialog("This is my content", "Title");
// If you want to add custom buttons
dialog.Commands.Add(new UICommand("Click me!", delegate (IUICommand command)
{
// Your command action here
}));
// Show dialog and save result
var result = await dialog.ShowAsync();
It is better to put MessageDialog codes into a function that has a keyword async and returns type of Task For example:
public async Task displayMessageAsync(String title, String content,String dialogType)
{
var messageDialog = new MessageDialog(content, title);
if (dialogType == "notification")
{
//Do nothing here.Display normal notification MessageDialog
}
else
{
//Dipplay questions-Yes or No- MessageDialog
messageDialog.Commands.Add(new UICommand("Yes", null));
messageDialog.Commands.Add(new UICommand("No", null));
messageDialog.DefaultCommandIndex = 0;
}
messageDialog.CancelCommandIndex = 1;
var cmdResult = await messageDialog.ShowAsync();
if (cmdResult.Label == "Yes")
{
Debug.WriteLine("My Dialog answer label is:: " + cmdResult.Label);
}
else
{
Debug.WriteLine("My Dialog answer label is:: " + cmdResult.Label);
}
}
Related
I'm new to Protractor.
I'm trying to select a button based on the button title. I want to make this into a function, and pass the button title in as a parameter.
This is the hard-coded version which works:
it('I click on a button based on the button title', async function() {
let button = element(by.css('button[title=example_button_title]'));
await button.click();
});
I created a global variable and a function to try and replace this, where 'buttonTitle' is the parameter I'm passing into the function:
Variable:
let dynamicButton = buttonTitle => { return element(by.css("'button[title=" + buttonTitle + "]'")) };
Function:
this.selectDynamicButton = async function(buttonTitle) {
await browser.waitForAngularEnabled(false);
await dynamicButton(buttonTitle).click();
};
When I try this I get the following error:
Failed: invalid selector: An invalid or illegal selector was specified
Apologies if there appear to be basic errors here, I am still learning. I appreciate any help that anyone can give me. Thanks.
You can add a custom locator using protractors addLocator functionality. (this is actually a very similar use case to the example listed in the link)
This would look like the following:
onPrepare: function () {
by.addLocator('buttonTitle', function (titleText, opt_parentElement) {
// This function will be serialized as a string and will execute in the
// browser. The first argument is the text for the button. The second
// argument is the parent element, if any.
const using = opt_parentElement || document;
const matchingButtons = using.querySelectorAll(`button[title="${titleText}"]`);
let result = undefined;
if (matchingButtons.length === 0) {
result = null;
} else if (matchingButtons.length === 1) {
result = matchingButtons[0];
} else {
result = matchingButtons;
}
return result;
});
}
This is called like
const firstMatchingButton = element(by.buttonTitle('example_button_title'));
const allMatchingButtons = element.all(by.buttonTitle('example_button_title'));
I had to edit this code before posting so let me know if this does not work. My work here is largely based off this previous answer
let dynamicButton = buttonTitle => { return element(by.css('button[title=${buttonTitle} ]')) };
Use template literals instead of string concatenation with +.
Protractor already has a built in locator which allows you to get a button using the text. I think you are looking at something like that. See the element(by.buttonText('text of button')) locator.
For more reference see here.
I need to create a workflow in AEM that for a page (specified as payload) finds all the assets used on the page and uploads a list of them to an external service. So far I have most of the code ready, but business process requires me to use a special code for each of the pages (different for each run of the workflow), so that the list is uploaded to correct place.
That is when I have a question - Can you somehow add more input values for an AEM workflow? Maybe by extending the starting dialog, or adding some special step that takes user input? I need to be able to somehow specify the code when launching the workflow or during its runtime.
I have read a lot of documentation but as this is my first time using workflows, I might be missing something really obvious. I will be grateful for any piece of advice, including a link to a relevant piece of docs.
Yes, that is possible. You need to implement a dialog step in your workflow: https://docs.adobe.com/content/help/en/experience-manager-64/developing/extending-aem/extending-workflows/workflows-step-ref.html#dialog-participant-step
You could:
Create a custom menu entry somewhere in AEM (e.g. Page Editor, /apps/wcm/core/content/editor/_jcr_content/content/items/content/header/items/headerbar/items/pageinfopopover/items/list/items/<my-action>, see under libs for examples)
Create a client-library with the categories="[cq.authoring.editor]". So it is loaded as part of the page editor (and not inside the iframe with your page)
Create a JS-Listener, that opens a dialog if the menu-entry was clicked (see code). You can either use plain Coral UI dialogs, or my example misused a Granite page dialog (Granite reads the data-structure in cq:dialog, and creates a Coral UI component edit-dialog out of it - while Coral is the plain JS UI-framework)
Create a Java-Servlet, that catches your request, and creates the workflow. You could theoretically use the AEM servlet. But I often have to write my own, because it lacks some features.
Here is the JS Listener:
/*global Granite,jQuery,document,window */
(function ($, ns, channel, window) {
"use strict";
var START_WORKFLOW_ACTIVATOR_SELECTOR = ".js-editor-myexample-activator";
function onSuccess() {
ns.ui.helpers.notify({
heading: "Example Workflow",
content: "successfully started",
type: ns.ui.helpers.NOTIFICATION_TYPES.SUCCESS
});
}
function onSubmitFail(event, jqXHR) {
var errorMsg = Granite.I18n.getVar($(jqXHR.responseText).find("#Message").html());
ns.ui.helpers.notify({
heading: "Example Workflow",
content: errorMsg,
type: ns.ui.helpers.NOTIFICATION_TYPES.ERROR
});
}
function onReady() {
// add selector for special servlet to form action-url
var $form = ns.DialogFrame.currentFloatingDialog.find("form");
var action = $form.attr("action");
if (action) {
$form.attr("action", action + ".myexample-selector.html");
}
// register dialog-fail event, to show a relevant error message
$(document).on("dialog-fail", onSubmitFail);
// init your dialog here ...
}
function onClose() {
$(document).off("dialog-fail", onSubmitFail);
}
// Listen for the tap on the 'myexample' activator
channel.on("click", START_WORKFLOW_ACTIVATOR_SELECTOR, function () {
var activator = $(this);
// this is a dirty trick, to use a Granite dialog directly (point to data-structure like in cq:dialog)
var dialogUrl = Granite.HTTP.externalize("/apps/...." + Granite.author.ContentFrame.getContentPath());
var dlg = new ns.ui.Dialog({
getConfig: function () {
return {
src: dialogUrl,
loadingMode: "auto",
layout: "auto"
}
},
getRequestData: function () {
return {};
},
"onSuccess": onSuccess,
"onReady": onReady,
"onClose": onClose
});
ns.DialogFrame.openDialog(dlg);
});
}(jQuery, Granite.author, jQuery(document), window));
And here is the servlet
#Component(service = Servlet.class,
property = {
SLING_SERVLET_RESOURCE_TYPES + "=cq:Page",
SLING_SERVLET_SELECTORS + "=myexample-selector",
SLING_SERVLET_METHODS + "=POST",
SLING_SERVLET_EXTENSIONS + "=html"
})
public class RequestExampleWorkflowServlet extends SlingAllMethodsServlet {
#Override
protected void doPost(#Nonnull SlingHttpServletRequest request, #Nonnull SlingHttpServletResponse response) throws IOException {
final Page page = request.getResource().adaptTo(Page.class);
if (page != null) {
Map<String, Object> wfMetaData = new HashMap<>();
wfMetaData.put("workflowTitle", "Request Translation for " + page.getTitle());
wfMetaData.put("something", "Hello World");
try {
WorkflowSession wfSession = request.getResourceResolver().adaptTo(WorkflowSession.class);
if (wfSession != null) {
WorkflowModel wfModel = wfSession.getModel("/var/workflow/models/example-workflow");
WorkflowData wfData = wfSession.newWorkflowData(PayloadInfo.PAYLOAD_TYPE.JCR_PATH.name(), page.getPath());
wfSession.startWorkflow(wfModel, wfData, wfMetaData);
MyServletUtil.respondSlingStyleHtml(response, HttpServletResponse.SC_OK, "Triggered Example Workflow");
} else {
throw new WorkflowException("Cannot retrieve WorkflowSession");
}
} catch (WorkflowException e) {
MyServletUtil.respondSlingStyleHtml(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
} else {
MyServletUtil.respondSlingStyleHtml(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal error - cannot get page");
}
}
}
I have a Gjs app that will need to save files. I can open the file chooser dialog just fine from my menu, and I have added a "save" and "cancel" button, but I can't get the "save" button to trigger anything.
I know I'm supposed to pass it a response_id, but I'm not sure what that's supposed to look like nor what I'm supposed to do with it afterwards.
I read that part here:
https://www.roojs.com/seed/gir-1.2-gtk-3.0/gjs/Gtk.FileChooserDialog.html#expand
let actionSaveAs = new Gio.SimpleAction ({ name: 'saveAs' });
actionSaveAs.connect('activate', () => {
const saver = new Gtk.FileChooserDialog({title:'Select a destination'});
saver.set_action(Gtk.FileChooserAction.SAVE);
saver.add_button('save', 'GTK_RESPONSE_ACCEPT');
saver.add_button('cancel', 'GTK_RESPONSE_CANCEL');
const res = saver.run();
if (res) {
print(res);
const filename = saver.get_filename();
print(filename);
}
saver.destroy();
});
APP.add_action(actionSaveAs);
I can catch res and fire the associated little logging action when I close the dialog, but both the "save" and "cancel" buttons just close the dialog without doing or saying anything.
My question is, what are GTK_RESPONSE_ACCEPT and GTK_RESPONSE_CANCEL supposed to be (look like) in GJS and how do I use them?
In GJS enums like GTK_RESPONSE_* are numbers and effectively look like this:
// imagine this is the Gtk import
const Gtk = {
ResponseType: {
NONE: -1,
REJECT: -2,
ACCEPT: -3,
DELETE_EVENT: -4,
...
}
};
// access like so
let response_id = -3;
if (response_id === Gtk.ResponseType.ACCEPT) {
log(true);
}
There's a bit more information here about that.
let saver = new Gtk.FileChooserDialog({
title:'Select a destination',
// you had the enum usage correct here
action: Gtk.FileChooserAction.SAVE
});
// Really the response code doesn't matter much, since you're
// deciding what to do with it. You could pass number literals
// like 1, 2 or 3. Probably this was not working because you were
// passing a string as a response id.
saver.add_button('Cancel', Gtk.ResponseType.CANCEL);
saver.add_button('Save', Gtk.ResponseType.OK);
// run() is handy, but be aware that it will block the current (only)
// thread until it returns, so I usually prefer to connect to the
// GtkDialog::response signal and use GtkWidget.show()
saver.connect('response', (dialog, response_id) => {
if (response_id === Gtk.ResponseType.OK) {
// outputs "-5"
print(response_id);
// NOTE: we're using #dialog instead of 'saver' in the callback to
// avoid a possible cyclic reference which could prevent the dialog
// from being garbage collected.
let filename = dialog.get_filename();
// here's where you do your stuff with the filename. You might consider
// wrapping this whole thing in a re-usable Promise. Then you could call
// `resolve(filename)` or maybe `resolve(null)` if the response_id
// was not Gtk.ResponseType.OK. You could then `await` the result to get
// the same functionality as run() but allow other code to execute while
// you wait for the user.
print(filename);
// Also note, you actually have to do the writing yourself, such as
// with a GFile. GtkFileChooserDialog is really just for getting a
// file path from the user
let file = Gio.File.new_for_path(filename);
file.replace_contents_bytes_async(
// of course you actually need bytes to write, since GActions
// have no way to return a value, unless you're passing all the
// data through as a parameter, it might not be the best option
new GLib.Bytes('file contents to write to disk'),
null,
false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
null,
// "shadowing" variable with the same name is another way
// to prevent cyclic references in callbacks.
(file, res) => {
try {
file.replace_contents_finish(res);
} catch (e) {
logError(e);
}
}
);
}
// destroy the dialog regardless of the response when we're done.
dialog.destroy();
});
// for bonus points, here's how you'd implement a simple preview widget ;)
saver.preview_widget = new Gtk.Image();
saver.preview_widget_active = false;
this.connect('update-preview', (dialog) => {
try {
// you'll have to import GdkPixbuf to use this
let pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
dialog.get_preview_filename(),
dialog.get_scale_factor() * 128,
-1
);
dialog.preview_widget.pixbuf = pixbuf;
dialog.preview_widget.visible = true;
dialog.preview_widget_active = true;
// if there's some kind of error or the file isn't an image
// we'll just hide the preview widget
} catch (e) {
dialog.preview_widget.visible = false;
dialog.preview_widget_active = false;
}
});
// this is how we'll show the dialog to the user
saver.show();
Actaully i am using "before submit" listener to do some validation for my selection box
I have reffered the following link:
https://helpx.adobe.com/experience-manager/using/classic_dialog_validation.html.
But "before submit" method calling only when i place ,
dialog listener in the dialog root level only.
how to place dialog listener in dialog root level(I checked in my project there is no dialog.xml file ,they using only java code to construct component dialog).
Can anyone please help me in this ?enter image description here
Dialog property construction code :
#DialogField(name ="./validateProgram",
fieldLabel = "Validate Program",
fieldDescription = "(synchronized)",
additionalProperties = {
#Property(renderIn = Property.RenderValue.TOUCH,
name = "validation",
value = "validation-program")
},
listeners = {
#Listener(name ="beforesubmit",
value = "function(dialog){" +
"return programValidation.beforeSubmit(dialog);"+
"}")
})
#Selection(
type ="select",
optionsProvider = " ",
dataSource = "/resourcetype/data")
public final String validateProgram;
Java Script code:
window.onload = function() {
programValidation.init();
};
var programValidation= programValidation|| (function($) {
function initialize() {
};
function validate() {
alert("inside validate method");
var res = true;
return res;
};
return {
beforeSubmit: validate,
init: initialize
}
})(jQuery);
You are using the cq component maven plugin this a very vital piece of information to get your question answered.
I have not used this plugin before, but in your case, I assume you are looking for the Listener annotation where you can set the name as beforesubmit and the value as function(){alert(1)}
you'll probably have to set the annotation on a local variable similar to how you would annotate a dialog field '#DialogField', find more docs in the plugin's usage page here: http://code.digitalatolson.com/cq-component-maven-plugin/usage.html
Hope this helps.
Thanks for your support. Found the following way to solve the issue .
I added ValidateFields method from within the 2 listeners (FIELD_LISTENER_LOAD_CONTENT and FIELD_LISTENER_SELECTION_CHANGED)
function ValidateFields(dialog) {
dialog.on("beforesubmit", function(e) {
if(<condtion failed>)
CQ.Ext.Msg.alert(CQ.I18n.getMessage("Error"), CQ.I18n.getMessage("<error message>"));
return false;
} else {
return true;
}
}, this);
}
I looked everywhere on the internet but I couldn't find any clear documentation or some examples to create my verySimplePlugin for videoJS 5 (Since it uses ES6).
I just want to add a button next to the big play button... Can someone help me?
Thanks...
PS: I'm using it in angularJS but I guess this can not a problem
This is how you can add download button to the end of control bar without any plugins or other complicated code:
var vjsButtonComponent = videojs.getComponent('Button');
videojs.registerComponent('DownloadButton', videojs.extend(vjsButtonComponent, {
constructor: function () {
vjsButtonComponent.apply(this, arguments);
},
handleClick: function () {
document.location = '/path/to/your/video.mp4'; //< there are many variants here so it is up to you how to get video url
},
buildCSSClass: function () {
return 'vjs-control vjs-download-button';
},
createControlTextEl: function (button) {
return $(button).html($('<span class="glyphicon glyphicon-download-alt"></span>').attr('title', 'Download'));
}
}));
videojs(
'player-id',
{fluid: true},
function () {
this.getChild('controlBar').addChild('DownloadButton', {});
}
);
I used 'glyphicon glyphicon-download-alt' icon and a title for it so it fits to the player control bar styling.
How it works:
We registering a new component called 'DownloadButton' that extends built-in 'Button' component of video.js lib
In constructor we're calling constructor of the 'Button' component (it is quite complicated for me to understand it 100% but it is similar as calling parent::__construct() in php)
buildCSSClass - set button classes ('vjs-control' is must have!)
createControlTextEl - adds content to the button (in this case - an icon and title for it)
handleClick - does something when user presses this button
After player was initialized we're adding 'DownloadButton' to 'controlBar'
Note: there also should be a way to place your button anywhere within 'controlBar' but I haven't figured out how because download button is ok in the end of the control bar
This is how I created a simple button plugin for videojs 5:
(function() {
var vsComponent = videojs.getComponent('Button');
// Create the button
videojs.SampleButton = videojs.extend(vsComponent, {
constructor: function() {
vsComponent.call(this, videojs, null);
}
});
// Set the text for the button
videojs.SampleButton.prototype.buttonText = 'Mute Icon';
// These are the defaults for this class.
videojs.SampleButton.prototype.options_ = {};
// videojs.Button uses this function to build the class name.
videojs.SampleButton.prototype.buildCSSClass = function() {
// Add our className to the returned className
return 'vjs-mute-button ' + vsComponent.prototype.buildCSSClass.call(this);
};
// videojs.Button already sets up the onclick event handler, we just need to overwrite the function
videojs.SampleButton.prototype.handleClick = function( e ) {
// Add specific click actions here.
console.log('clicked');
};
videojs.SampleButton.prototype.createEl = function(type, properties, attributes) {
return videojs.createEl('button', {}, {class: 'vjs-mute-btn'});
};
var pluginFn = function(options) {
var SampleButton = new videojs.SampleButton(this, options);
this.addChild(SampleButton);
return SampleButton;
};
videojs.plugin('sampleButton', pluginFn);
})();
You can use it this way:
var properties = { "plugins": { "muteBtn": {} } }
var player = videojs('really-cool-video', properties , function() { //do something cool here });
Or this way:
player.sampleButton()