I am trying to get help with a particular vue-tables-2 package implementation. In order to do so, I am trying to set up a jsfiddle and keep getting a t is undefined error for even the most basic implementation. Has anyone else run into this error? I suspect it has to do with the importing of dependencies, but cant seem to resolve it.
I appreciate any suggestions on how to get the jsfiddle up and running.
HTML
<div id="app">
<div class="col-md-8 col-md-offset-2">
<div id="people">
<v-server-table url="https://jsonplaceholder.typicode.com/users" :columns="columns" :options="options">
</v-server-table>
</div>
</div>
</div>
JavaScript
Vue.use(VueTables.ServerTable);
new Vue({
el: "#people",
data: {
columns: ['name', 'username'],
options: {
// see the options API
}
}
});
https://jsfiddle.net/kbpq5vb3/35/
It seems the server is not providing the correct data format vue-tables-2 is requiring as stated in the docs:
You need to return a JSON object with two properties:
data : array - An array of row objects with identical keys.
count: number - Total count before limit.
If you can't change what the server returns, you probably have to use the client table where you can grab the data with axios.
A minimal client table example using axios to grab data.
https://jsfiddle.net/kbpq5vb3/38/
If you can't change what the server returns, you probably have to use
the client table where you can grab the data with axios.
Not necessarily. You can stick with the server component and use the requestAdapter and responseAdapter options to mould the request and the response to the expected format.
For Example (Using Github's API):
<div id="app">
<h3>
Vue Tables 2 - Server Side Demo
</h3>
<div class="col-md-8 col-md-offset-2">
<div id="people">
<v-server-table url="https://api.github.com/users/matfish2/repos" :columns="columns" :options="options">
</v-server-table>
</div>
</div>
</div>
JavaScript
Vue.use(VueTables.ServerTable);
new Vue({
el: "#people",
methods: {
formatDate(date) {
return moment(date).format('DD-MM-YYYY HH:mm:ss');
}
},
data: {
columns: ['name', 'created_at', 'updated_at', 'pushed_at'],
tableData: [],
options: {
perPage: 25,
perPageValues: [25],
orderBy: {
column: 'name',
ascending: true
},
requestAdapter(data) {
return {
sort: data.orderBy,
direction: data.ascending ? 'asc' : 'desc'
}
},
responseAdapter({data}) {
return {
data,
count: data.length
}
},
filterable: false,
templates: {
created_at(h, row) {
return this.formatDate(row.created_at);
},
updated_at(h, row) {
return this.formatDate(row.updated_at);
},
pushed_at(h, row) {
return this.formatDate(row.pushed_at);
}
}
}
}
});
https://jsfiddle.net/matfish2/js4bmdbL/
Related
I'm pulling data from a API and using jQuery's getJson method to extract the data I'm then trying to assign the data to a vue array object by utilizing app.$set.
So far I've been able to extract the data and assign it to the vue array but I can only access one thing at a time.
<div id="app">
<div v-once id="movies">
{{movieCall()}}
</div>
<div v-for="(movie,index) of movies" class="card" style="width: 18rem;">
<!-- <img src="..." class="card-img-top" alt="..."> -->
<div class="card-body">
<div class="card-title">{{movie}}</div>
</div>
</div>
</div>
var app = new Vue({
el: "#app",
movies: [
],
},
methods:
$.getJSON("https://api.themoviedb.org/3/movie/now_playing?api_key=9d9f46b8451885697e5cf7d1927da80f", function (movie) {
for (let i = 0; i < 3; i++) {
app.$set(app.movies, i, movie.results[i].title);
}
for (var x = 0; x < app.movies.length; x++) {
console.log(app.movies[x])
}
})
},
I'm extracting the movie and setting the title to the movie array but I'm wanting to assign it instead to a movie{title} object. This is so when I go through my v-for loop I can refer to the movie object array as movie.title, movie.overview, etc. to print them all. e.g.
In other words, is there a way to do:
app.$set(app.movies.title, i, movie.results[i].title);
app.$set(app.movies.overview, i, movie.results[i].description);
etc.
and have my movie array set up as:
movie[
{title:}
{description:}
]
and finally loop through like:
<div v-for(movie, index) of movies>
<div class="titles">
{{movie.title}}
</div>
<div class="descriptions">
{{movie.description}}
</div>
</div>
If you want to access movies like:
<div v-for="(movie, index) of movies">
...
{{movie.title}}
...
{{movie.description}}
Then populate it as:
app.$set(app.movies, i, {title: movie.results[i].title, description: movie.results[i].description});
Or, if i is incrementing one by one, the equivalent:
app.movies.push({title: movie.results[i].title, overview: movie.results[i].description});
Your code needs a bit of an upgrade / correction before it's OK, so I prepared a snippet for you that does the same thing (with a mockup JSON response), so you can see that you don't need app or $set for this.
var app = new Vue({
el: "#app",
data: {
movies: []
},
methods: {
movieCall() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json => {
json.forEach(movie => this.movies.push(movie))
// or just simply:
// this.movies = json
})
}
},
created() {
this.movieCall()
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(movie,index) in movies">
<div>
<div>{{index + ". Title: " + movie.title}}</div>
<div>{{index + ". Body: " + movie.body}}</div>
</div>
</div>
</div>
I tried to keep this as close to your code as possible (but CSS classes were taken out).
Basically Vue is reactive (data property), so if you update the data property movies the template should immediately react to that and update the UI. That's the core idea of a JS frontend framework.
Accessing data in object syntax (like movies.title, with a dot), is another matter. In your project you set up a data property - movies, that's an array. An array can hold any type of elements - objects too.
The idea is that you download the objects and then read them (one by one) into the movies data property. Or, if you receive an array, then this.movies = response (make them equal, assigning the response to the movies array.
Then there's an other thing: Vue templates have their lifecycle, and created() is a hook, that can be used to execute functions when the template is created. If you want something to run once, you should utilize these lifecycle hooks. (Of course, if your app reloads this template, then it executes the hook many times. To avoid this (downloading something multiple times) you should look into state management, but that's a large topic in itself.)
Add data key to hold the data set
<script>
var app = new Vue({
el: "#app",
data: {
movies: [],
},
methods:
$.getJSON("https://api.themoviedb.org/3/movie/now_playing?api_key=9d9f46b8451885697e5cf7d1927da80f", function (movies) {
for (var x = 0; x < movies.results.length; x++) {
//console.log("\nTitle"+movies.results[x].title);
//app.movies.$set(x, movie.results[x].title);
app.movies.push(movies.results[x].title);
console.log(JSON.stringify(app.movies))
}
})
});
</script>
And try with this command
app.movies.push(movie.results[i].title);
Here is a working example or sample which i created : https://plnkr.co/edit/EnepqQqXzEquJlxqjzn6?p=preview
Ref1: https://v2.vuejs.org/v2/guide/list.html
I'm using v-validate with Vue. I'm trying to figure out how to force v-validate to update rules. For example, I have something like this:
<template>
<div v-for="field in fields">
<input :name="field.name" v-validate="field.rules">
</div>
</template>
<script>
export default {
data() {
fields: [
{
name: "city",
rules: {
included: []
}
}
]
}
}
</script>
As you can see, my "included" array is empty on page load. I get the array from an AJAX request, and then I update my data:
this.fields[0].rules.included = cities
But v-validate doesn't seem to acknowledge the newly-added array. It only works if I hardcode the cities into my data. How can I force v-validate to respond to the updated rules?
Vue.js is unable to track updates on nested reference types.
Try:
let fields = [...this.fields]
fields[0].rules = cities
this.fields = fields
Use Vue.set to track changes : https://v2.vuejs.org/v2/guide/reactivity.html
Vue.set(this.fields[0], 'rules', cities);
The Issue
I've created a light-weight wrapper around jQuery DataTables for VueJS like so:
<template>
<table ref="table" class="display table table-striped" cellspacing="0" width="100%">
<thead>
<tr>
<th v-for="(column, index) in columns">
{{ column.name }}
</th>
</tr>
</thead>
</table>
</template>
<script>
export default {
props: ['columns', 'url'],
mounted: function () {
$(this.$refs.table).dataTable({
ajax: this.url,
columns: this.columns
});
// Add any elements created by DataTable
this.$compile(this.$refs.table);
}
}
</script>
I'm utilizing the data table like so:
<data-table
:columns="
[
{
name: 'County',
data: 'location.county',
},
{
name: 'Acres',
data: 'details.lot_size',
},
{
name: 'Price',
data: 'details.price',
className: 'text-xs-right',
},
{
name: 'Actions',
data: null,
render: (row) => {
return "\
<a #click='editProperty' class='btn btn-warning'><i class='fa fa-pencil'></i> Edit</a>\
";
}
},
]
"
url="/api/properties"
></data-table>
Note the "render" method for the Actions column. This function runs just fine and renders the button as expected, however the #click handler is not functional.
Looking around I've found two links which were not helpful:
Issue 254 on the VueJS GitHub repo provides a solution for VueJS 1.0 (using this.$compile) however this was removed in VueJS 2.0
A blog post by Will Vincent discusses how to make the DataTable re-render when local data changes dynamically, but doesn't provide a solution for attaching handlers to the rendered elements
Minimum Viable Solution
If the rendered element can't be compiled and mounted, that would alright so long as I could run methods of the DataTable component on-click. Perhaps something like:
render: (row) => {
return "\
<a onclick='Vue.$refs.MyComponent.methods.whatever();' />\
";
}
Is there any such way to call methods from outside of the Vue context?
This meets your minimum viable solution.
In your columns definition:
render: function(data, type, row, meta) {
return `<span class="edit-placeholder">Edit</span>`
}
And in your DataTable component:
methods:{
editProperty(data){
console.log(data)
}
},
mounted: function() {
const table = $(this.$refs.table).dataTable({
ajax: this.url,
columns: this.columns
});
const self = this
$('tbody', this.$refs.table).on( 'click', '.edit-placeholder', function(){
const cell = table.api().cell( $(this).closest("td") );
self.editProperty(cell.data())
});
}
Example (uses a different API, but the same idea).
This is using jQuery, but you're already using jQuery so it doesn't feel that terrible.
I played some games trying to get a component to mount in the render function of the data table with some success, but I'm not familiar enough with the DataTable API to make it work completely. The biggest issue was the DataTable API expects the render function to return a string, which is... limiting. The API also very irritatingly doesn't give you a reference to the cell you are currently in, which seems obvious. Otherwise you could do something like
render(columnData){
const container = document.createElement("div")
new EditComponent({data: {columnData}).$mount(container)
return container
}
Also, the render function is called with multiple modes. I was able to render a component into the cell, but had to play a lot of games with the mode, etc. This is an attempt, but it has several issues. I'm linking it to give you an idea what I was trying. Maybe you will have more success.
Finally, you can mount a component onto a placeholder rendered by DataTable. Consider this component.
const Edit = Vue.extend({
template: `<a #click='editProperty' class='btn btn-warning'><i class='fa fa-pencil'></i> Edit</a>`,
methods:{
editProperty(){
console.log(this.data.name)
this.$emit("edit-property")
}
}
});
In your mounted method you could do this:
mounted: function() {
const table = $(this.$refs.table).dataTable({
ajax: this.url,
columns: this.columns
});
table.on("draw.dt", function(){
$(".edit-placeholder").each(function(i, el){
const data = table.api().cell( $(this).closest("td") ).data();
new Edit({data:{data}}).$mount(el)
})
})
}
This will render a Vue on top of each placeholder, and re-render it when it is drawn. Here is an example of that.
Very new to Polymer and Polymerfire. I couldn't find an answer here so hoping I can get help here. The basic question I have is "how do I work with the data that polymerfire/firebase-query sends?" Note I'm using polymerfire version 0.9.4, and polymer is version 1.4.0.
I can load my data from Firebase no problem using Firebase query, however some of the values are raw numbers that I need to convert to user friendly information. For example I have time stored in ms that I want to convert to a date, and a numeric field that indicates the "type" of data that is stored and I want to show an icon for it, not just a raw number. I figured my best option would be to use the transactions-complete promise or an observer. Both fire but neither seems to give me access to the data. The Observer's newData is an empty array, and transactions-complete.. well I don't really know what to do with that when the promise fires. Below is my relevant code. I also tried using notify: true, but I seem to not be grasping the concept correctly.
<firebase-query
id="query"
app-name="data"
path="/dataPath"
transactions-complete="transactionCompleted"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{data}}">
<div class="card">
<div>Title: <span>{{item.title}}</span></div>
<div>Date Created: <span>{{item.dateCreated}})</span></div>
<div>Date Modified: <span>{{item.dateModified}}</span></div>
<div>Status: <span>{{item.status}}</span></div>
</div>
</template>
Polymer({
is: 'my-view1',
properties: {
data: {
notify: true,
type: Object,
observer: 'dataChanged'
}
},
dataChanged: function (newData, oldData) {
console.log(newData[0]);
// do something when the query returns values?
},
transactionCompleted: new Promise(function(resolve, reject) {
// how can I access "data" here?
})`
I wound up going another way entirely, which seemed to be a cleaner approach to what I was doing anyways. I broke it down into separate components. This way when the detail component was loaded, the ready function would allow me to adjust the data before it got displayed:
list.html:
<firebase-query
id="query"
app-name="data"
path="/dataPath"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{data}}">
<my-details dataItem={{item}}></my-details>
</template>
details.html
<template>
<div id="details">
<paper-card heading="{{item.title}}">
<div class="card-content">
<span id="description">{{item.description}}</span><br/><br/>
<div class="details">Date Created: <span id="dateCreated">{{item.dateCreated}}</span><br/></div>
<div class="details">Last Modified: <span id="dateModified">{{item.dateModified}}</span><br/></div>
<div class="status"><span id="status">{{item.status}}</span><br/></div>
</div>
</paper-card>
</template>
Then in the javascript ready function I can intercept and adjust the data accordingly:
Polymer({
is: 'my-details',
properties: {
item: {
notify: true,
},
},
ready: function() {
this.$.dateModified.textContent = this.getDate(this.item.dateModified);
this.$.dateCreated.textContent = this.getDate(this.item.dateCreated);
this.$.status.textContent = this.getStatus(this.item.status);
},
Try the following changes:
Take out the transactions-completed attribute - it is only relevant when the query is updating data to Firebase
Change the dom-repeat template to get it's items attribute from convertedData - this allows you to do the data conversions to## Heading ## the results of the firebase-query
<firebase-query
id="query"
app-name="data"
path="/dataPath"
data="{{data}}">
</firebase-query>
<template is="dom-repeat" items="{{convertedData}}">
<div class="card">
<div>Title: <span>{{item.title}}</span></div>
<div>Date Created: <span>{{item.dateCreated}})</span></div>
<div>Date Modified: <span>{{item.dateModified}}</span></div>
<div>Status: <span>{{item.status}}</span></div>
</div>
</template>
Add a convertedData property to do your data conversions from data which has the raw data
Change the observer syntax as per the example. This sets up the observer to to observe for changes to deep property values which results in the observer method being fired - see: https://www.polymer-project.org/1.0/docs/devguide/observers#deep-observation
In the observer method you can populate the convertedData object from the data object which should then render the content
Polymer({
is: 'my-view1',
properties: {
data: {
notify: true,
type: Object
},
convertedData: {
notify: true,
type: Object
}
},
// observer syntax to monitor for deep changes on "data"
observers: [
'dataChanged(data.*)'
]
dataChanged: function (newData, oldData) {
console.log(newData);
// convert the "newData" object to the "convertedData" object
}
}
I have a factory, which goes into a controller, and I am trying to get data from that display on an HTML page. I am having trouble specifying an Object's pathway however.
My Factory:
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
apis:
[{
accounts: [
{
v1: [
{
uri: Head+"/v1/accounts/",
item1: "AccountNumber",
item2: "MoneyInAccount"
}],
v2: [
{
uri: Head+"/v2/accounts/",
item1: "AccountNumber",
item2: "MoneyInAccount"
}]
}
],
customers: [
{
v1: [
{
uri: Head+"/v1/customers/",
item1: "CustomerName",
item2: "CustomerID",
item3: "CustomerEmail"
}]
}
]
}]
};
});
My Controller:
app.controller('APIController', function($scope, APIMethodService) {
$scope.title = "API";
$scope.apiList = APIMethodService;
$scope.accountList = $scope.apiList.accounts.v1;
$scope.accountList2 = $scope.apiList[0][0];
});
My HTML
<div ng-controller="APIController">
<div id="api" class="row">
<div class="col-xs-12">
<div class="row" style="font-size:20px">
{{title}} Page!
<table class="table table-striped">
<tr ng-repeat="api in apiList | orderBy:'uri' | filter:search">
<td>{{api.uri}}</td>
<td>{{api.item1}}</td>
<td>{{api.item2}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
The errors I get are in regards to the Controller trying to parse out the individual objects I wish to grab, like accounts or customers, and then any version v#, they may have.
So it will say something such as
TypeError: Cannot read property 'v1' of undefined
I just need some help specifying the proper pathways into my factory service.
You have a few problems. First, you are referring to the object returned from the factory incorrectly. APIMethodService is the factory that you're injecting, so you need to first reference the object that that factory is returning like this:
APIMethodService.apis
This will give you your entire JSON object.
From there, the rest of your object is made up of arrays of objects, so referring to 'v1' won't do you any good. You need to specify an index instead. If you want v1, you'll need:
APIMethodService.apis[0].accounts[0].v1
This will give you the v1 array, which again is an array of objects.
Customers would be:
APIMethodService.apis[0].customers[0].v1
The first problem you have is that the factory returns an object with a single property called apis. So basically this $scope.apiList.accounts.v1 should be $scope.apiList.apis.accounts.v1. Bu that's not all as this won't either work since dotting(.) into apis is an array you'd have to use the index. In this case it would be $scope.apiList.apis[0] and then you could .accounts[0].v1 which is also an array containing a single object.
Now if you can I would suggest to you that you'd change how you represent this data structure.
This is how you could do it.
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
accounts: {
v1: {
uri: Head+"/v1/accounts/",
items: ["AccountNumber","MoneyInAccount"]
},
v2: {
... // skipped for brevity
}
},
customer: {
... // code skipped for brevity
}
};
});
And then it's just a matter of dotting into your APIMethodService-object like APIMethodService.accounts.v1.items[0] if you want the AccountNumber method name.
Constructing your url could then be done like this.
var baseUrl = APIMethodService.accounts.v1.uri; // 'api.example.com'
var url = baseUrl + APIMethodService.accounts.v1.items[0]; // 'AccountNumber'
// url = "api.example.com/v1/accounts/AccountNumber"
Again, this is one way you could do it but this can be further enhanced upon. The examples I provided are simply for demo purposes and this is not in any way the only way to do it.
Expanding upon recieved comments/questions your service (and data representation) could now look like this.
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
accounts: {
v1: {
uri: Head+"/v1/accounts/",
items: [
{
name:'AccountNumber',
description:'Show the account number'
},
{
name:'AccountOwner',
description:'Show information about the owner of the account'
},
{
name:'MoneyInAccount',
description:'Show money in the Account'
}
]
},
v2: {
... // skipped for brevity
}
},
customer: {
... // code skipped for brevity
}
};
});
// Get descriptions
var accountNumberDescription = APIMethodService.accounts.v1.items[0].description; // 'Show the account number'
var accountOwnerDescription = APIMethodService.accounts.v1.items[1].description; // 'Show information about the owner of the account'
var moneyInAccountDescription = APIMethodService.accounts.v1.items[2].description; // 'Show money in the Account'
By using objects with properties like this it's alot easier to understand what you are trying to do. With arrays with indexes you'd have to know or take a look at the source to see what's going on. Here, someone viewing your code they can instantly understand that it is the description you are getting.