Frappe: cur_frm.add_custom_button() does not add a Custom-Button - frappe

I'm creating custom-buttons in a Client Script. This code works:
// Buttons do appear, everything is fine
frappe.ui.form.on('Article', {
refresh(frm) {
frm.add_custom_button("Hello", () => {
msgprint("Hello");
}, "Greet");
frm.add_custom_button("Ciao", () => {
msgprint("Ciao");
}, "Greet");
}
Then I thought about creating the buttons outside the events with cur_frm, which doesn't work. Why is this so?
// Buttons don't appear
cur_frm.add_custom_button("Hello", () => {
msgprint("Hello");
}, "Greet");
cur_frm.add_custom_button("Ciao", () => {
msgprint("Ciao");
}, "Greet");

cur_frm is deprecated API. Why can't you use frm from one of the JS event methods?

Related

Detect change to modelValue in Vue 3

Is there a way to detect change to modelValue in a custom component? I want to push the change to a wysiwyg editor.
I tried watching modelValue but emitting update for modelValue triggered that watch, which created circular data flow.
Code:
export default {
props: ['modelValue'],
watch: {
modelValue (val) {
this.editor.editor.loadHTML(val)
}
},
mounted () {
this.editor.editor.loadHTML(val)
this.editor.addEventListener('trix-change',
(event) => this.$emit('update:modelValue', event.target.value))
}
}
<TextEditor v-model="someHtml"></TextEditor>
In VueJS v3, the event name for custom v-model handling changed to 'update:modelValue'.
You can listen to these events like this: v-on:update:modelValue="handler"
For a more complete example, lets assume you have a Toggle component with these properties/methods:
...
props: {
modelValue: Boolean,
},
data() {
return {
toggleState: false,
};
},
methods: {
toggle() {
this.toggleState = !this.toggleState;
this.$emit('update:modelValue', this.toggleState);
}
}
...
You can use that Toggle component:
<Toggle v-model="someProperty" v-on:update:modelValue="myMethodForTheEvent"/>
As a side note, you could also v-model on a computed property with a setter; allowing you to internalise your state changes without using the update:modelValue event. In this example, it assumes you v-model="customProperty" on your custom Toggle component.
computed: {
customProperty: {
get() {
return this.internalProperty;
},
set(v) {
this.internalProperty = v;
console.log("This runs when the custom component 'updates' the v-model value.");
},
}
},
I had the same problem and solved it using a slight tweak to the way you call the watch function:
setup(props) {
watch(() => props.modelValue, (newValue) => {
// do something
})
}
Hence, the important thing is to add () => props.modelValue instead of just putting props.modelValue as the first argument of the watch function.
try that:
watch: {
...
modelValue: function(val) {
console.log('!!! model value changed ', val);
},
...

How do I force an observeable to complete?

Kind of a niche question, but I know what the issue is so hopefully someone here can help me out. This is an Observable/RXFire issue, not an xstate issue.
I have this machine that invokes an observable:
export const tribeMachine = Machine(
{
id: "council",
initial: "init",
context: {},
states: {
init: {
invoke: {
id: "gettribes",
src: () =>
collectionData(database.collection("tribes")).pipe(
concatAll(),
map(x => ({ type: "STORE", x }))
),
onDone: "loaded"
},
on: {
STORE: {
actions: "storetribes"
},
CANCEL: "loaded"
}
},
loaded: {
entry: () => console.log("loaded")
},
error: {
entry: () => console.log("error")
}
}
},
{
actions: {
storetribes: (context, event) => console.log("hello")
}
}
);
The way it's supposed to work is that the machine invokes the observable on load, and then once the obs is done emitting its values and calls complete(), invoke.onDone is called and the machine transitions to the 'loaded' state.
When I use a normal observable that i created with a complete() call, or when i add take(#) to the end of my .pipe(), the transition works.
But for some reason the observable that comes from collectionData() from RXFire doesn't send out a 'complete' signal... and the machine just sits there.
I've tried adding a empty() to the end and concat()-ing the observables to add a complete signal to the end of the pipe... but then I found out that empty() is deprecated and it didn't seem to work anyway.
Been banging my head against the wall for awhile. any help is appreciated.
Edit:
Solution:
I misunderstood the purpose of collectionData(). It is a listener, so it's not supposed to complete. I was putting a square peg in round hole. The solution is to refactor the xstate machine so I don't need to call onDone at all.
Thank you for the answers nonetheless.
EDIT2: GOT IT TO WORK.
take(1) can be called BEFORE concatAll(). I thought if you called it first it would end the stream, but it doesn't. The rest of the operators in the pipe still apply. So i take(1) to get the single array, use concatAll() to flatten the array into a stream of individual objects, then map that data to a new object which triggers the STORE action. the store action then sets the data to the context of the machine.
export const tribeMachine = Machine({
id: 'council',
initial: 'init',
context: {
tribes: {},
markers: []
},
states: {
init: {
invoke: {
id: 'gettribes',
src: () => collectionData(database.collection('tribes')).pipe(
take(1),
concatAll(),
map(value => ({ type: 'TRIBESTORE', value })),
),
onDone: 'loaded'
},
on: {
TRIBESTORE: {
actions: ['storetribes', 'logtribes']
},
CANCEL: 'loaded'
}
},
loaded: {
},
error: {
}
}
},
{
actions: {
storetribes: assign((context, event) => {
return {
tribes: {
...context.tribes,
[event.value.id]: event.value
},
markers: [
...context.markers,
{
lat: event.value.lat,
lng: event.value.lng,
title: event.value.tribeName
}
]
}
})
}
}
)
Thanks for everyone's help!
Observables can return multiple values over time, so it is up to collectionData() to decide when to finish (i.e. causing complete() to be called).
However, if you only want to take 1 value from the observable, you can try:
collectionData(database.collection("tribes")).pipe(
take(1),
concatAll(),
map(x => ({ type: "STORE", x }))
),
This will cause the observable to complete once you take 1 value from collectionData().
Note: This may not be the best solution as it depends on how the observable streams you are using works. I am just highlighting that you can use take(1) to just take 1 value and complete the source observable.

How to pass data into the template in Framework7?

I trying to pass data that is fetched from the server to a popup.I tried doing something like this but its not working.Please help-
{
path:'/merchant/:id',
beforeEnter: function (routeTo, routeFrom, resolve, reject) {
console.log(routeTo.params.id);
Meteor.call('getOne',routeTo.params.id,(error,result) => {
if (result) {
resolve(
{
popup: {
el:document.querySelector('#sample-popup')
}
},
// Custom template context
{
context: {
users: result,
},
}
)
}
});
} ,
},
According to the docs, you use resolve callback wrong !
You can also read this understand how to achieve this

In Meteor when trying to access an attribute, I get TypeError: Cannot read property in the console. But the site is working

When trying to read an attribute, meteor gives me a TypeError: Cannot read property 'featuredImage' of undefined error in the browser console. But it reads featuredImage and the site is working fine. How can I get rid of this error? Is it happening because my subscriptions are not yet ready? Is that's the case, how to fix it? (PS : Im using the flow router so I can't wait for subscriptions in the router)
My template code :
Template.About.helpers({
page: () => {
return findPage();
},
featuredImage: () => {
var thisPage = findPage();
return Images.findOne({
"_id": thisPage.featuredImage
});
}
});
function findPage() {
return Pages.findOne({
slug: 'about'
});
}
The router code :
FlowRouter.route('/about', {
name: 'about',
subscriptions: function() {
this.register('page', Meteor.subscribe('pages', 'about'));
this.register('image', Meteor.subscribe('images'));
},
action() {
BlazeLayout.render('MainLayout', {
content: 'About'
});
setTitle('About Us');
},
fastRender: true
});
The subscription is probably not ready yet. FlowRouter provides a utility for dealing with this, your helpers should look like this:
Template.About.helpers({
page: () => {
// If you only need a specific subscription to be ready
return FlowRouter.subsReady('page') && findPage() || null;
},
featuredImage: () => {
// Ensure ALL subscriptions are ready
if ( FlowRouter.subsReady() ) {
var thisPage = findPage();
return Images.findOne({
"_id": thisPage.featuredImage // Probably should be thisPage.featuredImage._id
});
}
return null;
}
});
However, for maximum performance, you should use if (FlowRouter.subsReady('page') && Flowrouter.subsReady('image')) rather than FlowRouter.subsReady() since if you have other pending subscriptions which are large, it will wait for those even though you don't need them.

SemanticUI: how to trigger a form validation maually / in modal

When a modal window is started, there will be no form validation if you use the default way:
$('#someModalWindow')
.modal({
inline: true,
onDeny: function () {
// someting
},
onApprove: function () {
// some action
}
})
.modal('show');
How can a form validation be triggered manually or automatically in the modal window.
I am using meteor below SemanticUI
thanks
I figured out how to do it:
$('#someModalWindow')
.modal({
onDeny: function () {
// someting
},
onApprove: function () {
var validated = $('#myFormId').form('validate form');
if(!validated){
return false;
}
// some action
}
})
.modal('show');
hopefully this can help you.

Resources