Dear Taher,
Since all suggestions in previous messages were conceptual, perhaps they were not made clear enough.
Of course that if you use provide
you will have to use inject
at the other end. But once you start to add custom scripts inline in a screen, you may need to coordinate them. For example, things from a form, with things from a dialog, and a subscreen, and a dynamic container, etc. Here, provide/inject and the event bus would be useful.
Going back to the origin of the discussion, you wanted to interact with Vue directly from a screen XML.
So let us see a working example (a proof of concept) which does not need to override anything in Moqui, just markup and scripts directly in the screen XML.
Let us take the Moqui Example component. Then, replace the EditExample.xml
screen with the one below.
Basically, as it was exposed in a previous message, you can define Vue components inline with <component :is="/* options object */"></component>
.
In the following example, a custom component is defined at the beginning of the screen widgets. Then, all the original screen content is inside this component’s slot.
This way, you can expose slot props for the rest of the screen. The mySlotProps
in the example.
The tricky part is that if you define a component on the fly inside the form’s slot, then it will be destoyed and created again every time the form is rendered, because you are using a object definition directly in the :is attribute. This can be solved just reusing a component object. Instead of registering a component in a external js, this component options object is defined in the top custom component and exposed in mySlotProps
. This is the sampleComp
in the example.
Then, inside the form, an additional field is defined with <render-mode>
. Because now we are inside the Moqui Vue form’s slot, we have the formProps
available, which is the slot props provided by the mForm component. The formProps has a fields
property with the current values of all fields in the form.
In this example, the component sampComp
, just as a proof of concept, allows to bind a value with v-model. We use this for binding the Example entity comments
field, so that we will be able to set its value programmatically. Then, we bind the field exampleTypeEnumId
that we watch with a watcher, and every time it changes we set a value in comments
.
This just demonstrates that we can react to changes in form fields and to modify their values programmatically from a screen XML.
Just for completion, mySlotProps
exposes a method too, demonstrating how we can provide a method to other parts of the screen.
Essentially, mySlotProps
is a simplied way to achieve a provide/inject effect.
As you see, inside <render-mode>
we can use Freemarker, Quasar components and Moqui Vue components. So there is a lot of flexibility here.
To sum up, you can define Vue components on the fly in a screen XML, without previous registration and without external js files. Just as you requested.
Of course, if what you need is to interact more deeply with Moqui components (such as reacting to autocomplete events, etc.) you may require to redefine your own components anyway, but perhaps with simple wrapper components defined inline and Freemarker inside <render-mode>
you may solve several of your use cases.
<?xml version="1.0" encoding="UTF-8"?>
<!--
This software is in the public domain under CC0 1.0 Universal plus a
Grant of Patent License.
To the extent possible under law, the author(s) have dedicated all
copyright and related and neighboring rights to this software to the
public domain worldwide. This software is distributed without any
warranty.
You should have received a copy of the CC0 Public Domain Dedication
along with this software (see the LICENSE.md file). If not, see
<http://creativecommons.org/publicdomain/zero/1.0/>.
-->
<screen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/xml-screen-2.1.xsd"
default-menu-title="Example" default-menu-index="1">
<parameter name="exampleId" required="true"/>
<transition name="updateExample"><service-call name="moqui.example.ExampleServices.updateExample"/>
<default-response url="."/></transition>
<transition name="edit"><path-parameter name="exampleId"/><default-response url=".">
<parameter name="exampleId" from="exampleId"/></default-response></transition>
<transition name="features"><path-parameter name="exampleId"/>
<default-response url="../EditExampleFeatureAppls">
<parameter name="exampleId" from="exampleId"/>
<parameter name="test" value="foo"/>
</default-response>
</transition>
<actions>
<entity-find-one entity-name="moqui.example.Example" value-field="example"/>
<log message="EditExample exampleId [${exampleId}] example [${example}]"/>
</actions>
<widgets>
<!-- Opening of a custom component for providing slot data.
Note that with text template="true" we can use FreeMarker here.
You can also use Quasar and Moqui Vue components -->
<render-mode>
<text template="true" type="qvt"><![CDATA[
<component :is="{
data: function() { return {
message: 'This is the Example ${exampleId}',
// An options object for defining a component.
// We do this here so we can reuse the object. See more comments below.
// Just a proof of concept.
sampleComponent: {
props: ['value', 'fieldToWatch', 'doSomething'],
data: function() { return {
sample: 'This is the sample component.',
counter: 0
} },
watch: {
fieldToWatch: function(newVal,oldVal) {
this.counter++;
this.$emit('input', 'Hello, this field is filled dynamically after changing other field from '
+ oldVal + ' to ' + newVal);
moqui.notifyGrowl({type:'info', title:'Field filled in dynamically!'});
}
},
// created() just to check that this component is created only once.
created: function() { this.sample = this.sample + ' Created ' + Date.now() },
template: `<div>
<p>{{sample}}</p>
<p>Watched field has changed {{counter}} times, now it is {{fieldToWatch}}</p>
<p><q-btn @click="doSomething">Click me for doing something!</q-btn></p>
</div>`
}
} },
methods: {
doSomething: function() { window.alert('This works!') }
},
template: `<div><slot :message="message" :sampleComp="sampleComponent" :method="doSomething"></slot></div>`
}" v-slot="mySlotProps">
]]></text>
</render-mode>
<section name="ExampleMenu" condition="example"><widgets>
<label text="Test/Example Links:"/>
<link url="edit" text="Edit" link-type="anchor"/>
<link url="features" text="Features" link-type="anchor"/>
<link url="/apps/example/Example/EditExample/features/${exampleId}" text="Features - Path Parameter" link-type="anchor"/>
</widgets></section>
<form-single name="UpdateExample" transition="updateExample" map="example">
<auto-fields-service service-name="moqui.example.ExampleServices.create#Example"/>
<!-- for the auto-service, basically the entity and operation: <auto-fields-service service-name="create#Example"/> -->
<field name="exampleId"><default-field><display/></default-field></field>
<field name="exampleTypeEnumId">
<default-field title="Type" tooltip="This is the type of example">
<auto-widget-entity entity-name="Example" field-type="edit"/>
<!-- the auto-widget-entity element will basically produce this:
<drop-down allow-empty="false">
<entity-options text="${description}">
<entity-find entity-name="moqui.basic.Enumeration">
<econdition field-name="enumTypeId" value="ExampleType"/>
<order-by field-name="description"/>
</entity-find>
</entity-options>
</drop-down> -->
</default-field>
</field>
<field name="statusId">
<default-field title="Status" tooltip="This is the status of the example">
<drop-down allow-empty="false" current-description="${example?.'Example#moqui.basic.StatusItem'?.description}">
<entity-options key="${toStatusId}" text="StatusTransitionNameTemplate">
<entity-find entity-name="moqui.basic.StatusFlowTransitionToDetail">
<econdition field-name="statusId" from="example.statusId"/>
</entity-find>
</entity-options>
</drop-down>
</default-field>
</field>
<field name="exampleName"><default-field tooltip="The name of the example"><text-line/></default-field></field>
<field name="description"><default-field tooltip="The description of the example"><text-line/></default-field></field>
<!-- uncomment to see example of a render-mode embedded in a field:
<field name="testRenderMode"><default-field>
<render-mode><text type="html,vuet,qvt"><![CDATA[<span><div>This is test HTML text.</div><div>This is another line.</div></span>]]></text></render-mode>
</default-field></field>
-->
<!-- A field in a form to demonstrate how to interact with Vue components from a Moqui screen -->
<field name="myExperiment">
<default-field>
<render-mode>
<text type="qvt"><![CDATA[
<!-- Now we are including markup inside the Moqui Vue form slot, therefore,
we have formProps available as slot props. It has a 'field' property
with the current values of all fields in the form -->
<hr>
<p>Let's try mySlotProps: {{mySlotProps.message}}</p>
<hr>
<!-- Defining a component directly with an options object inline is handy, but doing it
inside a form slot will make it to be destroyed and created again every time
the form is rendered (which happens when a field changes). This is not ideal and we will
not be able to use watchers, etc. -->
<keep-alive> <!-- keep-alive has nothing to do in this case, just for demonstrating it -->
<component :is="{
data: function() { return {
myStuff: 'Hey, I\'m a component defined on the fly, inside a mForm slot. Type in a form field and you will see that I am created again.'
}},
created: function() { this.myStuff = this.myStuff + ' Created ' + Date.now()},
template: `<p>{{myStuff}}</p>`
}"></component>
</keep-alive>
<hr>
<p>Try to change the value of the "Type" field in the dropdown and you will see what happens.</p>
<!-- The destroy/create problem does not happen with registered components -->
<!-- And it does not happen either if we reuse an object -->
<component :is="mySlotProps.sampleComp"
v-model="formProps.fields.comments"
:fieldToWatch="formProps.fields.exampleTypeEnumId"
:doSomething="mySlotProps.method"></component>
<hr>
<p>This is what you have in formProps:</p>
<pre>{{formProps.fields}}</pre>
]]></text>
</render-mode>
</default-field>
</field>
<field name="submitButton"><default-field title="Update"><submit/></default-field></field>
<field-layout>
<field-ref name="exampleId"/>
<fields-not-referenced/>
<field-row><field-ref name="exampleSize"/><field-ref name="exampleDate"/></field-row>
<field-row><field-ref name="testDate"/><field-ref name="testTime"/></field-row>
<field-accordion active="1">
<field-group title="Special Fields">
<field-ref name="auditedField"/>
<field-ref name="encryptedField"/>
</field-group>
<field-group title="Validated Fields">
<field-ref name="exampleEmail"/>
<field-ref name="exampleUrl"/>
</field-group>
<field-group title="My experiment">
<field-ref name="myExperiment"/>
</field-group>
</field-accordion>
<!-- <field-ref name="testRenderMode"/> -->
<field-ref name="submitButton"/>
</field-layout>
</form-single>
<!-- Closing custom component -->
<render-mode>
<text type="qvt"><![CDATA[
</component>
]]></text>
</render-mode>
</widgets>
</screen>
Best,
Francisco Faes
www.blurbiness.com