How to properly use the new asynchronous fragment loading - sapui5

OpenUI5 newbie here. I am trying to use OpenUI5 fragments, much like shown in the Walkthrough example, Step 16, in the documentation. I have a problem seeing how this can work properly.
The code below is a copy and paste from the Walkthrough example, Step 16, in the documentation:
onOpenDialog : function () {
var oView = this.getView();
// create dialog lazily
if (!this.byId("helloDialog")) {
// load asynchronous XML fragment
Fragment.load({
id: oView.getId(),
name: "sap.ui.demo.walkthrough.view.HelloDialog"
}).then(function (oDialog) {
// connect dialog to the root view of this component
oView.addDependent(oDialog);
oDialog.open();
});
} else {
this.byId("helloDialog").open();
}
}
Since the HelloDialog fragment is loaded asynchronously, it is clear that the onOpenDialog function may return control to the user before the dialog has been created and opened. It is in the nature of asynchronous calls that we must not make any assumptions about how long it will take until the asynchronous code is executed. Anything is possible. Therefore, we must allow for the possibility that the user has control over the web page for any amount of time before the dialog shows up, say, several seconds. What is the user going to do? They're going to click the button for opening the dialog again, and again, and again, thereby creating the dialog multiple times, subverting the intended logic of the code. To be honest, I am not sure if I would be comfortable having that kind of thing in my code. How should I deal with this?

in general, you're right, the dialog could take time to load but usually, the process takes a fraction of time and the user should not see any "loading" time.
This is possible only if your dialog load data asynchronously too.
If you really want to be sure to give graphical feedback to the user you could do like this:
onOpenDialog : function () {
var oView = this.getView();
// create dialog lazily
if (!this.byId("helloDialog")) {]
// set the view to busy state
oView.setBusy(true);
// load asynchronous XML fragment
Fragment.load({
id: oView.getId(),
name: "sap.ui.demo.walkthrough.view.HelloDialog"
}).then(function (oDialog) {
// remove the busy state
oView.setBusy(false);
// connect dialog to the root view of this component (models, lifecycle)
oView.addDependent(oDialog);
oDialog.open();
});
} else {
this.byId("helloDialog").open();
}
}

Related

How to wait the page to test is loaded in non angular site?

I've tried this:
browser.wait(function () {
return browser.executeScript('return document.readyState==="complete" &&' +
' jQuery !== undefined && jQuery.active==0;').then(function (text) {
return text === true;
});
}, 30000);
If jQuery.active==0 then page is completely loaded. This should work for sites with JQuery and non angular pages.
However, I have many problems of instability to test for non angular sites.
How to fix this?
By default protractor waits until the page is loaded completely. If you are facing any error then it is because protractor is waiting for the default time to be completed, that you have specified in your conf.js file to wait until page loads. Change the value to wait a for longer time if you think your app is slow -
// How long to wait for a page to load.
getPageTimeout: 10000, //Increase this time to whatever you think is better
You can also increase the defaultTimeoutInterval to make protractor wait a little longer before the test fails -
jasmineNodeOpts: {
// Default time to wait in ms before a test fails.
defaultTimeoutInterval: 30000
},
If you want to wait for any particular element, then you can do so by using wait() function. Probably waiting for last element to load is the best way to test it. Here's how -
var EC = protractor.ExpectedConditions;
var lastElement = element(LOCATOR_OF_LAST_ELEMENT);
browser.wait(EC.visibilityOf(lastElement), 10000).then(function(){ //Alternatively change the visibilityOf to presenceOf to check for the element's presence only
//Perform operation on the last element
});
Hope it helps.
I use ExpectedConditions to wait for, and verify page loads. I walk through it a bit on my site, and example code on GitHub. Here's the gist...
Base Page: (gets extended by all page objects)
// wait for & verify correct page is loaded
this.at = function() {
var that = this;
return browser.wait(function() {
// call the page's pageLoaded method
return that.pageLoaded();
}, 5000);
};
// navigate to a page
this.to = function() {
browser.get(this.url, 5000);
// wait and verify we're on the expected page
return this.at();
};
...
Page Object:
var QsHomePage = function() {
this.url = 'http://qualityshepherd.com';
// pageLoaded uses Expected Conditions `and()`, that allows us to use
// any number of functions to wait for, and test we're on a given page
this.pageLoaded = this.and(
this.hasText($('h1.site-title'), 'Quality Shepherd')
...
};
QsHomePage.prototype = basePage; // extend basePage
module.exports = new QsHomePage();
The page object may contain a url (if direct access is possible), and a pageLoaded property that returns the ExepectedCondition function that we use to prove the page is loaded (and the right page).
Usage:
describe('Quality Shepherd blog', function() {
beforeEach(function() {
// go to page
qsHomePage.to();
});
it('home link should navigate home', function() {
qsHomePage.homeLink.click();
// wait and verify we're on expected page
expect(qsHomePage.at()).toBe(true);
});
});
Calling at() calls the ExpectedCondidion (which can be be an and() or an or(), etc...).
Hope this helps...

Best practices when editing form with emberjs

Is there any good solutions when editing a form to save the model only if the user clicked on the save button and retrieve the old datas if the user canceled the action ?
I've seen some solutions like duplicating the object that is data-binded with each form fields and set the the initial object with the duplicated one when it is saved.
If you could give answers without using ember data could be great.
I understand you would prefer a solution that doesn't use ember-data, but I would argue that using ember-data is best practices. Here is a solution using ember-data because I imagine a lot of people may come across this question...
If you set up your route as follows, it will do exactly that.
App.CommentEditRoute = Em.Route.extend({
model: function(params) {
return this.store.find('comment', params.comment_id);
},
actions: {
willTransition: function(transition) {
var model = this.get('controller.content');
if (model.get('isDirty')) {
model.rollback();
}
}
},
});
If you call this.get('content').save() in the controller (because the user clicked the save button) it will persist the changes through the adapter and isDirty will be set to false. Thus, the model will not rollback. Otherwise, if you did not call this.get('content').save() in the controller, the isDirty property will be true and the unsaved changes will be discarded. See the DS.Model docs for more info.
willTransition is an event automatically called when the route is about to change - you don't have to call it directly.
Your controller might look like this:
App.CommentEditController = Em.ObjectController.extend({
save: function() {
var _this = this;
_this.get('content').save().then(function() {
// Success
_this.transitionToRoute('comments');
}, function() {
// Failure
Em.assert('Uh oh!');
});
},
cancel: function() {
this.transitionToRoute('comments');
},
});
Also, be sure to utilize the default HTML form submission using a proper HTML button or input for submission so you can capture the submission event in your view as follows:
App.CommentEditView = Em.View.extend({
submit: function() {
this.get('controller').save();
},
});

Using jquery-ias with async-loaded content

I've somewhat successfully integrated the jQuery Infinite Ajax Scroll plugin into my development site - it is used twice, first on the thread list on the left, second when you load up an individual topic - but I'm having trouble with the second ias instance here.
Basically the content of a topic is loaded via $.get and then rendered into the page, and then I trigger setupThreadDetailDownwardScroll() in JS which creates an instance of ias:
var iasDetail = jQuery.ias({
container: "#reply-holder",
item: ".post",
pagination: ".threaddetail-pagination",
next: ".load-next-inner-link a",
delay: 2000,
});
if (iasDetail.extension) {
iasDetail.extension(new IASPagingExtension());
iasDetail.extension(new IASTriggerExtension({
text: 'More Replies',
html: '<div class="scroll-pager"><span>{text}</span></div>',
offset: 10,
}));
iasDetail.extension(new IASNoneLeftExtension({html: '<div class="scroll-message"><span>No more replies</span></div>'}));
iasDetail.extension(new IASHistoryExtension({
prev: '.load-previous-inner-link a',
}));
}
iasDetail.on('load', function() {
$('#reply-holder').append(scrollLoading);
})
iasDetail.on('rendered', function() {
$('.scroll-loading').remove();
iasDetail.unbind();
})
But the problem is that this only works with whatever the first topic you load is - you'll get working pagination in the first thread, but then it'll fallback to anchor links when you open the next thread.
So I figured that I needed to rebind ias once this new content is inserted, and this is why I have added the unbind() call in rendered, and then I re-call setupThreadDetailDownwardScroll() whenever another thread is loaded. This doesn't work either though.
Is there a correct procedure here?
You are using jQuery.ias(...) which binds to the scroll event of $(window). In your case you probably want to bind to an overflow div. Therefor you should specify the scrollContainer like this:
$('#scrollContainer').ias(...)
Edit:
Based on you comment I took another look at it and might have found an answer. When you call jQuery.ias({...}); IAS gets setup and waits for $(document).ready to initialize. You say you want to initialize IAS in your setupThreadDetailDownwardScroll function. You can try to initialize IAS yourself with the following code
iasDetail.initialize();

durandal 2.0 custom dialog master-detail

After upgrading to durandal 2.0, I found I needed to convert my master list page to use the showDialog function instead of showModal.
Previously my master model looked like this:
define(['durandal/amd/require', 'durandal/app', 'durandal/viewLocator', 'durandal/system', 'durandal/plugins/router', 'durandal/lib/tableModel', 'viewmodels/product'], function (require, app, viewLocator, system, router, table, product) {
var tm = {
tableModel: new tableModel(),
createProduct: function (data) {
app.showModal(product, data);
}
}
//...
return tm;
}
Then in my product detail view page I could close the modal easily like so
data-bind="click: $root.modal.close">Close
Now in Durandal 2.0 it is much harder to get right.
The code in the masterpage is now
define(['durandal/app', 'durandal/viewLocator', 'durandal/system', 'plugins/router', 'lib/xhrs', 'lib/tableModel'], function (app, viewLocator, system, router, xhrs, table, product) {
var tm = {
tableModel: new tableModel(),
createProduct: function (data) {
app.showDialog(product, data);
}
}
//...
return tm; }
But the way to access the close function is annoying:
Firstly I have to require the 'plugins/dialog' into the product detail viewmodel; which I would prefer not to do as I don't think the detail viewmodel needs to know that it is a dialog, only the master list viewmodel needs to know that.
then in the compositionComplete event of the product detail view model I assign:
prodedit.close = function () {
dialog.close(prodedit);
}
(prodedit is the returned as the product detail vm)
In this way the product detail dialog can be closed using this:
data-bind="click: $root.close"
OK NOW HERE IS MY ISSUE:
This will work to popup the dialog once or twice, but then fails from then onward without an error. The only thing I can see is that dialogActivator.activateItem hits its fail line: dfd.resolve(false);
Interestingly if I do pause long enough on breakpoints the issue does not occur. But once it occurs once, it never works again to open the dialog.
Is there a better way to do this?
Thankyou.

Customize or change default message boxes issued by workflow dialogs on errors in Alfresco

Presently, a messagebox appears with the failing class name:
Is it possible to override the default behavior in Alfresco? Could we use forms service to present a different message ?
Additional to zladuric answer,
you can use failureCallback method to show message what you want.
But it is difficult to search failureCallback method of workflow forms for a new one because workflow forms such as "Start Workflow", "Task Edit", "Task Detail" are used form engine.
For example, in "Start Workflow" form, you can add our own successCallBack and failureCallBack by writing onBeforeFormRuntimeInit event handler in start-workflow.js like this.
onBeforeFormRuntimeInit: function StartWorkflow_onBeforeFormRuntimeInit(layer, args)
{
var startWorkflowForm = Dom.get(this.generateId + "-form");
Event.addListener(startWorkflowForm, "submit", this._submitInvoked, this);
args[1].runtime.setAJAXSubmit(true,
{
successCallback:
{
fn: this.onFormSubmitSuccess,
scope: this
},
failureCallback:
{
fn: this.onFormSubmitFailure,
scope: this
}
});
}
onFormSubmitSuccess: function StartWorkflow_onFormSubmitSuccess(response)
{
this.navigateForward(true);
// Show your success message or do something.
}
onFormSubmitFailure: function StartWorkflow_onFormSubmitFailure(response)
{
var msgTitle = this.msg(this.options.failureMessageKey);
var msgBody = this.msg(this.options.failureMessageKey);
// example of showing processing response message
// you can write your own logic
if (response.json && response.json.message)
{
if(response.json.message.indexOf("ConcurrencyFailureException") != -1)
{
msgTitle = this.msg("message.concurrencyFailure");
msgBody = this.msg("message.startedAgain");
}
else
msgBody = response.json.message;
}
Alfresco.util.PopupManager.displayPrompt(
{
title: msgTitle,
text: msgBody
});
}
Since Alfresco.component.StartWorkflow(in start-workflow.js) extends Alfresco.component.ShareFormManager(in alfresco.js). You can override onBeforeFormRuntimeInit event in start-workflow.js. I hope this your help you.
I'm not looking at the code right now, but this looks like a regular YUI dialog. So it's fired by YUI. So this YUI is client side, probably in My-tasks dashlet or my tasks page.
Furthermore, the error message looks like it is a status.message from the failed backend message/service.
You could probably locate that client-side javascript file, find the method that starts the task and see what its' failureCallback handler is. Then edit that failureCallback method and make it show something different then the response.status.message or whatever it is. Perhaps something like this.msg("message.my-custom-error-message"); which you then customize on your own.
Modifying YUI dialog scripts will might affect the other functionalities as well.
If we customize start-workflow. js, its only going to be achieved in start workflow form.
So as generic solution, below is the suggestion.
When alfresco is rendering the workflow form , it is rendering the transition button using the activiti-transition.js file.Basically this buttons are doing nothing more but submitting the workflow form.
So the best way would be , customizing this activiti-transition.ftl and activiti-transition.js file , to make an ajax call and handle the response as we want.
I just had a look on full flow of how this front end error is shown.
activiti-transition is submiting the workflow form.
Using a function named as submitForm which resides inside alfresco.js, it is invoking an submit event of form
Inside the forms-runtime.js file there is one function named as _submitInvoked(handles the submit event of form), which is responsible for making an ajax call and submitting the workflow form.If there is error while submitting , it will display the error which is from backend.