I have this ScrollViewer, which I add elements to programically:
<ScrollViewer VerticalSnapPointsAlignment="Near"
VerticalSnapPointsType="Mandatory"
VerticalScrollMode="Enabled"
VerticalScrollBarVisibility="Hidden"
ZoomMode="Disabled"
Width="400"
Height="400"
x:Name="MainFeatureScrollViewer"
ViewChanging="ScrollViewer_ViewChanging">
<StackPanel x:Name="MainFeatureStackPanel" />
</ScrollViewer>
When I call ChangeView like below it returns false and nothing happens. Why wouldn't this be working?
bool result = this.MainFeatureScrollViewer.ChangeView(null, 400, null, true);
Just had the same problem - the base problem here was that my code did call ChangeView() twice in the same cycle - once on a non-GUI thread (which returned true, but of course did not really scroll to the desired position as it was not on the GUI thread), and later in a dispatcher method on the GUI thread (which returned false, because the scrollViewer apparently saw that it already had gotten a new scroll position which it was not showing yet).
Once I removed the ChangeView() calls that were not done on the GUI thread it worked fine. It would help if the documentation of ScrollViewer would explain in which cases it will return false, though ...
I believe this could be a factor of some of your property settings. Try stripping your ScrollViewer to it's minimum and also change your offset. You are telling it to scroll to 400 when your height is 400 so it could be a factor of it not being able to scroll to where you want (try setting it to (null, 200, null, null) to see if that works.
<ScrollViewer VerticalSnapPointsAlignment="Near"
VerticalScrollBarVisibility="Hidden"
ZoomMode="Disabled"
Width="400"
Height="400"
x:Name="MainFeatureScrollViewer"
ViewChanging="ScrollViewer_ViewChanging">
<StackPanel x:Name="MainFeatureStackPanel" />
</ScrollViewer>
bool result = this.MainFeatureScrollViewer.ChangeView(null, 200, null, true);
Related
In the process of experimenting with scaling/panning an inline SVG image by applying a matrix transform I have discovered a rather peculiar thing. After loading the image I am attaching a touchstart event listener to some of the elements in the SVG image and the event fires right away when the object is touched. However, after applying a transform
document.getElementById('mysvg').setAttribute('transform''matrix(a b c d e)')
which has the effect of scaling and/or translating the entire SVG image touching the same object no longer triggered the expected touch event. After some experiment I found that the event could still be triggered by the touch location on screen had no bearing to the actual new placement of the object on the screen. I then proceeded to first removeEventListener followed by addEventListener for the object after issuing the matrix transform and lo & behold the touch event handling was back to normal.
Quite apart from the fact that I would like to avoid the rather expensive operations of removing & then reassigning the same event listener after each pan/zoom I would like to understand just why this is happening. It is like the browser is locating the pixel location of the object at the addEventListener stage and then holds on to that somewhere in its memory blissfully ignorant of any object displacements that might have occurred later.
Can anyone here tell me what is going on here and how I can go about retaining the utility of the touch event after pan & zoom in a more efficient manner?
I've set up a similar issue:
There is a <circle> element, with a transform attribute inside an <svg>.
The 'touchstart' event fires only at the first tap on the <circle>. After that it doesn't trigger the 'touchstart' event anymore.
I have found a strange workaround: Add a 'touchstart' eventListener to the <svg> element with a noop handler:
document.querySelector('svg').addEventListener('touchstart', () => {});
After this the <circle> triggers the 'touchstart' events perfectly.
You can test it with the folllowing snipet:
let debugLines = [];
let lines = 0;
function writeDebug(...t) {
let d = document.getElementById('debug');
debugLines.unshift(`${lines++}: ${t.join(' ')}`);
debugLines.splice(5);
d.innerHTML = debugLines.join('<br />');
}
document.querySelectorAll('circle')[0].addEventListener('touchstart', writeDebug);
/* remove comment from the line below to test workaround */
// document.querySelector('svg').addEventListener('touchstart', () => {});
<!doctype html>
<html>
<head>
<style>
svg { background: #f0f0f0; width: 200px; float: left; }
</style>
</head>
<body>
<div id="wrap">
<svg viewBox="-50, -50, 100, 100" class="b-circular-slider-svg">
<circle cx="0" cy="0" r="8"
stroke="#ccc" fill="#fafafa"
transform="translate(0, -10)"></circle>
</svg>
</div>
<strong>debug:</strong>
<div id="debug"></div>
</body>
</html>
I'm using the react-virtualized ScrollSync component to sync the scrolling of a couple fixed headers — very similar to the example in the docs.
My question: is it possible to provide an initial scrollTop value to ScrollSync (or its children)? I realize that the better way to do this would be through use of scrollToRow on the Grid component that controls the scroll position — for what it's worth, I am using scrollToColumn for this purpose. But because, vertically, I'm only rendering one very tall cell, scrollToRow doesn't provide the fidelity needed.
I do realize that this is a slightly bastardized use of a grid component, but it all works quite nicely as a horizontal, infinitely loading scroller, while allowing me to re-use an existing component. If I could just set an initial scrollTop, I'd be golden.
Thanks.
Unfortunately this is not currently supported without a bit of a hack.
First, the reason for the hack: Scroll offsets flow in one direction with ScrollSync (main Grid to synchronized Grids). This means that even if ScrollSync accepted default left/top offsets as props- they would get overridden by the first render of the main Grid. I think this is probably the right thing to do to avoid ugliness inside of react-virtualized.
However you could work around it in application code like this if you wanted to:
class YourComponent extends Component {
render () {
// These are arbitrary
const defaultScrollLeft = 300
const defaultScrollTop = 500
return (
<ScrollSync>
{({ clientHeight, clientWidth, onScroll, scrollHeight, scrollLeft, scrollTop, scrollWidth }) => {
if (!this._initialized) {
scrollLeft = defaultScrollLeft
scrollTop = defaultScrollTop
this._initialized = true
}
return (
<div>
<Grid
{...otherSyncedGridProps}
scrollLeft={scrollLeft}
/>
<Grid
{...otherMainGridProps}
onScroll={onScroll}
scrollLeft={defaultScrollLeft}
scrollTop={defaultScrollTop}
/>
</div>
)
}}
</ScrollSync>
)
}
}
I have a PXNumberEdit field which, on enter, adds a product to a grid on a customized SO301000 page. The insert works and the field
is emptied after the product is added. However, I would like to return the focus to that field. There doesn't seem to be
a SetFocus method for the field.
I have tried using the SO301000.cs code behind to set the focus, by adding a function for onValueChanged
to save the object as session variable and on pageload to set the focus on the saved object. This causes
the page to never finish loading.
I have also tried to use jquery in various ways but that hasn't worked either. Is there a way to do this?
The Hack
There is no SetFocus method in the framework like there is for SetEnabled/SetDisplayName/SetVisibility because most events are raised on focus changes and the framework ensures that the focus is not lost on every record updates. To set it up manually, you will then need to wait that the callback is completed before setting the focus.
To do so you will need to add a Javascript delegate to the list of event handlers to be called once the callback is over. The following code will set the focus on Customer Reference Nbr. every time Customer ID is changed (in SO301000):
<script type="text/javascript">
function FormView_Load() {
px_callback.addHandler(setFocusOnCustRef);
return;
}
var setFocus = false;
function CustomerID_ValueChanged() {
setFocus = true;
return;
}
function setFocusOnCustRef(context, error) {
if (setFocus === true)
{
setFocus = false;
var refNbr = px_alls["edCustomerRefNbr"];
refNbr.focus();
}
return;
}
</script>
<px:PXFormView ID="form" runat="server" DataSourceID="ds" Style="z-index: 100" Width="100%" DataMember="Document" Caption="Order Summary"
NoteIndicator="True" FilesIndicator="True" LinkIndicator="True" EmailingGraph="PX.Objects.CR.CREmailActivityMaint,PX.Objects"
ActivityIndicator="True" ActivityField="NoteActivity" DefaultControlID="edOrderType" NotifyIndicator="True"
TabIndex="14900" ClientEvents-Initialize="FormView_Load">
...
<px:PXSegmentMask CommitChanges="True" ID="edCustomerID" runat="server" DataField="CustomerID" AllowAddNew="True"
AllowEdit="True" DataSourceID="ds" ClientEvents-ValueChanged="CustomerID_ValueChanged"/>
...
</px:PXFormView>
Note that I added ClientEvents-Initialize="FormView_Load" on the PXFormView and ClientEvents-ValueChanged="CustomerID_ValueChanged" on the CustomerID's PXSegmentedMask.
As you can see this is a hack... When the setFocusOnCustRef is raised we have just refreshed the record (RowSelected) and we don't know what was changed prior to that (what field has been changed? was the change canceled?). The context that is passed to the delegate is only related to re-updating the records. To get a better understanding of what events are raised and in which order, please refer to the Update Scenario Event Model:
Thoughts and Tips
I don't know much of your implementation but I would like to point out that your needs look very similar to the function Add Stock Item that opens a SmartPanel with the buttons Add/Add & Close/Cancel. If the callback is raised from a button you will have meaningful information in your context and won't need to add a Javascript event on ValueChanged.
When you Save/Cancel. The focus will return to your first form element in the tab order (if successful).
You can set the Tab Order directly in the PXUIFieldAttribute :
PXUIField(DisplayName = "Asset ID", Visibility = PXUIVisibility.SelectorVisible, TabOrder=1)]
We have a p:inputText element that should display an overlay panel for various options. (Its a global search, so you can tick categories to search in)
Users usually click in the textbox, start typing and THEN look at the screen again
The Problem is: As soon as the overlay panel is shown, the textbox looses its focus.
<p:inputText id="searchItem"></p:inputText>
<p:overlayPanel id="gsOverlay" for="searchItem" my="left top"
at="left bottom" dynamic="true"
onShow="resizeGSOverlay();">
So i tried to fix this, by immediately focusing back on the "search" inputtext using
<p:overlayPanel id="gsOverlay" for="searchItem" my="left top"
at="left bottom" dynamic="true"
onShow="PrimeFaces.focus('globalSearchForm:searchItem'); resizeGSOverlay();">
However, there is a split second, where the inputfield lost focus, leading to searches missing the first charater.
Can i display the overlay panel, without having the inputtext loosing its focus? (Each component inside the overlay panel will focus back after clicking, that's fast enough - just the initial focus-back is to slow)
Just found the "holy grail":
Default for the overlayPanel is:
PrimeFaces.widget.OverlayPanel.prototype.applyFocus = function(){
this.jq.find(':not(:submit):not(:button):input:visible:enabled:first').focus();
}
so, I just put the following javascript AFTER including the primefaces resources, which will then override the default implementation:
<script type="text/javascript">
PrimeFaces.widget.OverlayPanel.prototype.applyFocus = function() {
if (this.id == "globalSearchForm:gsOverlay")
return;
else
this.jq.find(':not(:submit):not(:button):input:visible:enabled:first').focus();
}
</script>
So - No focus for any element within the overlay panel in question once it becomes visible. Works like a charm.
Update:
using the Proxy Pattern (http://api.jquery.com/Types/#Proxy_Pattern) seems a more reliable solution, as it avoids the need to duplicate the content of the original implementation, which might be different in one of the next Primefaces releases:
<script type="text/javascript">
(function() {
var proxied = PrimeFaces.widget.OverlayPanel.prototype.applyFocus;
PrimeFaces.widget.OverlayPanel.prototype.applyFocus = function(){
if (this.id == "globalSearchForm:gsOverlay")
return;
return proxied.apply(this, arguments);
};
})();
</script>
In JavaScript we have document.elementfrompoint to get an element based on coordinates.
Is there any thing like that in Openlaszlo to get a view based on coordinate?
There is no direct support for that functionality in OpenLaszlo, but for ActionScript 3 based runtimes you can utilize the flash.display.DisplayObjectContainer#getObjectsUnderPoint() method. In the DHTML runtime, you can use the document.elementFromPoint(x, y), and based on Quirksmode that should be supported by all modern browsers.
Here is an example program implementing an canvas.elementFromPoint() method:
<canvas debug="true">
<passthrough when="$as3">
import flash.geom.Point;
</passthrough>
<view id="background" width="100%" height="100%" bgcolor="#eeeeee" clickable="true"/>
<view id="red" x="200" y="100" width="200" height="200" bgcolor="#ff0000" opacity="0.3" clickable="true" />
<view id="green" x="150" y="200" width="200" height="200" bgcolor="#00ff00" opacity="0.3" clickable="true"/>
<view id="blue" x="250" y="200" width="200" height="200" bgcolor="#0000ff" opacity="0.3" clickable="true"/>
<handler name="onclick" reference="lz.GlobalMouse">
canvas.elementFromPoint();
</handler>
<method name="elementFromPoint"><![CDATA[
var mouseX = canvas.getMouse('x'),
mouseY = canvas.getMouse('y'),
objects = null, // array of objects at mouse pointer in SWF runtime
element = null; // The element we are looking for
Debug.info( 'mouse position: x=' + mouseX + ' / mouseY=' + mouseY );
if ($as3) {
// in SWF runtime, use the DisplayObjectContainer.getObjectsUnderPoint() method
objects = canvas.getDisplayObject().getObjectsUnderPoint(new Point(mouseX, mouseY));
element = objects[objects.length-1].owner;
} else {
// in DHTML, we can use elementFromPoint, and need to retrieve the owner view of the div
element = document.elementFromPoint(mouseX, mouseY).owner.owner;
}
Debug.info('View under mousecursor:', element);
return element;
]]></method>
</canvas>
There are 4 views, one background view scaled to 100% x 100%. And three color views: red, green and blue - with the blue one being the top one. When clicking on the view, the correct view object is returned.
The code has been tested in the DHTML runtime with Chrome 22.0, Firefox 16.0.1, and Opera 12.02. Flash should work in every browser, I haven't tested with IE.
I don't think so.
You will have to build your own custom array or observer object, collect all views and then loop through all items and do a check if the coordinates are inside the bounding box of the view.
In Flash there is also something like "hitTest", that might be similar to JavaScript's "document.elementfrompoint" to get the exact pixel matching, in case the bounding box is not enough for you.
Sebastian