Remember scroll position in QML element - javascript

I have some qml that acts as the output from the application (kind of like a read-only console). However, it's annoying to use because as it prints information it scrolls back to the top.
For example, lets say I print a line to my TextArea every second, after a minute or so, I'll have enough lines that I now have a scroll bar. I scroll to the bottom, a second later, a line of text is printed causing it to scroll back to the top.
The desired functionality I would like is to automatically scroll to the bottom (much like how a console is when it prints stuff out) unless the user overrides this by scrolling up, then the text should stay put. I hope that made sense, here is some code:
ScrollArea {
id: helpTextScrollArea
anchors.left: parent.left
anchors.right: parent.right
anchors.top: myButton.bottom
anchors.topMargin: 5
anchors.bottom: parent.bottom
visible: false
horizontalScrollBar.visible: false
onVisibleChanged: {
helpText.visible = visible
}
HelpTextArea {
id: helpText
width: parent.parent.width - helpTextScrollArea.verticalScrollBar.width
text: "Oops, no documentation."
onVisibleChanged: {
if(helpTextScrollArea.visible != visible) {
helpTextScrollArea.visible = visible
}
}
}
}
Flickable
{
id: flick
anchors.left: parent.left
anchors.right: parent.right
anchors.top: runStopButton.bottom
anchors.bottom: parent.bottom
anchors.topMargin: 5
TextArea{
id: messageArea
anchors.fill: parent
focus: true
readOnly: true
visible: !helpTextScrollArea.visible
wrapMode: TextEdit.Wrap
function addText(newText) {
text += newText + "\n"
}
}
}
Note: I don't think my Flickable does anything, it was part of my experimenting to fix the problem. Also, I use the function addText to print to the text area. I hardly know anything about qml and most of this code was written by someone else and I'm trying to work with it. Thank you!

You can bind contentY property of Flickable to an expression that will calculate proper position.
Flickable {
id: flick
states: State {
name: "autoscroll"
PropertyChanges {
target: flick
contentY: messageArea.height - height
}
}
onMovementEnded: {
if (contentY === messageArea.height - height) {
state = "autoscroll"
}
else {
state = "" // default state
}
}
// ...
}

Flickable has got 4 readonly properties in called visibleArea.* (you can read their meaning in the QML documentation) :
visibleArea.xPosition : xPosition = contentX / contentWidth
visibleArea.yPosition : yPosition = contentY / contentHeight
visibleArea.widthRatio : widthRatio = width / contentWidth
visibleArea.heightRatio : heightRatio = height / contentHeight

If you are using GridView instead of Flickable, you can use these methods.
positionViewAtBeginning()
positionViewAtEnd()
positionViewAtIndex(int index, PositionMode mode)
I found these to be really helpful, as when the line of text is being added, just call the positionViewAtEnd() method inside of the GridView.
Hope it helps

Related

Mithril: render one DOM element bases on another

I have a fixed height div (body) containing two children, the header and the content. The header's height is changing on click of a button, and the content's height should be auto adjusting to fill the rest of the body. Now the problem is, the new header's height is calculated after the header and content divs got rendered, so the content div's height won't be updated upon the button click. Here's the shortened code:
return m('.body', {
style: {
height: '312px'
}
}, [
m('.header', /* header contents */),
m('.content', {
style: {
height: (312 - this._viewModel._headerHeight()) + 'px'
}
}, /* some contents */)
])
The headerHeight function calculates the header's height and applies changes to it. However the new height is calculated after it's rendered thus won't be applied immediately to the calculation of content's height - there's always a lag.
Any idea to fix it?
This is a regular problem when dealing with dynamic DOM layouts in which some writable DOM properties are derived from other readable DOM properties. This is especially tough to reason about in declarative virtual DOM idioms like Mithril because they're based on the premise that every view function should be self-complete snapshots of UI state — which in this case isn't possible.
You have 3 options: you can either break out of the virtual DOM idiom to achieve this functionality by directly manipulating the DOM outside of Mithril's view, or you can model your component to operate on a '2 pass draw', whereby each potential change to the header element results in 1 draw to update the header and a second draw to update the content accordingly. Alternatively, you might be able to get away with a pure CSS solution.
Because you only need to update one property, you're almost certainly better off going for the first option. By using the config function, you can write custom functionality that executes after the view on every draw.
return m('.body', {
style: {
height: '312px'
},
config : function( el ){
el.lastChild.style.height = ( 312 - el.firstChild.offsetHeight ) + 'px'
}
}, [
m('.header', /* header contents */),
m('.content', /* some contents */)
])
The second option is more idiomatic in terms of virtual DOM philosophy because it avoids direct DOM manipulation and keeps all stateful data in a model read and applied by the view. This approach becomes more useful when you have a multitude of dynamic DOM-related properties, because you can inspect the entire view model as the view is rendered — but it's also a lot more complicated and inefficient, especially for your scenario:
controller : function(){
this.headerHeight = 0
},
view : function( ctrl ){
return m('.body', {
style: {
height: '312px'
}
}, [
m('.header', {
config : function( el ){
if( el.offsetHeight != ctrl.headerHeight ){
ctrl.headerHeight = el.offsetHeight
window.requestAnimationFrame( m.redraw )
}
}, /* header contents */),
m('.content', {
style : {
height : ( 312 - ctrl.headerHeight ) + 'px'
}
}, /* some contents */)
])
}
A third option — depending on which browsers you need to support — would be to use the CSS flexbox module.
return m('.body', {
style: {
height: '312px',
display: 'flex',
flexDirection: 'column'
}
}, [
m('.header', {
style : {
flexGrow: 1,
flexShrink: 0
}
}, /* header contents */),
m('.content', {
style : {
flexGrow: 0,
flexShrink: 1
}
}, /* some contents */)
])
This way, you can simply state that the container is a flexbox, that the header should grow to fit its content and never shrink, and that the contents should shrink but never grow.

Function to animate progress bar based on another function

Given a function with parameters in an array, where the first number is the delay time, and the width property is how much to fill the bar by:
var barFill = new AnimationSequence(bar, [
[100, { width: '10%' }],
[200, { width: '20%' }],
[200, { width: '50%' }],
[200, { width: '80%' }],
[300, { width: '90%' }],
[100, { width: '100%' }]
]);
barFill.animate();
I'm trying to write a function to take those two parameters and animate the filling of the progress bar. So far I have this function:
function AnimationSequence() {
var elem = document.getElementById("myBar");
var width = 10;
var id = setInterval(frame, 10);
function frame() {
if (width >= 100) {
clearInterval(id);
} else {
width++;
elem.style.width = width + '%';
document.getElementById("label").innerHTML = width * 1 + '%';
}
}
}
This is the current JSFIDDLE: link
Ok... whats wrong with Your code:
You defined onclick in html - please don't do that. Use JS to attach listeners
You are firing 'showProgress' which doesn't exist
You are using 'new' operator. You should only use it when You want to create new instance of given 'class' (JS has no real classes), and in this case - You just want to execute function.
You are using setInterval when what You really want is setTimeout (or at least I think so)
You want to animate elements without requestAnimationFrame - well it's basically bad idea, but You can do that.
I'm attaching fiddle, which does what You wanted (or at least I think so), but be aware that such a simple thing should have been done with CSS. If You do not care about actual progress of process, and You just want to show user an progress bar, than use CSS transition (I've already done it in snippet), and trigger class which will set width to 100%, on button click.
Theres fiddle for You.
.

How can I determine how many squares are needed to fill a browser window, dynamically?

I'm making colored squares to fill a browser window (say, 20px by 20px repeated horizontally and vertically).
There are 100 different colors, each link to a different link (blog post relevant to that color).
I want to fill the browser window with at least 1 of each colored square, and then repeat as necessary to fill the window, so that there are colored squares on the whole background, as the user drags the window smaller and larger.
If these were just images, setting a repeatable background would work. But, I would like them to be links. I'm not sure where to start on this. Any ideas, tips?
Here's the link to the site I'm working on: http://spakonacompany.com/
I think the most specific piece I need here is this: how can I determine the number of squares needed to repeat to fill the background, using jQuery that dynamically calculates that using the size of the browser window, including when dragged, resized, etc?
Many thanks. :)
To get browser's window width and height I use this function ->
//checking if the browser is Internet Explorer
var isIEX = navigator.userAgent.match(/Trident/);
var doc = isIEX ? document.documentElement : document.body;
function getwWH() {
var wD_ = window;
innerW = wD_.innerWidth || doc.clientWidth;
innerH = wD_.innerHeight || doc.clientHeight;
return {iW:innerW, iH:innerH}
}
There is also a native method of detecting when the browser's window is being resized which works in all major browsers (including IE 8, if you're planning on supporting it) ->
window.onresize = function(){
//here goes the code whenever the window is getting resized
}
So, in order to define how many squares are required to fill the window, you can get the window's width and divide it by the width of the square you are going to fill the window with ->
//getting total number of squares for filling the width and the height
width_ = getwWH().iW; //the width of the window
height_ = getwWH().iH; //the height of the window
If your square's width and height are static 20 by 20, than we can calculate total number of squares per window by dividing our width_ variable by 20 (the same for the height_) ->
squaresPerWidth = width_/20;
squaresPerHeight = height_/20;
So every time our browser window is getting resized we do this ->
window.onresize = function(){
width_ = getwWH().iW;
height_ = getwWH().iH;
squaresPerWidth = width_/20;
squaresPerHeight = height_/20;
//and the rest of the code goes here
}
Haven't tested it but this should work.
Here's something I whipped up. It uses a fixed number of resizable squares, but if you need squares of a fixed size, you just set the window to overflow: hidden and generate an unreasonably large number of squares.
var fillGrid = function(getColor, onClick) {
var tenTimes = function(f){
return $.map(new Array(10),
function(n, i) {
return f(i);
});
};
var DIV = function() {
return $('<div></div>');
};
var appendAll = function(d, all) {
$.map(all, function(e) {
d.append(e);
});
return d;
};
appendAll($('body'),
tenTimes(function(col) {
return appendAll(DIV().css({ height : "10%" }),
tenTimes(function(row) {
return DIV().css({
height : "100%",
width : "10%",
backgroundColor: getColor(row, col),
'float' : "left"
}).click(function() { onClick(row, col); });
}));
}));
};
You have to supply two functions, one to specify the color, the other to be invoked when the user clicks.

How to show cursor on TextInput using forwardTo in QML

I have two elements on screen, one Rectangle and one TextInput.When the activefocus is set on the rectangle and I type anything, I need to get the typed input to the TextInput
For this, I have used curRect.Keys.forwardTo = [curTextbox];
It works properly, but i also want to see the cursor while the focus is on the rectangle.
http://qt-project.org/doc/qt-4.8/qml-textinput.html#cursorVisible-prop
In the above link by QT, it says
that It can be set directly in script, for example if a KeyProxy
might forward keys to it and you desire it to look active when this
happens (but without actually giving it active focus).
But that does not happen in my case.
Can anyone explain to me why that is and how I can achieve my goal, i.e. to show the cursor even when the activeFocus is on the Rectangle?
Even if the cursor is shown at the time of entering the text it would be great.
UPDATE:
import QtQuick 1.0
FocusScope{
width: 400
height: 400
TextInput {Rectangle{anchors.fill: parent;color:"#66ff0000"}
id: mytext
anchors.top:parent.top
cursorVisible: true
width: rect.width
MouseArea{
anchors.fill: parent;
onClicked: mytext.forceActiveFocus()
}
Keys.onDownPressed: {
mytext2.forceActiveFocus();
}
}
TextInput {Rectangle{anchors.fill: parent;color:"#66ff0000"}
id: mytext2
anchors.top:mytext.bottom
cursorVisible: true
width: rect.width
MouseArea{
anchors.fill: parent;
onClicked: mytext2.forceActiveFocus()
}
Keys.onUpPressed: {
mytext.forceActiveFocus();
}
}
Rectangle {
id:rect
width: 360
height: 360
color:rect.activeFocus?"#6600ff00":"#660000ff"
focus: true
Keys.forwardTo: mytext
MouseArea{
anchors.fill: parent;
onClicked: rect.forceActiveFocus()
}
}
Component.onCompleted: rect.forceActiveFocus();
}
Thanks in advance.
The change of focus resets the cursorVisible property... so, when you click rect, and the focus is transferred from mytext to it via forceActiveRecord, mytext.cursorVisible is automatically set to false. You should reset it to true if you want to keep the cursor visible. This can be done by changing your rect.MouseArea.onClicked to this:
onClicked: {
rect.forceActiveFocus();
mytext.cursorVisible = true;
}
If you want the cursor on mytext to be always visible, you can also use the onCursorVisibleChanged handler to force it to be true. Use this inside your mytext object:
onCursorVisibleChanged: {
if (!cursorVisible)
cursorVisible = true;
}
PS: I'm posting a new answer, since added details have changed it a lot... I might delete the previous one later.

Change div height with buttons

I need some scripting like the TYPO3 extension / module that runs on this site : http://nyati-safari.dk/index.php?id=125 (Scroll to: Detaljeret Dagsprogram (inkluderet)).
The div is shown with a pixelspecific height and when the arrow is clicked the div changes to contentspecific height also the arrow changes when the div toggles.
Do this:
var div = $('#div');
$('#arrow').click(function () {
if (div.height() == 100) {
autoHeight = div.css('height', 'auto').height();
div.height(100).animate({
height: autoHeight
}, 500);
} else {
$('#div').animate({
height: '100'
}, 500);
}
});
JSFiddle: http://jsfiddle.net/ZG8ug/5/
Can even do something like this: http://jsfiddle.net/ZG8ug/6/ where the 'hidden' div is small on page load but when viewed and returned it is bigger. Might be useful to help users distinguish what has already been viewed. Could even do it the other way around too so the div takes up even less space when it has been viewed.

Categories

Resources