Profile Toolbar, Single App, Vue Events, Component Buttons

Creates a single app which manages multiple toolbars, one for each profile. Uses Vue's event handling instead of generic event binding. Each button is its own component.

by Rob Cameron

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/x-template" id="profile-toolbar">
  <div class="profile-toolbar">
    <ul>
      <li>Following: {{ isFollowing }}</li>
      <li>Favorite: {{ isFavorited }}</li>
      <li>Lists: {{ isListed }}</li>
      <li>VouchedFor: {{ isVouchedFor }}</li>
      <li>HasNote: {{ hasNote }}</li>
    </ul>

		<action-button attribute="vouches" label-on="Remove Vouch" label-off="Add Vouch"></action-button>
    <action-button attribute="following" label-on="Unfollow" label-off="Follow"></action-button>
  </div>
</script>

<script type="text/x-template" id="action-button">
	<button @click="toggle">
    <span v-if="$parent.isVouchedFor">{{ labelOn }}</span>
    <span v-else>{{ labelOff }}</span>
  </button>
</script>

<div id="profiles">
  <div class="profile" data-id="1" data-username="rob" data-following="1,2,3" data-favorites="1,3,5" data-lists="4,5,6" data-vouches="7,8,9" data-notes="1,5,9">
    <h1>Rob</h1>
    <profile-toolbar
      :profile="profiles['rob']"
      :key="profiles['rob'].id"></profile-toolbar>
  </div>
  
  <div class="profile" data-id="2" data-username="joel" data-following="2,3" data-favorites="3,5" data-lists="4,5,6" data-vouches="1,8,9" data-notes="5,9">
    <h1>Joel</h1>
    <profile-toolbar
      :profile="profiles['joel']"
      :key="profiles['joel'].id"></profile-toolbar>
  </div>
</div>

CSS

body {
  font-family: 'Helvetica Neue', sans-serif;
}
h1 {
  margin: 0;
  padding: 0;
}
ul {
  margin: 1em 0;
  padding: 0;
  list-style: none;
}
.profile {
  display: inline-block;
  border: 1px solid #cccccc;
  padding: 1em;
  margin: 1em;
}

JavaScript

// id of the user viewing this page
var userId = 1;

// Helper method that turns the data attributes of the profile 
// into an array of integers (by default they'll be an array 
// of strings)
var attributeToData = function($profile, name) {
	return $profile.data(name).toString().split(',').map(function(id) {
  	return parseInt(id);
  });
};

// Fakes make an ajax request to toggle a vouch/follow on/off and
// then responds with the list of vouches/follows the user now has.
var ajax = function(prop, component, callback) {
  var obj = {}
  obj[prop] = [component.$parent.profile[prop].indexOf(userId) == -1 ? 1 : null];
  
  $.post('/echo/json/', {
    json:JSON.stringify(obj)
  }, function(data) {
    callback.call(component, data);
  });
}


// build data structure for Vue
var profilesData = {};

$.map($('.profile'), function(profile) {
	var $profile = $(profile);
  profilesData[$profile.data('username')] = {
    id: $profile.data('id'),
    username: $profile.data('username'),
    following: attributeToData($profile, 'following'),
    favorites: attributeToData($profile, 'favorites'),
    lists: attributeToData($profile, 'lists'),
    vouches: attributeToData($profile, 'vouches'),
    notes: attributeToData($profile, 'notes')
  };
});



// profile-toolbar is a Vue component with its own template
Vue.component('profile-toolbar', {
	template: '#profile-toolbar',
  props: ['profile'],
  computed: {
    isFollowing: function() {
      return this.profile.following.indexOf(userId) != -1;
    },
    isFavorited: function() {
      return this.profile.favorites.indexOf(userId) != -1;
    },
    isListed: function() {
      return this.profile.lists.indexOf(userId) != -1;
    },
    isVouchedFor: function() {
      return this.profile.vouches.indexOf(userId) != -1;
    },
    hasNote: function() {
      return this.profile.notes.indexOf(userId) != -1;
    }
  }
});

Vue.component('action-button', {
	template: '#action-button',
  props: ['attribute', 'labelOn',...