Dynamic creation of QML ObjectModel elements - javascript

I'am trying to dynamically create bunch of QML ObjectModel elements such as simple rectangles and then show them in ListView. But when I build my application nothing is appear. Console log shows only the message: "Created graphical object was not placed in the graphics scene". Is there any way to do it right with this approach, or anything else?
UPD: code
main.qml
import "imgRectsCreation.js" as ImgRectsCreationScript
import QtQuick 2.0
import QtQml.Models 2.1
Rectangle {
id: root
ObjectModel{
id: itemModel
Component.onCompleted: ImgRectsCreationScript.createImgRects();
}
ListView {
id: view
clip: true
anchors { fill: root; bottomMargin: 30 }
model: itemModel
preferredHighlightBegin: 0; preferredHighlightEnd: 0
highlightRangeMode: ListView.StrictlyEnforceRange
orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem; flickDeceleration: 2000
cacheBuffer: 200
}
Rectangle {
width: root.width; height: 30
x: 10
y: 330
color: "gray"
Row {
anchors.centerIn: parent
spacing: 20
Repeater { // little points at the bottom
model: itemModel.count
Rectangle {
width: 5; height: 5
radius: 3
color: view.currentIndex == index ? "sandybrown" : "white"
MouseArea {
width: 20; height: 20
anchors.centerIn: parent
onClicked: view.currentIndex = index
}
}
}
}
}
}
imgRectsCreation.js
var sprite;
var component;
function createImgRects() {
component = Qt.createComponent("ImgRectSprite.qml");
if (component.status === Component.Ready)
finishCreation();
else
component.statusChanged.connect(finishCreation);
}
function finishCreation() {
for (var i = 0; i < 3; i++) {
if (component.status === Component.Ready) {
sprite = component.createObject(itemModel, {"x": 10, "y": 10});
if (sprite === null) { // Error Handling
console.log("Error creating object");
}
}
else if (component.status === Component.Error) { // Error Handling
console.log("Error loading component:", component.errorString());
}
}
}
and finally - ImgRectSprite.qml
import QtQuick 2.0
Rectangle {
width: 100; height: 100;
color: "red"
Image {
width: parent.width
height: parent.height
source: window.slotGetFileUrl()
}
}

I'm not a big fan of component creation from JS code - i tend to put QML code in .qml files, and "heavy" (well it's JS afterall) code inside .js files - .
Have you tried to dynamically create qml objects using the Loader object instead?

Well, okay, I've solved it by myself. But I'm not completely sure that it's a right decision.
I've decided to remove ObjectModel from my main.qml
... and replace it with ListModel
ListModel {
id: dataModel
dynamicRoles: true
Component.onCompleted: {
for (var i = 0; i < 3; i++)
{
append({ color: "orange" })
}
}
}
finally, I've added delegate to my ListView
delegate: Rectangle {
width: view.width
height: view.height
color: model.color
Image {
width: parent.width
height: parent.height
source: window.slotGetFileUrl() // includes logic to select images
}
???
PROFIT!
Thanks for your attention :)

Try to replace itemModel as a parent for dynamically created object with null or undefined value:
sprite = component.createObject(null, {"x": 10, "y": 10});

Related

How to load visual into entire div using Power Bi embed report

I am embedding a Power BI report with a single page and single view in to my application using javascript.The visual is creating and loading fine but it is occupying in a particular portion of the parent div in which i am embedding my report.I need to stretch that visual into full width and height of the parent div.I tried this but it is not working for me.
Here is my current output
Here my visual is fit in to the centre of my parent div but i need to stretch that visual to cover all remaining red portion of my parent div
Here is my code
let visualConfig = {
type: 'report',
tokenType: models.TokenType.Aad,
accessToken: loggedInUser.accessToken,
embedUrl: globalData.embedUrl,
id: globalData.id,
viewMode: models.ViewMode.View,
permissions: models.Permissions.ReadWrite,
datasetBinding: {
datasetId: CurrentDataset
},
settings: {
//layoutType: models.LayoutType.Custom,
//customLayout: {
// pageSize: {
// type: models.PageSizeType.Custom,
// width: $("#idViewPreview").width(),
// height: 300
// },
// displayOption: models.DisplayOption.FitToPage
//},
panes: {
filters: {
visible: false
},
pageNavigation: {
visible: false
},
},
background: models.BackgroundType.Transparent,
visualSettings: {
visualHeaders: [
{
settings: {
visible: false
}
}
]
}
}
};
currentVisual.report = powerbi.embed($("#idViewPreview")[0], visualConfig);
//required for untag load event
currentVisual.report.off("loaded");
// Triggers when a report schema is successfully loaded
currentVisual.report.on("loaded", async function () {
try {
var newPage = await currentVisual.report.addPage("test_display");
currentVisual.page = newPage;
currentVisual.page.setActive();
let models = window['powerbi-client'].models;
const customLayout = {
x: 0,
y: 0,
width: 1000,
height: 800,
displayState: {
// Change the selected visuals display mode to visible
mode: models.VisualContainerDisplayMode.Visible
}
};
currentVisual.visuals = await currentVisual.page.createVisual("funnel", customLayout);
}
catch (ex) {
console.log(ex);
}
When we change the page size, the report will be fixed rather than contracted or expanded. If we make the page width smaller than the report width, the visual report will be contracted. However, when we change the div size, the iframe resizes the report automatically. If you want to customise the height and width of your visual, you can do so.
Set custom height and width of your visual.
defaultLayout: defaultLayout,
visualsLayout: {
"Required_Visual_Name": {
x: "Required_Width",
y: "Required_Height",
displayState: {
mode: models.VisualContainerDisplayMode.Visible
}
},
}
};
2.Now Update the settings
layoutType: models.LayoutType.Custom,
customLayout: {
displayOption: models.DisplayOption.FitToPage,
pagesLayout: {
"Your_Report_Id": pageLayout
}
},
}
3.Apply the settings
await report.updateSettings(settings);
References:
https://learn.microsoft.com/javascript/api/overview/powerbi/custom-layout
I just made some changes in the actual answer and i am posting it here. After embedding the report before creating a visual i am updating the page layout.
let divWidth = $("div").width() - 10;
let divHeight = $("div").height() - 10;
let settings = {
layoutType: models.LayoutType.Custom,
customLayout: {
pageSize: {
type: models.PageSizeType.Custom,
width: divWidth,
height: divHeight
},
displayOption: models.DisplayOption.FitToPage
}
};
await embedReport.report.updateSettings(settings);
const customLayout = {
x: 0,
y: 0,
width: divWidth,
height: divHeight,
displayState: {
// Change the selected visuals display mode to visible
mode: models.VisualContainerDisplayMode.Visible
}
};
await embedReport.page.createVisual("column", customLayout);

vue.js mapbox map not showing

I want to have my map to show, but no mater what I do, it doesn't display a map.
The current code I use is the following:
<template>
<div>
<MglMap
:accessToken="mapboxAccessToken"
:mapStyle.sync="mapStyle"
:center="coordinates"
/>
<button class="btn btn-contained btn-success add-location" v-on:click="addLocation"><span>Button</span></button>
</div>
</template>
<script>
import Mapbox from "mapbox-gl";
import { MglMap } from "vue-mapbox";
export default {
components: {
MglMap
},
props: {
currLat: Number,
currLon: Number,
allPoints: Array
},
// Mounted (page load), we ask to track
mounted: function () {
const options = {
enableHighAccuracy: true
};
this.$watchLocation(options)
.then(coordinates => {
// Set the center to my coordinates
this.currLat = coordinates.lat;
this.currLon = coordinates.lng;
// Emit the user data to all connected devices.
this.$socket.emit('USER_COORDS', {
'user': this.$socket.id,
'lat': this.currLat,
'lon': this.currLon
});
});
},
data() {
return {
mapboxAccessToken: "removed",
mapStyle: "mapbox://styles/mapbox/streets-v10",
coordinates: [0.0, 0.0]
};
},
created() {
// We need to set mapbox-gl library here in order to use it in template
this.map = Mapbox;
},
methods: {
geolocateError(control, positionError) {
// console.log(positionError);
Latte.ui.notification.create({
title: "An error occurred!",
message: "We could not get your location, please try again by reloading the application."
});
},
geolocate(control, position) {
console.log(
`User position: ${position.coords.latitude}, ${position.coords.longitude}`
)
},
addLocation: function() {
this.$socket.emit('NEW_LOCATION', {
name: 'VK1',
lat: this.currLat,
lon: this.currLon
});
}
},
sockets: {
USER_COORDS_DATA: function (data) {
// user connects, data of the current location gets emitted
// this.map.flyTo({
// center: [data.lon, data.lat],
// zoom: 15,
// speed: 1
// });
// console.log(data)
},
NEW_LOCATION_DATA: function (data) {
// Returned data from the server (after storage)
console.log(data)
},
},
}
</script>
<style>
#import '../../node_modules/mapbox-gl/dist/mapbox-gl.css';
#map {
width: 100%;
height: calc(100vh - var(--app-bar-height));
}
.btn.add-location {
position: absolute;
left: 2rem;
bottom: 2rem;
}
</style>
I get the follwoing output in my console:
I have no clue on what is going wrong here, even no idea why I get a dom exception?
I just want to have my map to be displayed and show a marker on your location, any idea what is going on here? Why the map isn't rendering?
Packages used:
- "mapbox-gl": "^1.3.1",
- "vue-mapbox": "^0.4.1",
I had a similar problem, I was not able to show the map entirely, even demo from VueMapbox https://soal.github.io/vue-mapbox/ did not work for me.
However, I found out after inspection in chrome that the entire div has height set to 0, which is weird why.. I then set the height manually and it worked
I created an issue on project github so we will probably see where is the problem: https://github.com/soal/vue-mapbox/issues/158
TLDR: set the height manually
In my case the issue was caused by a height: 100% for the body tag. One I set the body tag to px, i.e. height: 500px hight it worked fine for me.

QML return NaN on attribute set by Javascript

I'm setting attributes of a QML object with java script on object creation.
It is working fine except on one parameter that needs a number.
this calls the script:
Component.onCompleted: CreateVerticalGaugeScript.createVerticalGauge(300,10,300,300,"MAP",Dashboard,"MAP");
The 300 before "MAP" is the needed number. Everything else is working.
var component;
var gauge;
function createVerticalGauge(setWidth,setX,setY,setMaxValue,setID,SetValueObject,SetValueProperty) {
component = Qt.createComponent("verticalbargauge.qml");
console.log(setMaxValue)
if (component.status == Component.Ready)
finishCreation(setWidth,setX,setY,setMaxValue,setID,SetValueObject,SetValueProperty);
else
component.statusChanged.connect(finishCreation);
}
function finishCreation(setWidth,setX,setY,setMaxValue,setID,SetValueObject,SetValueProperty) {
console.log(setMaxValue)
if (component.status == Component.Ready) {
gauge = component.createObject(adaptronicDash, {"id": setID, "gaugemaxvalue": setMaxValue,
"gaugetext": Qt.binding(function(){return SetValueObject[SetValueProperty]}),
"x": setX, "y": setY});
gauge.width = setWidth;
if (gauge == null) {
// Error Handling
//console.log("Error creating object");
}
} else if (component.status == Component.Error) {
// Error Handling
//console.log("Error loading component:", component.errorString());
}
}
Now when the object is created it has the value NaN in QML. In jas console.log shows me that the value 300 is set.
This is the QML thats created:
import QtQuick 2.8
import QtQuick.Controls.Styles 1.4
import QtQuick.Extras 1.4
import QtQml.Models 2.2
Rectangle {
id: initalID
width: 100
height: 80
color: "transparent"
antialiasing: false
Drag.active: dragArea.drag.active
property alias gaugetext: gaugetextfield.text
property alias gaugemaxvalue: gauge.maximumValue
MouseArea {
id: dragArea
width: parent.width
height: parent.height + 10 // easier to get
anchors.centerIn: parent
drag.target: parent
drag.axis: Drag.XAndYAxis
//onClicked: pieMenu.popup(mouseX, mouseY), console.log("clicked")
}
Gauge {
id: gauge
anchors.fill: parent
anchors.margins: 10
orientation : Qt.Horizontal
minorTickmarkCount: 4
tickmarkStepSize : 5000
//labelStepSize: 50
minimumValue: 0
maximumValue: 10000
//value: Dashboard.revs
Behavior on value {
NumberAnimation {
duration: 5
}
}
Text {
id: gaugetextfield
font.pixelSize: (parent.height / 3)
anchors.top : parent.top
font.bold: true
font.family: "Eurostile"
color: "white"
anchors.horizontalCenter: parent.horizontalCenter
}
style: GaugeStyle {
valueBar: Rectangle {
implicitWidth: rev.height /3
color: Qt.rgba(gauge.value / gauge.maximumValue, 0, 1 - gauge.value / gauge.maximumValue, 1)
}
}
}
}
I use property alias gaugemaxvalue to access the value. If I set a number directly in the Javascript it is working.
You are setting Gauge maximumValue to 300 and tickmarkStepSize is 5000.
Test by changing maximumValue to 30000 or commenting out tickmarkStepSize.

QML - Dynamically create Timers when event occurs

I have a ListView whose delegate is a red button. When button's color changes, I want the program to dynamically create a timer (which is specific for that delegate), which sets again the color to red after 5 seconds. Then I want the program to destroy the timer. How can I do it?
Here is my actual code:
ListView {
id: myListView
model: myListModel
anchors.fill: parent
anchors.leftMargin: 20; anchors.rightMargin: 20
orientation: Qt.Vertical
clip: true
spacing: 8
delegate: Button {
id: myDelegate
property int myDelegateIndex: index + 1
width: 100; height: 50
text: "Push"
background: Rectangle {
id: myDelegateBackground
color: "red"
onColorChanged: {
myTimer.start();
}
}
Timer {
id: myTimer
interval: 5000
running: true
repeat: true
onTriggered: {
myDelegateBackground.color = "red";
}
}
}
}
Thank you all a lot!!!
You create a Component
SelfdestroyingTimer.qml
Timer {
property var action // Assing a function to this, that will be executed
running: true
onTriggered: {
action()
this.destroy() // If this timer is dynamically instantitated it will be destroyed when triggered
}
}
And have a function:
function createOneShotTimer(duration, action) {
var comp = Qt.createComponent('SelfdestroyingTimer.qml')
comp.createObject(root, { action: action, interval: duration })
}
Or declare the Component in the same file (so you do not need to create it each time you want an instance), and it looks like this:
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Window 2.0
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: window
visible: true
width: 600
height: 600
Component {
id: singleShot
Timer {
property var action
running: true
onTriggered: {
if (action) action() // To check, whether it is a function, would be better.
this.destroy()
}
// This proves, it will be destroyed.
Component.onDestruction: console.log('Timer says bye bye!')
}
}
Button {
onClicked: {
singleShot.createObject(this, { action: function() { console.log('ACTION!!!') }, interval: 2000 })
}
}
}
There would appear to be no need to dynamically create timers. Create one Timer for each delegate and reuse it by calling restart().
See example below:
ListView {
id: myListView
model: 20
anchors.fill: parent
anchors.leftMargin: 20; anchors.rightMargin: 20
orientation: Qt.Vertical
clip: true
spacing: 8
delegate: Button {
id: myDelegate
property int myDelegateIndex: index + 1
width: 100; height: 50
text: "Push"
background: Rectangle {
id: myDelegateBackground
color: "red"
onColorChanged: {
myTimer.restart();
}
Timer {
id: myTimer
interval: 5000
running: false
repeat: false
onTriggered: {
myDelegateBackground.color = "red";
}
}
}
onClicked: {
background.color = "blue"
}
}
}
Regardless of what mechanism you choose to do this, you will have problems caused by the ListView destroying delegates which scroll out of the visible region of the ListView. When a delegate gets destroyed and recreated, it will have its original color and the timer will be in its default state.
There are two options to deal with this scenario:
Save the state of the delegates to a list (in a javascript var) outside the scope of the delegate.
Increase the Listview cacheBuffer so that the ListView does not destroy the delegates when they go out of the visible region.

Change height of Highcharts with JavaScript. By default only re-sizes on window re-size

We're using HighCharts in our app, and I've added a function to expand the chart fullsize. I change the styles as well as use Javascript to change the height of the div.
However nothing changes until you actually resize the browser window. Anyone else run into this issue?
<section id="highchart-container" ng-class="{'high-chart-expanded' : highChartMax}">
<highchart id="chart1" config="chartConfig" style="height:auto"></highchart>
</section>
ChartHeader scope
function expandChartPanel() {
vm.chartMaxed = !vm.chartMaxed;
highChart = ScopeFactory.getScope('highChart');
if (vm.chartMaxed) {
highChart.highChartMax = true;
}
else {
highChart.highChartMax = false;
}
highChart.toggleChartSize();
}
HighChartDirective scope
function toggleChartSize() {
var chart1 = document.getElementById("chart1");
if (vs.highChartMax) {
chart1.style.height = "100%";
} else {
chart1.style.height = "400px";
}
}
Styles (SASS)
.high-chart-expanded {
min-height: 100% !important;
max-height: 100% !important;
width: 100% !important;
height: 100% !important;
#highcharts-6,
#chart1,
.highcharts-background,
.highcharts-container {
min-height: 100% !important;
max-height: 100% !important;
width: 100% !important;
height: 100% !important;
}
}
HighChart chartConfig
ApiFactory.quotes(buildFullUrl(url)).then(function (data) {
var quote_data = formatQuotes(data, 'quotes');
// Create the chart
vs.chartConfig = {
options: {
legend: {
itemStyle: {
color: "#333333",
cursor: "pointer",
fontSize: "10px",
fontWeight: "normal"
},
enabled: true,
floating: true,
align: 'left',
verticalAlign: 'top',
x: 60
},
chart : {
zoomType: 'x',
events: {
load: function () {
// HighChart loaded callback:
broadcastChartloaded();
}
}
},
This is what I see when I console out the chartConfig
console.log('highChart.chartConfig = ', highChart.chartConfig);
Try chart.setSize(width, height) ?
Here's a working example
UPDATE : for angular directive
To Pull out the chart object from directive you can just go the jQuery route:
var chart = $('#theChart').highcharts();
chart.setSize(width, height);
Specifically for ng-highcharts users, here's how its recommended to pull out the high-charts object by the author of the directive. The above method will work just fine too.
var chart = this.chartConfig.getHighcharts();
chart.setSize(width, height);
Although you can do it anywhere in your controller/directive/service, I would recommend you create a new service that returns this object , and then inject it in your controller if you are strict about following Angular design pattern, but if not just those two lines should work fine anywhere that you have access to chartsConfig object.
To reset the chart to being responsive again check this answer.

Categories