Have a SAPUI5 application. Html view created by xml. JavaScript controllers. Some elements of this application, e.g. buttons are created somewhere dynamically within the controllers of the respective views. I.e. cannot use the id of an element to get it in the controller because the ids are dynamically created. Would like to get those dynamically created elements of the application to modify them, e.g. modify the buttons. What's the best way to trace a dynamically created element back to its code where it's created? E.g. how to trace a button back to it's origin in the JavaScript controller? It's a huge application and a view has multiple controller. I'm not just lazy.
The render function in the component's renderer class is what (eventually) creates the DOM element. You can inject a debugger statement into it like so:
let __buttonRender = sap.m.ButtonRenderer.render;
sap.m.ButtonRenderer.render = function() {
let control = arguments[1];
console.log('Creating button: ', control.sId);
debugger;
return __buttonRender.apply(this, arguments);
}
You can probably modify the control object based on the Id here. I'm not exactly sure what your end goal is here. The renderer is probably taking attributes and data from XML, so if you want to modify the buttons, I'd do it at the data source. The above could would allow you to hack the model before it reaches the DOM, but it's kinda nasty.
Related
I've tried to prepare data from an OData source to show it in a bar graph in my fiori app. For this, I setup the OData model in the manifest.json. A test with a list, simply using
items="{path : 'modelname>/dataset'}
works fine and shows the content.
To prepare data for a diagram (VizFrame), I used the onInit() function in the controller of the view (mvc:XMLView). The data preparation is similar to the one discussed in question.
At first I obtain the ODataModel:
var oODataModel = this.getOwnerComponent().getModel("modelname");
Next I do the binding:
var oBindings = oODataModel.bindList("/dataset");
Unfortunately, the oBindings().getContexts() array is always empty, and also oBindings.getLength() is zero. As a consequence, the VizFrame shows only "No Data".
May it be that the data model is not fully loaded during the onInit() function, or do I misunderstand the way to access data?
Thanks in advance
Update
I temporary solved the problem by using the automatically created bind from the view displaying the data as list. I grep the "dataReceived" event from the binding getView().byId("myList").getBindings("items") and do my calculation there. The model for the diagram (since it is used in a different view) is created in the Component.js, and registered in the Core sap.ui.getCore().setModel("graphModel").
I think this solution is dirty, because the graph data depends on the list data from a different view, which causes problems, e.g. when you use a growing list (because the data in the binding gets updated and a different range is selected from the odata model).
Any suggestions, how I can get the odata model entries without depending on a different list?
The following image outlines the lifecycle of your UI5 application.
Important are the steps which are highlighted with a red circle. Basically, in your onInit you don't have full access to your model via this.getView().getModel().
That's probably why you tried using this.getOwnerComponent().getModel(). This gives you access to the model, but it's not bound to the view yet so you don't get any contexts.
Similarly metadataLoaded() returns a Promise that is fullfilled a little too early: Right after the metadata has been loaded, which might be before any view binding has been done.
What I usually do is
use onBeforeRendering
This is the lifecycle hook that gets called right after onInit. The view and its models exist, but they are not yet shown to the user. Good possibility to do stuff with your model.
use onRouteMatched
This is not really a lifecycle hook but an event handler which can be bound to the router object of your app. Since you define the event handler in your onInit it will be called later (but not too late) and you can then do your desired stuff. This obviously works only if you've set up routing.
You'll have to wait until the models metadata has been loaded. Try this:
onInit: function() {
var oBindings;
var oODataModel = this.getComponent().getModel("modelname");
oODataModel.metadataLoaded().then(function() {
oBindings = oODataModel.bindList("/dataset");
}.bind(this));
},
May it be that the data model is not fully loaded during the onInit()
function, or do I misunderstand the way to access data?
You could test if your model is fully loaded by console log it before you do the list binding
console.log(oODataModel);
var oBindings = oODataModel.bindList("/dataset");
If your model contains no data, then that's the problem.
My basic misunderstanding was to force the use of the bindings. This seems to work only with UI elements, which organize the data handling. I switched to
oODataModel.read("/dataset", {success: function(oEvent) {
// do all my calculations on the oEvent.results array
// write result into graphModel
}
});
This whole calculation is in a function attached to the requestSent event of the graphModel, which is set as model for the VizFrame in the onBeforeRendering part of the view/controller.
I would like to use knockout.js in cross iframe binding. Existence of iframes is dictated by actual app structure I am working on.
This is the scenario (simplified):
Main window: Knockout.js included. window.top.DATA is a global container for data, ex. var DATA = { username: ko.observable('John') };
Module iframe window: Knockout.js also included. View wants do display data stored in window.top.DATA object, using code: <div data-bind="text: window.top.DATA.username></div>
What is the result?
DIV's innerHTML contains ko.observable().toString() contents instead of John.
The cause
Knockout.js is unable to recognize an observable created in parent frame while performing binding, because knockout checks if variable is observable with ko.hasPrototype by comparing references. Since prototypes are different between parent and child frame ko instances, it is impossible to bind values.
Solutions
The simplest solution would be writing something like: window.ko = window.top.ko || setupKO() on the top of script file. Unfortunately, in this case binding like with: window.someLocalObj is referencing to window.top instead of window - we are not able to access local variables and also local templates using template binding.
Another way to fix the problem is simply allow knockout to recognize observables as it should, what would allow observables to track dependency, bind values and just work well. Unfortunately I expect it might be difficult thing to achieve. What options do you see here?
Thank you for all your responses.
Edit
Knockout.js version: 3.2.0.
One solution is to use a single ko instance to handle main window and its frames elements at the same time. iframe elements are acessible through window.frames[frame_index].document:
var DATA = { username: ko.observable('John') };
ko.applyBindings(DATA);
ko.applyBindings(DATA, window.frames[0].document.body);
Working example: Plunker
I am using a listView control in my Windows8 JavaScript app and I am rendering the items on the page using some code which looks like:
listView.layout = new ui.GridLayout({
groupHeaderPosition: "top",
groupInfo: this.groupInfo,
itemInfo: this.computeItemSize
});
Now I want to do some action after these items are rendered on the page. Any idea where I can set a callback for this?
There are two ways of create a renderer: declare a WinJS.Binding.Template, or create a custom render function
WinJS.Binding.Template
To create a WinJS.Binding.Template, you use HTML markup to define a template for how an item is rendered. Within the template, you use data binding to associate properties of the HTML elements with fields in the data record
Custom render function
You can define custom render function for converting a data record into its HTML representation. The advantages of implementing a custom renderer are:
It can perform customized element recycling.
It can supply placeholder elements.
It can render items progressively.
It can make incremental requests for data, if needed.
The render function takes these parameters:
object renderItem(itemPromise, recycledElement)
itemPromise: a IItemPromise for the data for the item to render. With a synchronous datasource, the IItemPromise is usually complete, but with an async datasource, it will complete at some time in the future.
recycledElement : the DOM from a previous item that can be reused to display new content.
The render function must return either:
The root element of a DOM tree for the item.
An object that contains these properties:
element: the root element of a DOM tree for the item, or a promise that when completed will return the root element for the item.
renderComplete: a Promise that completes when the item is fully rendered.
The Entire information for the link is given below
http://msdn.microsoft.com/en-us/library/windows/apps/Hh781224.aspx#displaying_items_with_a_template_or_render_function
Hope it helps...Thanks
On your listview, listen for the loadingstatechanged event.
http://msdn.microsoft.com/en-us/library/windows/apps/hh700709.aspx
I have the following html that is bound to an object containing id and status. I want to translate status values into a specific color (hence the converter function convertStatus). I can see the converter work on the first binding, but if I change status in the binding list I do not see any UI update nor do I see convertStatus being subsequently called. My other issue is trying to bind the id property of the first span does not seem to work as expected (perhaps it is not possible to set this value via binding...)
HTML:
<span data-win-bind="id: id">person</span>
<span data-win-bind="textContent: status converter.convertStatus"></span>
Javascript (I have tried using to modify the status value):
// persons === WinJS.Binding.List
// updateStatus is a function that is called as a result of status changing in the system
function updateStatus(data) {
persons.forEach(function(value, index, array) {
if(value.id === data.id) {
value.status = data.status;
persons.notifyMutated(index);
}
}, this);
}
I have seen notifyMutated(index) work for values that are not using a converter.
Updating with github project
Public repo for sample (not-working) - this is a really basic app that has a listview with a set of default data and a function that is executed when the item is clicked. The function attempts to randomize one of the bound fields of the item and call notifyMutated(...) on the list to trigger a visual updated. Even with defining the WinJS.Binding.List({ binding: true }); I do not see updates unless I force it via notifyReload(), which produces a reload-flicker on the listview element.
To answer your two questions:
1) Why can't I set id through binding?
This is deliberately prevented. The WinJS binding system uses the ID to track the element that it's binding to (to avoid leaking DOM elements through dangling bindings). As such, it has to be able to control the id for bound templates.
2) Why isn't the converter firing more than once?
The Binding.List will tell the listview about changes in the contents of the list (items added, removed, or moved around) but it's the responsibility of the individual items to notify the listview about changes in their contents.
You need to have a data object that's bindable. There are a couple of options:
Call WinJS.Binding.as on the elements as you add them to the collection
Turn on binding mode on the Binding.List
The latter is probably easier. Basically, when you create your Binding.List, do this:
var list = new WinJS.Binding.List({binding: true});
That way the List will call binding.as on everything in the list, and things should start updating.
I've found that if I doing the following, I will see updates to the UI post-binding:
var list = new WinJS.Binding.List({binding: true});
var item = WinJS.Binding.as({
firstName: "Billy",
lastName: "Bob"
});
list.push(item);
Later in the application, you can change some values like so:
item.firstName = "Bobby";
item.lastName = "Joe";
...and you will see the changes in the UI
Here's a link on MSDN for more information:
MSDN - WinJS.Binding.as
Regarding setting the value of id.
I found that I was able to set the value of the name attribute, for a <button>.
I had been trying to set id, but that wouldn't work.
HTH
optimizeBindingReferences property
Determines whether or not binding should automatically set the ID of an element. This property should be set to true in apps that use Windows Library for JavaScript (WinJS) binding.
WinJS.Binding.optimizeBindingReferences = true;
source: http://msdn.microsoft.com/en-us/library/windows/apps/jj215606.aspx
I'm wrapping up a Javascript widget in a Wicket component. I want to let the JS side talk to the component. What I've got so far:
Component in question goes like
talker = new GridAjaxBehavior();
this.add(talker);
in constructor
and then, later on, puts something like
"var MyGridTalker = new talker(" + this.talker.getCallbackUrl() + ");";
into the JS.
where GridAjaxBehavior extends AbstractDefaultAjaxBehavior. I want GridAjaxBehavior to spit back some XML when the JS calls it.
Am I doing this the right way? What should GridAjaxBehaviour do to spit back the XML?
Thanks
Spit back some XML for what? Presumably to update the model or the view, yes?
The strength of Wicket is that you don't have to worry about the rendered HTML. In Model-View-Controller terms, you set up the Controller to correctly modify the Model, and Wicket takes care of the View.
The separation is not entirely clear: in fact you can show/hide view components, or change then, and that can be seen as altering the View.
But what you generally don't have to do is directly manage the browser or javascript. Wicket takes care of that, if you take care of making your changes in the Java code.
In Wicket, the Ajax will call a method on your AjaxBehavior with an AjaxRequestTarget target.
In that method (or in methods called from it), you do whatever you need to do, updating models or views, and then you add to the target any view component that that has changed. Wicket takes care of updating the browser.
Here's an example. It's taken from some code I did, but heavily altered just to make explication clearer. The idea is simple: "chained" dropdown choices, where the options in the child change when the select option in the parent changes, as in the series of [State] [County] [District].
(In the actual class, the Model change is passed to the child, which decides for itself if it has changed, and adds itself to the target if it has, then passes the target to its child. I've removed most of that to make a clearer example.)
Here's the ctor, which just adds to itself an anonymous subclass of an AjaxBehavior:
public AjaxChildNotifyingDropDownChoice(...code elided for clarity...) {
this.child = child;
// Ajax won't work without this:
setOutputMarkupId(true);
//
add( new OnChangeAjaxBehavior() {
#Override
public void onUpdate(final AjaxRequestTarget target) {
// tell child to update its list
// based on newly selected value
// when the Ajax is called,
// my owning component's model
// is already updated
// note we could just type getModel()
// I'm making explicit that we're calling it
// on the enclosing class
// (which a non-static inner class has a hidden ref to)
child.setNewModelBasedOnSelectionOf(
AjaxChildNotifyingDropDownChoice.this.getModel());
// now add the child to the target
// Wicket javascript will receive the new
// options and re-render the child dropdown
target.add(child);
}
});
}
We could also have hidden or un-hidden components, or added behaviors like CSS styles, or even swapped one Panel for another. As long as for each changed component we:
1) called setOutputMarkupId(true); so that the javascript can find it, and
2) added it to the AjaxRequestTarget
Note that different types (subclases) of Ajax Behavior have different callback functions, so be sure you're overriding the right one (add an #Override annotation so the compiler can complain if you got the name wrong).
But again, the basic wicket idea is that instead of sending raw data for the client to parse and act on, you update your model and view, and tell Wicket to re-render what you've changed, by adding the chnaged components to the target.
The only reason I can think of to send straight XML would to be to feed it to non-Wicket javascript. Let me know if that's your aim, and I completely missed the point. ;)
I don't really know what Wicket is or what it does, but there is a minor bug in your code (as it appears).
This:
"var MyGridTalker = new talker(" + this.talker.getCallbackUrl();
You seem to be missing your end parens:
"var MyGridTalker = new talker(" + this.talker.getCallbackUrl() + ")";
Anyway, not a big deal, but didn't know if it was intentional.