1 32 33 package com.nqadmin.swingSet; 34 35 import java.awt.*; 36 import java.awt.event.*; 37 import javax.swing.*; 38 import java.sql.*; 39 import java.io.*; 40 import com.nqadmin.swingSet.datasources.SSRowSet; 41 import javax.sql.RowSetListener ; 42 import javax.sql.RowSetEvent ; 43 44 65 public class SSDataNavigator extends JPanel { 66 67 70 protected JButton firstButton = new JButton(); 71 72 75 protected JButton previousButton = new JButton(); 76 77 80 protected JTextField txtCurrentRow = new JTextField(); 81 82 85 protected JButton nextButton = new JButton(); 86 87 90 protected JButton lastButton = new JButton(); 91 92 95 protected JButton commitButton = new JButton(); 97 100 protected JButton undoButton = new JButton(); 101 102 105 protected JButton refreshButton = new JButton(); 107 110 protected JButton addButton = new JButton(); 111 112 115 protected JButton deleteButton = new JButton(); 116 117 120 protected JLabel lblRowCount = new JLabel(); 121 122 125 protected boolean modification = true; 126 127 130 protected boolean deletion = true; 131 132 135 protected boolean insertion = true; 136 137 140 protected boolean confirmDeletes = true; 141 142 146 protected boolean callExecute = true; 147 148 151 protected SSRowSet sSRowSet = null; 152 153 156 protected SSDBNav dBNav = null; 157 158 161 protected int rowCount = 0; 162 163 166 protected int currentRow = 0; 167 168 171 protected boolean onInsertRow = false; 172 173 176 protected Dimension buttonSize = new Dimension(40, 20); 177 178 181 protected Dimension txtFieldSize = new Dimension(65, 20); 182 183 186 private final SSDBNavRowSetListener sSRowSetListener = new SSDBNavRowSetListener(); 187 188 192 public SSDataNavigator() { 193 addToolTips(); 194 createPanel(); 195 addListeners(); 196 } 197 198 203 public SSDataNavigator(SSRowSet _sSRowSet) { 204 setSSRowSet(_sSRowSet); 205 addToolTips(); 206 createPanel(); 207 addListeners(); 208 } 209 210 217 public SSDataNavigator(SSRowSet _sSRowSet, Dimension _buttonSize) { 218 buttonSize = _buttonSize; 219 setSSRowSet(_sSRowSet); 220 addToolTips(); 221 createPanel(); 222 addListeners(); 223 } 224 225 230 public boolean containsRows() { 231 232 if ( rowCount == 0 ) { 233 return false; 234 } 235 236 return true; 237 } 238 239 245 public void setCallExecute(boolean _callExecute) { 246 boolean oldValue = callExecute; 247 callExecute = _callExecute; 248 firePropertyChange("callExecute", oldValue, callExecute); 249 } 250 251 257 public boolean getCallExecute() { 258 return callExecute; 259 } 260 261 266 public void setButtonSize(Dimension _buttonSize) { 267 Dimension oldValue = buttonSize; 268 buttonSize = _buttonSize; 269 firePropertyChange("buttonSize", oldValue, buttonSize); 270 setButtonSizes(); 271 } 272 273 279 public Dimension getButtonSize() { 280 return buttonSize; 281 } 282 283 290 public void setDBNav(SSDBNav _dBNav) { 291 SSDBNav oldValue = dBNav; 292 dBNav = _dBNav; 293 firePropertyChange("dBNav", oldValue, dBNav); 294 } 295 296 302 public SSDBNav getDBNav() { 303 return dBNav; 304 } 305 306 314 public void setModification(boolean _modification) { 315 boolean oldValue = modification; 316 modification = _modification; 317 firePropertyChange("modification", oldValue, modification); 318 319 if (!modification) { 320 commitButton.setEnabled(false); 321 undoButton.setEnabled(false); 322 addButton.setEnabled(false); 323 deleteButton.setEnabled(false); 324 } else { 325 commitButton.setEnabled(true); 326 undoButton.setEnabled(true); 327 addButton.setEnabled(true); 328 deleteButton.setEnabled(true); 329 } 330 } 331 332 338 public boolean getModification() { 339 return modification; 340 } 341 342 348 public void setDeletion(boolean _deletion) { 349 boolean oldValue = deletion; 350 deletion = _deletion; 351 firePropertyChange("deletion", oldValue, deletion); 352 353 if (!deletion) { 354 deleteButton.setEnabled(false); 355 } else { 356 deleteButton.setEnabled(true); 357 } 358 } 359 360 365 public boolean getDeletion(){ 366 return deletion; 367 } 368 369 375 public void setInsertion(boolean _insertion) { 376 boolean oldValue = insertion; 377 insertion = _insertion; 378 firePropertyChange("insertion", oldValue, insertion); 379 380 if (!insertion) { 381 addButton.setEnabled(false); 382 } else{ 383 addButton.setEnabled(true); 384 } 385 } 386 387 392 public boolean getInsertion() { 393 return insertion; 394 } 395 396 403 public void setConfirmDeletes(boolean _confirmDeletes) { 404 boolean oldValue = confirmDeletes; 405 confirmDeletes = _confirmDeletes; 406 firePropertyChange("confirmDeletes", oldValue, confirmDeletes); 407 } 408 409 415 public boolean getConfirmDeletes() { 416 return confirmDeletes; 417 } 418 419 426 public void setSSRowSet(SSRowSet _sSRowSet) { 427 if(sSRowSet != null){ 428 sSRowSet.removeRowSetListener(sSRowSetListener); 429 } 430 431 SSRowSet oldValue = sSRowSet; 432 sSRowSet = _sSRowSet; 433 firePropertyChange("sSRowSet", oldValue, sSRowSet); 434 435 try { 437 if (callExecute) { 438 sSRowSet.execute(); 439 } 440 441 if (!sSRowSet.next()) { 442 rowCount = 0; 443 currentRow = 0; 444 } else { 445 sSRowSet.last(); 447 rowCount = sSRowSet.getRow(); 448 sSRowSet.first(); 449 currentRow = sSRowSet.getRow(); 450 } 451 lblRowCount.setText("of " + rowCount); 453 txtCurrentRow.setText(String.valueOf(currentRow)); 454 455 sSRowSet.addRowSetListener(sSRowSetListener); 456 } catch(SQLException se) { 457 se.printStackTrace(); 458 } 459 460 if (rowCount == 0) { 465 firstButton.setEnabled(false); 466 previousButton.setEnabled(false); 467 nextButton.setEnabled(false); 468 lastButton.setEnabled(false); 469 } else{ 470 firstButton.setEnabled(true); 471 previousButton.setEnabled(true); 472 nextButton.setEnabled(true); 473 lastButton.setEnabled(true); 474 } 475 476 try { 477 if (sSRowSet.isLast()) { 478 nextButton.setEnabled(false); 479 lastButton.setEnabled(false); 480 } 481 if (sSRowSet.isFirst()) { 482 firstButton.setEnabled(false); 483 previousButton.setEnabled(false); 484 } 485 486 } catch(SQLException se) { 487 se.printStackTrace(); 488 } 489 490 492 refreshButton.setEnabled(true); 498 if (insertion) { 499 addButton.setEnabled(true); 500 } 501 if (deletion) { 502 deleteButton.setEnabled(true); 503 } 504 } 505 506 511 public SSRowSet getSSRowSet() { 512 return sSRowSet; 513 } 514 515 521 public boolean updatePresentRow() { 522 try { 523 if (!onInsertRow) { 524 sSRowSet.updateRow(); 525 } 526 return true; 527 } catch(Exception e) { 528 return false; 529 } 530 } 531 532 535 public void doFirstButtonClick(){ 536 firstButton.doClick(); 537 } 538 539 542 public void doPreviousButtonClick(){ 543 previousButton.doClick(); 544 } 545 546 549 public void doNextButtonClick(){ 550 nextButton.doClick(); 551 } 552 553 556 public void doLastButtonClick(){ 557 lastButton.doClick(); 558 } 559 560 563 public void doRefreshButtonClick(){ 564 refreshButton.doClick(); 565 } 566 567 570 public void doCommitButtonClick(){ 571 commitButton.doClick(); 572 } 573 574 577 public void doUndoButtonClick(){ 578 undoButton.doClick(); 579 } 580 581 584 public void doAddButtonClick(){ 585 addButton.doClick(); 586 } 587 588 591 public void doDeleteButtonClick(){ 592 deleteButton.doClick(); 593 } 594 595 598 protected void addToolTips() { 599 600 try { 601 ClassLoader cl = this.getClass().getClassLoader(); 602 firstButton.setIcon(new ImageIcon(cl.getResource("images/first.gif"))); 603 previousButton.setIcon(new ImageIcon(cl.getResource("images/prev.gif"))); 604 nextButton.setIcon(new ImageIcon(cl.getResource("images/next.gif"))); 605 lastButton.setIcon(new ImageIcon(cl.getResource("images/last.gif"))); 606 commitButton.setIcon(new ImageIcon(cl.getResource("images/commit.gif"))); 607 undoButton.setIcon(new ImageIcon(cl.getResource("images/undo.gif"))); 608 refreshButton.setIcon(new ImageIcon(cl.getResource("images/refresh.gif"))); 609 addButton.setIcon(new ImageIcon(cl.getResource("images/add.gif"))); 610 deleteButton.setIcon(new ImageIcon(cl.getResource("images/delete.gif"))); 611 } catch(Exception e) { 612 firstButton.setText("<<"); 613 previousButton.setText("<"); 614 nextButton.setText(">"); 615 lastButton.setText(">>"); 616 commitButton.setText("Commit"); 617 undoButton.setText("Undo"); 618 refreshButton.setText("Refresh"); 619 addButton.setText("Add"); 620 deleteButton.setText("Delete"); 621 System.out.println("Unable to load images for navigator buttons"); 622 } 623 624 firstButton.setToolTipText("First"); 625 previousButton.setToolTipText("Previous"); 626 nextButton.setToolTipText("Next"); 627 lastButton.setToolTipText("Last"); 628 commitButton.setToolTipText("Commit"); 629 undoButton.setToolTipText("Undo"); 630 refreshButton.setToolTipText("Refresh"); 631 addButton.setToolTipText("Add Record"); 632 deleteButton.setToolTipText("Delete Record"); 633 634 } 636 639 protected void setButtonSizes() { 640 641 firstButton.setPreferredSize(buttonSize); 643 previousButton.setPreferredSize(buttonSize); 644 nextButton.setPreferredSize(buttonSize); 645 lastButton.setPreferredSize(buttonSize); 646 commitButton.setPreferredSize(buttonSize); 647 undoButton.setPreferredSize(buttonSize); 648 refreshButton.setPreferredSize(buttonSize); 649 addButton.setPreferredSize(buttonSize); 650 deleteButton.setPreferredSize(buttonSize); 651 txtCurrentRow.setPreferredSize(txtFieldSize); 652 lblRowCount.setPreferredSize(txtFieldSize); 653 lblRowCount.setHorizontalAlignment(SwingConstants.CENTER); 654 655 firstButton.setMinimumSize(buttonSize); 657 previousButton.setMinimumSize(buttonSize); 658 nextButton.setMinimumSize(buttonSize); 659 lastButton.setMinimumSize(buttonSize); 660 commitButton.setMinimumSize(buttonSize); 661 undoButton.setMinimumSize(buttonSize); 662 refreshButton.setMinimumSize(buttonSize); 663 addButton.setMinimumSize(buttonSize); 664 deleteButton.setMinimumSize(buttonSize); 665 txtCurrentRow.setMinimumSize(txtFieldSize); 666 lblRowCount.setMinimumSize(txtFieldSize); 667 } 668 669 672 protected void createPanel() { 673 674 setButtonSizes(); 675 setLayout(new BoxLayout(this,BoxLayout.LINE_AXIS) ); 677 678 add(firstButton); 680 add(previousButton); 681 add(txtCurrentRow); 682 add(nextButton); 683 add(lastButton); 684 add(commitButton); 685 add(undoButton); 686 add(refreshButton); 687 add(addButton); 688 add(deleteButton); 689 add(lblRowCount); 690 692 } 693 694 697 private void addListeners() { 698 699 firstButton.addActionListener(new ActionListener(){ 703 public void actionPerformed(ActionEvent ae) { 704 try { 705 if ( modification ) { 706 sSRowSet.updateRow(); 707 } 708 sSRowSet.first(); 709 710 firstButton.setEnabled(false); 711 previousButton.setEnabled(false); 712 if (!sSRowSet.isLast()) { 713 nextButton.setEnabled(true); 714 lastButton.setEnabled(true); 715 } else { 716 nextButton.setEnabled(false); 717 lastButton.setEnabled(false); 718 } 719 if ( dBNav != null ) { 720 dBNav.performNavigationOps(SSDBNav.NAVIGATION_FIRST); 721 } 722 currentRow = 1; 724 txtCurrentRow.setText(String.valueOf(currentRow)); 725 } catch(SQLException se) { 726 se.printStackTrace(); 727 JOptionPane.showMessageDialog(SSDataNavigator.this,"Exception occured while updating row or moving the cursor.\n"+se.getMessage()); 728 } 729 } 730 }); 731 732 previousButton.addActionListener(new ActionListener() { 737 public void actionPerformed(ActionEvent ae) { 738 try { 739 if ( modification ) { 741 sSRowSet.updateRow(); 742 } 743 if ( sSRowSet.getRow() != 0 && !sSRowSet.previous() ) { 744 sSRowSet.first(); 745 } 746 if (sSRowSet.isFirst() || sSRowSet.getRow() == 0){ 748 firstButton.setEnabled(false); 749 previousButton.setEnabled(false); 750 } 751 752 if ( !sSRowSet.isLast() ) { 754 nextButton.setEnabled(true); 755 lastButton.setEnabled(true); 756 } 757 758 if ( dBNav != null ) { 759 dBNav.performNavigationOps(SSDBNav.NAVIGATION_PREVIOUS); 760 } 761 currentRow = sSRowSet.getRow(); 763 txtCurrentRow.setText(String.valueOf(currentRow)); 764 } catch(SQLException se) { 765 se.printStackTrace(); 766 JOptionPane.showMessageDialog(SSDataNavigator.this,"Exception occured while updating row or moving the cursor.\n"+se.getMessage()); 767 } 768 } 769 }); 770 771 nextButton.addActionListener(new ActionListener() { 775 public void actionPerformed(ActionEvent ae) { 776 try { 777 if ( modification ) { 779 sSRowSet.updateRow(); 780 } 781 if ( !sSRowSet.next() ) { 782 nextButton.setEnabled(false); 783 lastButton.setEnabled(false); 784 sSRowSet.last(); 785 } 786 if ( sSRowSet.isLast() ) { 788 nextButton.setEnabled(false); 789 lastButton.setEnabled(false); 790 } 791 792 if ( !sSRowSet.isFirst() ) { 794 previousButton.setEnabled(true); 795 firstButton.setEnabled(true); 796 } 797 798 if ( dBNav != null ) { 799 dBNav.performNavigationOps(SSDBNav.NAVIGATION_NEXT); 800 } 801 currentRow = sSRowSet.getRow(); 803 txtCurrentRow.setText(String.valueOf(currentRow)); 804 } catch(SQLException se) { 805 se.printStackTrace(); 806 JOptionPane.showMessageDialog(SSDataNavigator.this,"Exception occured while updating row or moving the cursor.\n"+se.getMessage()); 807 } 808 } 809 }); 810 811 812 lastButton.addActionListener(new ActionListener() { 817 public void actionPerformed(ActionEvent ae) { 818 try { 819 if ( modification ) { 821 sSRowSet.updateRow(); 822 } 823 sSRowSet.last(); 824 825 nextButton.setEnabled(false); 826 lastButton.setEnabled(false); 827 if (!sSRowSet.isFirst()) { 828 firstButton.setEnabled(true); 829 previousButton.setEnabled(true); 830 } else { 831 firstButton.setEnabled(false); 832 previousButton.setEnabled(false); 833 } 834 if ( dBNav != null ) { 835 dBNav.performNavigationOps(SSDBNav.NAVIGATION_LAST); 836 } 837 currentRow = sSRowSet.getRow(); 839 txtCurrentRow.setText(String.valueOf(currentRow)); 840 } catch(SQLException se) { 841 se.printStackTrace(); 842 JOptionPane.showMessageDialog(SSDataNavigator.this,"Exception occured while updating row or moving the cursor.\n"+se.getMessage()); 843 } 844 } 845 }); 846 847 commitButton.addActionListener(new ActionListener() { 851 public void actionPerformed(ActionEvent ae) { 852 try { 853 if (onInsertRow) { 854 sSRowSet.insertRow(); 856 if ( dBNav != null ) { 857 dBNav.performPostInsertOps(); 858 } 859 860 sSRowSet.moveToCurrentRow(); 861 sSRowSet.last(); 867 rowCount++; 869 lblRowCount.setText("of " + rowCount); 871 currentRow = sSRowSet.getRow(); 873 txtCurrentRow.setText(String.valueOf(currentRow)); 875 } else { 876 sSRowSet.updateRow(); 878 } 879 880 onInsertRow = false; 881 882 if (!sSRowSet.isFirst()) { 883 firstButton.setEnabled(true); 884 previousButton.setEnabled(true); 885 } 886 if (!sSRowSet.isLast()) { 887 nextButton.setEnabled(true); 888 lastButton.setEnabled(true); 889 } 890 refreshButton.setEnabled(true); 891 892 if (insertion) { 893 addButton.setEnabled(true); 894 } 895 if (deletion) { 896 deleteButton.setEnabled(true); 897 } 898 } catch(SQLException se) { 899 JOptionPane.showMessageDialog(SSDataNavigator.this,"Exception occured while inserting row.\n"+se.getMessage()); 900 se.printStackTrace(); 901 } 902 } 903 }); 904 905 undoButton.addActionListener(new ActionListener() { 909 public void actionPerformed(ActionEvent ae) { 910 try { 911 if(onInsertRow){ 913 sSRowSet.moveToCurrentRow(); 914 } 915 sSRowSet.cancelRowUpdates(); 921 onInsertRow = false; 922 if (dBNav != null) { 923 dBNav.performCancelOps(); 924 } 925 firstButton.setEnabled(true); 928 previousButton.setEnabled(true); 929 nextButton.setEnabled(true); 930 lastButton.setEnabled(true); 931 refreshButton.setEnabled(true); 932 if (insertion) { 933 addButton.setEnabled(true); 934 } 935 if (deletion) { 936 deleteButton.setEnabled(true); 937 } 938 939 int row = sSRowSet.getRow(); 941 txtCurrentRow.setText(String.valueOf(currentRow)); 942 943 } catch(SQLException se) { 944 JOptionPane.showMessageDialog(SSDataNavigator.this,"Exception occured while undoing changes.\n"+se.getMessage()); 945 se.printStackTrace(); 946 } 947 } 948 }); 949 950 refreshButton.addActionListener(new ActionListener() { 955 public void actionPerformed(ActionEvent ae) { 956 sSRowSet.removeRowSetListener(sSRowSetListener); 957 try { 958 if (callExecute) { 959 sSRowSet.execute(); 960 if (!sSRowSet.next()) { 961 rowCount = 0; 962 currentRow = 0; 963 firstButton.setEnabled(false); 964 previousButton.setEnabled(false); 965 nextButton.setEnabled(false); 966 lastButton.setEnabled(false); 967 968 } else { 969 sSRowSet.last(); 971 rowCount = sSRowSet.getRow(); 972 sSRowSet.first(); 973 currentRow = sSRowSet.getRow(); 974 firstButton.setEnabled(false); 975 previousButton.setEnabled(false); 976 nextButton.setEnabled(true); 977 lastButton.setEnabled(true); 978 } 979 lblRowCount.setText("of " + rowCount); 981 txtCurrentRow.setText(String.valueOf(currentRow)); 982 } 983 984 if ( dBNav != null ) { 985 dBNav.performRefreshOps(); 986 } 987 988 } catch(SQLException se) { 989 se.printStackTrace(); 990 JOptionPane.showMessageDialog(SSDataNavigator.this,"Exception occured refreshing the data.\n"+se.getMessage()); 991 } 992 sSRowSet.addRowSetListener(sSRowSetListener); 993 } 994 }); 995 996 addButton.addActionListener(new ActionListener() { 1000 public void actionPerformed(ActionEvent ae) { 1001 try { 1002 sSRowSet.moveToInsertRow(); 1003 onInsertRow = true; 1004 1005 if ( dBNav != null ) { 1006 dBNav.performPreInsertOps(); 1007 } 1008 firstButton.setEnabled(false); 1012 previousButton.setEnabled(false); 1013 nextButton.setEnabled(false); 1014 lastButton.setEnabled(false); 1015 commitButton.setEnabled(true); 1016 undoButton.setEnabled(true); 1017 refreshButton.setEnabled(false); 1018 addButton.setEnabled(false); 1019 deleteButton.setEnabled(false); 1020 1021 } catch(SQLException se) { 1022 JOptionPane.showMessageDialog(SSDataNavigator.this,"Exception occured while moving to insert row.\n"+se.getMessage()); 1023 se.printStackTrace(); 1024 } 1025 } 1026 }); 1027 1028 deleteButton.addActionListener(new ActionListener() { 1032 public void actionPerformed(ActionEvent ae) { 1033 try { 1034 int answer = JOptionPane.showConfirmDialog(SSDataNavigator.this,"Are you sure you want to delete this record?","Delete Present Record", JOptionPane.YES_NO_OPTION); 1035 if ( answer != JOptionPane.YES_OPTION ) { 1036 return; 1037 } 1038 1039 if ( dBNav != null ) { 1040 dBNav.performPreDeletionOps(); 1041 } 1042 sSRowSet.deleteRow(); 1043 if ( dBNav != null ) { 1044 dBNav.performPostDeletionOps(); 1045 } 1046 1047 if (! sSRowSet.next() ) { 1048 sSRowSet.last(); 1049 } 1050 rowCount--; 1052 lblRowCount.setText("of " + rowCount); 1054 currentRow = sSRowSet.getRow(); 1056 txtCurrentRow.setText(String.valueOf(currentRow)); 1058 } catch(SQLException se) { 1059 JOptionPane.showMessageDialog(SSDataNavigator.this,"Exception occured while deleting row.\n"+se.getMessage()); 1060 se.printStackTrace(); 1061 } 1062 } 1063 }); 1064 1065 txtCurrentRow.addKeyListener(new KeyAdapter() { 1070 public void keyReleased(KeyEvent ke) { 1071 if (ke.getKeyCode() == KeyEvent.VK_ENTER) { 1072 try { 1073 int row = Integer.parseInt(txtCurrentRow.getText().trim()); 1074 if (row <= rowCount && row >0) { 1075 sSRowSet.absolute(row); 1076 } 1077 } catch(Exception e) { 1078 } 1080 } 1081 } 1082 }); 1083 1084 } 1085 1086 1089 private class SSDBNavRowSetListener implements RowSetListener { 1090 1091 public void cursorMoved(RowSetEvent rse){ 1092 try{ 1094 currentRow = sSRowSet.getRow(); 1095 updateInfo(); 1096 }catch(SQLException se){ 1097 se.printStackTrace(); 1098 } 1099 } 1100 1101 public void rowChanged(RowSetEvent rse){ 1102 } 1104 1105 public void rowSetChanged(RowSetEvent rse){ 1106 try{ 1108 sSRowSet.last(); 1109 rowCount = sSRowSet.getRow(); 1110 sSRowSet.first(); 1111 currentRow = sSRowSet.getRow(); 1112 updateInfo(); 1113 }catch(SQLException se){ 1114 se.printStackTrace(); 1115 } 1116 updateInfo(); 1117 } 1118 1119 protected void updateInfo(){ 1120 lblRowCount.setText("of " + rowCount); 1122 txtCurrentRow.setText(String.valueOf(currentRow)); 1123 if (rowCount == 0) { 1125 firstButton.setEnabled(false); 1126 previousButton.setEnabled(false); 1127 nextButton.setEnabled(false); 1128 lastButton.setEnabled(false); 1129 } else{ 1130 firstButton.setEnabled(true); 1131 previousButton.setEnabled(true); 1132 nextButton.setEnabled(true); 1133 lastButton.setEnabled(true); 1134 } 1135 1136 try { 1137 if (sSRowSet.isLast()) { 1138 nextButton.setEnabled(false); 1139 lastButton.setEnabled(false); 1140 } 1141 if (sSRowSet.isFirst()) { 1142 firstButton.setEnabled(false); 1143 previousButton.setEnabled(false); 1144 } 1145 1146 } catch(SQLException se) { 1147 se.printStackTrace(); 1148 } 1149 } 1150 } 1151 1152 1153 1155 1163 public void setRowSet(SSRowSet _sSRowSet) { 1164 setSSRowSet(_sSRowSet); 1165 } 1166 1167 1175 public SSRowSet getRowSet() { 1176 return sSRowSet; 1177 } 1178 1179} 1181 1182 1183 | Popular Tags |