Accessing parent functions in QML - javascript

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";
}
}
}
]

Related

How to update vue-meta on route/url change?

The meta data on my website is not updating when the route changes. The route itself has a watch on it which updates the view fine, but the metaInfo() from vue-meta is not keeping up. The <script> section of my code looks like this:
<script>
export default {
name: "Product",
watch: {
'$route.params.ProductID': {
deep: true,
immediate: true,
handler() {
this.getProduct(); // calls getProduct() on route change. Can I also call metaInfo() from here somehow?
}
}
},
metaInfo() {
return {
title: this.Product.ProductTitle,
meta: [
{
name: 'description', content: this.Product.ProductTitle
}
]
}
},
computed: {
Product() {
return this.$store.getters.getProduct
}
}, mounted() {
if (this.Product == null || !this.Product.length) {
this.getProduct();
}
}, methods: {
getProduct() {
return this.$store.dispatch('loadProduct', {ProductID: this.$route.params.ProductID})
}
}
}
</script>
What is happening is that when I change my route and go from /product/123 to /product/124, the metaInfo() still shows the meta data for /product/123. If I hit refresh, then the metaInfo() updates and shows the correct data for /product/124.
I need the watch to trigger an update of metaInfo() but don't know how to do it. I can't find this information in the docs anywhere. Please help?
For reactive, use variables outside return statements.
metaInfo() {
const title = this.Product.ProductTitle;
return {
title: title,
meta: [
{
name: 'description', content: title
}
]
}
}
https://vue-meta.nuxtjs.org/guide/caveats.html#reactive-variables-in-template-functions

editorjs image plugin return image url is not picked up

I configured editorjs into my application. I installed image plugin on it so that I can upload an image to the server. Now my server uploads the image and returns JSON with specified format i.e.,
{
"success": 1,
"file": {
"url": "https://www.tesla.com/tesla_theme/assets/img/_vehicle_redesign/roadster_and_semi/roadster/hero.jpg",
// ... and any additional fields you want to store, such as width, height, color, extension, etc
}
}
The response has file->url but still error uploading message
Here is my config code:
const editor = new EditorJS({
/**
* Id of Element that should contain Editor instance
*/
holder: 'heditor',
/**
* Available Tools list.
* Pass Tool's class or Settings object for each Tool you want to use
*/
tools: {
header: {
class: Header,
inlineToolbar: ['link'],
config: {
placeholder: 'Title...'
}
},
paragraph: {
class: Paragraph,
inlineToolbar: true,
},
image: {
class: ImageTool,
config: {
endpoints: {
accept: 'image/*',
byFile: $('#heditor').data('url'), // Your backend file uploader endpoint
byUrl: $('#heditor').data('url'), // Your endpoint that provides uploading by Url
},
additionalRequestHeaders: {
'x-auth-token': $('#heditor').data('token'),
}
}
},
linkTool: {
class: LinkTool,
config: {
endpoint: 'http://localhost:8008/fetchUrl', // Your backend endpoint for url data fetching
}
},
inlineCode: {
class: InlineCode,
shortcut: 'CMD+SHIFT+M',
},
Marker: {
class: Marker,
shortcut: 'CMD+SHIFT+M',
},
embed: Embed,
list: {
class: List,
inlineToolbar: true
}
},
/**
* This Tool will be used as default
*/
// initialBlock: 'paragraph',
/**
* Initial Editor data
*/
data: {
blocks: [
{
type: "header",
data: {
text: "Title of your story",
level: 2
}
},
{
type: 'paragraph',
data: {
text: 'Hey. Meet the new Story Editor. On this page you can start writing your story — try to edit this text...'
}
}
]
},
onReady: function () {
saveButton.click();
},
onChange: function () {
console.log('something changed');
}
});
/**
* Saving example
*/
saveButton.addEventListener('click', function () {
editor.save().then((savedData) => {
$("#output").html(savedData);
});
});
What I am doing wrong can anyone help?

mergeOptions changes not reflected

I'm trying to get a badge to display on one of the navigation tabs.
onClickSetBadge() {
console.log('Button pressed!')
Navigation.mergeOptions(this.props.componentId, {
bottomTab: {
badge: `TeSt`
}
});
}
I'm calling it inside the view I'm rendering.
<ButtonView text='SUP' onPress={() => {this.onClickSetBadge()}} />
The log statement shows up in my console, however, the badge is not shown.
According to the official document: https://wix.github.io/react-native-navigation/#/docs/layout-types?id=updating-options-for-a-specific-tab , that is how it works:
1) Define an id for the specific tab:
stack: {
id: 'CartTab',
children: [{
component: {
name: 'store-cart',
}
}],
options: {
bottomTab: {
text: 'Cart',
icon: require('../asset/image/menu/cart.png'),
}
}
}
2) Use the tab id to update when needed:
Navigation.mergeOptions('CartTab', {
bottomTab: {
badge: cart.length > 0 ? cart.length.toString() : ''
}
});

Sending data to a non parent component in VueJs

I am trying to replicate the TODO MVC in VueJs.
(Please checkout this codepen : http://codepen.io/sankalpsingha/pen/gwymJg )
I have created a component called 'todo-list' with the following code :
Vue.component('todo-list',{
template: '#todo-list',
props: ['todo'],
data: function() {
return {
// Let us set up a isEditing Boolean so that we can know if the user
// will edit something and we need to change the state to reflect it.
isEditing: false,
}
},
methods: {
enableEditing: function() {
this.isEditing = true;
},
editTodo: function(todo) {
// todo.todo = todo.todo.trim();
this.isEditing = false;
},
removeTodo: function(todo) {
//this.todos.$remove(todo); // --> This part is not working?
}
}
});
However, I have the data defined in the app instance :
var app = new Vue({
el: '#todo-section',
data: {
newTodo: '',
todos: [
{
id: 1,
todo: 'Go to the grocery',
completed: false,
},
{
id: 2,
todo: 'See the movie',
completed: true,
},
{
id: 3,
todo: 'Jack Reacher : Tom Cruise',
completed: false,
}
]
},
methods: {
addTodo: function() {
// This will not allow any empty items to be added.
if(this.newTodo.trim() == '') {
return;
}
this.todos.push({
todo: this.newTodo.trim(),
completed: false,
});
this.newTodo = '';
}
}
});
I am not able to delete a single Todo from the list. My guess is that I have to send a emit message to the app instance and put up a listener there to delete the data from it? How do I delete the data?
When I tried to delete by clicking the x button in your codePen example, I see the error: this.$parent.todos.$remove is not a function.
I have not looked deeply into your code. But attempting to access parent component methods using this.$parent is not a good idea. Reason: a component can be used anywhere, and assuming that it will have a $parent with a particular property or method is risky.
As you suggested in your question, you need to use $emit from the child component to delete the data.
There was another similar question here few days ago, for which I created a jsFiddle: https://jsfiddle.net/mani04/4kyzkgLu/
The child component has some code like:
<button #click="$emit('delete-row')">Delete</button>
This sends out an event to parent component. Parent component can subscribe to that event using v-on as seen in that jsFiddle example.
Here is that other question for reference: Delete a Vue child component
It's preferable to use your methods (DeleteTodo, EditTodo...) in your parent.
var app = new Vue({
el: '#app',
data: {
newTodo: '',
todos: [{
id: 1,
title: 'Go to the grocery',
completed: false
}, {
id: 2,
title: 'See the movie',
completed: true
}, {
id: 3,
title: 'Jack Reacher : Tom Cruise',
completed: false
}]
},
methods: {
addTodo: function() {
this.todos.push({
todo: this.newTodo.trim(),
completed: false
});
this.newTodo = ''
},
deleteTodo: function(todo) {
this.todos = this.todos.filter(function(i) {
return i !== todo
})
}
}
});
<div id="app">
<ul>
<li v-for="todo in todos">{{ todo.title }}
<button #click.prevent="deleteTodo(todo)">
Delete
</button>
</li>
</ul>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>

List and store binding... exact same data

I am using sencha to create a carousel which has multiple card panels. Each panel contains a list component that is attached to its own instance of a store.
All lists store instances call the same API to fetch the data but with different parameters.
Example:
Card 1, Has list 1 attached to Store 1 which calls mywebsite.com/api?node=1
Card 2, Has list 2 attached to Store 2 which calls mywebsite.com/api?node=2
Card 1 shows the right set of nodes retrieved from the API. But once i swipe to see card 2, both list 1 and list 2 show the exact same data although each one should have its own list od data.
Code:
Test.data.NodeStore = Ext.extend(Ext.data.Store, {
constructor : function(config) {
config = Ext.apply({
model: 'Test.models.Node',
autoLoad: false,
pageSize: 20,
proxy: {
type: 'scripttag',
url: Test.API.URL + '?action=getNodes',
extraParams: {
},
reader: {
type: 'json'
}
},
setSource: function(source) {
if(this.getProxy().extraParams.sourceID != source) {
this.getProxy().extraParams.sourceID = source;
}
}
}, config);
Test.data.NodeStore.superclass.constructor.call(this, config);
},
onDestroy : function(config) {
Test.data.NodeStore.superclass.onDestroy.apply(this, arguments);
}
});
Ext.reg('NodeStore', Test.data.NodeStore);
The list view:
Test.views.ListView = Ext.extend(Ext.Panel, {
sourceID: 0,
layout: {
type: 'vbox',
align: 'stretch'
},
items: [
{
xtype: 'list',
itemTpl : new Ext.XTemplate("<div class='node'>{title}</div>"),
store: Ext.create(Test.data.NodeStore, {}),
}
],
setSource: function(source) {
this.sourceID = source;
var store = this.items.get(0).getStore();
store.setSource(source);
store.load();
}
});
The main view which creates list views dynamically
Test.views.Viewer = Ext.extend(Ext.Carousel, {
indicator: false,
layout: 'card',
style: {
padding: '0 20px'
},
items: [
],
loadListView: function(listIndex) {
var currentRecord = Test.stores.ListStore.getAt(listIndex);
var newList = new Test.views.ListView();
newList.setSource(currentRecord.get('ID'));
this.add(newList);
this.doLayout();
},
initComponent: function() {
Test.views.Viewer.superclass.initComponent.apply(this, arguments);
loadListView(1);
loadListView(2);
}
});
This is really wierd... i am just wondering, is sencha assigning the exact same store, model, list component... don't know where to look
In the loadListView function, i had to create an object of store and assign it to the list dynamically rather than modifying existing store.
newList.items.get(0).store = Ext.create(Test.data.NodeStore, {});

Categories