You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

listView.vue 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. <template>
  2. <div>
  3. <div style="height:5px"></div>
  4. <div class="d-flex justify-content-between table-title">
  5. <div class="p-2" v-if="!hideSearch">
  6. <input v-model="searchItem" class="form-control" placeholder="Search...." />
  7. </div>
  8. <div class="p-2" v-if="title">
  9. <h2>{{ title }}</h2>
  10. </div>
  11. <div class="p-2">
  12. <div class="d-flex flex-row">
  13. <div class="p2" v-if="showColumnChooser">
  14. <div
  15. class="btn btn-primary myBackground btn-width cursor-pointer"
  16. data-toggle="modal"
  17. data-target="#myModal"
  18. >Column Chooser</div>
  19. <div class="col-md-12">
  20. <div id="myModal" class="modal fade" role="dialog">
  21. <div class="modal-dialog modal-lg">
  22. <!-- Modal content-->
  23. <div class="modal-content">
  24. <div class="modal-header">
  25. <h5>Column Chooser</h5>
  26. <button type="button" class="close" data-dismiss="modal">&times;</button>
  27. </div>
  28. <div style="margin-left:50px; margin-right:50px;margin-bottom:50px;">
  29. <ListViewControl :items="allColumn" @checkItem="checkItem" />
  30. </div>
  31. </div>
  32. </div>
  33. </div>
  34. </div>
  35. </div>
  36. <div class="p2" v-if="selectedItems.length > 0">
  37. <div
  38. class="btn btn-primary myBackground btn-width cursor-pointer"
  39. @click="onClearSelected()"
  40. >Clear Selected</div>
  41. </div>
  42. <div class="p2" v-if="showNew">
  43. <div class="btn btn-primary myBackground btn-width cursor-pointer" @click="onNew()">New</div>
  44. </div>
  45. </div>
  46. </div>
  47. </div>
  48. <div style="height:5px"></div>
  49. <div v-if="items && items.length > 0" class="table-responsive">
  50. <table
  51. id="table"
  52. :class="{ 'table table-hover': 1 === 1, 'table-sm': compact, 'table-bordered': bordered }"
  53. >
  54. <thead>
  55. <tr class="dnd-moved">
  56. <th v-for="(column, c) in Columns" :key="c">
  57. <div
  58. @click="sortBy(column)"
  59. @mouseover="hover = c"
  60. @mouseleave="hover = -1"
  61. :class="{ active: hover === c }"
  62. >
  63. <div class="d-flex bd-highlight">
  64. <div
  65. v-if="displayHeaders.length === 0"
  66. class="p-2 w-100 bd-highlight"
  67. >{{ column | toProper }}</div>
  68. <div
  69. v-else
  70. class="p-2 w-100 bd-highlight"
  71. >{{ displayHeaders[c] !== "" ? displayHeaders[c] : column | toProper }}</div>
  72. <div class="p-2 flex-shrink-1 bd-highlight">
  73. <img
  74. src="../../../public/img/sort-up.png"
  75. height="8px;"
  76. v-if="sortKey === column && reverse"
  77. />
  78. <img
  79. src="../../../public/img/sort-down.png"
  80. height="8px;"
  81. v-if="sortKey === column && !reverse"
  82. />
  83. </div>
  84. </div>
  85. </div>
  86. </th>
  87. <th v-if="showCustomAction"></th>
  88. <th v-if="editable"></th>
  89. <th v-if="deleteable"></th>
  90. </tr>
  91. </thead>
  92. <tbody class="my-table">
  93. <tr
  94. v-for="(item, i) in DisplayItems"
  95. :key="i"
  96. @click="onRowClick(item, i)"
  97. :class="{ selected: isSelected(i), 'cursor-pointer': allowSelect }"
  98. >
  99. <td v-for="(column, c) in Columns" :key="c">
  100. <div
  101. v-if="displayFormats.length === 0"
  102. >{{ isObject(item[column]) ? item[column].display : item[column] }}</div>
  103. <div v-else-if="displayFormats.length > 0 && displayFormats[c] === 'date'">
  104. <div
  105. v-if="item[column] !== '0001-01-01T00:00:00'"
  106. >{{ isObject(item[column]) ? item[column].display : item[column] | toDate }}</div>
  107. </div>
  108. <div
  109. v-else-if="displayFormats.length > 0 && displayFormats[c] === 'money'"
  110. >{{ isObject(item[column]) ? item[column].display : item[column] | toCurrency }}</div>
  111. <div v-else-if="displayFormats.length > 0 && displayFormats[c] === 'image'">
  112. <img :src="item[column]" style="height:100px; width:100px; object-fit: cover;" />
  113. </div>
  114. <div v-else>{{ isObject(item[column]) ? item[column].display : item[column] }}</div>
  115. </td>
  116. <td v-if="showCustomAction" class="my-width">
  117. <button
  118. type="button"
  119. class="btn my-btn"
  120. @click="onCustomClick(item)"
  121. >{{ CustomActionHeading }}</button>
  122. </td>
  123. <td v-if="editable" class="my-width">
  124. <button type="button" class="btn my-btn" @click="onEdit(item)">Edit</button>
  125. </td>
  126. <td v-if="deleteable" class="my-width">
  127. <button type="button" class="btn my-btn" @click="onDelete(item)">Delete</button>
  128. </td>
  129. </tr>
  130. </tbody>
  131. </table>
  132. <div class="d-flex justify-content-between" v-if="showPager">
  133. <div class="p-1">
  134. {{
  135. currentPage +
  136. " / " +
  137. PageCount +
  138. (!hideItemCount ? " - (" + FilteredItems.length + " items)" : "")
  139. }}
  140. </div>
  141. <div class="p-1">
  142. <BasePagination
  143. :currentPage="currentPage"
  144. :pageCount="PageCount"
  145. @nextPage="pageChangeHandle('next')"
  146. @previousPage="pageChangeHandle('previous')"
  147. @loadPage="pageChangeHandle"
  148. />
  149. </div>
  150. <div class="p-2">
  151. <div class="d-flex flex-row">
  152. <div>
  153. <select
  154. class="form-control"
  155. v-model="visibleItemsPerPageCount"
  156. @change="onChangeItemsPerPage()"
  157. >
  158. <option v-for="(item, i) in itemsPerPageList" :key="i">{{ item }}</option>
  159. </select>
  160. </div>
  161. </div>
  162. </div>
  163. </div>
  164. </div>
  165. <div v-else>
  166. <Alert :text="'No items found'" :type="'INFO'" />
  167. </div>
  168. </div>
  169. </template>
  170. <script>
  171. import _ from "lodash";
  172. import ItemsPerPageList from "../../assets/staticData/itemsPerPage";
  173. import BasePagination from "./basePagination.vue";
  174. import Alert from "./alert.vue";
  175. import ListViewControl from "./listViewControl.vue";
  176. export default {
  177. components: {
  178. BasePagination,
  179. Alert,
  180. ListViewControl
  181. },
  182. mounted() {
  183. if (this.itemsPerPageList && this.itemsPerPageList.length > 0) {
  184. const [startItem] = this.itemsPerPageList;
  185. this.visibleItemsPerPageCount = startItem;
  186. }
  187. this.getInitColumn();
  188. },
  189. props: {
  190. compact: {
  191. default: true
  192. },
  193. allowSelect: {
  194. default: true
  195. },
  196. allowMultipleSelect: {
  197. default: false
  198. },
  199. hideSearch: {
  200. default: false
  201. },
  202. showNew: {
  203. default: true
  204. },
  205. items: undefined,
  206. editable: {
  207. default: false
  208. },
  209. deleteable: {
  210. default: false
  211. },
  212. columnCount: {
  213. default: 6
  214. },
  215. showPager: {
  216. default: true
  217. },
  218. title: {
  219. default: undefined
  220. },
  221. sortKey: {
  222. default: "id"
  223. },
  224. hideItemCount: {
  225. default: false
  226. },
  227. currentPage: {
  228. default: 1
  229. },
  230. bordered: {
  231. default: true
  232. },
  233. striped: {
  234. default: true
  235. },
  236. showColumnChooser: {
  237. default: true
  238. },
  239. displayColumns: {
  240. type: Array,
  241. default: () => []
  242. },
  243. displayFormats: {
  244. type: Array,
  245. default: () => []
  246. },
  247. displayHeaders: {
  248. type: Array,
  249. default: () => []
  250. },
  251. showCustomAction: {
  252. default: false
  253. },
  254. CustomActionHeading: {
  255. default: ""
  256. },
  257. CustomActionCondition: {
  258. default: ""
  259. }
  260. },
  261. data() {
  262. return {
  263. hover: -1,
  264. selectedItems: [],
  265. showControl: false,
  266. reverse: false,
  267. searchItem: "",
  268. visibleItemsPerPageCount: 20,
  269. itemsPerPageList: ItemsPerPageList,
  270. visibleColumn: [],
  271. allColumn: []
  272. };
  273. },
  274. methods: {
  275. checkItem(column, show) {
  276. const list = [];
  277. for (const i in this.allColumn) {
  278. const item = this.allColumn[i];
  279. if (item && item.column === column) {
  280. item.show = show;
  281. }
  282. list.push(item);
  283. }
  284. this.allColumn = list;
  285. },
  286. getInitColumn() {
  287. const listAll = [];
  288. if (this.items) {
  289. for (const i in Object.keys(this.items)) {
  290. const item = this.items[i];
  291. for (const o in Object.keys(item)) {
  292. if (
  293. !listAll.includes(Object.keys(item)[o]) &&
  294. !Array.isArray(Object.values(item)[o])
  295. ) {
  296. const columnName = Object.keys(item)[o];
  297. if (!listAll.some(x => x.column === columnName)) {
  298. listAll.push({
  299. column: columnName,
  300. show: _.filter(listAll, x => x.show).length < this.columnCount
  301. });
  302. }
  303. }
  304. }
  305. }
  306. }
  307. this.allColumn = listAll;
  308. },
  309. onClearSelected() {
  310. this.selectedItems = [];
  311. this.$emit("onClearSelected");
  312. },
  313. isSelected(i) {
  314. const ind = this.getActualIndex(i);
  315. return _.some(this.selectedItems, x => x === ind);
  316. },
  317. onNew() {
  318. this.$emit("onNew");
  319. },
  320. isObject(item) {
  321. return !!item && item.constructor === Object;
  322. },
  323. isDate(item) {
  324. return !!item && item.constructor === Date;
  325. },
  326. isDecimal(item) {
  327. if (!!item && item.constructor === Number && item.indexOf(".") > 0) {
  328. return true;
  329. }
  330. return false;
  331. },
  332. isImage(item) {
  333. return (
  334. !!item &&
  335. item.constructor === String &&
  336. item.length > 9 &&
  337. item.substring(0, 9) === "data:image"
  338. );
  339. },
  340. onEdit(item) {
  341. this.$emit("onEdit", item);
  342. },
  343. onDelete(item) {
  344. this.$emit("onDelete", item);
  345. },
  346. onCustomClick(item) {
  347. this.$emit("onCustomClick", item);
  348. },
  349. onRowClick(item, i) {
  350. const ind = this.getActualIndex(i);
  351. if (_.some(this.selectedItems, x => x === ind)) {
  352. this.selectedItems = this.selectedItems.filter(x => x !== ind);
  353. } else {
  354. if (!this.allowMultipleSelect) {
  355. this.selectedItems = [];
  356. }
  357. this.selectedItems.push(item);
  358. }
  359. this.$emit("onRowClick", this.selectedItems);
  360. },
  361. getActualIndex(index) {
  362. return (this.currentPage - 1) * this.visibleItemsPerPageCount + index;
  363. },
  364. changeColumn(title, checked) {
  365. if (checked) {
  366. this.myColumns.push(title);
  367. } else {
  368. const ind = this.myColumns.indexOf(title);
  369. if (ind > -1) {
  370. this.myColumns.splice(ind, 1);
  371. }
  372. }
  373. },
  374. onControlVisibilityChange() {
  375. this.showControl = !this.showControl;
  376. },
  377. sortBy(sortKey) {
  378. this.reverse = this.sortKey === sortKey ? !this.reverse : false;
  379. this.sortKey = sortKey;
  380. },
  381. async pageChangeHandle(value) {
  382. console.log(value);
  383. switch (value) {
  384. case "next":
  385. this.currentPage += 1;
  386. break;
  387. case "previous":
  388. this.currentPage -= 1;
  389. break;
  390. default:
  391. this.currentPage = value;
  392. }
  393. },
  394. onChangeItemsPerPage() {
  395. if (this.currentPage !== 1) {
  396. this.currentPage = 1;
  397. }
  398. },
  399. inArray(array, value) {
  400. const lenght = array.length;
  401. for (let i = 0; i < lenght; i++) {
  402. if (array[i] === value) return true;
  403. }
  404. return false;
  405. }
  406. },
  407. computed: {
  408. ListWidth() {
  409. if (this.showControl) {
  410. return "col-md-9";
  411. }
  412. return "col-md-12";
  413. },
  414. SortDirection() {
  415. return this.reverse ? "desc" : "asc";
  416. },
  417. PageCount() {
  418. return this.visibleItemsPerPageCount !== 0
  419. ? Math.ceil(this.FilteredItems.length / this.visibleItemsPerPageCount)
  420. : 1;
  421. },
  422. Columns() {
  423. const listColumns = [];
  424. if (!this.allColumn || this.allColumn.length === 0) {
  425. this.getInitColumn();
  426. }
  427. if (this.displayColumns.length > 0) {
  428. for (let i = 0; i < this.displayColumns.length; i++) {
  429. listColumns.push(this.displayColumns[i]);
  430. }
  431. } else {
  432. const list = _.filter(this.allColumn, x => x.show);
  433. for (const i in list) {
  434. const item = list[i];
  435. if (item) {
  436. listColumns.push(item.column);
  437. }
  438. }
  439. }
  440. return listColumns;
  441. },
  442. AllColumns() {
  443. const list = [];
  444. return list;
  445. },
  446. FilteredItems() {
  447. const list = _.filter(this.items, item =>
  448. Object.values(item).some(
  449. i =>
  450. JSON.stringify(i)
  451. .toLowerCase()
  452. .indexOf(this.searchItem.toLowerCase()) > -1
  453. )
  454. );
  455. return _.orderBy(list, this.sortKey, this.SortDirection);
  456. },
  457. DisplayItems() {
  458. const list = this.FilteredItems;
  459. const startSlice = (this.currentPage - 1) * this.visibleItemsPerPageCount;
  460. let endSlice = this.currentPage * this.visibleItemsPerPageCount;
  461. if (endSlice > list.length) {
  462. endSlice = list.length;
  463. }
  464. return list.slice(startSlice, endSlice);
  465. }
  466. //GetAllColumn() {}
  467. }
  468. };
  469. </script>
  470. <style scoped>
  471. th[draggable] a,
  472. th[draggable] {
  473. cursor: move;
  474. }
  475. th[draggable] a:hover,
  476. th[draggable] a {
  477. display: block;
  478. text-decoration: none;
  479. color: #333333;
  480. }
  481. .table-striped > tbody > tr:nth-child(2n + 1) > td,
  482. .table-striped > tbody > tr:nth-child(2n + 1) > th {
  483. background-color: rgba(225, 225, 225, 0.8);
  484. }
  485. .active {
  486. background-color: rgba(255, 255, 255, 0.5);
  487. cursor: pointer;
  488. }
  489. .table > tbody > tr > td {
  490. vertical-align: middle;
  491. }
  492. .my-width {
  493. width: 20px;
  494. }
  495. .drag {
  496. background-color: rgba(0, 255, 0, 0.35);
  497. opacity: 0.25;
  498. }
  499. .dnd-drag {
  500. opacity: 0.25;
  501. }
  502. .over {
  503. background-color: rgba(0, 0, 255, 0.35);
  504. }
  505. .my-border {
  506. border: solid 3px silver;
  507. }
  508. .selected {
  509. background-color: rgba(96, 203, 235, 0.5);
  510. border: white 2px double;
  511. }
  512. .selected:hover {
  513. background-color: rgba(96, 203, 235, 0.85);
  514. }
  515. .btn-width {
  516. width: 125px;
  517. }
  518. .table-title {
  519. padding: 5px;
  520. border: rgba(200, 200, 200, 0.66) double 2px;
  521. border-radius: 5px;
  522. background-color: rgba(96, 203, 235, 0.25);
  523. }
  524. .table-title:hover {
  525. background-color: rgba(96, 203, 235, 0.4);
  526. }
  527. .table-header {
  528. background-color: rgba(200, 200, 200, 0.66);
  529. }
  530. .my-table {
  531. border: rgba(150, 150, 150, 0.75) 3px double;
  532. }
  533. </style>