How to add items to a QML Grid after creation? - javascript

I've done a bit of searching but I've failed to find an answer which must be so simple it hasn't warranted a question thus far.
Anyway I'm new to QML and struggling to find a way to add items to a Grid dynamically post-creation.
Basically I have a Flickable with a Grid inside it containing (by default) four instances of a custom class named ImageTile, and when the MouseArea detects there's a click it wants to add 2 instances of ImageTile to that grid.
Here is a snippet of the code:
Flickable {
anchors.fill: parent
contentWidth: 400
contentHeight: 800
clip: true
MouseArea {
id: mouseArea
anchors.fill: parent
onclicked: { /* ADD TWO ImageTile TO imageGrid */ }
}
Grid {
id: imageGrid
columns: 2
spacing: 2
Repeater {
model: 4
ImageTile { }
}
}
}
As you can see I'm wondering what I should put inside the onClicked event to achieve adding two new instances of ImageTile to imageGrid.
Thanks in advance for any help!

So thanks to being pushed in the right direction by MrEricSir I've figured the issue out.
First I had to specify a proper Data Model for the Repeater to use, and then assign a delegate function to convert information in the data model to actual QML elements. Appending to the data model automatically triggered the Repeater to execute the delegate function.
Flickable {
anchors.fill: parent
contentWidth: 400
contentHeight: 800
clip: true
ListModel {
id: imageModel
ListElement { _id: "tile0" }
ListElement { _id: "tile1" }
ListElement { _id: "tile2" }
ListElement { _id: "tile3" }
}
MouseArea {
id: mouseArea
anchors.fill: parent
onclicked: {
imageModel.append({ _id: "tile" + imageModel.count })
}
}
Grid {
id: imageGrid
columns: 2
spacing: 2
Repeater {
model: imageModel
delegate: ImageTile { id: _id }
}
}
}

Related

Ext JS Remove GUI Item From Its Container By xtype

A panel contains of 3 items. The last item has an event handler attache. In the handler a new item (widget) is added to the parent panel. Before adding a new item, an old item of the same xtype should be deleted.
Here is an example that does not work:
Ext.define('Retroplanner.view.dimension.DimensionMapping', {
extend: 'Ext.form.Panel',
defaultListenerScope: true,
items: [{
xtype: 'component',
html: '<h3> Dimension Mappings</h3>',
},{
xtype: 'bm-separator'
},{
xtype: 'siRemoteCombo',
...
listeners: {
'select': 'onSiRemoteCombo'
}
}
],
onSiRemoteCombo: function(cmb, rec, idx) {
var item;
for(item in this.items){
//here item - is undefined,
//although this.items.length=3, as expected
//alert(item.xtype);
//isXType is not defined for undefined element:
if (item.isXType('propGrid')) {
this.remove(item);
break;
}
}
//the following code works as expected, if the previous is commented
var dimensionMapping = Ext.widget('propGrid');
this.items.add(dimensionMapping);
this.updateLayout();
}
});
I tried to use index, but it also does not work:
Ext.define('Retroplanner.view.dimension.DimensionMapping', {
...
defaultListenerScope: true,
items: [{
xtype: 'component',
...
},{
xtype: 'bm-separator'
},{
xtype: 'siRemoteCombo',
...
listeners: {
'select': 'onSiRemoteCombo'
}
}
],
onSiRemoteCombo: function(cmb, rec, idx) {
//the following code does not remove item in GUI interface.
if (this.items.length == 4)
this.remove(this.items[3], true);
var dimensionMapping = Ext.widget('propGrid');
this.items.add(dimensionMapping);
this.updateLayout();
}
});
I would like to be able to remove item by xtype, without any id or other types of references. But if it is not possible, which is the best way to do it? To remove GUI component from its container.
Check out component queries. It allows you to search for ExtJS components based on their attributes, globally or from within a container.
For easy access to queries based from a particular Container see the
Ext.container.Container#query, Ext.container.Container#down and
Ext.container.Container#child methods. Also see Ext.Component#up.
For your case down or child are appropriate. Something like this:
this.remove(this.down('propGrid'))
Here is a working fiddle: https://fiddle.sencha.com/#view/editor&fiddle/27vh. Just select a value from the combo, and the grid will be removed.
To remove an item with the prodGrid xtype, try this:
onSiRemoteCombo: function(cmb, rec, idx) {
if (this.down('prodGrid'))
this.remove(this.down('prodGrid'))
var dimensionMapping = Ext.widget('propGrid');
this.items.add(dimensionMapping);
this.updateLayout();
}

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.

Show/hide subpanels dynamically in Ext JS

I have created a view (a panel) with 3 subpanels ...
When the view loads , I want a function to run in viewController and based on its outcome , I want subpanel 1 to be visible(subpanel2 to be invisible) or subpanel2 to be visible(subpanel1 to be invisible)
How can I accomplish this ?
You are looking for card layout. It is already implemented. So you don't have to implement again. Just tell it witch panel gonna be active it will do all layout things itself. Checkout this api doc.
May be the Accordion layout can help you:
This is a layout that manages multiple Panels in an expandable accordion style such that by default only one Panel can be expanded at any given time
Here's a full example, it's quite straight forward:
Fiddle
Ext.define('FooController', {
extend: 'Ext.app.ViewController',
alias: 'controller.foo',
init: function(view) {
var child = Math.random() < 0.5 ? 'p1' : 'p2';
view.setActiveItem(this.lookupReference(child));
}
})
Ext.define('Foo', {
extend: 'Ext.container.Container',
layout: 'card',
controller: 'foo',
items: [{
title: 'P1',
reference: 'p1'
}, {
title: 'P2',
reference: 'p2'
}]
});
Ext.onReady(function() {
new Foo({
renderTo: document.body,
width: 200,
height: 200
});
});
Give itemId to all three panel and then fireEvent.
Listener of view
listeners:{
show: function(){
me.fireEvent('showHidePanel');
}
}
define showHidePanel method in Controller and in that method get panel by using down() with item id and hide/show panel by using hide()/show() method.

Sencha Touch 2: How to override back button on Navigation View

I was wondering how to ovverride the back button on a navigation view. I tried using onBackButtonTap but it doesnt seem to work http://www.senchafiddle.com/#8zaXf
var view = Ext.Viewport.add({
xtype: 'navigationview',
onBackButtonTap: function () {
alert('Back Button Pressed');
},
//we only give it one item by default, which will be the only item in the 'stack' when it loads
items: [
{
//items can have titles
title: 'Navigation View',
padding: 10,
//inside this first item we are going to add a button
items: [
{
xtype: 'button',
text: 'Push another view!',
handler: function() {
//when someone taps this button, it will push another view into stack
view.push({
//this one also has a title
title: 'Second View',
padding: 10,
//once again, this view has one button
items: [
{
xtype: 'button',
text: 'Pop this view!',
handler: function() {
//and when you press this button, it will pop the current view (this) out of the stack
view.pop();
}
}
]
});
The fiddle you've mentioned works well in my local project on my machine. For some reason, it doesn't work on fiddle site. Try running it on your local project.
Still instead of using onBackButtonTap config, it's good to extend Ext.navigation.View class and override onBackButtonTap method. That way you'll have more control over whole components. You'd also like to override other configs as well. Here's what I'd use -
Ext.namespace('Ext.ux.so');
Ext.define('Ext.ux.so.CustomNav',{
extend:'Ext.navigation.View',
xtype:'customnav',
config:{
},
onBackButtonTap:function(){
this.callParent(arguments);
alert('back button pressed');
}
});
the line this.callParent(arguments) will allow component to behave in default way + the way to wanted it to behave. And if you want to completely override the back button behavior you can remove this line. Try doing both ways.
To use this custom component, you can use -
launch: function() {
// Destroy the #appLoadingIndicator element
Ext.fly('appLoadingIndicator').destroy();
var view = Ext.create('Ext.ux.so.CustomNav', {
fullscreen: true,
items: [{
title: 'First',
items: [{
xtype: 'button',
text: 'Push a new view!',
handler: function() {
//use the push() method to push another view. It works much like
//add() or setActiveItem(). it accepts a view instance, or you can give it
//a view config.
view.push({
title: 'Second',
html: 'Second view!'
});
}
}]
}]
});
}
Give this a shot. It'll work for you yoo.

Accessing parent functions in QML

I'm currently creating a Twitter client as a way of learning to develop for Blackberry 10. I'm currently trying to create a context menu for a custom ListView item, which on selection will show a dialog in the main layout. However, I cannot get the selected list item to call any functions from the parent Page.
Here is the QML from my custom list item:
import bb.cascades 1.0
Container {
property alias avatar: tracker.imageSource
property alias username: txtTweetUser.text
property alias tweetText: txtTweetContent.text
property alias tweetTime: txtTweetTime.text
signal sendReply(string username)
function cout(text) {
console.debug("[DEBUG] " + text);
}
id: itemTweet
preferredWidth: 768;
preferredHeight: 200
// Actions
contextActions: [
ActionSet {
title: "Action Set"
subtitle: "This is an action set."
actions: [
ActionItem {
title: "Reply"
imageSource: "asset:///reply.png"
onTriggered: {
itemTweet.sendReply(txtTweetUser.text);
}
},
ActionItem {
title: "Retweet"
imageSource: "asset:///retweet.png"
},
ActionItem {
title: "Favourite"
imageSource: "asset:///fav.png"
}
]
} // end of ActionSet
] // end of contextActions list
<Layout for the List Item>...
}
}
And for the main QML file:
import bb.cascades 1.0
TabbedPane {
id: tabbedPane
showTabsOnActionBar: true
Tab {
id: tabTimeline
title: "Timeline"
imageSource: "asset:///twitter-white.png"
Page {
id: pageTimeline
signal openReply
signal showNewTweet
function cout(text) {
console.debug("[DEBUG] " + text);
}
function showTweetWindow(username) {
pageTimeline.dialogNewTweet.text = username;
pageTimeline.dialogNewTweet.visible = true;
}
// Title bar
titleBar: TitleBar {
visibility: Overlay
title: "Twitter"
acceptAction: ActionItem {
id: btnNewTweet
title: "+"
ActionBar.placement: ActionBarPlacement.OnBar
onTriggered: {
pageTimeline.cout("action selected");
dialogNewTweet.visible = true;
}
}
}
// Main content
content: Container {
layout: AbsoluteLayout {}
Container {
// Listview for the tweets
ListView {
id: lstTweets
objectName: "lstTweets"
// Components to display the rows
listItemComponents: [
ListItemComponent {
id: listItem
type: "listItem"
TweetItem {
tweetText: ListItemData.content
tweetTime: ListItemData.time
avatar: ListItemData.avatar
username: ListItemData.username
onSendReply: {
cout("Reply selected in parent to " + username);
pageTimeline.showTweetWindow(username);
}
}
}
]
onSelectionChanged: {
}
function itemType(data, indexPath) {
if (indexPath.length == 1) {
return "header";
} else {
return "listItem";
}
}
}
}
DialogNewTweet {
id: dialogNewTweet
visible: false
onShowNewTweet: {
dialogNewTweet.visible = true;
}
}
}
// End container
}
}
... <Other tabs> ...
}
So when the main QML file receives the SendReply signal, it's suppposed to call showTweetWindow(username) which then makes dialogNewTweet visible, but instead I get the error ReferenceError: Can't find variable: pageTimeline. It's definitely a scope issue, but I can't figure out what I'm doing wrong, or if I need to restructure this.
Answer here:
https://community.blackberry.com/message/182467#182467
What I need to achieve is to operate on a list of data, e.g. delete an item using ContextAction in ListItem in QML, then call a C++ method of an object in QmlDocument's contextProperty. here is how I did it:
C++ code:
QmlDocument *qml = QmlDocument::create("accounts.qml");
root = qml->createRootNode<AbstractPane>();
//Here set the context property, accountsManager was already created
qml->setContextProperty("accountsManager", accountsManager);
//Set the model for the list view
ListView* accountsListView = root->findChild<ListView*>("accountsListView");
accountsListView->setDataModel(accountsManager->getDataModel());
QML code:
Page {
content: Container {
ListView {
id: listView
objectName: "accountsListView"
listItemComponents: [
ListItemComponent {
type: "listItem"
StandardListItem{
id: accountItem
title: ListItemData.username
contextActions: [
ActionSet {
deleteAction: DeleteActionItem {
title: "Delete"
onTriggered: {
//it does not work here, "ReferenceError: Can't find variable: accountsManager"
accountsManager.doSometing();
}
}
}
]
}
}
]
function itemType(data, indexPath) {
return "listItem";
}
} // end of ListView
} // end of Container
actions: [
ActionItem {
title: "Add account"
ActionBar.placement: ActionBarPlacement.OnBar
onTriggered: {
//It works fine here
accountsManager.doSomething();
}
}
]
} // end of Page
And another method:
https://community.blackberry.com/message/453794#453794
At the top Page level add the following line to make the label accessible.
Page {
id: topPage
onCreationCompleted: { Qt.topLabel = topLabel; }
Then in the button definition you can reference the Qt.topLabel from within the list.
listItemComponents: [
ListItemComponent {
type: "item"
Button {
text: "Change To Three"
onClicked: {
Qt.topLabel.text = "Three";
}
}
}
]

Categories