Dialog not appering in DB2, only the page is faded

Yes, msg._client contains socketId. The question is how can I compare the incoming id with 'my' id?

Partially answering my own question, in the template watch msg function the sessionId is already overwritten with the current sessionId so I don't see a way to access the id in the incoming message. I have found a workaround by copying _client to _incomingClient before the template and then testing it in the watch msg function, but that is obviously not ideal.

[Edit] Well I thought it did but now it isn't working, it is getting late so brain fade is probably the problem. Will try again tomorrow.

1 Like

To compare socket id's

watch: {
      msg: function(){
          if(this.msg.payload != undefined){
              //check if msg origin is dashboard.
              if(this.msg._client){
                //compare socket id's. Dont show if not from same instance
                if(this.msg._client.socketId !== this.$socket.id){                               
                  return
                }
              }
              else{
                // to not allow msg's from server, uncomment next line 
                // retrun
              }
              
              // if reached here, the msg is even from same dashboard
              // or it is server initiative and it is allowed to show. (all instances will do)              
              this.dialogData = this.msg.payload;
              this.dialogData.shown = false
              this.msg.payload = undefined
          }
      }
  }

Brilliant, that seems to be working. I will post my full solution when I have made a few more refinements.

This is my working version of an ok/cancel dialog template.

<!-- 
  This pops up an OK/Cancel dialog when sent a payload of the form
  {
    content: "The text to display in the window",
    okText: "OK",         // optional, if not present defaults to OK
    cancelText: "Cancel"   // optional, defaults to Cancel
  }
  All properties may contain embedded html tags such as <br/>
  If msg._client is present and contains a socketId (as will be the case if the template is triggered
  from another widget such as a button) then the popup will only appear on the matching session.
  For example, if a button widget is fed into the template then the popup will appear only on the browser where the button
  was clicked.
  If msg._client is not present then the popup will appear on all connected browsers and will have to be acknowleged on each one.

  When one of the buttons is clicked a message is sent with msg.payload containing the ok or cancel text
-->
<template>
  <v-dialog width="auto" v-model="showDialog">
    <v-card color="white" v-click-outside="{handler: onClickOutside}">
      <v-card-text>
        <div class="text-h6" v-html="content"></div>
      </v-card-text>
      <v-card-actions class="justify-end">
        <v-btn variant="elevated" size="large" @click="cancelDialog" v-html="cancelText"></v-btn>
        <v-btn variant="elevated" size="large" @click="okDialog" v-html="okText"></v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

<script>
export default {
  data() {
    return {
        dialogData:null
    }
  },
  watch: {
      msg: function(){
          // only show the dialog if msg.payload is an object and the socket id in the message
          // matches our socket id (which means the popup was initiated from this session) or
          // there is no _client property present which indicates it should be shown on all sessions.
          if (typeof this.msg.payload === "object" && (!this.msg._client || this.msg._client.socketId === this.$socket.id)) {                  
              this.dialogData = this.msg.payload;
              if (!this.dialogData.okText) {
                this.dialogData.okText = "OK"
              }
              if (!this.dialogData.cancelText) {
                this.dialogData.cancelText = "Cancel"
              }
              this.dialogData.show = true
          }
          // prevent redraw on deploy
          this.msg.payload = null
      }
  },
  methods:{
    okDialog:function(){
      this.dialogData.show = false;
      this.msg.payload = this.dialogData.okText
      this.send(this.msg);
    },
    cancelDialog:function(){
      this.dialogData.show = false;
      this.msg.payload = this.dialogData.cancelText
      this.send(this.msg);
    },
    onClickOutside () {
      this.dialogData.show = false;
      this.msg.payload = this.dialogData.cancelText
      this.send(this.msg);
    },
  },
  computed : {
    content:function(){
      return this.dialogData?.content ?? ""
    },
    okText:function(){
      return this.dialogData?.okText ?? "OK"
    },
    cancelText:function(){
      return this.dialogData?.cancelText ?? "Cancel"
    },
    showDialog: function (){
      return this.dialogData?.show === true
    }
  },
  unmounted () {
    this.dialogData = null
  }
}
</script>

[Edit] Minor change to above to improve robustness as suggested below

[Further Edit] Added explanatory comment after

  watch: {
      msg: function(){
1 Like

I see you have taken good precaution by using conditional chaining and nullish coalescing...

but you will get errors complaints in the browser console here ā†“ when dialogData is null (as it is in your data, by default)

so you should probably change ā†‘ to ā†“ to take advantage of that safe computed property.

        <div class="text-h6" v-html="content"></div>

alternatively, lose the computed property and use

        <div class="text-h6" v-html="dialogData?.content ?? ''"></div>

That should just have read v-html="content" as with the other computed text. Though in fact it doesn't show anything in the console as it currently is.
Does the template get interpreted even when the dialog is not visible?

Template yes, but Vue might not "touch" that code until the element is rendered - but still worth doing it right (as you now have).

OK, thanks. I will amend the previous post accordingly.

To improve readability and make it easier to adjust maybe replace this with validation function something like isValidInput(this.msg). Use "return early" strategy in it.

I (virtually) never use early returns in a function. I have too many times seen bugs introduced by inserting code later in a function, not noticing that it may not get executed due to an early return statement. That, along with writing if statements without braces are two of my pet hates. for example

if (condition) 
    x = true;
else
    x=false;
// carry on.

It is too easy to insert indented code after x=false, thinking that it will be inside the else clause.

I did intend to add a comment above that line clarifying it, though, so thanks for pointing that out. I will add a comment to the original post.

I just don't argue on any of such topics ..

OK one time cos for input validation there's no else thus it is most simple thing in the world to read.
for example :

 isValidValue:function (val){
                if(val === null){
                    return false
                }
                if(val === undefined){
                    return false
                }
                if(val === ""){
                    return false
                }
                if(!['number', 'string', 'boolean'].includes(typeof value)){
                    return false
                }
                if(this.options.findIndex(option => option.value === val ) == -1){
                    return false
                }
                return true

            }

I am not sure that is any easier to understand that the if statement with the explanatory comment. Does it pick up any conditions that the original does not?

Edit: what is this.options?

It is example code of input validation using return early strategy, not related to dialog code anyhow.

Oh, sorry I misunderstood. I do agree that in very simple cases such as you have shown then early returns may be acceptable, personally I would still prefer not to do it, simple functions tend to get more complex over time.

I used @Colin's example and modified a bit. I wanted a dialog where the user can pick two dates, but also choose from one of the preset intervals from a combobox:
image

First of all I had to remove the onOutsideClick handler from the v-card as the combobox selection always triggered this event (maybe the menu for the items grows beyond the bounday of the dialog). I assigned the combobox to variable preset using v-model, is included the preset in the export section and created a computed function to capture when the selection changes and I can update the from-to dates. But it looks like this computed function never fires. What did I do wrong?

[{"id":"ba23c5db8f9b0c83","type":"ui-template","z":"0e3249ddee2000e3","group":"","page":"","ui":"cb79bc4520925e32","name":"From-To Dates dialog","order":0,"width":0,"height":0,"head":"","format":"<!-- \n  This pops up an OK/Cancel dialog when sent a payload of the form\n  {\n    title: \"Kitchen thermostat\",\n    content: \"Enter the target temperature\",\n    okText: \"OK\",         // optional, if not present defaults to OK\n    cancelText: \"Cancel\",   // optional, defaults to Cancel\n    fromdatelabel: \"From date\",\n    fromdatevalue: \"2024-03-22\",\n    todatelabel: \"To date\",\n    todatevalue: \"2024-04-22\"\n  }\n  All properties may contain embedded html tags such as <br/>\n  If msg._client is present and contains a socketId (as will be the case if the template is triggered\n  from another widget such as a button) then the popup will only appear on the matching session.\n  For example, if a button widget is fed into the template then the popup will appear only on the browser where the button\n  was clicked.\n  If msg._client is not present then the popup will appear on all connected browsers and will have to be acknowleged on each one.\n\n  When one of the buttons is clicked a message is sent with msg.payload containing the ok or cancel text\n-->\n<template>\n  <v-dialog width=\"auto\" v-model=\"showDialog\">\n    <v-card color=\"white\" >\n      <v-toolbar color=\"primary\">\n        <v-card-title>\n          <span>{{title}}</span>\n        </v-card-title>\n      </v-toolbar>\n      <v-card-text>\n        <div v-html=\"content\"></div>\n        <div class=\"d-flex py-2 justify-space-between\"></div>\n        <v-row>\n          <v-col>\n            <v-text-field :label=\"fromdatelabel\" v-model=\"fromdatevalue\" type=\"date\"></v-text-field>\n          </v-col>\n          <v-col>\n            <v-text-field :label=\"todatelabel\" v-model=\"todatevalue\" type=\"date\"></v-text-field>\n          </v-col>\n        </v-row>\n        <div class=\"d-flex py-2 justify-space-between\"></div>\n        <v-row>\n          <v-col>\n            <v-combobox label=\"Presets\"\n              :items=\"['Today', 'Yesterday', 'Last 7 days', 'This week', 'Last Week', 'This Month', 'Last Month']\"\n              v-model=\"preset\"></v-combobox>\n          </v-col>\n        </v-row>\n      </v-card-text>\n\n      <v-card-actions class=\"justify-end\">\n        <v-btn density=\"compact\" variant=\"outlined\" size=\"large\" @click=\"cancelDialog\" v-html=\"cancelText\"></v-btn>\n        <v-btn density=\"compact\" variant=\"outlined\" size=\"large\" @click=\"okDialog\" v-html=\"okText\"></v-btn>\n      </v-card-actions>\n    </v-card>\n  </v-dialog>\n</template>\n\n<script>\n  export default {\n  data() {\n    return {\n        dialogData:null,\n        fromdatevalue: Date.now(),\n        todatevalue: Date.now(),\n        preset: ''\n    }\n  },\n  watch: {\n      msg: function(){\n          // only show the dialog if msg.payload is an object and the socket id in the message\n          // matches our socket id (which means the popup was initiated from this session) or\n          // there is no _client property present which indicates it should be shown on all sessions.\n          if (typeof this.msg.payload === \"object\" && (!this.msg._client || this.msg._client.socketId === this.$socket.id)) {                  \n              this.dialogData = this.msg.payload;\n              if (!this.dialogData.okText) {\n                this.dialogData.okText = \"OK\";\n              }\n              if (!this.dialogData.cancelText) {\n                this.dialogData.cancelText = \"Cancel\";\n              }\n              this.dialogData.show = true;\n              this.fromdatevalue = this.msg.payload?.fromdatevalue ?? \"\";\n              this.todatevalue = this.msg.payload?.todatevalue ?? \"\";\n          }\n          // prevent redraw on deploy\n          this.msg.payload = null\n      },\n      presetSelected: function() {\n        this.fromdatevalue = new Date();\n        this.todatevalue = this.fromdatevalue + 24*60*60*1000;\n      }\n  },\n  methods:{\n    okDialog:function(){\n      this.dialogData.show = false;\n      this.msg.payload = { action: this.dialogData.okText, fromdatevalue: this.fromdatevalue, todatevalue: this.todatevalue };\n      this.send(this.msg);\n    },\n    cancelDialog:function(){\n      this.dialogData.show = false;\n      this.msg.payload = { action: this.dialogData.cancelText };\n      this.send(this.msg);\n    },\n    onClickOutside () {\n      this.dialogData.show = false;\n      this.msg.payload = { action: this.dialogData.cancelText };\n      this.send(this.msg);\n    }\n  },\n  computed : {\n    title:function(){\n      return this.dialogData?.title ?? \"\"\n    },\n    content:function(){\n      return this.dialogData?.content ?? \"\"\n    },\n    okText:function(){\n      return this.dialogData?.okText ?? \"OK\"\n    },\n    cancelText:function(){\n      return this.dialogData?.cancelText ?? \"Cancel\"\n    },\n    showDialog: function (){\n      return this.dialogData?.show === true\n    },\n    fromdatelabel: function (){\n      return this.dialogData?.fromdatelabel ?? \"\"\n    },\n    todatelabel: function (){\n      return this.dialogData?.todatelabel ?? \"\"\n    },\n    preset: function() {\n      console.log(preset);\n      this.fromdatevalue = new Date();\n      this.todatevalue = this.fromdatevalue + 24*60*60*1000;\n    }\n  },\n  unmounted () {\n    this.dialogData = null\n  }\n}\n</script>","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"widget:ui","className":"","x":400,"y":2240,"wires":[["3a4255f2ffce7ddc"]]},{"id":"915ca95b708cb602","type":"inject","z":"0e3249ddee2000e3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Hello","payload":"{\"title\":\"Date filter\",\"content\":\"Specify the from and to date for the filter below\",\"okText\":\"OK\",\"cancelText\":\"Cancel\",\"fromdatelabel\":\"From date\",\"fromdatevalue\":\"2024-03-22\",\"todatelabel\":\"To date\",\"todatevalue\":\"2024-04-22\"}","payloadType":"json","x":160,"y":2240,"wires":[["ba23c5db8f9b0c83"]]},{"id":"3a4255f2ffce7ddc","type":"debug","z":"0e3249ddee2000e3","name":"debug 361","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":610,"y":2240,"wires":[]},{"id":"cb79bc4520925e32","type":"ui-base","name":"My UI","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false}]

Please ignore again. After posting this, I started reading the discussion here: VueJs - where to store my data
And realized computed methods work the other way around, not how I wanted to use them, I need a watch method... changing the code now...

[30 minutes later]

OK, sorry, lost the plot again.
so I bing the combovox to a value using v-model:

            <v-combobox label="Presets"
              :items="['Today', 'Yesterday', 'Last 7 days', 'This week', 'Last Week', 'This Month', 'Last Month']"
              v-model="preset"></v-combobox>

And I include the this preset value in the data model:

  data() {
    return {
        dialogData:null,
        fromdatevalue: Date.now(),
        todatevalue: Date.now(),
        preset: ''
    }

And I create a watch function to handle when this is getting changed:

      preset: function() {
        console.log(preset);
        var today = new Date();
        this.fromdatevalue = today.toISOString().split('T')[0];
        this.todatevalue = new Date(today.getTime() + 24*60*60*1000).toISOString().split('T')[0];
      }

But how do I get what option was selected in the combobox?

Something like this (just a rough outline)

 watch: {
      preset: function() {
        console.log(this.preset)

        if (this.preset === 'Today' {
          this.today()

        } elseif (this.preset === 'Yesterday') {
          this.yesterday()
       
        } .....

        this.send({payload: {start: this.fromdatevalue, end: this.todatevalue}})
}

methods:  {
    today: function() {
        let today = new Date();
        this.fromdatevalue = today.toISOString().split('T')[0];
        this.todatevalue = new Date(today.getTime() + 24*60*60*1000).toISOString().split('T')[0];
  
    },

    yesterday: function() { 
     .
     .
    },

  },

Thanks a lot. I am making this mistake over and over again. Forgetting to add this. for my variables. Of course it all makes sense now. Thanks a again. :man_facepalming:

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.