Turning jQuery Code into Vue.js

The following is some code I am working on the replace our navbar (someday) with a Vue component.

Vue.component("unix-navbar", {
  template: `<div class="neo-table-border vuenavbar"><div class="flex-item" style="margin-bottom:10px;padding-top:13px;"><a class="vuenavbarhome" :href="url">{{name}}</a></div>
	            <div id="avatar" class="flex-item" v-html="theavatar" v-on:click="OpenMemberPanel()" style="cursor:pointer;" align="center"></div></div>`,
  data() {
    return {
      unixtime: "",
      name: "The UNIX and Linux Forums",
      url: "https://www.unix.com/",
      theavatar:
        '<span  class="alt1" style="font-size:3em;cursor:pointer;float:right;background-color:transparent;margin-right:5px;margin-left:10px;top:0;padding:20px;" ><i style="color:#170072;" class="fas fa-user"></i></span>'
    };
  },
  methods: {
    GetAvatar() {
      if (vbuserId > 0)
        this.theavatar = '<img src="' + vbtheURL + '"  width="60px"></img>';
      else
        this.theavatar =
          '<span  class="alt1" style="font-size:3em;cursor:pointer;float:right;background-color:transparent;margin-right:5px;margin-left:10px;top:0;padding:20px;" ><i style="color:#170072;" class="fas fa-user"></i></span>';
    },
    GetUrl() {
      this.theurl = vbtheURL;
    },
    UpdateTime() {
      this.unixtime = Math.round(new Date().getTime() / 1000);
    },
    OpenMemberPanel() {
      if (vbuserId > 0) openMemberPanel();
      else openLogin();
    }
  },
  created() {
    GetAvatar();
    setInterval(() => {
      this.UpdateTime();
    }, 1000);
  }
});

var navbar =
  new Vue({
    el: "#vue-navbar"
  }) 


  $(function() {
    if (vbtheURL.length > 10)
      $("#avatar").html('<img src="' + vbtheURL + '"  width="60px"></img>');
    else
      $("#avatar").html(
        '<span  class="alt1" style="font-size:3em;cursor:pointer;float:right;background-color:transparent;margin-right:5px;margin-left:10px;top:0;padding:20px;" ><i style="color:#170072;" class="fas fa-user"></i></span>'
      );
  });

Basically, this is working OK; but I used jQuery to make it work.

Does anyone have any suggestions on how to do this using only Vue.js and no jQuery?

OBTW... this methods in the Vue.js code is not being used (or working). That's why I wrote the jQuery code as I could not get the code below to work right in Vue.js:

GetAvatar() {
      if (vbuserId > 0)
        this.theavatar = '<img src="' + vbtheURL + '"  width="60px"></img>';
      else
        this.theavatar =
          '<span  class="alt1" style="font-size:3em;cursor:pointer;float:right;background-color:transparent;margin-right:5px;margin-left:10px;top:0;padding:20px;" ><i style="color:#170072;" class="fas fa-user"></i></span>';
    },

Here is my latest try.... works with jQuery, so I deleted my failed code and only post the working Vue.js and jQuery code:

Vue.component("unix-navbar", {
  template: `<div class="neo-table-border vuenavbar"><div style="margin-bottom:10px;padding-top:13px;"><a class="vuenavbarhome" :href="url">{{name}}</a></div>
              <div id="avatar"  :click="OpenMemberPanel()" style="cursor:pointer;" align="center"></div></div>`,
  data() {
    return {
      unixtime: "",
      name: "The UNIX and Linux Forums",
      url: "https://www.unix.com/"
    };
  },
  methods: {
    OpenMemberPanel() {
      if (vbuserId > 0) openMemberPanel();
      else openLogin();
    }
  }
});

var navbar = new Vue({
  el: "#vue-navbar"
});

$(function() {
  if (vbtheURL.length > 10)
    $("#avatar").html('<img src="' + vbtheURL + '"  width="60px"></img>');
  else
    $("#avatar").html(
      '<span  class="alt1 nav-avatar"><i style="color:#170072;" class="fas fa-user"></i></span>'
    );
});

I see from the source in the test page link you sent that vbuserId and vbtheURL are both already in scope as Javascript variables, which means you can add them to your data function:

  data() {
    return {
      vbuserId: vbuserId,
      vbtheURL: vbtheURL,
      unixtime: '',
      name: "The UNIX and Linux Forums",
      url: "https://www.unix.com/",
      theavatar: ''
    };
  },

And then you can use them in your GetAvatar function:

    GetAvatar() {
      if (this.vbuserId > 0)
        this.theavatar = '<img src="' + this.vbtheURL + '" width="60px"></img>';
      else
        this.theavatar =
          '<span class="alt1" ...></span>';
    },

I guess you need for modify this...

  created() {
    GetAvatar();

to:

  created() {
    this.GetAvatar();
1 Like

Thanks Scott... Sorry, I cannot get your suggestions to work.

This "almost" works:

Vue.component("unix-navbar", {
  template: `<div class="neo-table-border vuenavbar"> <div style="margin-bottom:10px;padding-top:13px;"> <a class="vuenavbarhome" :href="url">{{name}}</a></div> <div id="avatar"  v-on:click="OpenPanel()" style="cursor:pointer;" v-html="theicon" align="center"></div> </div>`,
  data() {
    return {
      thehtml:
        '<span  class="alt1 nav-avatar"><i style="color:#170072;" class="fas fa-user"></i></span>',
      name: "The UNIX and Linux Forums",
      url: "https://www.unix.com/"
    };
  },
  computed: {
    id() {
      return vbuserId;
    },
    theURL() {
      return vbtheURL;
    },
    theavatar() {
      return '<img src="' + this.theURL + '" width="60px"></img>';
    },
    theicon() {
      //if (this.id > 0)
      // return '<img src="' + this.theURL + '" width="60px"></img>';
      return this.thehtml;
    }
  },
  methods: {
    OpenPanel() {
      if (this.userId > 0) openMemberPanel();
      else openLogin();
    }
  }
});

var navbar = new Vue({
  el: "#vue-navbar"
});

But the above code only works for the "theicon" text case in the v-html bindings .... if I change it to "theavatar" in the v-html bindings, it breaks.

In this code, the conditional works based on the vbuserId computed to id in vue.... but the problem that remains is that the "theavatar" case, breaks vue when it does the v-html binding.

The problem in the end was in the vB Template, where $jsGlobalsfromvB was given after the Vue components were loaded, meaning that vbuserId and vbtheURL were not set at the time the components were rendered.

Here's the final, working version:

Vue.component("unix-navbar", {
  template: `<div class="neo-table-border vuenavbar">
              <div class="flex-item" style="margin-bottom:10px;padding-top:13px;">
                <a class="vuenavbarhome" :href="url">{{name}}</a>
              </div>
              <div id="avatar" class="flex-item"  v-html="theavatar" @click="OpenMemberPanel()" style="cursor:pointer;" align="center"></div>
            </div>`,

  data() {
    return {
      unixtime: '',
      name: "The UNIX and Linux Forums",
      url: "https://www.unix.com/",
      theavatar: ''
    };
  },
  methods: {
    GetAvatar() {
      if ( vbuserId > 0 )
        this.theavatar = '<img src="' + vbtheURL + '" width="60px"></img>';
      else
        this.theavatar =
          '<span class="alt1" style="font-size:3em;cursor:pointer;float:right;background-color:transparent;margin-right:5px;margin-left:10px;top:0;padding:20px;" ><i style="color:#170072;" class="fas fa-user"></i></span>';
    },
    UpdateTime() {
      this.unixtime = Math.round(new Date().getTime() / 1000);
    },
    OpenMemberPanel() {
      if ( vbuserId > 0 )
        openMemberPanel();
      else
        openLogin();
    }
  },
  created() {
    setInterval(() => {
      this.UpdateTime();
    }, 1000);

    this.GetAvatar();
  }
});

var navbar =
  new Vue({
    el: "#vue-navbar"
  });
1 Like

Thanks Scott,

On a number of occasions I have had trouble with the order of execution of included Javascript file.

Looking back, it seems obvious that the JS vars from vB global_start should have been before the Vue files. That also seem to explain why I could get it to work as a computed property before.

Thanks for your help sorting this out!

Much appreciated!

Now, I need to figure out why the HTML layout is different between the two conditions (guest vs. logged in user)..... That should be an easy CSS issue (update: confirmed CSS fixed).

OK... here is the cleaned up code for this header component for the navbar:

Vue.js:

Vue.component("unix-navbar", {
  template: `<div class="neo-table-border vue-navbar">
              <div  class="vue-site-info">
                <a class="vue-site-link" :href="url">{{name}}</a>
              </div>
              <div id="avatar"  v-html="theavatar" @click="OpenMemberPanel()"></div>
            </div>`,

  data() {
    return {
      name: "The UNIX and Linux Forums",
      url: "https://www.unix.com/",
      theavatar: ""
    };
  },
  methods: {
    GetAvatar() {
      if (vbuserId > 0)
        this.theavatar = '<img id="avatar-img" src="' + vbtheURL + '"></img>';
      else this.theavatar = '<i id="avatar-icon" class="fas fa-user"></span>';
    },
    OpenMemberPanel() {
      if (vbuserId > 0) openMemberPanel();
      else openLogin();
    }
  },
  created() {
    this.GetAvatar();
  }
});

var navbar = new Vue({
  el: "#vue-navbar"
});

CSS:

<style>
.vue-site-info{
   margin-bottom:10px;
   padding-top:10px;
}
#avatar{
   cursor:pointer;
}
#avatar-img{
   width="60px;
}
.vue-navbar{
   padding:30px 30px 30px 30px;
   display: flex;
   flex-wrap: wrap;
   justify-content: space-between;
}
.vue-site-link{
  font-size:1.2em;
  text-decoration:none;
  
}
#avatar-icon{
  color:#170072;
  font-size:2.5em;
}
</style>

Not sure if it a good idea to use the userid in the client-side code for security reasons, but it's worth a thought. Since the userid is only used to determine which panel is opened and which icon to show, it might be worth a bit of hacking in the dev console to see how this could be exploited.

Otherwise, the main goal of moving the jQuery to Vue.js has been accomplished (thanks and hats off to Scott for helping) and this demo header component for a navbar is basically done. Next would be to move the navbar menu to a Vue.js component and to also move the panels in the header to Vue components.

Obviously, it is much easier to design a new web site from the beginning with Vue other than trying to retrofit the old site into Vue; but on the other hand, having an existing site to modify does provide opportunities to improve the site along the way.

Also, for completeness, I changed the GetAvatar() method to a computed property and it works as well. I personally like this method because it is a few less lines of code and it's easy to use the dev tools Vue debugger to look at the computed data:

Vue.component("unix-navbar", {
  template: `<div class="neo-table-border vue-navbar">
              <div  class="vue-site-info">
                <a class="vue-site-link" :href="url">{{name}}</a>
              </div>
              <div id="avatar"  v-html="theavatar" @click="OpenMemberPanel()"></div>
            </div>`,

  data() {
    return {
      name: "The UNIX and Linux Forums",
      url: "https://www.unix.com/"
    };
  },
  computed: {
    theavatar: function() {
      if (vbuserId > 0)
        return '<img id="avatar-img" src="' + vbtheURL + '"></img>';
      else return '<i id="avatar-icon" class="fas fa-user"></span>';
    }
  },
  methods: {
    OpenMemberPanel() {
      if (vbuserId > 0) openMemberPanel();
      else openLogin();
    }
  }
});

var navbar = new Vue({
  el: "#vue-navbar"
});

Vue debugger:

Here is another question I have. Given this Vue code below, why does id not update when I manually update vbuserId in the dev console?

Vue.component("unix-navbar", {
  template: `<div class="neo-table-border vue-navbar">
              <div  class="vue-site-info">
                <a class="vue-site-link" :href="url">{{name}}</a>
              </div>
              <div id="avatar"  v-html="theavatar" @click="OpenMemberPanel()"></div>
            </div>`,

  data() {
    return {
      name: "The UNIX and Linux Forums",
      url: "https://www.unix.com/",
      id: vbuserId
    };
  },
  computed: {
    theavatar: function() {
      if (this.id > 0)
        return '<img id="avatar-img" src="' + vbtheURL + '"></img>';
      else return '<i id="avatar-icon" class="fas fa-user"></span>';
    }
  },
  watch: {
    id() {
      console.log("The id has changed!");  /* This never logs as expected */
    }
  },
  methods: {
    OpenMemberPanel() {
      if (this.id > 0) openMemberPanel();
      else openLogin();
    }
  }
});

var navbar = new Vue({
  el: "#vue-navbar"
});

I also tried variations of this code, with no joy getting vbuserId changes in the dev console to be reactive in Vue;

Vue.prototype.$id = vbuserId;
Vue.prototype.$avatar = vbtheURL;

Vue.component("unix-navbar", {
  props: ["myid"],
  template: `<div class="neo-table-border vue-navbar">
        <div  class="vue-site-info">
                <a class="vue-site-link" :href="url">{{name}}</a>
              </div>
              <div id="avatar"  v-html="theavatar" @click="OpenMemberPanel()"></div>
            </div>`,

  data() {
    return {
      name: "The UNIX and Linux Forums",
      url: "https://www.unix.com/"
    };
  },
  computed: {
    theavatar: function() {
      if (this.id > 0)
        return '<img id="avatar-img" src="' + this.$avatar + '"></img>';
      else return '<i id="avatar-icon" class="fas fa-user"></span>';
    },
    id: function() {
      return this.$id;
    }
  },
  methods: {
    OpenMemberPanel() {
      if (this.id > 0) openMemberPanel();
      else openLogin();
    }
  }
});

var vm = new Vue({
  el: "#vue-navbar"
});

Tried various Vue.nextTick() and other Vue methods, but no joy either. And also tried many variations of:

vm.$el.myid = vbuserId;
vm.myid = vbuserId;

adding myid[/ICODE ]to data() in the component, but no joy.

Again, I must be missing something very basic in Vue :slight_smile:

Added vbuserId to the component:

  data() {
    return {
      ...
      vbuserId: vbuserId
    };
  },
  computed:{
    ...
    id: function() {
      return  this.vbuserId;
    }

I guess there's no need for a computed value there, as it's not actually computing anything.

What you propose did not change the fact it is not reactive to the JS var.

When I change vbuserId in the dev console, it it worked (was reactive), the avatar would change; but it does not change.

If the conditional in vue is from data() , computed: or from a method : all will change the value internal to Vue, but what I am trying to
do is get Vue to react to changes in the variable in the console which simulates a change outside of Vue.

IMHO, it is not related to data() , computed: or from a method directly, and there is something that needs to be added to get Vue to react (bind) to an external change outside of vue.

Update:

After looking into this; it seems that it's not really in the design of Vue.js to bind and react to Javascript variables outside the Vue instance sandbox.

No big deal... we can manage without this capability and indeed, as Scott has pointed out to me, there are probably good security reasons for this design (Vue.js as a sandbox).

However, in the interest of being complete, I asked this question over at the Vue forums:

Bind Vue.js variable to external Javascript variable for reactivity - Get Help - Vue Forum

My "security" suggestion was just a guess.. probably not a very good one, or the right one for the wrong reasons! Encapsulation is good!

When the Vue instance is instantiated the execution context in JS knows about vbuserId, but that doesn't bind it in any way to the Vue instance. I've googled it to death, and can't find a way to make that happen. But it's certainly easy to update both, outside of the context of the Vue components with, for example, a function:

var vm  =
  new Vue({
    el: "#vue-navbar"
  });

var u = vm.$children["0"];

function updateId(newId) {
  vbuserId = newId || 0;
  u.id = vbuserId;
}

Of course, to anyone who is concerned, this vbuserId is only used for the purposes of displaying an avatar (and only either your own or the default, silhouette one, and doesn't "make you" that user :slight_smile:

Yes, in your example you still need to call a function to update so it does not accomplish what we have set out to do, which is to insure that when vbuserId updates outside of vue, it updates in vue.

If I take your code and in the dev console type:

vbuserId  = 0;

The avatar does not change because your codes does not call the function nor update it.

However, if we do only this:

var timerID = setInterval(getUpdate, 1000);
  function getUpdate ()
    {
       vm.$children["0"].id = vbuserId;
  }

It updates when we change vbuserId in the dev console.

This is the intended result in the browser, to react to changes to an external var change.

The trick, which we have not figured out yet, it do bind vue ( vm in this instance) to vbuserId and react to a change outside of the vm instance. The setInterval() code above does that, but I want do do this without setInterval() using a native vue binding.

I just watched a YT video, and in that video, they also used setInterval() to update vue based on an JS var change. So, I'm still searching YT, Google and the entire world, LOL, for Vue method which binds to an external var and reacts when a change is made in the dev console.

See attached video to see this change in the browser by changing vbuserId in the console.

Note:

The interesting news is that we are now back on the cutting edge of web dev technology with Vue.

The good news is that Vue.js web dev is both powerful and really fun.

I encourage others to learn Vue with us and help us build new components for the forums or for your business or or personal web sites.

OBTW: Here is the current working code with setInterval() . Yes, we could move this function inside of vue (or alias vm.$children["0"] ), but I am still looking for a better solution where setInterval() is not required and we can use vue to bind directly to changes in an external var (changes in the dev console, not when a page reloads).

Vue.component("unix-navbar", {
  template: `<div class="neo-table-border vue-navbar"> 
	<div  class="vue-site-info">
                <a class="vue-site-link" :href="url">{{name}}</a>
              </div>
              <div id="avatar"  v-html="theavatar" @click="OpenMemberPanel()"></div>
            </div>`,

  data() {
    return {
      name: "The UNIX and Linux Forums",
      url: "https://www.unix.com/",
      id: vbuserId,
      avatar: vbtheURL
    };
  },
  computed: {
    theavatar: function() {
      if (this.id > 0)
        return '<img id="avatar-img" src="' + this.avatar + '"></img>';
      else return '<i id="avatar-icon" class="fas fa-user"></span>';
    }
  },
  methods: {
    OpenMemberPanel() {
      if (this.id > 0) openMemberPanel();
      else openLogin();
    }
  }
});

var vm = new Vue({
  el: "#vue-navbar"
});

var timerID = setInterval(getUpdate, 1000);
function getUpdate() {
  vm.$children["0"].id = vbuserId;
}

Also note that it has been nearly 24 hours and no one has responded to my question on this over in the vue forums, but it's the weekend so like here, maybe people are offline:

Bind Vue.js variable to external Javascript variable for reactivity - Get Help - Vue Forum

In this example, we move setInterval() inside the Vue component.

However, this is still suboptimal, as the goal is to get Vue to react (be reactive) and update to changes to external JS vars in the dev console without reloading the page. This is simply a rearrangement of the code in the prior post. I want to bind the Vue instance to external var changes outside the DOM (outside the Vue instance), and have Vue react to this. The means that when we do vbuserId = "some new value" in the console, the avatar will change. The code below works, but only because it polls vbuserId every second.

We are still searching for a way to do this ....

Vue.component("unix-navbar", {
 template: `<div class="neo-table-border vue-navbar"> 
	<div  class="vue-site-info">
                <a class="vue-site-link" :href="url">{{name}}</a>
              </div>
              <div id="avatar"  v-html="theavatar" @click="OpenMemberPanel()"></div>
            </div>`,

  data() {
    return {
      name: "The UNIX and Linux Forums",
      url: "https://www.unix.com/",
      id: vbuserId,
      avatar: vbtheURL
    };
  },
  computed:{
     theavatar: function () {
      if ( this.id > 0 )
        return '<img id="avatar-img" src="' + this.avatar + '"></img>';
      else   
       return '<i id="avatar-icon" class="fas fa-user"></span>';
    }
  },
  methods: {
    OpenMemberPanel() {
      if ( this.id > 0 )
        openMemberPanel();
      else
        openLogin();
    },
    GetUID() {
       this.id = vbuserId;
    }
  },
   created(){
    var timerID = setInterval(this.GetUID, 1000);
  }

});

var vm  =
  new Vue({
    el: "#vue-navbar"
  });

Googl'ing until all links are purple (as Scott says in PMs, LOL), I find that the " setInterval() " solution remains king:

javascript - Binding Vue component data to external state - Stack Overflow

FYI, I commented out the setInterval() method in the Vue instance and tried this, but it did not meet the stated objective:

Vue.nextTick()
  .then(function () {
    vm.$children["0"].id = vbuserId;
  })

The hunt continues ....... :slight_smile:

We got a suggestion from over a the vue forums:

Bind Vue.js variable to external Javascript variable for reactivity - Get Help - Vue Forum

Alex:

var app = new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue!'
    }
})

var myVar = {
    get a() {
        return app.message
    },
    set a(b) {
	app.message = b
    }
}
<img :src="vbtheURL" v-if="vbuserId">
<span :style="avatarStyle" v-else>
computed: {
    avatarStyle() {
        return {
             'font-size': '3em',
             cursor: 'pointer',
             /* etc... */
        }
    }
}

Thank you Alex from the Vue forums .... we will try these suggestions and post back our results.