I am trying to build a grid component in vue. And then use this to set up a sortable and searchable chart. I need to use Single File Component. I also used vue-router. Here in below are my two .vue files. I can only see the search bar in my localhost. And there are several errors showed up in the console. The original Fiddle of separate files https://jsfiddle.net/Tertia/vbyon64p/6/?utm_source=website&utm_medium=embed&utm_campaign=vbyon64p
This is my chart.vue
<template>
<div id="demo">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid></demo-grid>
</div>
</template>
<script>
import DemoGrid from "./grid";
export default {
name :'chart',
components: {DemoGrid},
props: {
heroes: Array,
columns: Array,
filterKey: String
},
data: function () {
var sortOrders = {}
this.columns.forEach(function (key) {
sortOrders[key] = 1
})
return {
sortKey: '',
sortOrders: sortOrders,
searchQuery: '',
gridColumns: ['name', 'power'],
gridData: [
{ name: 'Chuck Norris', power: Infinity },
{ name: 'Bruce Lee', power: 9000 },
{ name: 'Jackie Chan', power: 7000 },
{ name: 'Jet Li', power: 8000 }
]
}
},
computed: {
filteredHeroes: function () {
var sortKey = this.sortKey
var filterKey = this.filterKey && this.filterKey.toLowerCase()
var order = this.sortOrders[sortKey] || 1
var heroes = this.heroes
if (filterKey) {
heroes = heroes.filter(function (row) {
return Object.keys(row).some(function (key) {
return String(row[key]).toLowerCase().indexOf(filterKey) > -1
})
})
}
if (sortKey) {
heroes = heroes.slice().sort(function (a, b) {
a = a[sortKey]
b = b[sortKey]
return (a === b ? 0 : a > b ? 1 : -1) * order
})
}
return heroes
}
},
filters: {
capitalize: function (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
},
methods: {
sortBy: function (key) {
this.sortKey = key
this.sortOrders[key] = this.sortOrders[key] * -1
}
}
}
</script>
<style >
This is my grid.vue, a template
<table>
<thead>
<tr>
<th v-for="key in columns"
#click="sortBy(key)"
:class="{ active: sortKey == key }">
{{ key | capitalize }}
<span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in filteredHeroes">
<td v-for="key in columns">
{{entry[key]}}
</td>
</tr>
</tbody>
</table>
Here is the ideal look of the grid. It is sortable and searchable.
And here are my errors.
You've got your code in all the wrong places. Most likely due to the lack of understanding of the Single File Components concept and Component Basics.
Here's me literally copy and pasting code from the original fiddle into their respective components.
Your chart.vue should look like this:
<template>
<div id="demo">
<form id="search">
Search
<input name="query" v-model="searchQuery">
</form>
<demo-grid :heroes="gridData" :columns="gridColumns" :filter-key="searchQuery"></demo-grid>
</div>
</template>
<script>
import DemoGrid from "./grid.vue";
export default {
name: "Chart",
components: {
DemoGrid
},
data() {
return {
searchQuery: "",
gridColumns: ["name", "power"],
gridData: [
{ name: "Chuck Norris", power: Infinity },
{ name: "Bruce Lee", power: 9000 },
{ name: "Jackie Chan", power: 7000 },
{ name: "Jet Li", power: 8000 }
]
};
}
};
</script>
Your grid.vue should look like this:
<template>
<table>
<thead>
<tr>
<th
v-for="(key, index) in columns"
:key="index"
:class="{ active: sortKey == key }"
#click="sortBy(key)"
>
{{ key | capitalize }}
<span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'"></span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(entry, index) in filteredHeroes" :key="index">
<td v-for="(key, index) in columns" :key="index">{{ entry[key] }}</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "DemoGrid",
props: {
heroes: Array,
columns: Array,
filterKey: String
},
data: function() {
const sortOrders = {};
this.columns.forEach(function(key) {
sortOrders[key] = 1;
});
return {
sortKey: "",
sortOrders: sortOrders
};
},
computed: {
filteredHeroes() {
const sortKey = this.sortKey;
const filterKey = this.filterKey && this.filterKey.toLowerCase();
const order = this.sortOrders[sortKey] || 1;
let heroes = this.heroes;
if (filterKey) {
heroes = heroes.filter(function(row) {
return Object.keys(row).some(function(key) {
return (
String(row[key])
.toLowerCase()
.indexOf(filterKey) > -1
);
});
});
}
if (sortKey) {
heroes = heroes.slice().sort(function(a, b) {
a = a[sortKey];
b = b[sortKey];
return (a === b ? 0 : a > b ? 1 : -1) * order;
});
}
return heroes;
}
},
filters: {
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
},
methods: {
sortBy(key) {
this.sortKey = key;
this.sortOrders[key] = this.sortOrders[key] * -1;
}
}
};
</script>
<style scoped>
table {
border: 2px solid #42b983;
border-radius: 3px;
background-color: #fff;
}
th {
background-color: #42b983;
color: rgba(255, 255, 255, 0.66);
cursor: pointer;
user-select: none;
}
td {
background-color: #f9f9f9;
}
th,
td {
min-width: 120px;
padding: 10px 20px;
}
th.active {
color: #fff;
}
th.active .arrow {
opacity: 1;
}
.arrow {
display: inline-block;
vertical-align: middle;
width: 0;
height: 0;
margin-left: 5px;
opacity: 0.66;
}
.arrow.asc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid #fff;
}
.arrow.dsc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #fff;
}
</style>
Assuming both components are in the same directory.
Related
I want to implement a chart like below.
But my problem is collision of the numbers in this chart and I couldn't find a suitable solution for this. For example when two or three values are very close together, the collision happens. (the image below)
<template>
<div style="width: 100%">
<div class="box" style="width: 100%">
<div :style="{ left: `${bound1.spaceLeft}%` }" class="box__q1-line" />
<div :style="{ left: `${bound2.spaceLeft}%` }" class="box__q2-line" />
<div :style="{ width: `${theValue.width}%` }" class="box__value" />
</div>
<div class="box__values-container">
<div
v-for="(item, i) in sorted"
:key="i"
class="box__amount"
:style="{ left: `${item.spaceLeft}%` }"
>
{{ item.realValue }}
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from "#vue/composition-api";
function round(number: number) {
return Math.floor(number * 1000) / 1000;
}
export default defineComponent({
setup(props) {
const bound1 = ref({
realValue: 0,
spaceLeft: 0,
});
const bound2 = ref({
realValue: 0,
spaceLeft: 0,
});
const theValue = ref({
realValue: 0,
spaceLeft: 0,
width: 0,
});
const eachWidth = 6;
type ChartData = { realValue: number; spaceLeft: number };
const sorted = ref<Array<ChartData>>();
function compare(obj1: ChartData, obj2: ChartData) {
if (obj1.spaceLeft < obj2.spaceLeft) return -1;
if (obj1.spaceLeft > obj2.spaceLeft) return 1;
return 0;
}
watch(
props,
() => {
bound1.value.realValue = props.q1!;
bound2.value.realValue = props.q2!;
theValue.value.realValue = props.value!;
bound1.value.spaceLeft = round((props.q1! / props.max!) * 100);
bound2.value.spaceLeft = round((props.q2! / props.max!) * 100);
theValue.value.spaceLeft = round((props.value! / props.max!) * 100);
theValue.value.width = round((props.value! / props.max!) * 100);
sorted.value = [bound1.value, bound2.value, theValue.value].sort(
compare
);
if (
sorted.value[1].spaceLeft + eachWidth >
sorted.value[2].spaceLeft
) {
sorted.value[1].spaceLeft = sorted.value[2].spaceLeft - eachWidth;
}
if (
sorted.value[0].spaceLeft + eachWidth >
sorted.value[1].spaceLeft
) {
sorted.value[0].spaceLeft = sorted.value[1].spaceLeft - eachWidth;
}
if (theValue.value.width < 0) theValue.value.width = 3;
if (bound1.value.spaceLeft < 0) bound1.value.spaceLeft = 3;
if (bound2.value.spaceLeft < 0) bound2.value.spaceLeft = 3;
},
{ deep: true, immediate: true }
);
return {
bound1,
bound2,
theValue,
sorted,
};
},
props: {
q1: {
type: Number,
},
q2: {
type: Number,
},
max: {
type: Number,
},
value: {
type: Number,
},
},
});
</script>
<style>
.box {
width: 100%;
height: 50px;
position: relative;
border-radius: 5px;
border: 2px solid #fff;
background: #b8d0de;
overflow: hidden;
}
.box__q1-line,
.box__q2-line {
position: absolute;
width: 2px;
background: #fff;
height: 100%;
}
.box__value {
background: #3c8dbc;
height: 100%;
}
.box__amount {
position: absolute;
width: 60px;
bottom: -25px;
}
.box__values-container {
position: relative;
}
</style>
I considered two thin divs with absolute position for the two lines and I set the left property of them according to their values and another div for the amount and I set the width of it according to its value.
How do I rotate just that arrow icon based on the clicked item?
new Vue({
el: "#app",
data() {
return {
isToggled: false,
items: [{
id: 1,
name: "Test1"
},
{
id: 2,
name: "Test2"
},
{
id: 3,
name: "Test3"
},
{
id: 4,
name: "Test4"
},
]
}
},
methods: {
arrowToggle() {
this.isToggled = !this.isToggled;
},
getItems() {
return this.items;
}
},
mounted() {
this.getItems();
}
});
i {
border: solid black;
border-width: 0 3px 3px 0;
display: inline-block;
padding: 3px;
}
.down {
transform: rotate(45deg);
}
.up {
transform: rotate(-155deg);
}
.accordion {
display: flex;
background: lightblue;
align-items: center;
width: 100%;
width: 1000px;
justify-content: space-between;
height: 30px;
padding: 0 20px;
}
.arrow {
transform: rotate(-135deg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" style="display: flex; justify-content: center; align-items: center;">
<div v-for="(item, index) in items" :key="index">
<div class="accordion" #click="arrowToggle()">
<p> {{ item.name }}</p>
<i :class="{ 'down': item.isToggled }" class="arrow"> </i>
</div>
</div>
</div>
Based on the clicked element do I want my arrow to rotate?
If i have 10 items and click on 2 items i want the icon to rotate there.
Failing to bind id to the clicked item and to bind that class to rotate the item
One thing is very important, I cannot set the isOpen parameter in my json ITEMS which is false which everyone recommends to me. I get it from a database and I don't have a condition for it.
You will have to toggle at individual item level. Note that I have used isToggled per item. Here is full code at: https://jsfiddle.net/kdj62myg/
Even if you get your items from DB, you can iterate through array and add a key named isToggled to each item.
HTML
<div id="app" style="display: flex; justify-content: center; align-items: center;">
<div v-for="(item, index) in items" :key="index">
<div class="accordion" #click="arrowToggle(item)">
<p> {{ item.name }}</p>
<i :class="{ 'down': item.isToggled, 'up': !item.isToggled }"> </i>
</div>
</div>
</div>
Vue
new Vue({
el: "#app",
data() {
return {
isToggled: false,
items: [{
id: 1,
name: "Test1",
isToggled: false
},
{
id: 2,
name: "Test2",
isToggled: false
},
{
id: 3,
name: "Test3",
isToggled: false
},
{
id: 4,
name: "Test4",
isToggled: false
},
]
}
},
methods: {
arrowToggle(item) {
return item.isToggled = !item.isToggled;
},
getItems() {
return this.items;
}
},
mounted() {
this.getItems();
}
});
You have to map your items and attach a custom data on it to solve your problem.
Items data should be like this
items: [{
id: 1,
name: "Test1",
isToggled: false
},
{
id: 2,
name: "Test2",
isToggled: false
},
{
id: 3,
name: "Test3",
isToggled: false
},
{
id: 4,
name: "Test4",
isToggled: false
},
]
and your toogle function should look like this.
arrowToggle(item) {
return item.isToggled = !item.isToggled;
},
Now, after you fetched the items from the server. You have to map it to attach a isToggled data on every item you have. like this.
getItems() {
axios.get('api/for/items')
.then(({data}) => {
this.items = data.map(item => ({
return {
name:item.name,
id:item.id,
isToggled:false
}
}))
});
}
The above arrowToggle function breaks vue reactivity (google vue reactivity for docs). According to the docs, changing an object property directly will break reactivity. To keep reactivity, the function should change to:
arrowToggle(item) {
this.$set(this.item, 'isToggled', item.isToggled = !item.isToggled)
return item.isToggled;
},
Here is my HTLM Page:
<!DOCTYPE html>
<script src="Scripts/knockout-3.4.2.js" type="text/javascript"></script>
<script src="Scripts/jquery-3.1.1.min.js"></script>
<script src="Scripts/knockout.simpleGrid.js"></script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Knockout GridView Örnek</title>
<style>
body { font-family: arial; font-size: 14px; }
.liveExample { padding: 1em; background-color: #EEEEDD; border: 1px solid #CCC; max-width: 655px; }
.liveExample input { font-family: Arial; }
.liveExample b { font-weight: bold; }
.liveExample p { margin-top: 0.9em; margin-bottom: 0.9em; }
.liveExample select[multiple] { width: 100%; height: 8em; }
.liveExample h2 { margin-top: 0.4em; }
.ko-grid { margin-bottom: 1em; width: 25em; border: 1px solid silver; background-color:White; }
.ko-grid th { text-align:left; background-color: Black; color:White; }
.ko-grid td, th { padding: 0.4em; }
.ko-grid tr:nth-child(odd) { background-color: #DDD; }
.ko-grid-pageLinks { margin-bottom: 1em; }
.ko-grid-pageLinks a { padding: 0.5em; }
.ko-grid-pageLinks a.selected { background-color: Black; color: White; }
.liveExample { height:20em; overflow:auto }
li { list-style-type: disc; margin-left: 20px; }
</style>
</head>
<body>
<div data-bind='simpleGrid: gridViewModel'></div>
<div>Ad: </div> <input data-bind="value: Ad" /> <br />
<div>Satılan: </div> <input data-bind="value: Satis" /> <br />
<div>Fiyat: </div> <input data-bind="value: tutar" /> <br />
<button data-bind='click: addItem'>
Add item
</button>
<button data-bind='click: deleteFirst'>Delete first Row</button>
<button data-bind='click: deleteLast'>Delete Last Row</button>
<button data-bind='click: sortByName'>
Sort by name
</button>
<button data-bind='click: jumpToFirstPage, enable: gridViewModel.currentPageIndex'>
Jump to first page
</button>
</body>
</html>
Here is my JavaScript:
<script type="text/javascript">
var initialData = [
{ name: "Well-Travelled Kitten", sales: 352, price: 75.95 },
{ name: "Speedy Coyote", sales: 89, price: 190.00 },
{ name: "Furious Lizard", sales: 152, price: 25.00 },
{ name: "Indifferent Monkey", sales: 1, price: 99.95 },
{ name: "Brooding Dragon", sales: 0, price: 6350 },
{ name: "Ingenious Tadpole", sales: 39450, price: 0.35 },
{ name: "Optimistic Snail", sales: 420, price: 1.50 }
];
var PagedGridModel = function (items) {
this.items = ko.observableArray(items);
this.sortByName = function () {
this.items.sort(function (a, b) {
return a.name < b.name ? -1 : 1;
});
};
this.jumpToFirstPage = function () {
this.gridViewModel.currentPageIndex(0);
};
this.deleteFirst = function () {
this.items.shift();
}
this.deleteLast = function () {
this.items.pop();
}
this.removeGift = function (item) {
this.initialData.remove(item);
};
this.gridViewModel = new ko.simpleGrid.viewModel({
data: this.items,
columns: [
{ headerText: "Item Name", rowText: "name" },
{ headerText: "Sales Count", rowText: "sales" },
{ headerText: "Price", rowText: function (item) { return "$" + item.price.toFixed(2) } }
],
pageSize: 4
});
this.Ad = ko.observable("");
this.Satis = ko.observable("");
this.tutar = ko.observable("");
this.addItem = function () {
if (this.Ad() != "" && this.Satis() != "" && this.tutar() != "") {
this.tutar(Number(this.tutar()));
this.Satis(Number(this.Satis()));
this.items.push({ name: this.Ad(), sales: this.Satis(), price: this.tutar() });
this.Ad("");
this.Satis("");
this.tutar("");
}
}.bind(this);
};
ko.applyBindings(new PagedGridModel(initialData));
</script>
Here is my Grid JS:
(function () {
// Private function
function getColumnsForScaffolding(data) {
if ((typeof data.length !== 'number') || data.length === 0) {
return [];
}
var columns = [];
for (var propertyName in data[0]) {
columns.push({ headerText: propertyName, rowText: propertyName });
}
return columns;
}
ko.simpleGrid = {
// Defines a view model class you can use to populate a grid
viewModel: function (configuration) {
this.data = configuration.data;
this.currentPageIndex = ko.observable(0);
this.pageSize = configuration.pageSize || 5;
// If you don't specify columns configuration, we'll use scaffolding
this.columns = configuration.columns || getColumnsForScaffolding(ko.unwrap(this.data));
this.itemsOnCurrentPage = ko.computed(function () {
var startIndex = this.pageSize * this.currentPageIndex();
return ko.unwrap(this.data).slice(startIndex, startIndex + this.pageSize);
}, this);
this.maxPageIndex = ko.computed(function () {
return Math.ceil(ko.unwrap(this.data).length / this.pageSize) - 1;
}, this);
}
};
// Templates used to render the grid
var templateEngine = new ko.nativeTemplateEngine();
templateEngine.addTemplate = function (templateName, templateMarkup) {
document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "<" + "/script>");
};
templateEngine.addTemplate("ko_simpleGrid_grid", "\
<table class=\"ko-grid\" cellspacing=\"0\">\
<thead>\
<tr data-bind=\"foreach: columns\">\
<th data-bind=\"text: headerText\"></th>\
</tr>\
</thead>\
<tbody data-bind=\"foreach: itemsOnCurrentPage\">\
<tr data-bind=\"foreach: $parent.columns\">\
<td data-bind=\"text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText] \"></td>\
<td>Delete</td>\
</tr>\
</tbody>\
</table>");
templateEngine.addTemplate("ko_simpleGrid_pageLinks", "\
<div class=\"ko-grid-pageLinks\">\
<span>Page:</span>\
<!-- ko foreach: ko.utils.range(0, maxPageIndex) -->\
<a href=\"#\" data-bind=\"text: $data + 1, click: function() { $root.currentPageIndex($data) }, css: { selected: $data == $root.currentPageIndex() }\">\
</a>\
<!-- /ko -->\
</div>");
// The "simpleGrid" binding
ko.bindingHandlers.simpleGrid = {
init: function () {
return { 'controlsDescendantBindings': true };
},
// This method is called to initialize the node, and will also be called again if you change what the grid is bound to
update: function (element, viewModelAccessor, allBindings) {
var viewModel = viewModelAccessor();
// Empty the element
while (element.firstChild)
ko.removeNode(element.firstChild);
// Allow the default templates to be overridden
var gridTemplateName = allBindings.get('simpleGridTemplate') || "ko_simpleGrid_grid",
pageLinksTemplateName = allBindings.get('simpleGridPagerTemplate') || "ko_simpleGrid_pageLinks";
// Render the main grid
var gridContainer = element.appendChild(document.createElement("DIV"));
ko.renderTemplate(gridTemplateName, viewModel, { templateEngine: templateEngine }, gridContainer, "replaceNode");
// Render the page links
var pageLinksContainer = element.appendChild(document.createElement("DIV"));
ko.renderTemplate(pageLinksTemplateName, viewModel, { templateEngine: templateEngine }, pageLinksContainer, "replaceNode");
}
};
})();
I want like this
But Delete not working and repeat 3 times always 1 line. I want it 1 Line 1 delete and I want to delete it. How I can do this ? I need your help guys. Thank you in advance.
Now, I have tow buttons. The first button named "array1", the other button named 'array2'. I have an array called "newArray." I have an array named "array1". I have an array named "array2".I have an array called "unselectedArray."
When I click the array1 button, I want to show the item in array1, but the item is not in "newArray1". When I click the array2 button, I want to show the item in array2, but the item is not in "newArray1" This show array is "unselectedArray."
When I click the item in the "unselectedArray," the item is added in 'newArrray';
I use two hours to solve it, but I haven't written the right code.
This is my code:
<style>
.bigDiv {
width: 500px; height: 100%;
margin: 60px auto; background-color: red;
}
li {
float: left;
width: 50px; height: 50px;
}
.selected,.buttonArea,.unselected {
height: 100px;
}
</style>
<div class="bigDiv">
<div class="selected">
<ul>
<li ng-repeat="item in newArray">
{{item.text}}
</li>
</ul>
</div>
<div class="buttonArea">
<button ng-click="showArrayFun('array1')">array1</button>
<button ng-click="showArrayFun('array2')">array2</button>
</div>
<div class="unselected">
<ul>
<li ng-click="addToNewArrayFun($index)" ng-repeat="item in unselectedArray">
{{item.text}}
</li>
</ul>
</div>
</div>
angular.module('myApp', []).controller('con', function ($scope) {
$scope.array1 = [
{
id: 11,
text: 'one'
},
{
id: 12,
text: 'two'
},
];
$scope.array2 = [
{
id: 21,
text: 'winter'
},
{
id: 22,
text: 'spring'
},
];
$scope.newArray = [
{
id: 12,
text: 'two'
}
];
$scope.unselectedArray = [];
$scope.addToNewArrayFun = function (index) {
$scope.newArray.push($scope.unselectedArray[index]);
};
$scope.showArrayFun = function (arrayName) {
if (arrayName == 'array1') {
$scope.unselectedArray = $scope.array1.filter(function (item) {
console.log(($scope.newArray.indexOf(item) == -1));
return ( ($scope.newArray.indexOf(item) == -1) == true );
});
} else if (arrayName == 'array2') {
$scope.unselectedArray = $scope.array2.filter(function (item) {
console.log(($scope.newArray.indexOf(item) == -1));
return ( ($scope.newArray.indexOf(item) == -1) == true );
});
}
}
}
);
Why my code not work? Who can correct my code?
Please write the code which is using $filter.
Who can create AngularJS custom filters to realize it.
// Code goes here
angular.module('myApp', []).controller('con', function ($scope) {
$scope.array1 = [
{
id: 11,
text: 'one'
},
{
id: 12,
text: 'two'
},
];
$scope.array2 = [
{
id: 21,
text: 'winter'
},
{
id: 22,
text: 'spring'
},
];
$scope.newArray = [
{
id: 12,
text: 'two'
}
];
$scope.unselectedArray = [];
$scope.addToNewArrayFun = function (index) {
$scope.newArray.push($scope.unselectedArray[index]);
$scope.unselectedArray.splice(index, 1);
};
$scope.showArrayFun = function (arrayName) {
if (arrayName == 'array1') {
$scope.unselectedArray = $scope.array1.filter(function (item) {
return ( ($scope.newArray.indexOf(item) == -1));
});
} else if (arrayName == 'array2') {
$scope.unselectedArray = $scope.array2.filter(function (item) {
return ( ($scope.newArray.indexOf(item) == -1));
});
}
};
})
/* Styles go here */
.bigDiv {
width: 500px; height: 100%;
margin: 60px auto;
background-color: red;
}
li {
float: left;
width: 50px; height: 50px;
}
.selected,.buttonArea,.unselected {
height: 100px;
}
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="script.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body ng-controller="con">
<div class="bigDiv">
<div class="selected">
<ul>
<li ng-repeat="item in newArray">
{{item.text}}
</li>
</ul>
</div>
<div class="buttonArea">
<button ng-click="showArrayFun('array1')">array1</button>
<button ng-click="showArrayFun('array2')">array2</button>
</div>
<div class="unselected">
<ul>
<li ng-click="addToNewArrayFun($index)" ng-repeat="item in unselectedArray">
{{item.text}}
</li>
</ul>
</div>
</div>
</body>
</html>
<div class="bigDiv">
<div class="selected">
<ul>
<li ng-click=" deleteItemFun($index)" ng-repeat="item in newArray track by $index ">
{{item.text}}
</li>
</ul>
</div>
<div class="buttonArea">
<button ng-click="showArrayFun('array1')">array1</button>
<button ng-click="showArrayFun('array2')">array2</button>
</div>
<div class="unselected">
<ul>
<li ng-click="addToNewArrayFun($index)" ng-repeat="item in unselectedArray | fiArray : newArray ">
{{item.text}}
</li>
</ul>
</div>
</div>
angular.module('myApp', []).filter('fiArray', function () {
return function (array, aimArray) {
var tempArray = array;
if (tempArray.length == 0)
return [];
for (var i = 0; i < aimArray.length; i++) {
for (var j = 0; j < tempArray.length; j++) {
if (tempArray[j].id === aimArray[i].id) {
tempArray.splice(j, 1);
j--;
break;
}
}
}
return tempArray;
}
})
.controller('con', ['$scope', 'fiArrayFilter', function ($scope, fiArrayFilter) {
$scope.newArray = [
{
id: 12,
text: 'two'
}
];
$scope.array1 = [
{
id: 11,
text: 'one'
},
{
id: 12,
text: 'two'
},
];
$scope.array2 = [
{
id: 21,
text: 'winter'
},
{
id: 22,
text: 'spring'
},
];
$scope.unselectedArray = [];
$scope.addToNewArrayFun = function (index) {
$scope.newArray.push($scope.unselectedArray[index]);
};
$scope.deleteItemFun = function (index) {
$scope.newArray.splice(index, 1)
}
$scope.showArrayFun = function (arrayName) {
var copyArr = [];
if (arrayName == 'array1') {
copyArr = $scope.array1.concat();
}
else if (arrayName == 'array2') {
copyArr = $scope.array2.concat();
}
$scope.unselectedArray = copyArr;
}
}])
;
My table is based on the Grid Component Example in Vue.js' website
I'm having problem with sorting dates inside the table. I get all the table data from server side as JSON. So in the codes provided, I just mocked the data in var mockDataFromServerSide.
Here is the code: https://jsfiddle.net/5w1wzhvw/3/
HTML file:
<!-- component template -->
<script type="text/x-template" id="grid-template">
<table>
<thead>
<tr>
<th v-for="key in columns"
v-on:click="sortBy(key)"
:class="{active: sortKey == key}">
{{key | capitalize}}
<span class="arrow"
:class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="
entry in data
| filterBy filterKey
| orderBy sortKey sortOrders[sortKey]">
<td v-for="key in columns">
{{entry[key]}}
</td>
</tr>
</tbody>
</table>
</script>
<!-- demo root element -->
<div id="demo">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid
:filter-key="searchQuery">
</demo-grid>
</div>
Js file:
var gridColumns = ['name', 'date'];
var mockDataFromServerSide = [
{ name: 'Chuck Norris', date: "01 Dec 2016" },
{ name: 'Bruce Lee', date: "23 Apr 2005" },
{ name: 'Jackie C', date: "30 Jan 2012" },
{ name: 'Jet Li', date: "20 Apr 2006" }
];
// register the grid component
Vue.component('demo-grid', {
template: '#grid-template',
props: {
filterKey: String
},
data: function () {
var sortOrders = {}
gridColumns.forEach(function (key) {
sortOrders[key] = 1
})
return {
sortKey: '',
sortOrders: sortOrders,
columns: gridColumns,
data: mockDataFromServerSide
}
},
methods: {
sortBy: function (key) {
this.sortKey = key
this.sortOrders[key] = this.sortOrders[key] * -1
}
}
})
// bootstrap the demo
var demo = new Vue({
el: '#demo',
data: {
searchQuery: ''
}
})
I also tried to add a filter to the date. The sort is correct but the displayed dates are shown as "Thu Apr 02 2016 00:00:00 GMT+0800 (China Standard Time)". I want the dates to be displayed as 02 Apr 2016.
Added filter Code: https://jsfiddle.net/kr1m5de5/1/
HTML file (added filter):
<!-- component template -->
<script type="text/x-template" id="grid-template">
<table>
<thead>
<tr>
<th v-for="key in columns"
v-on:click="sortBy(key)"
:class="{active: sortKey == key}">
{{key | capitalize}}
<span class="arrow"
:class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="
entry in data
| filterBy filterKey
| orderBy sortKey sortOrders[sortKey]
| datesFilter">
<td v-for="key in columns">
{{entry[key]}}
</td>
</tr>
</tbody>
</table>
</script>
<!-- demo root element -->
<div id="demo">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid
:filter-key="searchQuery">
</demo-grid>
</div>
JS file (added filter):
var gridColumns = ['name', 'date'];
var mockDataFromServerSide = [
{ name: 'Chuck Norris', date: "01 Dec 2016" },
{ name: 'Bruce Lee', date: "23 Apr 2005" },
{ name: 'Jackie C', date: "30 Jan 2012" },
{ name: 'Jet Li', date: "20 Apr 2006" }
];
// register the grid component
Vue.component('demo-grid', {
template: '#grid-template',
props: {
filterKey: String
},
filters: {
datesFilter: function (data) {
data.forEach(function (row) {
row.date = new Date(row.date);
});
return data;
}
},
data: function () {
var sortOrders = {}
gridColumns.forEach(function (key) {
sortOrders[key] = 1
})
return {
sortKey: '',
sortOrders: sortOrders,
columns: gridColumns,
data: mockDataFromServerSide
}
},
methods: {
sortBy: function (key) {
this.sortKey = key
this.sortOrders[key] = this.sortOrders[key] * -1
}
}
})
// bootstrap the demo
var demo = new Vue({
el: '#demo',
data: {
searchQuery: ''
}
})
Please let me know how to fix it or if there is a better way to do it.
I solved this by making a TableHeader component it says semantic cause i used semantic-ui... sorry for the spanglish in the code, must of 'em are cognates anyway. Also, this code is working, but if you see improvements to the code/answer let me know please!
As you can see, i really don't sort at front... i make a new request with the sorted items.
<template>
<th #click="cycleSort(sth, $event)">
<span><span>{{ sth.texto }} </span><i class="icon" :class="sth.icon"></i><sub v-if="sth.posicion > 0"><small>{{ sth.posicion }}</small></sub></span>
</th>
</template>
<script>
export default {
name: "SemanticTableHeader",
props: {
sth : {
type : Object,
default: () => {}
},
sths : {
type : Array,
default: () => { return [] }
},
filtrosOrder : {
type : Array,
default: () => { return [] }
},
isSearching : {
type : Boolean,
required : true
}
},
methods: {
cycleSort(sth, event) {
if(this.isSearching == true){
return false;
}
switch (sth.direction) {
case null:
sth.direction = 'asc';
sth.icon = 'sort ascending';
break;
case 'asc':
sth.direction = 'desc';
sth.icon = 'sort descending';
break;
case 'desc':
sth.direction = null;
sth.icon = 'sort disabled';
break;
default:
sth.direction = null;
sth.icon = 'sort disabled';
}
this.manejaCambioHeader(sth);
},
manejaCambioHeader: _.debounce(function (sth) {
var self = this;
console.log(this.filtrosOrder);
let auxUser = _.find(this.filtrosOrder, function(o) { return o.id == sth.id; });
if( auxUser != null ){
auxUser.direction = sth.direction;
if(auxUser.direction == null){
for (var i=0 ; i < this.filtrosOrder.length ; i++){
if (this.filtrosOrder[i].id === auxUser.id) {
let auxSths = _.find(self.sths, function(o) { return o.id == sth.id; });
auxSths.posicion = 0;
this.filtrosOrder.splice(i, 1);
}
}
}
}else{
this.filtrosOrder.push({ id: sth.id, direction: sth.direction });
}
for (var i=0 ; i < self.filtrosOrder.length; i++){
let auxSths = _.find(this.sths, function(o) { return o.id == self.filtrosOrder[i].id; });
auxSths.posicion = i + 1;
}
console.log(this.filtrosOrder);
this.$emit('sortHeaderChanged', sth);
}, 400),
},
}
</script>
<style lang="css" scoped>
th span{
cursor: pointer !important;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */
}
i.icon{
margin: 0em -0.2em 0em 0em;
}
</style>
In my Index views i just load the component and use it like this
<template>
<table>
<thead>
<tr>
<semantic-table-header v-for="sth in sths" :key="sth.key"
:sth="sth"
:sths="sths"
:isSearching="isSearching"
:filtrosOrder="filtros.orderBy"
#sortHeaderChanged="fetchIndex"
></semantic-table-header>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="contact in contacts" :key="contact.key" :class="[contact.justAdded ? 'justAdded' : '']">
</tr>
</tbody>
</table>
</template>
export default {
name: "ContactsIndex",
data:() => ({
filtros:{
orderBy:[
{ id: 'nombre', direction: 'asc' } // orderBy is calculated through the headers component
]
},
sths:[
{ id: 'nombre', texto: 'Nombre', icon: 'sort ascending', direction: 'asc', posicion: 1 },
{ id: 'telefonos', texto: 'Teléfono(s)', icon: 'sort disabled', direction: null, posicion: 0 },
{ id: 'emails', texto: 'Correo Electrónico(s)', icon: 'sort disabled', direction: null, posicion: 0 },
{ id: 'estatus', texto: 'Estatus', icon: 'sort disabled', direction: null, posicion: 0 }
],
contacts: [],
}),
created() {
this.fetchIndex();
},
methods: {
resetFilters() {
// this function is to reset filters and headers
Object.assign(this.$data.filtros, this.$options.data().filtros);
this.$data.sths = this.$options.data().sths;
this.fetchIndex();
},
fetchIndex() {
let self = this;
// this is a wrapper i made for an axios post call you can replace it with a normal call
singleIndexRequest('/api/v1/contacts/index', self).then(response => {
self.contacts = response.data.contacts;
});
},
}
}