{"id":1027,"date":"2011-12-21T12:40:21","date_gmt":"2011-12-21T17:40:21","guid":{"rendered":"http:\/\/anvil-of-time.com\/wordpress\/?p=1027"},"modified":"2021-03-11T09:22:53","modified_gmt":"2021-03-11T14:22:53","slug":"powerbuilder-datawindow-dragdrop-rows-with-business-rules","status":"publish","type":"post","link":"https:\/\/anvil-of-time.com\/powerbuilder\/powerbuilder-datawindow-dragdrop-rows-with-business-rules\/","title":{"rendered":"PowerBuilder &#8211; Datawindow Drag\/Drop Rows with Business Rules"},"content":{"rendered":"<p>This time I&#8217;m demonstrating a basic bit of functionality common to many Windows based applications &#8211; drag and drop. In this case I am talking about the ability to drag a row of data to a new location in a list. However, to make it more interesting I am throwing in some business rules which control where exactly the dragged row is inserted.<\/p>\n<p>For the purposes of this exercise my data is hierarchical in nature, that is, there are Parent, Child, and Grandchild records. For any given Parent there can be an unlimited number of Children which are of two types. The first type are always listed first after the Parent and may have an unlimited number of Grandchildren associated with them. The second type are always listed after the first type. See the following example:<br \/>\n<a href=\"http:\/\/anvil-of-time.com\/wordpress\/wp-content\/uploads\/2011\/12\/dragdropsample1.png\"><img loading=\"lazy\" class=\"aligncenter wp-image-1030 size-full\" title=\"Drag Drop Sample Data\" src=\"https:\/\/anvil-of-time.com\/wp-content\/uploads\/2011\/12\/dragdropsample1.png\" alt=\"\" width=\"322\" height=\"284\" srcset=\"https:\/\/anvil-of-time.com\/wp-content\/uploads\/2011\/12\/dragdropsample1.png 322w, https:\/\/anvil-of-time.com\/wp-content\/uploads\/2011\/12\/dragdropsample1-300x264.png 300w, https:\/\/anvil-of-time.com\/wp-content\/uploads\/2011\/12\/dragdropsample1-150x132.png 150w\" sizes=\"(max-width: 322px) 100vw, 322px\" \/><\/a><br \/>\nRow Manipulation (Business) Rules<br \/>\nNow for the specifics regarding how the rows are manipulated when moved.<br \/>\nDragging a parent record will move all of its associated Children and GrandChildren records.<br \/>\nParent records can be placed in any order.<br \/>\nDragging a Child Type 1 record will move all of its associated Grandchildren records. Child Type 1 records can be placed under any Parent records.<br \/>\nChild Type 2 records can be dragged under any Parent record.<br \/>\nGrandchild records can be dragged only within its assigned Child Type 1 record; they cannot be moved to any other record type.<\/p>\n<p>A visual indicator appears on the rows as a record is dragged. This consists of two lines showing where the record would be placed if none of the business rules applied. Since there are business rules, this indicator serves mainly as a visual cue to the approximate location (based on the type of row being dragged and the type of row the target is) where you are dragging the selected record.<\/p>\n<p>Setting up for Drag\/Drop on a Datawindow<br \/>\nClicked event of the datawindow<\/p>\n<pre name=\"code\" class=\"VB\">this.selectrow(0, FALSE) \/\/ unselect all selected rows\nthis.selectrow(row, TRUE) \/\/ select the clicked row\nib_drag = (row &gt; 0) \/\/ are we capable of dragging?\nil_dragged_row = row \/\/ source row which is dragged (used for visual indicator on dw)\nIF ib_drag THEN\n  ib_mouse_down = TRUE \/\/ button is clicked\n  il_mouse_down_x = xpos \/\/ original coordinates of pointer\n  il_mouse_down_y = ypos\nELSE\n  ib_mouse_down = FALSE\nEND IF<\/pre>\n<p>User defined event on datawindow &#8216;ue_dwnmousemove&#8217;<br \/>\nThis is mapped to the event pbm_dwnmousemove<\/p>\n<pre name=\"code\" class=\"VB\">IF ib_drag THEN \/\/ we are capable of being dragged\n  IF ib_mouse_down THEN \/\/ mouse is down\n    IF (Abs(PointerX() - il_mouse_down_x) &gt; 50) OR (Abs(PointerY() - il_mouse_down_y) &gt; 50) OR (PointerX() = 0) OR (PointerY() = 0) THEN\n    \/\/ we have moved the pointer more than 50 powerbuilder units so we are dragging the row\n      Drag(Begin!)\n    END IF\n  END IF\nEND IF<\/pre>\n<p>Dragwithin event on the datawindow<\/p>\n<pre name=\"code\" class=\"VB\">\/\/ scroll the datawindow if not all row fit\nlong l_i, ll_firstrow, ll_lastrow\n\nll_firstrow = long(this.Object.DataWindow.FirstRowOnPage)\nll_lastrow = long(this.Object.DataWindow.LastRowOnPage)\n\nIF (row = ll_firstrow OR row = ll_firstrow + 1) AND ll_firstrow &gt; 1 THEN\n  this.scrollpriorrow( )\nELSEIF (row = ll_lastrow OR row = ll_lastrow - 1) AND ll_lastrow &lt; this.rowcount( ) THEN\n  this.scrollnextrow( )\nEND IF\n\n\/\/ set the visual indicator for the drop location\n\nFOR l_i = 1 TO this.RowCount()\n  this.SetItem(l_i, &quot;row_drag&quot;, &quot; &quot;)\nNEXT\n\/\/ set up for visual indicators\nIF il_dragged_row &gt; Row THEN\n  this.SetItem(Row, &quot;row_drag&quot;, &quot;T&quot;)\n  IF Row &lt;&gt; 1 THEN\n    this.SetItem(Row - 1, &quot;row_drag&quot;, &quot;B&quot;)\n  END IF\nELSE\n  this.SetItem(Row, &quot;row_drag&quot;, &quot;B&quot;)\n  IF Row &lt;&gt; this.RowCount() THEN\n    this.SetItem(Row + 1, &quot;row_drag&quot;, &quot;T&quot;)\n  END IF\nEND IF<\/pre>\n<p>User defined event on the datawindow ue_lbuttonup<br \/>\nThis is mapped to the event pbm_dwnlbuttonup<\/p>\n<pre name=\"code\" class=\"VB\">long l_i\nib_Mouse_Down = False\nIF ib_drag THEN \/\/ are we dragging?\n  ib_drag = False \/\/ stop dragging\n  This.Drag(End!)\nEND IF\n\/\/ reset the visual indicator on the datawindow\nFOR l_i = 1 TO this.RowCount() \n  this.SetItem(l_i, &quot;row_drag&quot;, &quot; &quot;)\nNEXT<\/pre>\n<p>Dragdrop event on the datawindow<\/p>\n<pre name=\"code\" class=\"VB\">\/\/ Green rows are the Parents\n\/\/ Yellow rows are Child Type 1 and are always just after the Parent\n\/\/ Orange rows are Grandchildren and are always associated with Child Type 1 records\n\/\/ Red rows are Child Type 2 records and are always after Child Type 1 and any associated Grandchildren\n\n\nlong ll_row, ll_rowcount, ll_dragrows\nlong ll_new_row, ll_prev_row, ll_i, ll_parent_row\nlong ll_diagrow, ll_child1_row, ll_child2_row, ll_original_parent, ll_new_parent, ll_find_row\nlong ll_start_child1, ll_end_child1\nstring ls_rowtype, ls_dest_rowtype\nstring ls_find\n\n\nCHOOSE CASE source\nCASE dw_1\n  ll_row = getselectedrow(0) \/\/ should be same as il_dragged_row\n  IF ll_row = 0 THEN RETURN\n  IF ll_row = row THEN RETURN\n  ls_rowtype = getitemstring( ll_row, &#039;row_type&#039;)\n  IF row &gt; 0 THEN\n    ls_dest_rowtype = getitemstring(row, &#039;row_type&#039;)\n  ELSE\n    ls_dest_rowtype = getitemstring(this.rowcount(), &#039;row_type&#039;)\n  END IF\n  CHOOSE CASE ls_rowtype \/\/ what is being dragged\n  CASE &#039;Parent&#039;\n    ll_original_parent = this.object.row_group_id[ll_row]\n    IF row = 0 THEN\n      ll_new_parent = this.object.row_group_id[this.rowcount()]\n    ELSE\n      ll_new_parent = this.object.row_group_id[row]         \n    END IF\n    IF ll_new_parent = ll_original_parent THEN RETURN \/\/ not moving since it&#039;s been dragged onto itself\n    \/\/ get the range of rows which will move\n    IF ll_row = this.rowcount() THEN \/\/ Parent at end of list\n      ll_dragrows = ll_row\n    ELSE\n      \/\/ get all the other stuff under this Parent\n      ls_find = &quot;row_type = &#039;Parent&#039;&quot;\n      ll_dragrows = this.find(ls_find, ll_row + 1, this.rowcount() + 1) \/\/ find the next Parent\n      IF ll_dragrows = 0 THEN \/\/ dragged green was last on the list\n        ll_dragrows = this.rowcount( ) \/\/ get all rows under the Parent         \n      ELSE\n        ll_dragrows = ll_dragrows - 1\n      END IF\n    END IF\n    \/\/ find the row of the next Parent before which the dragged items will be placed.\n    CHOOSE CASE ls_dest_rowtype\n      CASE &#039;Parent&#039;\n        ll_prev_row = row\n      CASE &#039;Child_1&#039;, &#039;Grandchild&#039;,&#039;Child_2&#039;\n        ls_find = &quot;row_type = &#039;Parent&#039;&quot; \/\/ next Parent\n        ll_find_row = this.find( ls_find, row, this.rowcount() + 1 ) \n        IF ll_find_row = 0 THEN \n          ll_prev_row = this.rowcount() + 1 \/\/ put at end of list\n        ELSE\n          ll_prev_row = ll_find_row \/\/ move to before the next Parent after the row dropped on\n        END IF\n    END CHOOSE\n    \/\/ move rows to new position in list\n    rowsmove( ll_row, ll_dragrows, Primary!, THIS, ll_prev_row , Primary!)\n    \n  CASE &#039;Child_1&#039;\n    \/\/ Child_1s can be associated with any Parent - if you move one, however, you must also move any bound Grandchilds\n    \/\/ get all the Grandchilds under this Child_1\n    IF ll_row = this.rowcount() THEN \/\/ Child_1 at end of list\n      ll_dragrows = ll_row\n      IF ll_dragrows = 2 THEN RETURN\n    ELSE\n      \/\/ get all the Grandchilds under this Child_1\n      \/\/ search for next Child_1 or a Child_2 on the Parent\n      \/\/ starting Child_1 is the selected row\n      \/\/ get last row of Grandchilds for this Child_1         \n      FOR ll_i = ll_row TO this.rowcount()\n        IF ll_i = ll_row THEN\n          ll_end_child1 = ll_i  \n        ELSE\n          IF this.object.row_type[ll_i] = &#039;Grandchild&#039; THEN\n            ll_end_child1 = ll_i\n          ELSE\n            \/\/ Grandchilds are sequential so any other row_type indicates the end\n            EXIT\n          END IF\n        END IF\n      NEXT\n      IF ll_end_child1 &gt; ll_row OR ll_end_child1 = ll_row THEN\n        ll_dragrows = ll_end_child1\n      END IF\n      \/\/ now the rows to move are from ll_row to ll_dragrows\n      \/\/ need to check if we are staying within the same Parent or not\n      \/\/ now get row of Child_1\n      ll_original_parent = getitemnumber(ll_row,&#039;row_group_id&#039;)\n      IF row &gt; 0 THEN\n        ll_new_parent = getitemnumber(row,&#039;row_group_id&#039;)\n        CHOOSE CASE ls_dest_rowtype\n          CASE &#039;Child_1&#039;\n            \/\/ \n            \/\/ ok to move row since &#039;target&#039; is a Child_1\n            IF ll_new_parent &lt;&gt; ll_original_parent THEN\n              FOR ll_i = ll_row to ll_dragrows\n                this.object.row_group_id[ll_i] = ll_new_parent\n              NEXT\n            END IF\n            ll_prev_row = row\n          CASE &#039;Grandchild&#039;\n            \/\/ Grandchilds always bound to an Child_1\n            ls_find = &quot;row_type = &#039;Child_1&#039;&quot;\n            ll_prev_row = this.find( ls_find, row, 1) \/\/ search up list to Child_1 this Grandchild is bound to\n            \/\/ Grandchild row has same Parent id as it&#039;s Child_1          \n            IF ll_new_parent &lt;&gt; ll_original_parent THEN\n              FOR ll_i = ll_row to ll_dragrows\n                this.object.row_group_id[ll_i] = ll_new_parent\n              NEXT\n            END IF\n          CASE &#039;Child_2&#039;\n            \/\/ Child_2s are after the last Child_1\/Grandchild grouping\n            \/\/ search up list to find last Child_2 for the target&#039;s Parent\n            FOR ll_i = row TO 1 STEP -1\n              IF this.object.row_type[ll_i] &lt;&gt; &#039;Child_2&#039; THEN\n                ll_prev_row = ll_i + 1 \/\/ row number of the last Child_2 bound to the Parent of the target row\n                EXIT\n              END IF\n            NEXT\n            \/\/ Child_2 row has same Parent id as the Child_1          \n            IF ll_new_parent &lt;&gt; ll_original_parent THEN\n              FOR ll_i = ll_row to ll_dragrows\n                this.object.row_group_id[ll_i] = ll_new_parent\n              NEXT\n            END IF\n            \n          CASE &#039;Parent&#039;\n            \/\/ Parent is at start of a group. put dragged Child_1\/Grandchild group right after the Parent (seq_no = 1)\n            IF row = 1 THEN\n              ll_prev_row = 2\n              IF ll_new_parent &lt;&gt; ll_original_parent THEN\n                FOR ll_i = ll_row to ll_dragrows\n                  this.object.row_group_id[ll_i] = ll_new_parent\n                NEXT\n              END IF\n            ELSE\n              \/\/put as first Child_1\n              ls_find = &quot;row_type = &#039;Child_1&#039;&quot;\n              ll_child1_row = this.find( ls_find, row + 1, this.rowcount() + 1) \/\/ search down list to Child_1\n              ls_find = &quot;row_type = &#039;Child_2&#039;&quot;\n              ll_child2_row = this.find( ls_find, row + 1, this.rowcount() + 1)\n              ls_find = &quot;row_type = &#039;Parent&#039;&quot;\n              ll_parent_row = this.find( ls_find, row + 1, this.rowcount() + 1)\n              IF row + 1 = ll_parent_row THEN ll_prev_row = ll_parent_row \/\/ next item was another Parent\n              IF row + 1 = ll_child2_row THEN ll_prev_row = ll_child2_row \/\/ next item was an Child_2\n              IF row + 1 = ll_child1_row THEN ll_prev_row = ll_child1_row \/\/ next item was an Child_1\n              IF ll_prev_row = 0 THEN ll_prev_row = row + 1 \/\/ no next item so adding to end of list\n              \/\/ Grandchild row has same Parent id as it&#039;s Child_1          \n              IF ll_new_parent &lt;&gt; ll_original_parent THEN\n                FOR ll_i = ll_row to ll_dragrows\n                  this.object.row_group_id[ll_i] = ll_new_parent\n                NEXT\n              END IF\n            END IF\n        END CHOOSE  \n      ELSE\n        \/\/put as first Child_1\n        ls_find = &quot;row_type = &#039;Child_1&#039;&quot;\n        ll_child1_row = this.find( ls_find, this.rowcount(), 1 ) \/\/ search up list to Child_1\n        ls_find = &quot;row_type = &#039;Child_2&#039;&quot;\n        ll_child2_row = this.find( ls_find, this.rowcount(), 1 )\n        ls_find = &quot;row_type = &#039;Parent&#039;&quot;\n        ll_parent_row = this.find( ls_find, this.rowcount(),  1)\n        IF this.rowcount() = ll_parent_row THEN ll_prev_row = ll_parent_row + 1 \/\/ next item was another Parent           \n        IF this.rowcount() = ll_child2_row THEN \n          \/\/ search upwards to find first Child_2\n          DO UNTIL ll_child2_row = 0\n            IF this.object.row_type[ll_child2_row - 1] = &#039;Child_2&#039; THEN\n              ll_child2_row --\n            ELSE\n              EXIT \/\/ next row up is not a Child_2 which means we have found the first one for the Parent\n            END IF\n          LOOP\n          ll_prev_row = ll_child2_row \n        END IF\n        IF this.rowcount() = ll_child1_row THEN \n          ll_prev_row = ll_child1_row + 1 \/\/ next item was an Child_1\n        ELSEIF ll_prev_row = 0 THEN\n          ls_find = &quot;row_type = &#039;Grandchild&#039;&quot; \/\/ last Child_1 has associated Grandchilds\n          ll_find_row = this.find( ls_find, this.rowcount(), ll_child1_row ) \/\/ search up list to Child_1\n          IF this.rowcount() = ll_find_row THEN ll_prev_row = ll_find_row + 1\n        END IF\n        IF ll_prev_row = 0 THEN ll_prev_row = row + 1 \/\/ no next item so adding to end of list\n        \n      END IF\n    END IF\n    \/\/ move the rows\n    rowsmove( ll_row, ll_dragrows, Primary!, THIS, ll_prev_row , Primary!)\n  CASE &#039;Grandchild&#039;\n    \/\/ Grandchilds are bound to a Child_1 - they cannot be moved to other Child_1s from this window\n\n    \/\/ can only drag within the same Child_1\n    ll_dragrows = ll_row\n    \/\/ need to check if we are staying within the same Parent or not\n    \/\/ now get row of Child_1\n    ls_find = &quot;row_type = &#039;Child_1&#039;&quot;\n    ll_start_child1 = this.find(ls_find, ll_row, 1) \/\/ search up to get the Child_1 the Grandchild is bound to\n    \/\/ get last row of Grandchilds for this Child_1         \n    FOR ll_i = ll_row TO this.rowcount()\n      IF this.object.row_type[ll_i] = &#039;Grandchild&#039; THEN \/\/ change in row_type indicates an end to the group\n        ll_end_child1 = ll_i\n      ELSE\n        EXIT\n      END IF\n    NEXT\n    IF ll_start_child1 + 1 = ll_end_child1 THEN RETURN \n    \/\/ can only drag within same Child_1\n    IF row &gt; ll_end_child1 OR row &lt; ll_start_child1 THEN RETURN\n    IF row = ll_start_child1 THEN\n      ll_prev_row = ll_start_child1 + 1\n    ELSE\n      ll_prev_row = row\n    END IF\n    rowsmove( ll_row, ll_dragrows, Primary!, THIS, ll_prev_row , Primary!)\n    \n  CASE &#039;Child_2&#039;\n      \/\/ Child_2 rows are always at the end under their corresponding Parent (after any Child_1s and Grandchilds)\n      ll_dragrows = ll_row \/\/ only one row is moved\n      ll_original_parent = getitemnumber(ll_row,&#039;row_group_id&#039;)\n      IF row &gt; 0 THEN\n        ll_new_parent = getitemnumber(row,&#039;row_group_id&#039;)\n        CHOOSE CASE ls_dest_rowtype \/\/ what kind of row item is dropped on\n          CASE &#039;Child_1&#039;,&#039;Grandchild&#039;,&#039;Parent&#039;\n            \/\/ Child_2s are last after all Child_1\/Grandchild groups\n            ll_child1_row = row\n\n            DO UNTIL ll_child2_row &gt; 0\n              ll_child1_row ++ \/\/ looking at next line\n              IF ll_child1_row &gt;= this.rowcount() THEN ll_child2_row = this.rowcount() + 1 \/\/ nothing after this row so add to end\n              IF this.object.row_group_id[ll_child1_row] &lt;&gt; ll_new_parent THEN ll_child2_row = ll_child1_row\/\/ last item on list for this Parent\n              IF this.object.row_type[ll_child1_row] = &#039;Child_2&#039; THEN ll_child2_row = ll_child1_row \/\/ item is an Child_2, put this one in front\n            LOOP\n            ll_prev_row = ll_child2_row\n          CASE &#039;Child_2&#039;\n            ll_prev_row = row\n        END CHOOSE\n      ELSE\n        ll_prev_row = this.rowcount() + 1 \/\/ end of entire list\n         ll_new_parent = this.object.row_group_id[this.rowcount()]\n      END IF\n      IF ll_original_parent &lt;&gt; ll_new_parent THEN\n        \/\/ set new Parent id on row\n        this.object.row_group_id[ll_row] = ll_new_parent\n      END IF\n      rowsmove( ll_row, ll_dragrows, Primary!, THIS, ll_prev_row , Primary!)\n\n  END CHOOSE \/\/ rowtype being dragged\n  \/\/ renumber\n  FOR ll_i = 1 to Rowcount()\n    THIS.object.row_new[ll_i] = ll_i\n  NEXT\n  ll_row = getselectedrow(0)\n  this.scrolltorow(ll_row)\nEND CHOOSE \/\/ drag source<\/pre>\n<p>Datawindow Setup<br \/>\nThe example has an external datasource. The columns are:<\/p>\n<pre name=\"code\" class=\"VB\">table(column=(type=number updatewhereclause=yes name=row_original dbname=&quot;row_original&quot; )\n column=(type=number updatewhereclause=yes name=row_new dbname=&quot;row_new&quot; )\n column=(type=char(16) updatewhereclause=yes name=row_type dbname=&quot;row_type&quot; )\n column=(type=number updatewhereclause=yes name=row_id dbname=&quot;row_id&quot; )\n column=(type=number updatewhereclause=yes name=row_parent_id dbname=&quot;row_parent_id&quot; )\n column=(type=char(1) updatewhereclause=yes name=row_drag dbname=&quot;row_drag&quot; )\n column=(type=number updatewhereclause=yes name=row_indent dbname=&quot;row_indent&quot; )\n column=(type=number updatewhereclause=yes name=row_group_id dbname=&quot;row_group_id&quot; )\n  sort=&quot;row_original A &quot; )<\/pre>\n<p>The data in the datawindow is:<\/p>\n<pre name=\"code\" class=\"VB\">data( 1, 0,&quot;Parent&quot;, 100, 0,null  0, 100, \n  2, 0,&quot;Child_1&quot;, 200, 100,null  1, 100, \n  3, 0,&quot;Child_1&quot;, 201, 100,null  1, 100, \n  4, 0,&quot;Grandchild&quot;, 300, 201,null  2, 100, \n  5, 0,&quot;Grandchild&quot;, 301, 201,null  2, 100, \n  6, 0,&quot;Child_2&quot;, 400, 100,null  1, 100, \n  7, 0,&quot;Parent&quot;, 101, 101,null  0, 101, \n  8, 0,&quot;Parent&quot;, 102, 0,null  0, 102, \n  9, 0,&quot;Child_1&quot;, 202, 102,null  1, 102, \n  10, 0,&quot;Grandchild&quot;, 302, 202,null  2, 102, \n  11, 0,&quot;Grandchild&quot;, 303, 202,null  2, 102, \n  12, 0,&quot;Child_1&quot;, 203, 102,null  1, 102, \n  13, 0,&quot;Grandchild&quot;, 304, 203,null  2, 102, \n  14, 0,&quot;Child_2&quot;, 401, 102,null  1, 102, \n  15, 0,&quot;Child_2&quot;, 402, 102,null  1, 102,)<\/pre>\n<p>The detail band has an expression on the Color property:<\/p>\n<pre name=\"code\" class=\"VB\">color=&quot;536870912~tcase(  row_type  when &#039;Parent&#039; then 1095774 when &#039;Child_2&#039; then 4991183 when &#039;Child_1&#039; then 47834 when &#039;Grandchild&#039; then 2519531 else 536870912)&quot;<\/pre>\n<p>There are two Line objects in the detail band. These form the indicator for drag\/drop:<\/p>\n<pre name=\"code\" class=\"VB\">line(band=detail x1=&quot;9&quot; y1=&quot;88&quot; x2=&quot;2395&quot; y2=&quot;88&quot;  name=l_bottom visible=&quot;0~tIF (row_drag = &#039;B&#039;,1,0)&quot; pen.style=&quot;0&quot; pen.width=&quot;14&quot;...\nline(band=detail x1=&quot;9&quot; y1=&quot;4&quot; x2=&quot;2395&quot; y2=&quot;4&quot;  name=l_top visible=&quot;0~tIF ( row_drag = &#039;T&#039;,1,0)&quot; pen.style=&quot;0&quot; pen.width=&quot;14&quot;...<\/pre>\n<p>The attached <a href=\"https:\/\/anvil-of-time.com\/wp-content\/uploads\/2011\/12\/dragdrop.zip\">PB11.5 DragDrop example<\/a> contains the Powerbuilder 11.5 objects and exports.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This time I&#8217;m demonstrating a basic bit of functionality common to many Windows based applications &#8211; drag and drop. In this case I am talking about the ability to drag a row of data to a new location in a list. However, to make it more interesting I am throwing in some business rules which&hellip;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[3,10],"tags":[21,12,16],"_links":{"self":[{"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/posts\/1027"}],"collection":[{"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/comments?post=1027"}],"version-history":[{"count":7,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/posts\/1027\/revisions"}],"predecessor-version":[{"id":2002,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/posts\/1027\/revisions\/2002"}],"wp:attachment":[{"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/media?parent=1027"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/categories?post=1027"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/tags?post=1027"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}