From 58c6369b4a4e57e3708393900fab9d2fdbd6759f Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Thu, 6 Jun 2013 21:55:10 +0100 Subject: [PATCH 01/16] Add hold_until_date and expiry_date fields. --- hooks.php | 1 + includes/db_order_lines.inc | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/hooks.php b/hooks.php index e51de86..818fe7a 100644 --- a/hooks.php +++ b/hooks.php @@ -45,6 +45,7 @@ class hooks_order_line_extra extends hooks { $updates = array( 'alter_sales_order_details.sql' => array('sales_order_details','required_date'), + 'alter_sales_order_details_2.sql' => array('sales_order_details','expiry_date'), 'create_denorm_order_details_queue.sql' => array('denorm_order_details_queue'), 'create_denorm_qoh.sql' => array('denorm_qoh'), 'create_order_summary_view.sql' => array('order_summary_view'), diff --git a/includes/db_order_lines.inc b/includes/db_order_lines.inc index 0719ef4..ffb30e4 100644 --- a/includes/db_order_lines.inc +++ b/includes/db_order_lines.inc @@ -120,7 +120,12 @@ function update_order_detail_defaults() { $sql = "UPDATE ".TB_PREF."sales_order_details SET priority = now() WHERE priority is null"; - return db_query($sql); + db_query($sql); + $sql = "UPDATE ".TB_PREF."sales_order_details + NATURAL JOIN 0_sales_orders + SET hold_until_date = delivery_date + WHERE hold_until_date is null"; + db_query($sql); } ?> -- 2.30.2 From d5c3d09b124380d812be405e6e9314140606f31d Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Thu, 6 Jun 2013 21:58:54 +0100 Subject: [PATCH 02/16] Add untracked files. --- TableDnD/.gitignore | 7 + TableDnD/MIT-LICENSE.txt | 22 + TableDnD/README.textile | 4 + TableDnD/images/updown2.gif | Bin 0 -> 339 bytes TableDnD/index.html | 550 +++++++++++++ TableDnD/js/jquery.tablednd.0.6.min.js | 1 + TableDnD/js/jquery.tablednd.0.7.min.js | 1 + TableDnD/js/jquery.tablednd.0.8.min.js | 1 + TableDnD/js/jquery.tablednd.0.9.rc1.js | 664 +++++++++++++++ TableDnD/js/jquery.tablednd.js | 664 +++++++++++++++ TableDnD/server/ajaxJSONTest.php | 13 + TableDnD/server/ajaxJSONTest_php.html | 3 + TableDnD/server/ajaxTest.php | 8 + TableDnD/server/ajaxTest_php.html | 4 + TableDnD/serverExample.html | 39 + TableDnD/stable/jquery.tablednd.js | 314 ++++++++ TableDnD/stripe.html | 294 +++++++ TableDnD/tablednd.css | 340 ++++++++ hincludes/lib/Date.class.php | 67 ++ hincludes/lib/DateTools.class.php | 44 + hincludes/lib/EReg.class.php | 93 +++ hincludes/lib/FA.class.php | 16 + hincludes/lib/HList.class.php | 161 ++++ hincludes/lib/Hash.class.php | 65 ++ hincludes/lib/IntHash.class.php | 65 ++ hincludes/lib/IntIter.class.php | 28 + hincludes/lib/ItemScheduler.class.php | 248 ++++++ hincludes/lib/Lambda.class.php | 173 ++++ hincludes/lib/Location.class.php | 48 ++ hincludes/lib/Math.class.php | 76 ++ hincludes/lib/Maybe.enum.php | 8 + hincludes/lib/QueryIterator.class.php | 44 + hincludes/lib/Reflect.class.php | 104 +++ hincludes/lib/ScheduleMode.enum.php | 11 + hincludes/lib/ScheduleParameters.class.php | 74 ++ hincludes/lib/Std.class.php | 38 + hincludes/lib/StringBuf.class.php | 39 + hincludes/lib/StringTools.class.php | 61 ++ hincludes/lib/Type.class.php | 298 +++++++ hincludes/lib/ValueType.enum.php | 21 + hincludes/lib/Xml.class.php | 376 +++++++++ hincludes/lib/haxe/Log.class.php | 21 + hincludes/lib/haxe/Serializer.class.php | 309 +++++++ hincludes/lib/haxe/Unserializer.class.php | 381 +++++++++ hincludes/lib/haxe/io/Bytes.class.php | 88 ++ hincludes/lib/haxe/io/Error.enum.php | 12 + hincludes/lib/php/Boot.class.php | 894 +++++++++++++++++++++ hincludes/lib/php/Lib.class.php | 119 +++ sql/alter_sales_order_details_2.sql | 4 + 49 files changed, 6915 insertions(+) create mode 100644 TableDnD/.gitignore create mode 100644 TableDnD/MIT-LICENSE.txt create mode 100644 TableDnD/README.textile create mode 100644 TableDnD/images/updown2.gif create mode 100644 TableDnD/index.html create mode 100644 TableDnD/js/jquery.tablednd.0.6.min.js create mode 100644 TableDnD/js/jquery.tablednd.0.7.min.js create mode 100644 TableDnD/js/jquery.tablednd.0.8.min.js create mode 100644 TableDnD/js/jquery.tablednd.0.9.rc1.js create mode 100644 TableDnD/js/jquery.tablednd.js create mode 100644 TableDnD/server/ajaxJSONTest.php create mode 100644 TableDnD/server/ajaxJSONTest_php.html create mode 100644 TableDnD/server/ajaxTest.php create mode 100644 TableDnD/server/ajaxTest_php.html create mode 100644 TableDnD/serverExample.html create mode 100644 TableDnD/stable/jquery.tablednd.js create mode 100644 TableDnD/stripe.html create mode 100644 TableDnD/tablednd.css create mode 100644 hincludes/lib/Date.class.php create mode 100644 hincludes/lib/DateTools.class.php create mode 100644 hincludes/lib/EReg.class.php create mode 100644 hincludes/lib/FA.class.php create mode 100644 hincludes/lib/HList.class.php create mode 100644 hincludes/lib/Hash.class.php create mode 100644 hincludes/lib/IntHash.class.php create mode 100644 hincludes/lib/IntIter.class.php create mode 100644 hincludes/lib/ItemScheduler.class.php create mode 100644 hincludes/lib/Lambda.class.php create mode 100644 hincludes/lib/Location.class.php create mode 100644 hincludes/lib/Math.class.php create mode 100644 hincludes/lib/Maybe.enum.php create mode 100644 hincludes/lib/QueryIterator.class.php create mode 100644 hincludes/lib/Reflect.class.php create mode 100644 hincludes/lib/ScheduleMode.enum.php create mode 100644 hincludes/lib/ScheduleParameters.class.php create mode 100644 hincludes/lib/Std.class.php create mode 100644 hincludes/lib/StringBuf.class.php create mode 100644 hincludes/lib/StringTools.class.php create mode 100644 hincludes/lib/Type.class.php create mode 100644 hincludes/lib/ValueType.enum.php create mode 100644 hincludes/lib/Xml.class.php create mode 100644 hincludes/lib/haxe/Log.class.php create mode 100644 hincludes/lib/haxe/Serializer.class.php create mode 100644 hincludes/lib/haxe/Unserializer.class.php create mode 100644 hincludes/lib/haxe/io/Bytes.class.php create mode 100644 hincludes/lib/haxe/io/Error.enum.php create mode 100644 hincludes/lib/php/Boot.class.php create mode 100644 hincludes/lib/php/Lib.class.php create mode 100644 sql/alter_sales_order_details_2.sql diff --git a/TableDnD/.gitignore b/TableDnD/.gitignore new file mode 100644 index 0000000..55374ca --- /dev/null +++ b/TableDnD/.gitignore @@ -0,0 +1,7 @@ +.svn +archive +java/TableDnD.iml + +Table Drag and Drop.iws + +Table Drag and Drop.ipr diff --git a/TableDnD/MIT-LICENSE.txt b/TableDnD/MIT-LICENSE.txt new file mode 100644 index 0000000..fb40abd --- /dev/null +++ b/TableDnD/MIT-LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) Denis Howlett +Copyright 2012 Nick Lombard - nickl- and other contributors +https://github.com/isocra/TableDnD + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/TableDnD/README.textile b/TableDnD/README.textile new file mode 100644 index 0000000..e996176 --- /dev/null +++ b/TableDnD/README.textile @@ -0,0 +1,4 @@ +h1. TableDnD + +Documentation will go here, for now, go to "Isocra's TableDnD Blog posting":http://www.isocra.com/2008/02/table-drag-and-drop-jquery-plugin/ + diff --git a/TableDnD/images/updown2.gif b/TableDnD/images/updown2.gif new file mode 100644 index 0000000000000000000000000000000000000000..3cb4482d69e7b18c4cc2ee747ae9de31f1a33cb7 GIT binary patch literal 339 zcmZ?wbhEHb6k-r$xXJ(m|NsB*-0?nZ$)nsQ_ls6Ot6cZ8Y~_Q>HBTEizo}pUq;<=y z_&K+d=HHK + + + Table Drag and Drop jQuery plugin + + + + +
+

Table Drag and Drop jQuery plugin

+

This page contains documentation and tests for the TableDnD jQuery plug-in. For more information and +to post comments, please go to isocra.com. +

+

If you have issues or bug reports, then you can post them at the TableDnD plug page +at plugins.jquery.com

+ +

How do I use it?

+
    +
  1. Since TableDnD is a jquery pligin you will need to include jquery in your page first. +

    No need for any downloads simply reference jQuery from the Google CDN (Content Distribution Network) At the time of this writing the latest release was 1.8.2. + All scripts are included at the bottom of the page, to facilitate quicker rendering of the HTML for more responsive pages. + The following is the way we are linking to jQuery in the examples and this method can be recommended for use in your implementations too.

    +
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
  2. +
  3. We will also need a copy of the TableDnD plugin (current version 0.9) which you can reference in the normal fashion, anywhere after jQuery.
  4. +
  5. In true jQuery style, the typical way to initialise the tabes is in the $(document).ready code black function. Use a selector to select your table and then call tableDnD(). You can optionally specify a set of properties (described below).
  6. +
+ +

A basic table

+
+
+ + + + + + + +
1Onesome text
2Twosome text
3Threesome text
4Foursome text
5Fivesome text
6Sixsome text
+
+

The HTML for the table is very straight forward (no Javascript, pure HTML):

+ +
+<table id="table-1" cellspacing="0" cellpadding="2">
+    <tr id="1"><td>1</td><td>One</td><td>some text</td></tr>
+    <tr id="2"><td>2</td><td>Two</td><td>some text</td></tr>
+    <tr id="3"><td>3</td><td>Three</td><td>some text</td></tr>
+    <tr id="4"><td>4</td><td>Four</td><td>some text</td></tr>
+    <tr id="5"><td>5</td><td>Five</td><td>some text</td></tr>
+    <tr id="6"><td>6</td><td>Six</td><td>some text</td></tr>
+</table>
+
+

To add in the "draggability" all we need to do is add a line to the $(document).ready(...) function +as follows:

+
+<script type="text/javascript">
+$(document).ready(function() {
+    // Initialise the table
+    $("#table-1").tableDnD();
+});
+</script>
+
+

In the example above we're not setting any parameters at all so we get the default settings. There are a number + of parameters you can set in order to control the look and feel of the table and also to add custom behaviour + on drag or on drop. The parameters are specified as a map in the usual way and are described below:

+ +

Settings

+
+
onDragStyle
+
This is the style that is assigned to the row during drag. There are limitations to the styles that can be + associated with a row (such as you can't assign a border—well you can, but it won't be + displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as + a map (as used in the jQuery css(...) function).
+
onDropStyle
+
This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations + to what you can do. Also this replaces the original style, so again consider using onDragClass which + is simply added and then removed on drop.
+
onDragClass
+
This class is added for the duration of the drag and then removed when the row is dropped. It is more + flexible than using onDragStyle since it can be inherited by the row cells and other content. The default + is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your + stylesheet.
+
onDrop
+
Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table + and the row that was dropped. You can work out the new order of the rows by using + table.tBodies[0].rows.
+
onDragStart
+
Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the + table and the row which the user has started to drag.
+
scrollAmount
+
This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the + window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2, + FF3 beta)
+
+ +

OnDrag custom table

+

This second table has has an onDrop function applied as well as an onDragClass. The javascript to set this up is +as follows:

+ +
+$(document).ready(function() {
+
+	// Initialise the first table (as before)
+	$("#table-1").tableDnD();
+
+	// Make a nice striped effect on the table
+	$("#table-2 tr:even').addClass('alt')");
+
+	// Initialise the second table specifying a dragClass and an onDrop function that will display an alert
+	$("#table-2").tableDnD({
+	    onDragClass: "myDragClass",
+	    onDrop: function(table, row) {
+            var rows = table.tBodies[0].rows;
+            var debugStr = "Row dropped was "+row.id+". New order: ";
+            for (var i=0; i<rows.length; i++) {
+                debugStr += rows[i].id+" ";
+            }
+	        $(table).parent().find('.result').text(debugStr);
+	    },
+		onDragStart: function(table, row) {
+			$(table).parent().find('.result').text("Started dragging row "+row.id);
+		}
+	});
+});
+
+
+ + + + + + + + + + + + + + + +
1OneVCN
2TwoVCN
3ThreeVCN
4FourVCN
5FiveVCN
6SixVCN
7SevenVCN
8EightVCN
9NineVCN
10TenVCN
11ElevenVCN
12TwelveVCN
13ThirteenVCN
14FourteenVCN
+
 
+
+

Communicating with the back-end

+

Generally once the user has dropped a row, you need to inform the server of the new order. To do this, we've + added a method called serialize(). It takes no parameters but knows the current table from the + context. The method returns a string of the form tableId[]=rowId1&tableId[]=rowId2&tableId[]=rowId3... + You can then use this as part of an Ajax load. +

+

+ Since version 0.9, instead of manually creating the serialized data string we instead use jQuery's param method which has the added benefit of url encoding the data string as well. +

+

This third table demonstrates calling the serialize function inside onDrop (as shown below). It also + demonstrates the "nodrop" class on row 3 and "nodrag" class on row 5, so you can't pick up row 5 and + you can't drop any row on row 3 (but you can drag it).

+
+    $('#table-3').tableDnD({
+        onDrop: function(table, row) {
+            alert($.tableDnD.serialize());
+        }
+    });
+
+
+
+

Ajax result

+

Drag and drop in this table to test out serialise and using JQuery.load()

+
+ + + + + + + + +
1One
2Two
3Three (Can't drop on this row)
4Four (Can't drop on this row)
5Five
6Six (Can't drag this row)
7Seven
+
+
+

Multiple tbody table

+

This table has multiple TBODYs. The functionality isn't quite working properly. You can only drag the rows inside their +own TBODY, you can't drag them outside it. Now this might or might not be what you want, but unfortunately if you then drop a row outside its TBODY you get a Javascript error because inserting after a sibling doesn't work. This will be fixed in the next version. The header rows all have the classes "nodrop" and "nodrag" so that they can't be dragged or dropped on.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
H1H2H3
4.1One
4.2Two
4.3Three
4.4Four
4.5Five
4.6Six
H1H2H3
5.1One
5.2Two
5.3Three
5.4Four
5.5Five
5.6Six
H1H2H3
6.1One
6.2Two
6.3Three
6.4Four
6.5Five
6.6Six
+
+

Identify rows

+

+The following table demonstrates the use of the default regular expression. The rows have IDs of the +form table5-row-1, table5-row-2, etc., but the regular expression is /[^\-]*$/ (this is the same +as used in the NestedSortable plugin for consistency). +This removes everything before and including the last hyphen, so the serialised string just has 1, 2, 3 etc. +You can replace the regular expression by setting the serializeRegexp option, you can also just set it +to null to stop this behaviour. +

+
+    $('#table-5').tableDnD({
+        onDrop: function(table, row) {
+            alert($.tableDnD.serialize());
+        },
+        dragHandle: ".dragHandle"
+    });
+
+
+ + + + + + + +
 1Onesome text
 2Twosome text
 3Threesome text
 4Foursome text
 5Fivesome text
 6Sixsome text
+
+
+

In fact you will notice that I have also set the dragHandle on this table. This has two effects: firstly only +the cell with the drag handle class is draggable and secondly it doesn't automatically add the cursor: move +style to the row (or the drag handle cell), so you are responsible for setting up the style as you see fit.

+

Here I've actually added an extra effect which adds a background image to the first cell in the row whenever +you enter it using the jQuery hover function as follows:

+
+    $("#table-5 tr").hover(function() {
+          $(this.cells[0]).addClass('showDragHandle');
+    }, function() {
+          $(this.cells[0]).removeClass('showDragHandle');
+    });
+
+

This provides a better visualisation of what you can do to the row and where you need to go to drag it (I hope).

+ +

Meta table (auto configure)

+ + +
+ + + + + + + + +
Basic example with extra fancy
row styles bot this trick really
only works with single column
because it looses the corumn
width when displaying a table
in block style unfortunately
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameStart DateEnd DateActions
Present Sprints
First round - in sprint2012-09-192012-09-27
Future Sprints
Second round2012-09-282012-10-04
Third round2012-10-042012-10-11
Fourth round2012-10-132012-10-20
Release prep2012-10-202012-10-27
Fifth run2012-10-272012-11-03
Sixth run2012-11-032012-11-10
Seventh run2012-11-102012-11-17
Release 2 prep2012-11-172012-11-24
Past Sprints
Backlog creation - complete2012-09-012012-09-08
No Sprints
+ Hide JSON
+
+ +

Hierarchy table

+

This table allows row order to be dragged horizontally and placed in a hierarchy under a parent row (since version 0.9). We also get a chance to look at the new jsonize method for JSON serialized form of the data.

+

In the onDrop event handler we pass the JSON as data to jquery through a HTTP POST ajax call to the server:

+
+    $.post("server/ajaxJSONTest.php", $.tableDnD.jsonize(), function (data) {
+        $('#table-7-response').html('
'+ data); + }); +
+

On the back-end we have a PHP example that simply retrieves the JSON POST data from the built in stream php://input, decodes the payload and proceeds to build the hierarchy through recursion.

+

To keep the data simple and also stay compatible with the http variable methods as mentioned previously the data structure is formed with separate collections. If a parent has children the children first level are listed and if any of the children have subsequent children an additional collection is created for the first level of these.

+

The following hierarchy for example would generate 3 collections:

+
    +
  • 7.00
      +
    • 7.01
    • +
    • 7.02
      • 7.03
    • +
  • +
  • 7.04
  • +
+

In JSON the dataset looks like this:

+Hide JSON
+{
+	"table-7": [
+		"7.00",
+		"7.04"
+	],
+	"7.00": [
+		"7.01",
+		"7.02"
+	],
+	"7.02": [
+		"7.03"
+	]
+}
+
+

We use the setting hierarchyLevel to indicate how many levels are supported, the example uses 4 levels deep. When populating the table you can use the the data-leve tag to indicate at which level the current row is represented at.

+ +
+
+
+

Ajax result

+

Drag and drop in this table to test out hierarcies and using JSON payload.

+
+
+ + + + + + + + + + + + + + + + + + + + + + +
7.0 One
7.1 Two
7.2 Three
7.3 Four
7.4 Five
7.5 Six
7.6 Seven
7.7 Eight
7.8 Nine
7.9 Ten
7.0 One
7.1 Two
7.2 Three
7.3 Four
7.4 Five
7.5 Six
7.6 Seven
7.7 Eight
7.8 Nine
7.9 Ten
+ Hide JSON
+
+ +

Version History

+ + + + + + + + + +
0.22008-02-20First public release
0.32008-02-27Added onDragStart option
Made the scroll amount configurable (default is 5 as before)
0.42008-03-28Fixed the scrollAmount so that if you set this to zero then it switches off this functionality
Fixed the auto-scrolling in IE6 thanks to Phil
Changed the NoDrop attribute to the class "nodrop" (so any row with this class won't allow dropping)
Changed the NoDrag attribute to the class "nodrag" (so any row with this class can't be dragged)
Added support for multiple TBODYs--though it's still not perfect
Added onAllowDrop to allow the developer to customise this behaviour
Added a serialize() method to return the order of the rows in a form suitable for POSTing back to the server
0.52008-06-04Changed so that if you specify a dragHandle class it doesn't make the whole row
draggable
Improved the serialize method to use a default (and settable) regular expression.
Added tableDnDupate() and tableDnDSerialize() to be called when you are outside the table
0.62011-12-02Support for touch devices
0.72012-04-09DenisH - Code cleanup,
0.82012-09-13Minified version of 0.8dev release - LightDot
0.92012-10-21Changelog from different contributors: + + + + + + + + + + + +
2012-01-24Daniel BøndergaardDon't include blank ids in serialization
2012-05-08Matt SniderMajor refactor and optimization, dispersed codebase merge, , Hierarchy DnD
2012-06-04Vyacheslav SalakhutdinovRemove alert with "has Touch"
2012-06-11AndreZteelMajor refactor and optimization, dispersed codebase merge, , Hierarchy DnD
2012-06-11Daniel FranzGlobal footprint reduction by limiting variable scope
2012-06-07Andrew NagyAdded jsonize for JSON Serialization.
2012-07-12Adam HodgesMultiple <tbody> issues fixed
2012-09-16nickl-Major refactor and optimization including code reduction, code cleanup and normalization, dispersed codebase merge, Hierarchy DnD, jQuery object modifiers, improved scope reduction, style enhancements, improved event handling and state awareness, motion sensitivity thresholds and direction detection, relation integrity, refactored data serialization,
2012-10-21nickl-JQuery via CDN, documentation updates, Added code pretifier and other style adjustments
2012-03-23nickl-Added auto init support and ability to configure DnD via data attributes, improved data integrity and common points of failure, optimization and style clean-up. Added license and attribution.
+
+
+ + + + + + + + diff --git a/TableDnD/js/jquery.tablednd.0.6.min.js b/TableDnD/js/jquery.tablednd.0.6.min.js new file mode 100644 index 0000000..7201855 --- /dev/null +++ b/TableDnD/js/jquery.tablednd.0.6.min.js @@ -0,0 +1 @@ +var hasTouch='ontouchstart'in document.documentElement,startEvent=hasTouch?'touchstart':'mousedown',moveEvent=hasTouch?'touchmove':'mousemove',endEvent=hasTouch?'touchend':'mouseup';jQuery.tableDnD={currentTable:null,dragObject:null,mouseOffset:null,oldY:0,build:function(options){this.each(function(){this.tableDnDConfig=jQuery.extend({onDragStyle:null,onDropStyle:null,onDragClass:"tDnD_whileDrag",onDrop:null,onDragStart:null,scrollAmount:5,serializeRegexp:/[^\-]*$/,serializeParamName:null,dragHandle:null},options||{});jQuery.tableDnD.makeDraggable(this)});return this},makeDraggable:function(table){var config=table.tableDnDConfig;if(config.dragHandle){var cells=jQuery("td."+table.tableDnDConfig.dragHandle,table);cells.each(function(){jQuery(this).bind(startEvent,function(ev){jQuery.tableDnD.initialiseDrag(this.parentNode,table,this,ev,config);return false})})}else{var rows=jQuery("tr",table);rows.each(function(){var row=jQuery(this);if(!row.hasClass("nodrag")){row.bind(startEvent,function(ev){if(ev.target.tagName=="TD"){jQuery.tableDnD.initialiseDrag(this,table,this,ev,config);return false}}).css("cursor","move")}})}},initialiseDrag:function(dragObject,table,target,evnt,config){jQuery.tableDnD.dragObject=dragObject;jQuery.tableDnD.currentTable=table;jQuery.tableDnD.mouseOffset=jQuery.tableDnD.getMouseOffset(target,evnt);jQuery.tableDnD.originalOrder=jQuery.tableDnD.serialize();jQuery(document).bind(moveEvent,jQuery.tableDnD.mousemove).bind(endEvent,jQuery.tableDnD.mouseup);if(config.onDragStart){config.onDragStart(table,target)}},updateTables:function(){this.each(function(){if(this.tableDnDConfig){jQuery.tableDnD.makeDraggable(this)}})},mouseCoords:function(ev){if(ev.pageX||ev.pageY){return{x:ev.pageX,y:ev.pageY}}return{x:ev.clientX+document.body.scrollLeft-document.body.clientLeft,y:ev.clientY+document.body.scrollTop-document.body.clientTop}},getMouseOffset:function(target,ev){ev=ev||window.event;var docPos=this.getPosition(target);var mousePos=this.mouseCoords(ev);return{x:mousePos.x-docPos.x,y:mousePos.y-docPos.y}},getPosition:function(e){var left=0;var top=0;if(e.offsetHeight==0){e=e.firstChild}while(e.offsetParent){left+=e.offsetLeft;top+=e.offsetTop;e=e.offsetParent}left+=e.offsetLeft;top+=e.offsetTop;return{x:left,y:top}},mousemove:function(ev){if(jQuery.tableDnD.dragObject==null){return}if(ev.type=='touchmove'){event.preventDefault()}var dragObj=jQuery(jQuery.tableDnD.dragObject);var config=jQuery.tableDnD.currentTable.tableDnDConfig;var mousePos=jQuery.tableDnD.mouseCoords(ev);var y=mousePos.y-jQuery.tableDnD.mouseOffset.y;var yOffset=window.pageYOffset;if(document.all){if(typeof document.compatMode!='undefined'&&document.compatMode!='BackCompat'){yOffset=document.documentElement.scrollTop}else if(typeof document.body!='undefined'){yOffset=document.body.scrollTop}}if(mousePos.y-yOffsetjQuery.tableDnD.oldY;jQuery.tableDnD.oldY=y;if(config.onDragClass){dragObj.addClass(config.onDragClass)}else{dragObj.css(config.onDragStyle)}var currentRow=jQuery.tableDnD.findDropTargetRow(dragObj,y);if(currentRow){if(movingDown&&jQuery.tableDnD.dragObject!=currentRow){jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject,currentRow.nextSibling)}else if(!movingDown&&jQuery.tableDnD.dragObject!=currentRow){jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject,currentRow)}}}return false},findDropTargetRow:function(draggedRow,y){var rows=jQuery.tableDnD.currentTable.rows;for(var i=0;irowY-rowHeight)&&(y<(rowY+rowHeight))){if(row==draggedRow){return null}var config=jQuery.tableDnD.currentTable.tableDnDConfig;if(config.onAllowDrop){if(config.onAllowDrop(draggedRow,row)){return row}else{return null}}else{var nodrop=jQuery(row).hasClass("nodrop");if(!nodrop){return row}else{return null}}return row}}return null},mouseup:function(e){if(jQuery.tableDnD.currentTable&&jQuery.tableDnD.dragObject){jQuery(document).unbind(moveEvent,jQuery.tableDnD.mousemove).unbind(endEvent,jQuery.tableDnD.mouseup);var droppedRow=jQuery.tableDnD.dragObject;var config=jQuery.tableDnD.currentTable.tableDnDConfig;if(config.onDragClass){jQuery(droppedRow).removeClass(config.onDragClass)}else{jQuery(droppedRow).css(config.onDropStyle)}jQuery.tableDnD.dragObject=null;var newOrder=jQuery.tableDnD.serialize();if(config.onDrop&&(jQuery.tableDnD.originalOrder!=newOrder)){config.onDrop(jQuery.tableDnD.currentTable,droppedRow)}jQuery.tableDnD.currentTable=null}},serialize:function(){if(jQuery.tableDnD.currentTable){return jQuery.tableDnD.serializeTable(jQuery.tableDnD.currentTable)}else{return"Error: No Table id set, you need to set an id on your table and every row"}},serializeTable:function(table){var result="";var tableId=table.id;var rows=table.rows;for(var i=0;i0)result+="&";var rowId=rows[i].id;if(rowId&&rowId&&table.tableDnDConfig&&table.tableDnDConfig.serializeRegexp){rowId=rowId.match(table.tableDnDConfig.serializeRegexp)[0]}result+=tableId+'[]='+rowId}return result},serializeTables:function(){var result="";this.each(function(){result+=jQuery.tableDnD.serializeTable(this)});return result}};jQuery.fn.extend({tableDnD:jQuery.tableDnD.build,tableDnDUpdate:jQuery.tableDnD.updateTables,tableDnDSerialize:jQuery.tableDnD.serializeTables}); \ No newline at end of file diff --git a/TableDnD/js/jquery.tablednd.0.7.min.js b/TableDnD/js/jquery.tablednd.0.7.min.js new file mode 100644 index 0000000..09b0bed --- /dev/null +++ b/TableDnD/js/jquery.tablednd.0.7.min.js @@ -0,0 +1 @@ +var hasTouch='ontouchstart'in document.documentElement,startEvent=hasTouch?'touchstart':'mousedown',moveEvent=hasTouch?'touchmove':'mousemove',endEvent=hasTouch?'touchend':'mouseup';if(hasTouch){$.each("touchstart touchmove touchend".split(" "),function(i,name){jQuery.event.fixHooks[name]=jQuery.event.mouseHooks});alert("has Touch")}jQuery.tableDnD={currentTable:null,dragObject:null,mouseOffset:null,oldY:0,build:function(options){this.each(function(){this.tableDnDConfig=jQuery.extend({onDragStyle:null,onDropStyle:null,onDragClass:"tDnD_whileDrag",onDrop:null,onDragStart:null,scrollAmount:5,serializeRegexp:/[^\-]*$/,serializeParamName:null,dragHandle:null},options||{});jQuery.tableDnD.makeDraggable(this)});return this},makeDraggable:function(table){var config=table.tableDnDConfig;if(config.dragHandle){var cells=jQuery(table.tableDnDConfig.dragHandle,table);cells.each(function(){jQuery(this).bind(startEvent,function(ev){jQuery.tableDnD.initialiseDrag(jQuery(this).parents('tr')[0],table,this,ev,config);return false})})}else{var rows=jQuery("tr",table);rows.each(function(){var row=jQuery(this);if(!row.hasClass("nodrag")){row.bind(startEvent,function(ev){if(ev.target.tagName=="TD"){jQuery.tableDnD.initialiseDrag(this,table,this,ev,config);return false}}).css("cursor","move")}})}},initialiseDrag:function(dragObject,table,target,evnt,config){jQuery.tableDnD.dragObject=dragObject;jQuery.tableDnD.currentTable=table;jQuery.tableDnD.mouseOffset=jQuery.tableDnD.getMouseOffset(target,evnt);jQuery.tableDnD.originalOrder=jQuery.tableDnD.serialize();jQuery(document).bind(moveEvent,jQuery.tableDnD.mousemove).bind(endEvent,jQuery.tableDnD.mouseup);if(config.onDragStart){config.onDragStart(table,target)}},updateTables:function(){this.each(function(){if(this.tableDnDConfig){jQuery.tableDnD.makeDraggable(this)}})},mouseCoords:function(ev){if(ev.pageX||ev.pageY){return{x:ev.pageX,y:ev.pageY}}return{x:ev.clientX+document.body.scrollLeft-document.body.clientLeft,y:ev.clientY+document.body.scrollTop-document.body.clientTop}},getMouseOffset:function(target,ev){ev=ev||window.event;var docPos=this.getPosition(target);var mousePos=this.mouseCoords(ev);return{x:mousePos.x-docPos.x,y:mousePos.y-docPos.y}},getPosition:function(e){var left=0;var top=0;if(e.offsetHeight==0){e=e.firstChild}while(e.offsetParent){left+=e.offsetLeft;top+=e.offsetTop;e=e.offsetParent}left+=e.offsetLeft;top+=e.offsetTop;return{x:left,y:top}},mousemove:function(ev){if(jQuery.tableDnD.dragObject==null){return}if(ev.type=='touchmove'){event.preventDefault()}var dragObj=jQuery(jQuery.tableDnD.dragObject);var config=jQuery.tableDnD.currentTable.tableDnDConfig;var mousePos=jQuery.tableDnD.mouseCoords(ev);var y=mousePos.y-jQuery.tableDnD.mouseOffset.y;var yOffset=window.pageYOffset;if(document.all){if(typeof document.compatMode!='undefined'&&document.compatMode!='BackCompat'){yOffset=document.documentElement.scrollTop}else if(typeof document.body!='undefined'){yOffset=document.body.scrollTop}}if(mousePos.y-yOffsetjQuery.tableDnD.oldY;jQuery.tableDnD.oldY=y;if(config.onDragClass){dragObj.addClass(config.onDragClass)}else{dragObj.css(config.onDragStyle)}var currentRow=jQuery.tableDnD.findDropTargetRow(dragObj,y);if(currentRow){if(movingDown&&jQuery.tableDnD.dragObject!=currentRow){jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject,currentRow.nextSibling)}else if(!movingDown&&jQuery.tableDnD.dragObject!=currentRow){jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject,currentRow)}}}return false},findDropTargetRow:function(draggedRow,y){var rows=jQuery.tableDnD.currentTable.rows;for(var i=0;irowY-rowHeight)&&(y<(rowY+rowHeight))){if(row==draggedRow){return null}var config=jQuery.tableDnD.currentTable.tableDnDConfig;if(config.onAllowDrop){if(config.onAllowDrop(draggedRow,row)){return row}else{return null}}else{var nodrop=jQuery(row).hasClass("nodrop");if(!nodrop){return row}else{return null}}return row}}return null},mouseup:function(e){if(jQuery.tableDnD.currentTable&&jQuery.tableDnD.dragObject){jQuery(document).unbind(moveEvent,jQuery.tableDnD.mousemove).unbind(endEvent,jQuery.tableDnD.mouseup);var droppedRow=jQuery.tableDnD.dragObject;var config=jQuery.tableDnD.currentTable.tableDnDConfig;if(config.onDragClass){jQuery(droppedRow).removeClass(config.onDragClass)}else{jQuery(droppedRow).css(config.onDropStyle)}jQuery.tableDnD.dragObject=null;var newOrder=jQuery.tableDnD.serialize();if(config.onDrop&&(jQuery.tableDnD.originalOrder!=newOrder)){config.onDrop(jQuery.tableDnD.currentTable,droppedRow)}jQuery.tableDnD.currentTable=null}},serialize:function(){if(jQuery.tableDnD.currentTable){return jQuery.tableDnD.serializeTable(jQuery.tableDnD.currentTable)}else{return"Error: No Table id set, you need to set an id on your table and every row"}},serializeTable:function(table){var result="";var tableId=table.id;var rows=table.rows;for(var i=0;i0)result+="&";var rowId=rows[i].id;if(rowId&&rowId&&table.tableDnDConfig&&table.tableDnDConfig.serializeRegexp){rowId=rowId.match(table.tableDnDConfig.serializeRegexp)[0]}result+=tableId+'[]='+rowId}return result},serializeTables:function(){var result="";this.each(function(){result+=jQuery.tableDnD.serializeTable(this)});return result}};jQuery.fn.extend({tableDnD:jQuery.tableDnD.build,tableDnDUpdate:jQuery.tableDnD.updateTables,tableDnDSerialize:jQuery.tableDnD.serializeTables}); \ No newline at end of file diff --git a/TableDnD/js/jquery.tablednd.0.8.min.js b/TableDnD/js/jquery.tablednd.0.8.min.js new file mode 100644 index 0000000..681ae71 --- /dev/null +++ b/TableDnD/js/jquery.tablednd.0.8.min.js @@ -0,0 +1 @@ +(function($){var hasTouch="ontouchstart" in document.documentElement,startEvent=hasTouch?"touchstart":"mousedown",moveEvent=hasTouch?"touchmove":"mousemove",endEvent=hasTouch?"touchend":"mouseup";if(hasTouch){$.each("touchstart touchmove touchend".split(" "),function(i,name){jQuery.event.fixHooks[name]=jQuery.event.mouseHooks})}jQuery.tableDnD={currentTable:null,dragObject:null,mouseOffset:null,oldY:0,build:function(options){this.each(function(){this.tableDnDConfig=jQuery.extend({onDragStyle:null,onDropStyle:null,onDragClass:"tDnD_whileDrag",onDrop:null,onDragStart:null,scrollAmount:5,serializeRegexp:/[^\-]*$/,serializeParamName:null,dragHandle:null},options||{});jQuery.tableDnD.makeDraggable(this)});return this},makeDraggable:function(table){var config=table.tableDnDConfig;if(config.dragHandle){var cells=jQuery(table.tableDnDConfig.dragHandle,table);cells.each(function(){jQuery(this).bind(startEvent,function(ev){jQuery.tableDnD.initialiseDrag(jQuery(this).parents("tr")[0],table,this,ev,config);return false})})}else{var rows=jQuery("tr",table);rows.each(function(){var row=jQuery(this);if(!row.hasClass("nodrag")){row.bind(startEvent,function(ev){if(ev.target.tagName=="TD"){jQuery.tableDnD.initialiseDrag(this,table,this,ev,config);return false}}).css("cursor","move")}})}},initialiseDrag:function(dragObject,table,target,evnt,config){jQuery.tableDnD.dragObject=dragObject;jQuery.tableDnD.currentTable=table;jQuery.tableDnD.mouseOffset=jQuery.tableDnD.getMouseOffset(target,evnt);jQuery.tableDnD.originalOrder=jQuery.tableDnD.serialize();jQuery(document).bind(moveEvent,jQuery.tableDnD.mousemove).bind(endEvent,jQuery.tableDnD.mouseup);if(config.onDragStart){config.onDragStart(table,target)}},updateTables:function(){this.each(function(){if(this.tableDnDConfig){jQuery.tableDnD.makeDraggable(this)}})},mouseCoords:function(ev){if(ev.pageX||ev.pageY){return{x:ev.pageX,y:ev.pageY}}return{x:ev.clientX+document.body.scrollLeft-document.body.clientLeft,y:ev.clientY+document.body.scrollTop-document.body.clientTop}},getMouseOffset:function(target,ev){ev=ev||window.event;var docPos=this.getPosition(target);var mousePos=this.mouseCoords(ev);return{x:mousePos.x-docPos.x,y:mousePos.y-docPos.y}},getPosition:function(e){var left=0;var top=0;if(e.offsetHeight==0){e=e.firstChild}while(e.offsetParent){left+=e.offsetLeft;top+=e.offsetTop;e=e.offsetParent}left+=e.offsetLeft;top+=e.offsetTop;return{x:left,y:top}},mousemove:function(ev){if(jQuery.tableDnD.dragObject==null){return}if(ev.type=="touchmove"){event.preventDefault()}var dragObj=jQuery(jQuery.tableDnD.dragObject);var config=jQuery.tableDnD.currentTable.tableDnDConfig;var mousePos=jQuery.tableDnD.mouseCoords(ev);var y=mousePos.y-jQuery.tableDnD.mouseOffset.y;var yOffset=window.pageYOffset;if(document.all){if(typeof document.compatMode!="undefined"&&document.compatMode!="BackCompat"){yOffset=document.documentElement.scrollTop}else{if(typeof document.body!="undefined"){yOffset=document.body.scrollTop}}}if(mousePos.y-yOffsetjQuery.tableDnD.oldY;jQuery.tableDnD.oldY=y;if(config.onDragClass){dragObj.addClass(config.onDragClass)}else{dragObj.css(config.onDragStyle)}var currentRow=jQuery.tableDnD.findDropTargetRow(dragObj,y);if(currentRow){if(movingDown&&jQuery.tableDnD.dragObject!=currentRow){jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject,currentRow.nextSibling)}else{if(!movingDown&&jQuery.tableDnD.dragObject!=currentRow){jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject,currentRow)}}}}return false},findDropTargetRow:function(draggedRow,y){var rows=jQuery.tableDnD.currentTable.rows;for(var i=0;irowY-rowHeight)&&(y<(rowY+rowHeight))){if(row==draggedRow){return null}var config=jQuery.tableDnD.currentTable.tableDnDConfig;if(config.onAllowDrop){if(config.onAllowDrop(draggedRow,row)){return row}else{return null}}else{var nodrop=jQuery(row).hasClass("nodrop");if(!nodrop){return row}else{return null}}return row}}return null},mouseup:function(e){if(jQuery.tableDnD.currentTable&&jQuery.tableDnD.dragObject){jQuery(document).unbind(moveEvent,jQuery.tableDnD.mousemove).unbind(endEvent,jQuery.tableDnD.mouseup);var droppedRow=jQuery.tableDnD.dragObject;var config=jQuery.tableDnD.currentTable.tableDnDConfig;if(config.onDragClass){jQuery(droppedRow).removeClass(config.onDragClass)}else{jQuery(droppedRow).css(config.onDropStyle)}jQuery.tableDnD.dragObject=null;var newOrder=jQuery.tableDnD.serialize();if(config.onDrop&&(jQuery.tableDnD.originalOrder!=newOrder)){config.onDrop(jQuery.tableDnD.currentTable,droppedRow)}jQuery.tableDnD.currentTable=null}},jsonize:function(){if(jQuery.tableDnD.currentTable){return jQuery.tableDnD.jsonizeTable(jQuery.tableDnD.currentTable)}else{return"Error: No Table id set, you need to set an id on your table and every row"}},jsonizeTable:function(table){var result="{";var tableId=table.id;var rows=table.rows;result+='"'+tableId+'" : [';for(var i=0;i0){result+="&"}var rowId=rows[i].id;if(rowId&&table.tableDnDConfig&&table.tableDnDConfig.serializeRegexp){rowId=rowId.match(table.tableDnDConfig.serializeRegexp)[0]}result+=paramName+"[]="+rowId}return result},serializeTables:function(){var result="";this.each(function(){result+=jQuery.tableDnD.serializeTable(this)});return result}};jQuery.fn.extend({tableDnD:jQuery.tableDnD.build,tableDnDUpdate:jQuery.tableDnD.updateTables,tableDnDSerialize:jQuery.tableDnD.serializeTables})})(jQuery); \ No newline at end of file diff --git a/TableDnD/js/jquery.tablednd.0.9.rc1.js b/TableDnD/js/jquery.tablednd.0.9.rc1.js new file mode 100644 index 0000000..8eb2cf0 --- /dev/null +++ b/TableDnD/js/jquery.tablednd.0.9.rc1.js @@ -0,0 +1,664 @@ +/** + * TableDnD plug-in for JQuery, allows you to drag and drop table rows + * You can set up various options to control how the system will work + * Copyright (c) Denis Howlett + * Licensed like jQuery, see http://docs.jquery.com/License. + * + * Configuration options: + * + * onDragStyle + * This is the style that is assigned to the row during drag. There are limitations to the styles that can be + * associated with a row (such as you can't assign a border--well you can, but it won't be + * displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as + * a map (as used in the jQuery css(...) function). + * onDropStyle + * This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations + * to what you can do. Also this replaces the original style, so again consider using onDragClass which + * is simply added and then removed on drop. + * onDragClass + * This class is added for the duration of the drag and then removed when the row is dropped. It is more + * flexible than using onDragStyle since it can be inherited by the row cells and other content. The default + * is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your + * stylesheet. + * onDrop + * Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table + * and the row that was dropped. You can work out the new order of the rows by using + * table.rows. + * onDragStart + * Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the + * table and the row which the user has started to drag. + * onAllowDrop + * Pass a function that will be called as a row is over another row. If the function returns true, allow + * dropping on that row, otherwise not. The function takes 2 parameters: the dragged row and the row under + * the cursor. It returns a boolean: true allows the drop, false doesn't allow it. + * scrollAmount + * This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the + * window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2, + * FF3 beta + * dragHandle + * This is a jQuery mach string for one or more cells in each row that is draggable. If you + * specify this, then you are responsible for setting cursor: move in the CSS and only these cells + * will have the drag behaviour. If you do not specify a dragHandle, then you get the old behaviour where + * the whole row is draggable. + * + * Other ways to control behaviour: + * + * Add class="nodrop" to any rows for which you don't want to allow dropping, and class="nodrag" to any rows + * that you don't want to be draggable. + * + * Inside the onDrop method you can also call $.tableDnD.serialize() this returns a string of the form + * []=&[]= so that you can send this back to the server. The table must have + * an ID as must all the rows. + * + * Other methods: + * + * $("...").tableDnDUpdate() + * Will update all the matching tables, that is it will reapply the mousedown method to the rows (or handle cells). + * This is useful if you have updated the table rows using Ajax and you want to make the table draggable again. + * The table maintains the original configuration (so you don't have to specify it again). + * + * $("...").tableDnDSerialize() + * Will serialize and return the serialized string as above, but for each of the matching tables--so it can be + * called from anywhere and isn't dependent on the currentTable being set up correctly before calling + * + * Known problems: + * - Auto-scoll has some problems with IE7 (it scrolls even when it shouldn't), work-around: set scrollAmount to 0 + * + * Version 0.2: 2008-02-20 First public version + * Version 0.3: 2008-02-07 Added onDragStart option + * Made the scroll amount configurable (default is 5 as before) + * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop classes + * Added onAllowDrop to control dropping + * Fixed a bug which meant that you couldn't set the scroll amount in both directions + * Added serialize method + * Version 0.5: 2008-05-16 Changed so that if you specify a dragHandle class it doesn't make the whole row + * draggable + * Improved the serialize method to use a default (and settable) regular expression. + * Added tableDnDupate() and tableDnDSerialize() to be called when you are outside the table + * Version 0.6: 2011-12-02 Added support for touch devices + * Version 0.7 2012-04-09 Now works with jQuery 1.7 and supports touch, tidied up tabs and spaces + */ +!function ($, window, document, undefined) { +// Determine if this is a touch device +var hasTouch = 'ontouchstart' in document.documentElement, + startEvent = hasTouch ? 'touchstart' : 'mousedown', + moveEvent = hasTouch ? 'touchmove' : 'mousemove', + endEvent = hasTouch ? 'touchend' : 'mouseup'; + +// If we're on a touch device, then wire up the events +// see http://stackoverflow.com/a/8456194/1316086 +hasTouch + && $.each("touchstart touchmove touchend".split(" "), function(i, name) { + $.event.fixHooks[name] = $.event.mouseHooks; + }); + + +$(document).ready(function () { + function parseStyle(css) { + var objMap = {}, + parts = css.match(/([^;:]+)/g) || []; + while (parts.length) + objMap[parts.shift()] = parts.shift().trim(); + + return objMap; + } + $('table').each(function () { + if ($(this).data('table') == 'dnd') { + + $(this).tableDnD({ + onDragStyle: $(this).data('ondragstyle') && parseStyle($(this).data('ondragstyle')) || null, + onDropStyle: $(this).data('ondropstyle') && parseStyle($(this).data('ondropstyle')) || null, + onDragClass: $(this).data('ondragclass') == undefined && "tDnD_whileDrag" || $(this).data('ondragclass'), + onDrop: $(this).data('ondrop') && new Function('table', 'row', $(this).data('ondrop')), // 'return eval("'+$(this).data('ondrop')+'");') || null, + onDragStart: $(this).data('ondragstart') && new Function('table', 'row' ,$(this).data('ondragstart')), // 'return eval("'+$(this).data('ondragstart')+'");') || null, + scrollAmount: $(this).data('scrollamount') || 5, + sensitivity: $(this).data('sensitivity') || 10, + hierarchyLevel: $(this).data('hierarchylevel') || 0, + indentArtifact: $(this).data('indentartifact') || '
 
', + autoWidthAdjust: $(this).data('autowidthadjust') || true, + autoCleanRelations: $(this).data('autocleanrelations') || true, + jsonPretifySeparator: $(this).data('jsonpretifyseparator') || '\t', + serializeRegexp: $(this).data('serializeregexp') && new RegExp($(this).data('serializeregexp')) || /[^\-]*$/, + serializeParamName: $(this).data('serializeparamname') || false, + dragHandle: $(this).data('draghandle') || null + }); + } + + + }); +}); + +window.jQuery.tableDnD = { + /** Keep hold of the current table being dragged */ + currentTable: null, + /** Keep hold of the current drag object if any */ + dragObject: null, + /** The current mouse offset */ + mouseOffset: null, + /** Remember the old value of X and Y so that we don't do too much processing */ + oldX: 0, + oldY: 0, + + /** Actually build the structure */ + build: function(options) { + // Set up the defaults if any + + this.each(function() { + // This is bound to each matching table, set up the defaults and override with user options + this.tableDnDConfig = $.extend({ + onDragStyle: null, + onDropStyle: null, + // Add in the default class for whileDragging + onDragClass: "tDnD_whileDrag", + onDrop: null, + onDragStart: null, + scrollAmount: 5, + /** Sensitivity setting will throttle the trigger rate for movement detection */ + sensitivity: 10, + /** Hierarchy level to support parent child. 0 switches this functionality off */ + hierarchyLevel: 0, + /** The html artifact to prepend the first cell with as indentation */ + indentArtifact: '
 
', + /** Automatically adjust width of first cell */ + autoWidthAdjust: true, + /** Automatic clean-up to ensure relationship integrity */ + autoCleanRelations: true, + /** Specify a number (4) as number of spaces or any indent string for JSON.stringify */ + jsonPretifySeparator: '\t', + /** The regular expression to use to trim row IDs */ + serializeRegexp: /[^\-]*$/, + /** If you want to specify another parameter name instead of the table ID */ + serializeParamName: false, + /** If you give the name of a class here, then only Cells with this class will be draggable */ + dragHandle: null + }, options || {}); + + // Now make the rows draggable + $.tableDnD.makeDraggable(this); + // Prepare hierarchy support + this.tableDnDConfig.hierarchyLevel + && $.tableDnD.makeIndented(this); + }); + + // Don't break the chain + return this; + }, + makeIndented: function (table) { + var config = table.tableDnDConfig, + rows = table.rows, + firstCell = $(rows).first().find('td:first')[0], + indentLevel = 0, + cellWidth = 0, + longestCell, + tableStyle; + + if ($(table).hasClass('indtd')) + return null; + + tableStyle = $(table).addClass('indtd').attr('style'); + $(table).css({whiteSpace: "nowrap"}); + + for (var w = 0; w < rows.length; w++) { + if (cellWidth < $(rows[w]).find('td:first').text().length) { + cellWidth = $(rows[w]).find('td:first').text().length; + longestCell = w; + } + } + $(firstCell).css({width: 'auto'}); + for (w = 0; w < config.hierarchyLevel; w++) + $(rows[longestCell]).find('td:first').prepend(config.indentArtifact); + firstCell && $(firstCell).css({width: firstCell.offsetWidth}); + tableStyle && $(table).css(tableStyle); + + for (w = 0; w < config.hierarchyLevel; w++) + $(rows[longestCell]).find('td:first').children(':first').remove(); + + config.hierarchyLevel + && $(rows).each(function () { + indentLevel = $(this).data('level') || 0; + indentLevel <= config.hierarchyLevel + && $(this).data('level', indentLevel) + || $(this).data('level', 0); + for (var i = 0; i < $(this).data('level'); i++) + $(this).find('td:first').prepend(config.indentArtifact); + }); + + return this; + }, + /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */ + makeDraggable: function(table) { + var config = table.tableDnDConfig; + + config.dragHandle + // We only need to add the event to the specified cells + && $(config.dragHandle, table).each(function() { + // The cell is bound to "this" + $(this).bind(startEvent, function(e) { + $.tableDnD.initialiseDrag($(this).parents('tr')[0], table, this, e, config); + return false; + }); + }) + // For backwards compatibility, we add the event to the whole row + // get all the rows as a wrapped set + || $(table.rows).each(function() { + // Iterate through each row, the row is bound to "this" + if (! $(this).hasClass("nodrag")) { + $(this).bind(startEvent, function(e) { + if (e.target.tagName == "TD") { + $.tableDnD.initialiseDrag(this, table, this, e, config); + return false; + } + }).css("cursor", "move"); // Store the tableDnD object + } + }); + }, + currentOrder: function() { + var rows = this.currentTable.rows; + return $.map(rows, function (val) { + return ($(val).data('level') + val.id).replace(/\s/g, ''); + }).join(''); + }, + initialiseDrag: function(dragObject, table, target, e, config) { + this.dragObject = dragObject; + this.currentTable = table; + this.mouseOffset = this.getMouseOffset(target, e); + this.originalOrder = this.currentOrder(); + + // Now we need to capture the mouse up and mouse move event + // We can use bind so that we don't interfere with other event handlers + $(document) + .bind(moveEvent, this.mousemove) + .bind(endEvent, this.mouseup); + + // Call the onDragStart method if there is one + config.onDragStart + && config.onDragStart(table, target); + }, + updateTables: function() { + this.each(function() { + // this is now bound to each matching table + if (this.tableDnDConfig) + $.tableDnD.makeDraggable(this); + }); + }, + /** Get the mouse coordinates from the event (allowing for browser differences) */ + mouseCoords: function(e) { + if(e.pageX || e.pageY) + return { + x: e.pageX, + y: e.pageY + }; + + return { + x: e.clientX + document.body.scrollLeft - document.body.clientLeft, + y: e.clientY + document.body.scrollTop - document.body.clientTop + }; + }, + /** Given a target element and a mouse eent, get the mouse offset from that element. + To do this we need the element's position and the mouse position */ + getMouseOffset: function(target, e) { + var mousePos, + docPos; + + e = e || window.event; + + docPos = this.getPosition(target); + mousePos = this.mouseCoords(e); + + return { + x: mousePos.x - docPos.x, + y: mousePos.y - docPos.y + }; + }, + /** Get the position of an element by going up the DOM tree and adding up all the offsets */ + getPosition: function(element) { + var left = 0, + top = 0; + + // Safari fix -- thanks to Luis Chato for this! + // Safari 2 doesn't correctly grab the offsetTop of a table row + // this is detailed here: + // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/ + // the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild. + // note that firefox will return a text node as a first child, so designing a more thorough + // solution may need to take that into account, for now this seems to work in firefox, safari, ie + if (element.offsetHeight == 0) + element = element.firstChild; // a table cell + + while (element.offsetParent) { + left += element.offsetLeft; + top += element.offsetTop; + element = element.offsetParent; + } + + left += element.offsetLeft; + top += element.offsetTop; + + return { + x: left, + y: top + }; + }, + autoScroll: function (mousePos) { + var config = this.currentTable.tableDnDConfig, + yOffset = window.pageYOffset, + windowHeight = window.innerHeight + ? window.innerHeight + : document.documentElement.clientHeight + ? document.documentElement.clientHeight + : document.body.clientHeight; + + // Windows version + // yOffset=document.body.scrollTop; + if (document.all) + if (typeof document.compatMode != 'undefined' + && document.compatMode != 'BackCompat') + yOffset = document.documentElement.scrollTop; + else if (typeof document.body != 'undefined') + yOffset = document.body.scrollTop; + + mousePos.y - yOffset < config.scrollAmount + && window.scrollBy(0, - config.scrollAmount) + || windowHeight - (mousePos.y - yOffset) < config.scrollAmount + && window.scrollBy(0, config.scrollAmount); + + }, + moveVerticle: function (moving, currentRow) { + + if (0 != moving.vertical + // If we're over a row then move the dragged row to there so that the user sees the + // effect dynamically + && currentRow + && this.dragObject != currentRow + && this.dragObject.parentNode == currentRow.parentNode) + 0 > moving.vertical + && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow.nextSibling) + || 0 < moving.vertical + && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow); + + }, + moveHorizontal: function (moving, currentRow) { + var config = this.currentTable.tableDnDConfig, + currentLevel; + + if (!config.hierarchyLevel + || 0 == moving.horizontal + // We only care if moving left or right on the current row + || !currentRow + || this.dragObject != currentRow) + return null; + + currentLevel = $(currentRow).data('level'); + + 0 < moving.horizontal + && currentLevel > 0 + && $(currentRow).find('td:first').children(':first').remove() + && $(currentRow).data('level', --currentLevel); + + 0 > moving.horizontal + && currentLevel < config.hierarchyLevel + && $(currentRow).prev().data('level') >= currentLevel + && $(currentRow).children(':first').prepend(config.indentArtifact) + && $(currentRow).data('level', ++currentLevel); + + }, + mousemove: function(e) { + var dragObj = $($.tableDnD.dragObject), + config = $.tableDnD.currentTable.tableDnDConfig, + currentRow, + mousePos, + moving, + x, + y; + + e && e.preventDefault(); + + if (!$.tableDnD.dragObject) + return false; + + // prevent touch device screen scrolling + e.type == 'touchmove' + && event.preventDefault(); // TODO verify this is event and not really e + + // update the style to show we're dragging + config.onDragClass + && dragObj.addClass(config.onDragClass) + || dragObj.css(config.onDragStyle); + + mousePos = $.tableDnD.mouseCoords(e); + x = mousePos.x - $.tableDnD.mouseOffset.x; + y = mousePos.y - $.tableDnD.mouseOffset.y; + + // auto scroll the window + $.tableDnD.autoScroll(mousePos); + + currentRow = $.tableDnD.findDropTargetRow(dragObj, y); + moving = $.tableDnD.findDragDirection(x, y); + + $.tableDnD.moveVerticle(moving, currentRow); + $.tableDnD.moveHorizontal(moving, currentRow); + + return false; + }, + findDragDirection: function (x,y) { + var sensitivity = this.currentTable.tableDnDConfig.sensitivity, + oldX = this.oldX, + oldY = this.oldY, + xMin = oldX - sensitivity, + xMax = oldX + sensitivity, + yMin = oldY - sensitivity, + yMax = oldY + sensitivity, + moving = { + horizontal: x >= xMin && x <= xMax ? 0 : x > oldX ? -1 : 1, + vertical : y >= yMin && y <= yMax ? 0 : y > oldY ? -1 : 1 + }; + + // update the old value + if (moving.horizontal != 0) + this.oldX = x; + if (moving.vertical != 0) + this.oldY = y; + + return moving; + }, + /** We're only worried about the y position really, because we can only move rows up and down */ + findDropTargetRow: function(draggedRow, y) { + var rowHeight = 0, + rows = this.currentTable.rows, + config = this.currentTable.tableDnDConfig, + rowY = 0, + row = null; + + for (var i = 0; i < rows.length; i++) { + row = rows[i]; + rowY = this.getPosition(row).y; + rowHeight = parseInt(row.offsetHeight) / 2; + if (row.offsetHeight == 0) { + rowY = this.getPosition(row.firstChild).y; + rowHeight = parseInt(row.firstChild.offsetHeight) / 2; + } + // Because we always have to insert before, we need to offset the height a bit + if (y > (rowY - rowHeight) && y < (rowY + rowHeight)) + // that's the row we're over + // If it's the same as the current row, ignore it + if (row == draggedRow + || (config.onAllowDrop + && !config.onAllowDrop(draggedRow, row)) + // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic) + || $(row).hasClass("nodrop")) + return null; + else + return row; + } + return null; + }, + processMouseup: function() { + var config = this.currentTable.tableDnDConfig, + droppedRow = this.dragObject, + parentLevel = 0, + myLevel = 0; + + if (!this.currentTable || !droppedRow) + return null; + + // Unbind the event handlers + $(document) + .unbind(moveEvent, this.mousemove) + .unbind(endEvent, this.mouseup); + + config.hierarchyLevel + && config.autoCleanRelations + && $(this.currentTable.rows).first().find('td:first').children().each(function () { + myLevel = $(this).parents('tr:first').data('level'); + myLevel + && $(this).parents('tr:first').data('level', --myLevel) + && $(this).remove(); + }) + && config.hierarchyLevel > 1 + && $(this.currentTable.rows).each(function () { + myLevel = $(this).data('level'); + if (myLevel > 1) { + parentLevel = $(this).prev().data('level'); + while (myLevel > parentLevel + 1) { + $(this).find('td:first').children(':first').remove(); + $(this).data('level', --myLevel); + } + } + }); + + // If we have a dragObject, then we need to release it, + // The row will already have been moved to the right place so we just reset stuff + config.onDragClass + && $(droppedRow).removeClass(config.onDragClass) + || $(droppedRow).css(config.onDropStyle); + + this.dragObject = null; + // Call the onDrop method if there is one + config.onDrop + && this.originalOrder != this.currentOrder() + && $(droppedRow).hide().fadeIn('fast') + && config.onDrop(this.currentTable, droppedRow); + + this.currentTable = null; // let go of the table too + }, + mouseup: function(e) { + e && e.preventDefault(); + $.tableDnD.processMouseup(); + return false; + }, + jsonize: function(pretify) { + var table = this.currentTable; + if (pretify) + return JSON.stringify( + this.tableData(table), + null, + table.tableDnDConfig.jsonPretifySeparator + ); + return JSON.stringify(this.tableData(table)); + }, + serialize: function() { + return $.param(this.tableData(this.currentTable)); + }, + serializeTable: function(table) { + var result = ""; + var paramName = table.tableDnDConfig.serializeParamName || table.id; + var rows = table.rows; + for (var i=0; i 0) result += "&"; + var rowId = rows[i].id; + if (rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) { + rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0]; + result += paramName + '[]=' + rowId; + } + } + return result; + }, + serializeTables: function() { + var result = []; + $('table').each(function() { + this.id && result.push($.param(this.tableData(this))); + }); + return result.join('&'); + }, + tableData: function (table) { + var config = table.tableDnDConfig, + previousIDs = [], + currentLevel = 0, + indentLevel = 0, + rowID = null, + data = {}, + getSerializeRegexp, + paramName, + currentID, + rows; + + if (!table) + table = this.currentTable; + if (!table || !table.id || !table.rows || !table.rows.length) + return {error: { code: 500, message: "Not a valid table, no serializable unique id provided."}}; + + rows = config.autoCleanRelations + && table.rows + || $.makeArray(table.rows); + paramName = config.serializeParamName || table.id; + currentID = paramName; + + getSerializeRegexp = function (rowId) { + if (rowId && config && config.serializeRegexp) + return rowId.match(config.serializeRegexp)[0]; + return rowId; + }; + + data[currentID] = []; + !config.autoCleanRelations + && $(rows[0]).data('level') + && rows.unshift({id: 'undefined'}); + + + + for (var i=0; i < rows.length; i++) { + if (config.hierarchyLevel) { + indentLevel = $(rows[i]).data('level') || 0; + if (indentLevel == 0) { + currentID = paramName; + previousIDs = []; + } + else if (indentLevel > currentLevel) { + previousIDs.push([currentID, currentLevel]); + currentID = getSerializeRegexp(rows[i-1].id); + } + else if (indentLevel < currentLevel) { + for (var h = 0; h < previousIDs.length; h++) { + if (previousIDs[h][1] == indentLevel) + currentID = previousIDs[h][0]; + if (previousIDs[h][1] >= currentLevel) + previousIDs[h][1] = 0; + } + } + currentLevel = indentLevel; + + if (!$.isArray(data[currentID])) + data[currentID] = []; + rowID = getSerializeRegexp(rows[i].id); + rowID && data[currentID].push(rowID); + } + else { + rowID = getSerializeRegexp(rows[i].id); + rowID && data[currentID].push(rowID); + } + } + return data; + } +}; + +window.jQuery.fn.extend( + { + tableDnD : $.tableDnD.build, + tableDnDUpdate : $.tableDnD.updateTables, + tableDnDSerialize : $.proxy($.tableDnD.serialize, $.tableDnD), + tableDnDSerializeAll : $.tableDnD.serializeTables, + tableDnDData : $.proxy($.tableDnD.tableData, $.tableDnD) + } +); + +}(window.jQuery, window, window.document); diff --git a/TableDnD/js/jquery.tablednd.js b/TableDnD/js/jquery.tablednd.js new file mode 100644 index 0000000..8eb2cf0 --- /dev/null +++ b/TableDnD/js/jquery.tablednd.js @@ -0,0 +1,664 @@ +/** + * TableDnD plug-in for JQuery, allows you to drag and drop table rows + * You can set up various options to control how the system will work + * Copyright (c) Denis Howlett + * Licensed like jQuery, see http://docs.jquery.com/License. + * + * Configuration options: + * + * onDragStyle + * This is the style that is assigned to the row during drag. There are limitations to the styles that can be + * associated with a row (such as you can't assign a border--well you can, but it won't be + * displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as + * a map (as used in the jQuery css(...) function). + * onDropStyle + * This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations + * to what you can do. Also this replaces the original style, so again consider using onDragClass which + * is simply added and then removed on drop. + * onDragClass + * This class is added for the duration of the drag and then removed when the row is dropped. It is more + * flexible than using onDragStyle since it can be inherited by the row cells and other content. The default + * is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your + * stylesheet. + * onDrop + * Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table + * and the row that was dropped. You can work out the new order of the rows by using + * table.rows. + * onDragStart + * Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the + * table and the row which the user has started to drag. + * onAllowDrop + * Pass a function that will be called as a row is over another row. If the function returns true, allow + * dropping on that row, otherwise not. The function takes 2 parameters: the dragged row and the row under + * the cursor. It returns a boolean: true allows the drop, false doesn't allow it. + * scrollAmount + * This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the + * window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2, + * FF3 beta + * dragHandle + * This is a jQuery mach string for one or more cells in each row that is draggable. If you + * specify this, then you are responsible for setting cursor: move in the CSS and only these cells + * will have the drag behaviour. If you do not specify a dragHandle, then you get the old behaviour where + * the whole row is draggable. + * + * Other ways to control behaviour: + * + * Add class="nodrop" to any rows for which you don't want to allow dropping, and class="nodrag" to any rows + * that you don't want to be draggable. + * + * Inside the onDrop method you can also call $.tableDnD.serialize() this returns a string of the form + * []=&[]= so that you can send this back to the server. The table must have + * an ID as must all the rows. + * + * Other methods: + * + * $("...").tableDnDUpdate() + * Will update all the matching tables, that is it will reapply the mousedown method to the rows (or handle cells). + * This is useful if you have updated the table rows using Ajax and you want to make the table draggable again. + * The table maintains the original configuration (so you don't have to specify it again). + * + * $("...").tableDnDSerialize() + * Will serialize and return the serialized string as above, but for each of the matching tables--so it can be + * called from anywhere and isn't dependent on the currentTable being set up correctly before calling + * + * Known problems: + * - Auto-scoll has some problems with IE7 (it scrolls even when it shouldn't), work-around: set scrollAmount to 0 + * + * Version 0.2: 2008-02-20 First public version + * Version 0.3: 2008-02-07 Added onDragStart option + * Made the scroll amount configurable (default is 5 as before) + * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop classes + * Added onAllowDrop to control dropping + * Fixed a bug which meant that you couldn't set the scroll amount in both directions + * Added serialize method + * Version 0.5: 2008-05-16 Changed so that if you specify a dragHandle class it doesn't make the whole row + * draggable + * Improved the serialize method to use a default (and settable) regular expression. + * Added tableDnDupate() and tableDnDSerialize() to be called when you are outside the table + * Version 0.6: 2011-12-02 Added support for touch devices + * Version 0.7 2012-04-09 Now works with jQuery 1.7 and supports touch, tidied up tabs and spaces + */ +!function ($, window, document, undefined) { +// Determine if this is a touch device +var hasTouch = 'ontouchstart' in document.documentElement, + startEvent = hasTouch ? 'touchstart' : 'mousedown', + moveEvent = hasTouch ? 'touchmove' : 'mousemove', + endEvent = hasTouch ? 'touchend' : 'mouseup'; + +// If we're on a touch device, then wire up the events +// see http://stackoverflow.com/a/8456194/1316086 +hasTouch + && $.each("touchstart touchmove touchend".split(" "), function(i, name) { + $.event.fixHooks[name] = $.event.mouseHooks; + }); + + +$(document).ready(function () { + function parseStyle(css) { + var objMap = {}, + parts = css.match(/([^;:]+)/g) || []; + while (parts.length) + objMap[parts.shift()] = parts.shift().trim(); + + return objMap; + } + $('table').each(function () { + if ($(this).data('table') == 'dnd') { + + $(this).tableDnD({ + onDragStyle: $(this).data('ondragstyle') && parseStyle($(this).data('ondragstyle')) || null, + onDropStyle: $(this).data('ondropstyle') && parseStyle($(this).data('ondropstyle')) || null, + onDragClass: $(this).data('ondragclass') == undefined && "tDnD_whileDrag" || $(this).data('ondragclass'), + onDrop: $(this).data('ondrop') && new Function('table', 'row', $(this).data('ondrop')), // 'return eval("'+$(this).data('ondrop')+'");') || null, + onDragStart: $(this).data('ondragstart') && new Function('table', 'row' ,$(this).data('ondragstart')), // 'return eval("'+$(this).data('ondragstart')+'");') || null, + scrollAmount: $(this).data('scrollamount') || 5, + sensitivity: $(this).data('sensitivity') || 10, + hierarchyLevel: $(this).data('hierarchylevel') || 0, + indentArtifact: $(this).data('indentartifact') || '
 
', + autoWidthAdjust: $(this).data('autowidthadjust') || true, + autoCleanRelations: $(this).data('autocleanrelations') || true, + jsonPretifySeparator: $(this).data('jsonpretifyseparator') || '\t', + serializeRegexp: $(this).data('serializeregexp') && new RegExp($(this).data('serializeregexp')) || /[^\-]*$/, + serializeParamName: $(this).data('serializeparamname') || false, + dragHandle: $(this).data('draghandle') || null + }); + } + + + }); +}); + +window.jQuery.tableDnD = { + /** Keep hold of the current table being dragged */ + currentTable: null, + /** Keep hold of the current drag object if any */ + dragObject: null, + /** The current mouse offset */ + mouseOffset: null, + /** Remember the old value of X and Y so that we don't do too much processing */ + oldX: 0, + oldY: 0, + + /** Actually build the structure */ + build: function(options) { + // Set up the defaults if any + + this.each(function() { + // This is bound to each matching table, set up the defaults and override with user options + this.tableDnDConfig = $.extend({ + onDragStyle: null, + onDropStyle: null, + // Add in the default class for whileDragging + onDragClass: "tDnD_whileDrag", + onDrop: null, + onDragStart: null, + scrollAmount: 5, + /** Sensitivity setting will throttle the trigger rate for movement detection */ + sensitivity: 10, + /** Hierarchy level to support parent child. 0 switches this functionality off */ + hierarchyLevel: 0, + /** The html artifact to prepend the first cell with as indentation */ + indentArtifact: '
 
', + /** Automatically adjust width of first cell */ + autoWidthAdjust: true, + /** Automatic clean-up to ensure relationship integrity */ + autoCleanRelations: true, + /** Specify a number (4) as number of spaces or any indent string for JSON.stringify */ + jsonPretifySeparator: '\t', + /** The regular expression to use to trim row IDs */ + serializeRegexp: /[^\-]*$/, + /** If you want to specify another parameter name instead of the table ID */ + serializeParamName: false, + /** If you give the name of a class here, then only Cells with this class will be draggable */ + dragHandle: null + }, options || {}); + + // Now make the rows draggable + $.tableDnD.makeDraggable(this); + // Prepare hierarchy support + this.tableDnDConfig.hierarchyLevel + && $.tableDnD.makeIndented(this); + }); + + // Don't break the chain + return this; + }, + makeIndented: function (table) { + var config = table.tableDnDConfig, + rows = table.rows, + firstCell = $(rows).first().find('td:first')[0], + indentLevel = 0, + cellWidth = 0, + longestCell, + tableStyle; + + if ($(table).hasClass('indtd')) + return null; + + tableStyle = $(table).addClass('indtd').attr('style'); + $(table).css({whiteSpace: "nowrap"}); + + for (var w = 0; w < rows.length; w++) { + if (cellWidth < $(rows[w]).find('td:first').text().length) { + cellWidth = $(rows[w]).find('td:first').text().length; + longestCell = w; + } + } + $(firstCell).css({width: 'auto'}); + for (w = 0; w < config.hierarchyLevel; w++) + $(rows[longestCell]).find('td:first').prepend(config.indentArtifact); + firstCell && $(firstCell).css({width: firstCell.offsetWidth}); + tableStyle && $(table).css(tableStyle); + + for (w = 0; w < config.hierarchyLevel; w++) + $(rows[longestCell]).find('td:first').children(':first').remove(); + + config.hierarchyLevel + && $(rows).each(function () { + indentLevel = $(this).data('level') || 0; + indentLevel <= config.hierarchyLevel + && $(this).data('level', indentLevel) + || $(this).data('level', 0); + for (var i = 0; i < $(this).data('level'); i++) + $(this).find('td:first').prepend(config.indentArtifact); + }); + + return this; + }, + /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */ + makeDraggable: function(table) { + var config = table.tableDnDConfig; + + config.dragHandle + // We only need to add the event to the specified cells + && $(config.dragHandle, table).each(function() { + // The cell is bound to "this" + $(this).bind(startEvent, function(e) { + $.tableDnD.initialiseDrag($(this).parents('tr')[0], table, this, e, config); + return false; + }); + }) + // For backwards compatibility, we add the event to the whole row + // get all the rows as a wrapped set + || $(table.rows).each(function() { + // Iterate through each row, the row is bound to "this" + if (! $(this).hasClass("nodrag")) { + $(this).bind(startEvent, function(e) { + if (e.target.tagName == "TD") { + $.tableDnD.initialiseDrag(this, table, this, e, config); + return false; + } + }).css("cursor", "move"); // Store the tableDnD object + } + }); + }, + currentOrder: function() { + var rows = this.currentTable.rows; + return $.map(rows, function (val) { + return ($(val).data('level') + val.id).replace(/\s/g, ''); + }).join(''); + }, + initialiseDrag: function(dragObject, table, target, e, config) { + this.dragObject = dragObject; + this.currentTable = table; + this.mouseOffset = this.getMouseOffset(target, e); + this.originalOrder = this.currentOrder(); + + // Now we need to capture the mouse up and mouse move event + // We can use bind so that we don't interfere with other event handlers + $(document) + .bind(moveEvent, this.mousemove) + .bind(endEvent, this.mouseup); + + // Call the onDragStart method if there is one + config.onDragStart + && config.onDragStart(table, target); + }, + updateTables: function() { + this.each(function() { + // this is now bound to each matching table + if (this.tableDnDConfig) + $.tableDnD.makeDraggable(this); + }); + }, + /** Get the mouse coordinates from the event (allowing for browser differences) */ + mouseCoords: function(e) { + if(e.pageX || e.pageY) + return { + x: e.pageX, + y: e.pageY + }; + + return { + x: e.clientX + document.body.scrollLeft - document.body.clientLeft, + y: e.clientY + document.body.scrollTop - document.body.clientTop + }; + }, + /** Given a target element and a mouse eent, get the mouse offset from that element. + To do this we need the element's position and the mouse position */ + getMouseOffset: function(target, e) { + var mousePos, + docPos; + + e = e || window.event; + + docPos = this.getPosition(target); + mousePos = this.mouseCoords(e); + + return { + x: mousePos.x - docPos.x, + y: mousePos.y - docPos.y + }; + }, + /** Get the position of an element by going up the DOM tree and adding up all the offsets */ + getPosition: function(element) { + var left = 0, + top = 0; + + // Safari fix -- thanks to Luis Chato for this! + // Safari 2 doesn't correctly grab the offsetTop of a table row + // this is detailed here: + // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/ + // the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild. + // note that firefox will return a text node as a first child, so designing a more thorough + // solution may need to take that into account, for now this seems to work in firefox, safari, ie + if (element.offsetHeight == 0) + element = element.firstChild; // a table cell + + while (element.offsetParent) { + left += element.offsetLeft; + top += element.offsetTop; + element = element.offsetParent; + } + + left += element.offsetLeft; + top += element.offsetTop; + + return { + x: left, + y: top + }; + }, + autoScroll: function (mousePos) { + var config = this.currentTable.tableDnDConfig, + yOffset = window.pageYOffset, + windowHeight = window.innerHeight + ? window.innerHeight + : document.documentElement.clientHeight + ? document.documentElement.clientHeight + : document.body.clientHeight; + + // Windows version + // yOffset=document.body.scrollTop; + if (document.all) + if (typeof document.compatMode != 'undefined' + && document.compatMode != 'BackCompat') + yOffset = document.documentElement.scrollTop; + else if (typeof document.body != 'undefined') + yOffset = document.body.scrollTop; + + mousePos.y - yOffset < config.scrollAmount + && window.scrollBy(0, - config.scrollAmount) + || windowHeight - (mousePos.y - yOffset) < config.scrollAmount + && window.scrollBy(0, config.scrollAmount); + + }, + moveVerticle: function (moving, currentRow) { + + if (0 != moving.vertical + // If we're over a row then move the dragged row to there so that the user sees the + // effect dynamically + && currentRow + && this.dragObject != currentRow + && this.dragObject.parentNode == currentRow.parentNode) + 0 > moving.vertical + && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow.nextSibling) + || 0 < moving.vertical + && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow); + + }, + moveHorizontal: function (moving, currentRow) { + var config = this.currentTable.tableDnDConfig, + currentLevel; + + if (!config.hierarchyLevel + || 0 == moving.horizontal + // We only care if moving left or right on the current row + || !currentRow + || this.dragObject != currentRow) + return null; + + currentLevel = $(currentRow).data('level'); + + 0 < moving.horizontal + && currentLevel > 0 + && $(currentRow).find('td:first').children(':first').remove() + && $(currentRow).data('level', --currentLevel); + + 0 > moving.horizontal + && currentLevel < config.hierarchyLevel + && $(currentRow).prev().data('level') >= currentLevel + && $(currentRow).children(':first').prepend(config.indentArtifact) + && $(currentRow).data('level', ++currentLevel); + + }, + mousemove: function(e) { + var dragObj = $($.tableDnD.dragObject), + config = $.tableDnD.currentTable.tableDnDConfig, + currentRow, + mousePos, + moving, + x, + y; + + e && e.preventDefault(); + + if (!$.tableDnD.dragObject) + return false; + + // prevent touch device screen scrolling + e.type == 'touchmove' + && event.preventDefault(); // TODO verify this is event and not really e + + // update the style to show we're dragging + config.onDragClass + && dragObj.addClass(config.onDragClass) + || dragObj.css(config.onDragStyle); + + mousePos = $.tableDnD.mouseCoords(e); + x = mousePos.x - $.tableDnD.mouseOffset.x; + y = mousePos.y - $.tableDnD.mouseOffset.y; + + // auto scroll the window + $.tableDnD.autoScroll(mousePos); + + currentRow = $.tableDnD.findDropTargetRow(dragObj, y); + moving = $.tableDnD.findDragDirection(x, y); + + $.tableDnD.moveVerticle(moving, currentRow); + $.tableDnD.moveHorizontal(moving, currentRow); + + return false; + }, + findDragDirection: function (x,y) { + var sensitivity = this.currentTable.tableDnDConfig.sensitivity, + oldX = this.oldX, + oldY = this.oldY, + xMin = oldX - sensitivity, + xMax = oldX + sensitivity, + yMin = oldY - sensitivity, + yMax = oldY + sensitivity, + moving = { + horizontal: x >= xMin && x <= xMax ? 0 : x > oldX ? -1 : 1, + vertical : y >= yMin && y <= yMax ? 0 : y > oldY ? -1 : 1 + }; + + // update the old value + if (moving.horizontal != 0) + this.oldX = x; + if (moving.vertical != 0) + this.oldY = y; + + return moving; + }, + /** We're only worried about the y position really, because we can only move rows up and down */ + findDropTargetRow: function(draggedRow, y) { + var rowHeight = 0, + rows = this.currentTable.rows, + config = this.currentTable.tableDnDConfig, + rowY = 0, + row = null; + + for (var i = 0; i < rows.length; i++) { + row = rows[i]; + rowY = this.getPosition(row).y; + rowHeight = parseInt(row.offsetHeight) / 2; + if (row.offsetHeight == 0) { + rowY = this.getPosition(row.firstChild).y; + rowHeight = parseInt(row.firstChild.offsetHeight) / 2; + } + // Because we always have to insert before, we need to offset the height a bit + if (y > (rowY - rowHeight) && y < (rowY + rowHeight)) + // that's the row we're over + // If it's the same as the current row, ignore it + if (row == draggedRow + || (config.onAllowDrop + && !config.onAllowDrop(draggedRow, row)) + // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic) + || $(row).hasClass("nodrop")) + return null; + else + return row; + } + return null; + }, + processMouseup: function() { + var config = this.currentTable.tableDnDConfig, + droppedRow = this.dragObject, + parentLevel = 0, + myLevel = 0; + + if (!this.currentTable || !droppedRow) + return null; + + // Unbind the event handlers + $(document) + .unbind(moveEvent, this.mousemove) + .unbind(endEvent, this.mouseup); + + config.hierarchyLevel + && config.autoCleanRelations + && $(this.currentTable.rows).first().find('td:first').children().each(function () { + myLevel = $(this).parents('tr:first').data('level'); + myLevel + && $(this).parents('tr:first').data('level', --myLevel) + && $(this).remove(); + }) + && config.hierarchyLevel > 1 + && $(this.currentTable.rows).each(function () { + myLevel = $(this).data('level'); + if (myLevel > 1) { + parentLevel = $(this).prev().data('level'); + while (myLevel > parentLevel + 1) { + $(this).find('td:first').children(':first').remove(); + $(this).data('level', --myLevel); + } + } + }); + + // If we have a dragObject, then we need to release it, + // The row will already have been moved to the right place so we just reset stuff + config.onDragClass + && $(droppedRow).removeClass(config.onDragClass) + || $(droppedRow).css(config.onDropStyle); + + this.dragObject = null; + // Call the onDrop method if there is one + config.onDrop + && this.originalOrder != this.currentOrder() + && $(droppedRow).hide().fadeIn('fast') + && config.onDrop(this.currentTable, droppedRow); + + this.currentTable = null; // let go of the table too + }, + mouseup: function(e) { + e && e.preventDefault(); + $.tableDnD.processMouseup(); + return false; + }, + jsonize: function(pretify) { + var table = this.currentTable; + if (pretify) + return JSON.stringify( + this.tableData(table), + null, + table.tableDnDConfig.jsonPretifySeparator + ); + return JSON.stringify(this.tableData(table)); + }, + serialize: function() { + return $.param(this.tableData(this.currentTable)); + }, + serializeTable: function(table) { + var result = ""; + var paramName = table.tableDnDConfig.serializeParamName || table.id; + var rows = table.rows; + for (var i=0; i 0) result += "&"; + var rowId = rows[i].id; + if (rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) { + rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0]; + result += paramName + '[]=' + rowId; + } + } + return result; + }, + serializeTables: function() { + var result = []; + $('table').each(function() { + this.id && result.push($.param(this.tableData(this))); + }); + return result.join('&'); + }, + tableData: function (table) { + var config = table.tableDnDConfig, + previousIDs = [], + currentLevel = 0, + indentLevel = 0, + rowID = null, + data = {}, + getSerializeRegexp, + paramName, + currentID, + rows; + + if (!table) + table = this.currentTable; + if (!table || !table.id || !table.rows || !table.rows.length) + return {error: { code: 500, message: "Not a valid table, no serializable unique id provided."}}; + + rows = config.autoCleanRelations + && table.rows + || $.makeArray(table.rows); + paramName = config.serializeParamName || table.id; + currentID = paramName; + + getSerializeRegexp = function (rowId) { + if (rowId && config && config.serializeRegexp) + return rowId.match(config.serializeRegexp)[0]; + return rowId; + }; + + data[currentID] = []; + !config.autoCleanRelations + && $(rows[0]).data('level') + && rows.unshift({id: 'undefined'}); + + + + for (var i=0; i < rows.length; i++) { + if (config.hierarchyLevel) { + indentLevel = $(rows[i]).data('level') || 0; + if (indentLevel == 0) { + currentID = paramName; + previousIDs = []; + } + else if (indentLevel > currentLevel) { + previousIDs.push([currentID, currentLevel]); + currentID = getSerializeRegexp(rows[i-1].id); + } + else if (indentLevel < currentLevel) { + for (var h = 0; h < previousIDs.length; h++) { + if (previousIDs[h][1] == indentLevel) + currentID = previousIDs[h][0]; + if (previousIDs[h][1] >= currentLevel) + previousIDs[h][1] = 0; + } + } + currentLevel = indentLevel; + + if (!$.isArray(data[currentID])) + data[currentID] = []; + rowID = getSerializeRegexp(rows[i].id); + rowID && data[currentID].push(rowID); + } + else { + rowID = getSerializeRegexp(rows[i].id); + rowID && data[currentID].push(rowID); + } + } + return data; + } +}; + +window.jQuery.fn.extend( + { + tableDnD : $.tableDnD.build, + tableDnDUpdate : $.tableDnD.updateTables, + tableDnDSerialize : $.proxy($.tableDnD.serialize, $.tableDnD), + tableDnDSerializeAll : $.tableDnD.serializeTables, + tableDnDData : $.proxy($.tableDnD.tableData, $.tableDnD) + } +); + +}(window.jQuery, window, window.document); diff --git a/TableDnD/server/ajaxJSONTest.php b/TableDnD/server/ajaxJSONTest.php new file mode 100644 index 0000000..60b2a63 --- /dev/null +++ b/TableDnD/server/ajaxJSONTest.php @@ -0,0 +1,13 @@ +The server says: your row order was
+"; + if (isset($result["$value"])) + show_results($result, $value, $indent.implode(' ', array_fill(0, 12, ''))); + } +} +?> +See the PHP Source
diff --git a/TableDnD/server/ajaxJSONTest_php.html b/TableDnD/server/ajaxJSONTest_php.html new file mode 100644 index 0000000..cc648a7 --- /dev/null +++ b/TableDnD/server/ajaxJSONTest_php.html @@ -0,0 +1,3 @@ + +The server says: your row order was<br/>
<?php
$result 
json_decode(file_get_contents('php://input'), true);
show_results($result"table-7");
function 
show_results($result$id$indent null) {
    foreach(
$result[$id] as $value) {
        echo 
"$indent$value<br/>";
        if (isset(
$result["$value"]))
            
show_results($result$value$indent.implode('&nbsp;'array_fill(012'')));
    }
}
?>
See the <a href="server/ajaxJSONTest_php.html" target="_BLANK">PHP Source</a><br/>
+
\ No newline at end of file diff --git a/TableDnD/server/ajaxTest.php b/TableDnD/server/ajaxTest.php new file mode 100644 index 0000000..6bf4f45 --- /dev/null +++ b/TableDnD/server/ajaxTest.php @@ -0,0 +1,8 @@ +The server says: your row order was
+"; +} +?> +See the PHP Source
\ No newline at end of file diff --git a/TableDnD/server/ajaxTest_php.html b/TableDnD/server/ajaxTest_php.html new file mode 100644 index 0000000..e3b2306 --- /dev/null +++ b/TableDnD/server/ajaxTest_php.html @@ -0,0 +1,4 @@ + + +The server says: your row order was<br/>
<?php
$result 
$_REQUEST["table-3"];
foreach(
$result as $value) {
    echo 
"$value<br/>";
}
?>
See the <a href="server/ajaxTest_php.html" target="_BLANK">PHP Source</a><br/>
+
diff --git a/TableDnD/serverExample.html b/TableDnD/serverExample.html new file mode 100644 index 0000000..72ca39f --- /dev/null +++ b/TableDnD/serverExample.html @@ -0,0 +1,39 @@ + + + + + TableDnD Server Example + + + + + + + + + + + + + +
1Onesome text
2Twosome text
3Threesome text
4Foursome text
5Fivesome text
6Sixsome text
+ +
+ + // TODO serialise doesn't work very well with a form does it!!! + +

+
+ + + + + diff --git a/TableDnD/stable/jquery.tablednd.js b/TableDnD/stable/jquery.tablednd.js new file mode 100644 index 0000000..6cf3a85 --- /dev/null +++ b/TableDnD/stable/jquery.tablednd.js @@ -0,0 +1,314 @@ +/** + * TableDnD plug-in for JQuery, allows you to drag and drop table rows + * You can set up various options to control how the system will work + * Copyright © Denis Howlett + * Licensed like jQuery, see http://docs.jquery.com/License. + * + * Configuration options: + * + * onDragStyle + * This is the style that is assigned to the row during drag. There are limitations to the styles that can be + * associated with a row (such as you can't assign a border—well you can, but it won't be + * displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as + * a map (as used in the jQuery css(...) function). + * onDropStyle + * This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations + * to what you can do. Also this replaces the original style, so again consider using onDragClass which + * is simply added and then removed on drop. + * onDragClass + * This class is added for the duration of the drag and then removed when the row is dropped. It is more + * flexible than using onDragStyle since it can be inherited by the row cells and other content. The default + * is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your + * stylesheet. + * onDrop + * Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table + * and the row that was dropped. You can work out the new order of the rows by using + * table.rows. + * onDragStart + * Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the + * table and the row which the user has started to drag. + * onAllowDrop + * Pass a function that will be called as a row is over another row. If the function returns true, allow + * dropping on that row, otherwise not. The function takes 2 parameters: the dragged row and the row under + * the cursor. It returns a boolean: true allows the drop, false doesn't allow it. + * scrollAmount + * This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the + * window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2, + * FF3 beta) + * + * Other ways to control behaviour: + * + * Add class="nodrop" to any rows for which you don't want to allow dropping, and class="nodrag" to any rows + * that you don't want to be draggable. + * + * Inside the onDrop method you can also call $.tableDnD.serialize() this returns a string of the form + * []=&[]= so that you can send this back to the server. The table must have + * an ID as must all the rows. + * + * Known problems: + * - Auto-scoll has some problems with IE7 (it scrolls even when it shouldn't), work-around: set scrollAmount to 0 + * + * Version 0.2: 2008-02-20 First public version + * Version 0.3: 2008-02-07 Added onDragStart option + * Made the scroll amount configurable (default is 5 as before) + * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop classes + * Added onAllowDrop to control dropping + * Fixed a bug which meant that you couldn't set the scroll amount in both directions + * Added serialise method + */ +jQuery.tableDnD = { + /** Keep hold of the current table being dragged */ + currentTable : null, + /** Keep hold of the current drag object if any */ + dragObject: null, + /** The current mouse offset */ + mouseOffset: null, + /** Remember the old value of Y so that we don't do too much processing */ + oldY: 0, + + /** Actually build the structure */ + build: function(options) { + // Make sure options exists + options = options || {}; + // Set up the defaults if any + + this.each(function() { + // Remember the options + this.tableDnDConfig = { + onDragStyle: options.onDragStyle, + onDropStyle: options.onDropStyle, + // Add in the default class for whileDragging + onDragClass: options.onDragClass ? options.onDragClass : "tDnD_whileDrag", + onDrop: options.onDrop, + onDragStart: options.onDragStart, + scrollAmount: options.scrollAmount ? options.scrollAmount : 5 + }; + // Now make the rows draggable + jQuery.tableDnD.makeDraggable(this); + }); + + // Now we need to capture the mouse up and mouse move event + // We can use bind so that we don't interfere with other event handlers + jQuery(document) + .bind('mousemove', jQuery.tableDnD.mousemove) + .bind('mouseup', jQuery.tableDnD.mouseup); + + // Don't break the chain + return this; + }, + + /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */ + makeDraggable: function(table) { + // Now initialise the rows + var rows = table.rows; //getElementsByTagName("tr") + var config = table.tableDnDConfig; + for (var i=0; i jQuery.tableDnD.oldY; + // update the old value + jQuery.tableDnD.oldY = y; + // update the style to show we're dragging + if (config.onDragClass) { + dragObj.addClass(config.onDragClass); + } else { + dragObj.css(config.onDragStyle); + } + // If we're over a row then move the dragged row to there so that the user sees the + // effect dynamically + var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y); + if (currentRow) { + // TODO worry about what happens when there are multiple TBODIES + if (movingDown && jQuery.tableDnD.dragObject != currentRow) { + jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling); + } else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) { + jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow); + } + } + } + + return false; + }, + + /** We're only worried about the y position really, because we can only move rows up and down */ + findDropTargetRow: function(draggedRow, y) { + var rows = jQuery.tableDnD.currentTable.rows; + for (var i=0; i rowY - rowHeight) && (y < (rowY + rowHeight))) { + // that's the row we're over + // If it's the same as the current row, ignore it + if (row == draggedRow) {return null;} + var config = jQuery.tableDnD.currentTable.tableDnDConfig; + if (config.onAllowDrop) { + if (config.onAllowDrop(draggedRow, row)) { + return row; + } else { + return null; + } + } else { + // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic) + var nodrop = $(row).hasClass("nodrop"); + if (! nodrop) { + return row; + } else { + return null; + } + } + return row; + } + } + return null; + }, + + mouseup: function(e) { + if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) { + var droppedRow = jQuery.tableDnD.dragObject; + var config = jQuery.tableDnD.currentTable.tableDnDConfig; + // If we have a dragObject, then we need to release it, + // The row will already have been moved to the right place so we just reset stuff + if (config.onDragClass) { + jQuery(droppedRow).removeClass(config.onDragClass); + } else { + jQuery(droppedRow).css(config.onDropStyle); + } + jQuery.tableDnD.dragObject = null; + if (config.onDrop) { + // Call the onDrop method if there is one + config.onDrop(jQuery.tableDnD.currentTable, droppedRow); + } + jQuery.tableDnD.currentTable = null; // let go of the table too + } + }, + + serialize: function() { + if (jQuery.tableDnD.currentTable) { + var result = ""; + var tableId = jQuery.tableDnD.currentTable.id; + var rows = jQuery.tableDnD.currentTable.rows; + for (var i=0; i 0) result += "&"; + result += tableId + '[]=' + rows[i].id; + } + return result; + } else { + return "Error: No Table id set, you need to set an id on your table and every row"; + } + } +} + +jQuery.fn.extend( + { + tableDnD : jQuery.tableDnD.build + } +); \ No newline at end of file diff --git a/TableDnD/stripe.html b/TableDnD/stripe.html new file mode 100644 index 0000000..1bc5cc2 --- /dev/null +++ b/TableDnD/stripe.html @@ -0,0 +1,294 @@ + + + + Table Drag and Drop jQuery plugin + + + +
+

Table Drag and Drop jQuery plugin

+

This page contains documentation and tests for the TableDnD jQuery plug-in. For more information and +to post comments, please go to isocra.com. +

+

If you have issues or bug reports, then you can post them at the TableDnD plug page +at plugins.jquery.com

+ +

How do I use it?

+
    +
  1. Download Download jQuery (version 1.2 or above), then the TableDnD plugin (current version 0.4).
  2. +
  3. Reference both scripts in your HTML page in the normal way.
  4. +
  5. In true jQuery style, the typical way to initialise the tabes is in the $(document).ready function. Use a selector to select your table and then call tableDnD(). You can optionally specify a set of properties (described below).
  6. +
+
+
+ + + + + + + +
1Onesome text
2Twosome text
3Threesome text
4Foursome text
5Fivesome text
6Sixsome text
+
+

The HTML for the table is very straight forward (no Javascript, pure HTML):

+ +
+<table id="table-1" cellspacing="0" cellpadding="2">
+    <tr id="1"><td>1</td><td>One</td><td>some text</td></tr>
+    <tr id="2"><td>2</td><td>Two</td><td>some text</td></tr>
+    <tr id="3"><td>3</td><td>Three</td><td>some text</td></tr>
+    <tr id="4"><td>4</td><td>Four</td><td>some text</td></tr>
+    <tr id="5"><td>5</td><td>Five</td><td>some text</td></tr>
+    <tr id="6"><td>6</td><td>Six</td><td>some text</td></tr>
+</table>
+
+

To add in the "draggability" all we need to do is add a line to the $(document).ready(...) function +as follows:

+
+<script type="text/javascript">
+$(document).ready(function() {
+    // Initialise the table
+    $("#table-1").tableDnD();
+});
+</script>
+
+

In the example above we're not setting any parameters at all so we get the default settings. There are a number + of parameters you can set in order to control the look and feel of the table and also to add custom behaviour + on drag or on drop. The parameters are specified as a map in the usual way and are described below:

+ +
+
onDragStyle
+
This is the style that is assigned to the row during drag. There are limitations to the styles that can be + associated with a row (such as you can't assign a border—well you can, but it won't be + displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as + a map (as used in the jQuery css(...) function).
+
onDropStyle
+
This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations + to what you can do. Also this replaces the original style, so again consider using onDragClass which + is simply added and then removed on drop.
+
onDragClass
+
This class is added for the duration of the drag and then removed when the row is dropped. It is more + flexible than using onDragStyle since it can be inherited by the row cells and other content. The default + is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your + stylesheet.
+
onDrop
+
Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table + and the row that was dropped. You can work out the new order of the rows by using + table.tBodies[0].rows.
+
onDragStart
+
Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the + table and the row which the user has started to drag.
+
scrollAmount
+
This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the + window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2, + FF3 beta)
+
+

This second table has has an onDrop function applied as well as an onDragClass. The javascript to set this up is +as follows:

+
+$(document).ready(function() {
+
+	// Initialise the first table (as before)
+	$("#table-1").tableDnD();
+
+	// Make a nice striped effect on the table
+	$("#table-2 tr:even').addClass('alt')");
+
+	// Initialise the second table specifying a dragClass and an onDrop function that will display an alert
+	$("#table-2").tableDnD({
+	    onDragClass: "myDragClass",
+	    onDrop: function(table, row) {
+            var rows = table.tBodies[0].rows;
+            var debugStr = "Row dropped was "+row.id+". New order: ";
+            for (var i=0; i<rows.length; i++) {
+                debugStr += rows[i].id+" ";
+            }
+	        $(#debugArea).html(debugStr);
+	    },
+		onDragStart: function(table, row) {
+			$(#debugArea).html("Started dragging row "+row.id);
+		}
+	});
+});
+
+
+
 
+ + + + + + + + + + + + + + + +
1One
2Two
3Three
4Four
5Five
6Six
7Seven
8Eight
9Nine
10Ten
11Eleven
12Twelve
13Thirteen
14Fourteen
+
+

What to do afterwards?

+

Generally once the user has dropped a row, you need to inform the server of the new order. To do this, we've + added a method called serialise(). It takes no parameters but knows the current table from the + context. The method returns a string of the form tableId[]=rowId1&tableId[]=rowId2&tableId[]=rowId3... + You can then use this as part of an Ajax load. +

+

This third table demonstrates calling the serialise function inside onDrop (as shown below). It also + demonstrates the "nodrop" class on row 3 and "nodrag" class on row 5, so you can't pick up row 5 and + you can't drop any row on row 3 (but you can drag it).

+
+    $('#table-3').tableDnD({
+        onDrop: function(table, row) {
+            alert($.tableDnD.serialize());
+        }
+    });
+
+
+
+

Ajax result

+

Drag and drop in this table to test out serialise and using JQuery.load()

+
+ + + + + + + +
1One
2Two
3Three (Can't drop on this row)
4Four
5Five (Can't drag this row)
6Six
+
+

This table has multiple TBODYs. The functionality isn't quite working properly. You can only drag the rows inside their +own TBODY, you can't drag them outside it. Now this might or might not be what you want, but unfortunately if you then drop a row outside its TBODY you get a Javascript error because inserting after a sibling doesn't work. This will be fixed in the next version. The header rows all have the classes "nodrop" and "nodrag" so that they can't be dragged or dropped on.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
H1H2H3
4.1One
4.2Two
4.3Three
4.4Four
4.5Five
4.6Six
H1H2H3
5.1One
5.2Two
5.3Three
5.4Four
5.5Five
5.6Six
H1H2H3
6.1One
6.2Two
6.3Three
6.4Four
6.5Five
6.6Six
+
+

+The following table demonstrates the use of the default regular expression. The rows have IDs of the +form table5-row-1, table5-row-2, etc., but the regular expression is /[^\-]*$/ (this is the same +as used in the NestedSortable plugin for consistency). +This removes everything before and including the last hyphen, so the serialised string just has 1, 2, 3 etc. +You can replace the regular expression by setting the serializeRegexp option, you can also just set it +to null to stop this behaviour. +

+
+    $('#table-5').tableDnD({
+        onDrop: function(table, row) {
+            alert($.tableDnD.serialize());
+        },
+        dragHandle: "dragHandle"
+    });
+
+
+ + + + + + + +
 1Onesome text
 2Twosome text
 3Threesome text
 4Foursome text
 5Fivesome text
 6Sixsome text
+
+

In fact you will notice that I have also set the dragHandle on this table. This has two effects: firstly only +the cell with the drag handle class is draggable and secondly it doesn't automatically add the cursor: move +style to the row (or the drag handle cell), so you are responsible for setting up the style as you see fit.

+

Here I've actually added an extra effect which adds a background image to the first cell in the row whenever +you enter it using the jQuery hover function as follows:

+
+    $("#table-5 tr").hover(function() {
+          $(this.cells[0]).addClass('showDragHandle');
+    }, function() {
+          $(this.cells[0]).removeClass('showDragHandle');
+    });
+
+

This provides a better visualisation of what you can do to the row and where you need to go to drag it (I hope).

+

Version History

+ + + + + +
0.22008-02-20First public release
0.32008-02-27Added onDragStart option
Made the scroll amount configurable (default is 5 as before)
0.42008-03-28Fixed the scrollAmount so that if you set this to zero then it switches off this functionality
Fixed the auto-scrolling in IE6 thanks to Phil
Changed the NoDrop attribute to the class "nodrop" (so any row with this class won't allow dropping)
Changed the NoDrag attribute to the class "nodrag" (so any row with this class can't be dragged)
Added support for multiple TBODYs--though it's still not perfect
Added onAllowDrop to allow the developer to customise this behaviour
Added a serialize() method to return the order of the rows in a form suitable for POSTing back to the server
0.52008-06-04Changed so that if you specify a dragHandle class it doesn't make the whole row
draggable
Improved the serialize method to use a default (and settable) regular expression.
Added tableDnDupate() and tableDnDSerialize() to be called when you are outside the table
+
+ + + + + \ No newline at end of file diff --git a/TableDnD/tablednd.css b/TableDnD/tablednd.css new file mode 100644 index 0000000..c25b079 --- /dev/null +++ b/TableDnD/tablednd.css @@ -0,0 +1,340 @@ +body { + color:#333333; + font-family:'Lucida Grande',Verdana,Arial,Sans-Serif; + font-size: 80%; + margin: 1em 9em; + background-color: #F0F0F0; + line-height: 1.4em; +} + +p { + padding: 10px; + width: 60%; +} + +h1 { + color: #114477; +} + +pre.prettyprint { + margin-bottom: 20px; + padding-top: 15px; +} +.prettyprint { + background-color: #F7F7F9; +} +code, pre { + display: block; + margin: 0 0 10px; + line-height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +pre { + word-break: break-all; + word-wrap: break-word; + white-space: pre; + font-weight:normal; +} + +pre, code { + color:#264A94; + font-family:Monaco,Lucida Console,monospace; + font-size:90%; +} + +div#page { + background-color: white; + padding: 1em; +} + +.tableDemo { + background-color: white; + border: 1px solid #666699; + margin-right: 10px; + padding: 6px; +} + +.tableDemo table { + border: 1px solid silver; +} + +.tableDemo td { + padding: 2px 6px +} + +.tableDemo th { + color: white; + text-shadow: 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A, 0 0 18px #29215A; + border-bottom: 8px double #29215A; + padding: 10px; +} + +#table-2 th { + background-color: #29215A; + color: white; +} + +#table-2 td, th { + padding-right: 8px; +} + +.category td { + background-color: #E4EBF3; +} + +table { + /*position: relative;*/ + border-collapse: separate; + border-spacing: 0; +} +tr { + /*position: relative;*/ + + /*display: block;*/ +} + +.tDnD_whileDrag { + /*z-index: 500;*/ + /*width: 90%;*/ + /*margin: -10px;*/ + /*display: table-cell;*/ + /*color: transparent;*/ + /*width: 0px*/ +} +.tDnD_whileDrag td { + background-color: #eee; + /*-webkit-box-shadow: 11px 5px 12px 2px #333, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;*/ + -webkit-box-shadow: 6px 3px 5px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset; + /*-moz-box-shadow: 6px 4px 5px 1px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;*/ + /*-box-shadow: 6px 4px 5px 1px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;*/ +} +/*.tDnD_whileDrag td:first-child {*/ + /*-webkit-box-shadow: 5px 4px 5px 1px #111, 0 1px 0 #ccc inset, 1px -1px 0 #ccc inset;*/ + /*-moz-box-shadow: 6px 3px 5px 2px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset, 1px 0 0 #ccc inset;*/ + /*-box-shadow: 6px 3px 5px 2px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset, 1px 0 0 #ccc inset;*/ +/*}*/ +.tDnD_whileDrag td:last-child { + /*-webkit-box-shadow: 8px 7px 12px 0 #333, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;*/ + -webkit-box-shadow: 1px 8px 6px -4px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset; + /*-moz-box-shadow: 0 9px 4px -4px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset, -1px 0 0 #ccc inset;*/ + /*-box-shadow: 0 9px 4px -4px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset, -1px 0 0 #ccc inset;*/ +} + /*-webkit-box-shadow: 0 0 40px white, 10px 10px 15px black;*/ + /*-moz-box-shadow: 0 4px 2px -3px rgba(0, 0, 0, 0.5) inset;*/ + /*-webkit-box-shadow: 0px 0px 40px 1px white, 0px 1px 1px 1px black;*/ + /*box-shadow: 0 4px 2px -3px rgba(0, 0, 0, 0.5) inset;*/ +/*}*/ +/*.tDnD_whileDrag {*/ + /*background-color: black;*/ + /*position: relative;*/ + /*display: block;*/ + /*-moz-box-shadow: 0px 0px 40px 1px white, 0px 1px 1px 1px black;*/ + /*-webkit-box-shadow: 0px 0px 40px 1px white, 0px 1px 1px 1px black;*/ + /*box-shadow: 0px 0px 40px 1px white, 0px 1px 1px 1px black ;*/ + /*-webkit-box-shadow: 0 15px 10px -10px black, 0 1px 4px black, 0 0 10px darkgray inset;*/ + /*-webkit-box-shadow: 0 15px 10px -10px black, 0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;*/ + /*-moz-box-shadow: 0 15px 10px -10px rgba(0, 0, 0, 0.5), 0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;*/ + /*box-shadow: 0 15px 10px -10px rgba(0, 0, 0, 0.5), 0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;*/ + /*width: 90%;*/ + /*height: 90%;*/ + /*width: 100%;*/ + /*overflow: visible;*/ + /*z-index: 10000;*/ + /*opacity: .4;*/ + /*border-collapse: separate;*/ + /*filter:Alpha(Opacity=50);*/ + /*width: auto;*/ + +/*}*/ + +/*tr.alt td {*/ + /*width:200px*/ + /*-ms-transform:rotate(1deg;*/ + /*-moz-transform: rotate(0deg);*/ + /*-webkit-transform:rotate(0deg);*/ + /*-o-transform:rotate(0deg);*/ +/*}*/ + +tr.alt td { + background-color: #ecf6fc; + padding-top: 5px; + padding-bottom: 5px; +} + +td { + padding-top: 5px; + padding-bottom: 5px; + white-space: nowrap; +} + +tr.myDragClass td { + /*position: fixed;*/ + color: yellow; + text-shadow: 0 0 10px black, 0 0 10px black, 0 0 8px black, 0 0 6px black, 0 0 6px black; + background-color: #999; + -webkit-box-shadow: 0 12px 14px -12px #111 inset, 0 -2px 2px -1px #333 inset; +} +tr.myDragClass td:first-child { + -webkit-box-shadow: 0 12px 14px -12px #111 inset, 12px 0 14px -12px #111 inset, 0 -2px 2px -1px #333 inset; +} +tr.myDragClass td:last-child { + -webkit-box-shadow: 0 12px 14px -12px #111 inset, -12px 0 14px -12px #111 inset, 0 -2px 2px -1px #333 inset; +} + +#table-2 { + margin: 0 0 1em 0; padding: 0 +} +tr.nodrop td { + border-bottom: 1px solid #00bb00; + color: #00bb00; +} +tr.nodrag td { + border-bottom: 1px solid #FF6600; + color: #FF6600; +} +div.result { + background-color: #F7F7F9; +} + +tr.alt tr:after, .group:after { + visibility: hidden; + display: block; + content: ""; + clear: both; + height: 0; +} + +table input, +tr td input, +tr input, +tr.myDragClass input, +tbody tr td input { + z-index: -10; + text-align: right; + float: right; + height: 12px; + margin-top: 5px; + margin-bottom: 5px; +} + +td.dragHandle { + +} + +td.showDragHandle { + background-image: url(images/updown2.gif); + background-repeat: no-repeat; + background-position: center center; + cursor: move; +} + +.versionHistory td { + vertical-align: top; + padding: 0.3em; + white-space: normal; +} + +div.indent { + width: 30px; + float: left; +} + +#sprintlist_table th { + color: white; + /*border-style: ;*/ + text-shadow: 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600, 0 0 18px #FF6600; + border-bottom: 14px double #FF6600; + padding: 10px; +} +.sprintlist-drag td.small_buttons div button { + /*font-size: xx-small;*/ + /*height: 18px;*/ + /*color: #333;*/ + /*cursor: pointer;*/ + /*background-color: #f4a460;*/ + box-shadow:0px 2px 3px black inset; + -moz-box-shadow:0px 2px 3px black inset; + -webkit-box-shadow:0px 2px 3px black inset; + /*border: 0px;*/ + /*background-color: #ccc;*/ +} +.sprintlist-drag td.small_buttons div button:first-child { + box-shadow:2px 2px 3px black inset; + -moz-box-shadow:2px 2px 3px black inset; + -webkit-box-shadow:2px 2px 3px black inset; + /*border: 0px;*/ + /*background-color: #ccc;*/ +} +.sprintlist-drag td { + background-color: #f4a460; + /*-webkit-box-shadow: 11px 5px 12px 2px #333, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;*/ + -webkit-box-shadow: 6px 3px 5px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset; + /*-moz-box-shadow: 6px 4px 5px 1px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;*/ + /*-box-shadow: 6px 4px 5px 1px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;*/ +} +.sprintlist-drag td:last-child { + /*-webkit-box-shadow: 8px 7px 12px 0 #333, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;*/ + -webkit-box-shadow: 1px 8px 6px -4px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset; +/*-moz-box-shadow: 0 9px 4px -4px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset, -1px 0 0 #ccc inset;*/ +} +tr.group_heading td eojq + border-bottom: 8px double #FF6600; + color: #FF6600; + font-size: larger; + font-weight: bolder; +} + +td.small_buttons div { + display: inline-block; + position: relative; +} +#table-6 tr { + background-color: red; + z-index: -1000; + /*background-color:rgb(165, 182, 229);*/ + display: block; + margin-bottom: 5px; + /*box-shadow:0 0 0 black;*/ + /*-moz-box-shadow:0 0 0 black,;*/ + /*-webkit-box-shadow:0 0 0 black;*/ +} +#table-6 td { + padding:5px; + /*text-align:left;*/ +} + +#table-7 { + border: #000000 solid 1px; +} + +td.small_buttons div button { + font-size: xx-small; + height: 18px; + color: #333; + cursor: pointer; + background-color: whiteSmoke; +} + +td.small_buttons div button:first-child +{ + margin-left: 0; + -webkit-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-topleft: 4px; +} + +td.small_buttons div button:last-child +{ + margin-left: -2px; + -webkit-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -moz-border-radius-bottomright: 4px; + -moz-border-radius-topright: 4px; +} diff --git a/hincludes/lib/Date.class.php b/hincludes/lib/Date.class.php new file mode 100644 index 0000000..44f54e5 --- /dev/null +++ b/hincludes/lib/Date.class.php @@ -0,0 +1,67 @@ +__t = mktime($hour, $min, $sec, $month + 1, $day, $year); + }} + public function toString() { + return date("Y-m-d H:i:s", $this->__t); + } + public function getDay() { + return intval(date("w", $this->__t)); + } + public function getSeconds() { + return intval(date("s", $this->__t)); + } + public function getMinutes() { + return intval(date("i", $this->__t)); + } + public function getHours() { + return intval(date("G", $this->__t)); + } + public function getDate() { + return intval(date("j", $this->__t)); + } + public function getMonth() { + $m = intval(date("n", $this->__t)); + return -1 + $m; + } + public function getFullYear() { + return intval(date("Y", $this->__t)); + } + public function getPhpTime() { + return $this->__t; + } + public function getTime() { + return $this->__t * 1000; + } + public $__t; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + static function now() { + return Date::fromPhpTime(round(microtime(true), 3)); + } + static function fromPhpTime($t) { + $d = new Date(2000, 1, 1, 0, 0, 0); + $d->__t = $t; + return $d; + } + static function fromTime($t) { + $d = new Date(2000, 1, 1, 0, 0, 0); + $d->__t = $t / 1000; + return $d; + } + static function fromString($s) { + return Date::fromPhpTime(strtotime($s)); + } + function __toString() { return $this->toString(); } +} diff --git a/hincludes/lib/DateTools.class.php b/hincludes/lib/DateTools.class.php new file mode 100644 index 0000000..14f83fc --- /dev/null +++ b/hincludes/lib/DateTools.class.php @@ -0,0 +1,44 @@ +__t); + } + static function delta($d, $t) { + return Date::fromTime($d->getTime() + $t); + } + static $DAYS_OF_MONTH; + static function getMonthDays($d) { + $month = $d->getMonth(); + $year = $d->getFullYear(); + if($month !== 1) { + return DateTools::$DAYS_OF_MONTH[$month]; + } + $isB = _hx_mod($year, 4) === 0 && _hx_mod($year, 100) !== 0 || _hx_mod($year, 400) === 0; + return (($isB) ? 29 : 28); + } + static function seconds($n) { + return $n * 1000.0; + } + static function minutes($n) { + return $n * 60.0 * 1000.0; + } + static function hours($n) { + return $n * 60.0 * 60.0 * 1000.0; + } + static function days($n) { + return $n * 24.0 * 60.0 * 60.0 * 1000.0; + } + static function parse($t) { + $s = $t / 1000; + $m = $s / 60; + $h = $m / 60; + return _hx_anonymous(array("ms" => _hx_mod($t, 1000), "seconds" => intval(_hx_mod($s, 60)), "minutes" => intval(_hx_mod($m, 60)), "hours" => intval(_hx_mod($h, 24)), "days" => intval($h / 24))); + } + static function make($o) { + return $o->ms + 1000.0 * ($o->seconds + 60.0 * ($o->minutes + 60.0 * ($o->hours + 24.0 * $o->days))); + } + function __toString() { return 'DateTools'; } +} +DateTools::$DAYS_OF_MONTH = new _hx_array(array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)); diff --git a/hincludes/lib/EReg.class.php b/hincludes/lib/EReg.class.php new file mode 100644 index 0000000..c403775 --- /dev/null +++ b/hincludes/lib/EReg.class.php @@ -0,0 +1,93 @@ +pattern = $r; + $a = _hx_explode("g", $opt); + $this->hglobal = $a->length > 1; + if($this->hglobal) { + $opt = $a->join(""); + } + $this->options = $opt; + $this->re = "/" . ((str_replace("/", "\\/", $r) . "/") . $opt); + }} + public $r; + public $last; + public $hglobal; + public $pattern; + public $options; + public $re; + public $matches; + public function match($s) { + $p = preg_match($this->re, $s, $this->matches, PREG_OFFSET_CAPTURE); + if($p > 0) { + $this->last = $s; + } + else { + $this->last = null; + } + return $p > 0; + } + public function matched($n) { + if($n < 0) { + throw new HException("EReg::matched"); + } + if($n >= count($this->matches)) { + return null; + } + if($this->matches[$n][1] < 0) { + return null; + } + return $this->matches[$n][0]; + } + public function matchedLeft() { + if(count($this->matches) === 0) { + throw new HException("No string matched"); + } + return _hx_substr($this->last, 0, $this->matches[0][1]); + } + public function matchedRight() { + if(count($this->matches) === 0) { + throw new HException("No string matched"); + } + $x = $this->matches[0][1] + strlen($this->matches[0][0]); + return _hx_substr($this->last, $x, null); + } + public function matchedPos() { + return _hx_anonymous(array("pos" => $this->matches[0][1], "len" => strlen($this->matches[0][0]))); + } + public function split($s) { + return new _hx_array(preg_split($this->re, $s, $this->hglobal ? -1 : 2)); + } + public function replace($s, $by) { + $by = str_replace("\$\$", "\\\$", $by); + if(!preg_match('/\\([^?].+?\\)/', $this->re)) $by = preg_replace('/\$(\d+)/', '\\\$\1', $by); + return preg_replace($this->re, $by, $s, ($this->hglobal ? -1 : 1)); + } + public function customReplace($s, $f) { + $buf = new StringBuf(); + while(true) { + if(!$this->match($s)) { + break; + } + $buf->b .= $this->matchedLeft(); + $buf->b .= call_user_func_array($f, array($this)); + $s = $this->matchedRight(); + ; + } + $buf->b .= $s; + return $buf->b; + } + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + function __toString() { return 'EReg'; } +} diff --git a/hincludes/lib/FA.class.php b/hincludes/lib/FA.class.php new file mode 100644 index 0000000..774dfb1 --- /dev/null +++ b/hincludes/lib/FA.class.php @@ -0,0 +1,16 @@ +length = 0; + }} + public function getIterator() { + return $this->iterator(); + } + public function map($f) { + $b = new HList(); + $l = $this->h; + while($l !== null) { + $v = $l[0]; + $l = $l[1]; + $b->add(call_user_func_array($f, array($v))); + unset($v); + } + return $b; + } + public function filter($f) { + $l2 = new HList(); + $l = $this->h; + while($l !== null) { + $v = $l[0]; + $l = $l[1]; + if(call_user_func_array($f, array($v))) { + $l2->add($v); + } + unset($v); + } + return $l2; + } + public function join($sep) { + $s = ""; + $first = true; + $l = $this->h; + while($l !== null) { + if($first) { + $first = false; + } else { + $s .= $sep; + } + $s .= Std::string($l[0]); + $l = $l[1]; + } + return $s; + } + public function toString() { + $s = ""; + $first = true; + $l = $this->h; + while($l !== null) { + if($first) { + $first = false; + } else { + $s .= ", "; + } + $s .= Std::string($l[0]); + $l = $l[1]; + } + return "{" . $s . "}"; + } + public function iterator() { + return new _hx_list_iterator($this); + } + public function remove($v) { + $prev = null; + $l = & $this->h; + while($l !== null) { + if($l[0] === $v) { + if($prev === null) { + $this->h =& $l[1]; + } else { + $prev[1] =& $l[1]; + } + if(($this->q === $l)) { + $this->q =& $prev; + } + $this->length--; + return true; + } + $prev =& $l; + $l =& $l[1]; + } + return false; + } + public function clear() { + $this->h = null; + $this->q = null; + $this->length = 0; + } + public function isEmpty() { + return $this->h === null; + } + public function pop() { + if($this->h === null) { + return null; + } + $x = $this->h[0]; + $this->h = $this->h[1]; + if($this->h === null) { + $this->q = null; + } + $this->length--; + return $x; + } + public function last() { + return HList_0($this); + } + public function first() { + return HList_1($this); + } + public function push($item) { + $x = array($item, &$this->h); + $this->h =& $x; + if($this->q === null) { + $this->q =& $x; + } + $this->length++; + } + public function add($item) { + $x = array($item, null); + if($this->h === null) { + $this->h =& $x; + } else { + $this->q[1] =& $x; + } + $this->q =& $x; + $this->length++; + } + public $length; + public $q; + public $h; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + function __toString() { return $this->toString(); } +} +function HList_0(&$»this) { + if($»this->q === null) { + return null; + } else { + return $»this->q[0]; + } +} +function HList_1(&$»this) { + if($»this->h === null) { + return null; + } else { + return $»this->h[0]; + } +} diff --git a/hincludes/lib/Hash.class.php b/hincludes/lib/Hash.class.php new file mode 100644 index 0000000..ab87ed7 --- /dev/null +++ b/hincludes/lib/Hash.class.php @@ -0,0 +1,65 @@ +h = array(); + }} + public function getIterator() { + return $this->iterator(); + } + public function toString() { + $s = "{"; + $it = $this->keys(); + $»it = $it; + while($»it->hasNext()) { + $i = $»it->next(); + $s .= $i; + $s .= " => "; + $s .= Std::string($this->get($i)); + if($it->hasNext()) { + $s .= ", "; + } + } + return $s . "}"; + } + public function iterator() { + return new _hx_array_iterator(array_values($this->h)); + } + public function keys() { + return new _hx_array_iterator(array_keys($this->h)); + } + public function remove($key) { + if(array_key_exists($key, $this->h)) { + unset($this->h[$key]); + return true; + } else { + return false; + } + } + public function exists($key) { + return array_key_exists($key, $this->h); + } + public function get($key) { + if(array_key_exists($key, $this->h)) { + return $this->h[$key]; + } else { + return null; + } + } + public function set($key, $value) { + $this->h[$key] = $value; + } + public $h; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + function __toString() { return $this->toString(); } +} diff --git a/hincludes/lib/IntHash.class.php b/hincludes/lib/IntHash.class.php new file mode 100644 index 0000000..aaf35c5 --- /dev/null +++ b/hincludes/lib/IntHash.class.php @@ -0,0 +1,65 @@ +h = array(); + }} + public function getIterator() { + return $this->iterator(); + } + public function toString() { + $s = "{"; + $it = $this->keys(); + $»it = $it; + while($»it->hasNext()) { + $i = $»it->next(); + $s .= _hx_string_rec($i, ""); + $s .= " => "; + $s .= Std::string($this->get($i)); + if($it->hasNext()) { + $s .= ", "; + } + } + return $s . "}"; + } + public function iterator() { + return new _hx_array_iterator(array_values($this->h)); + } + public function keys() { + return new _hx_array_iterator(array_keys($this->h)); + } + public function remove($key) { + if(array_key_exists($key, $this->h)) { + unset($this->h[$key]); + return true; + } else { + return false; + } + } + public function exists($key) { + return array_key_exists($key, $this->h); + } + public function get($key) { + if(array_key_exists($key, $this->h)) { + return $this->h[$key]; + } else { + return null; + } + } + public function set($key, $value) { + $this->h[$key] = $value; + } + public $h; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + function __toString() { return $this->toString(); } +} diff --git a/hincludes/lib/IntIter.class.php b/hincludes/lib/IntIter.class.php new file mode 100644 index 0000000..76457a6 --- /dev/null +++ b/hincludes/lib/IntIter.class.php @@ -0,0 +1,28 @@ +min = $min; + $this->max = $max; + }} + public function next() { + return $this->min++; + } + public function hasNext() { + return $this->min < $this->max; + } + public $max; + public $min; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + function __toString() { return 'IntIter'; } +} diff --git a/hincludes/lib/ItemScheduler.class.php b/hincludes/lib/ItemScheduler.class.php new file mode 100644 index 0000000..e2d29eb --- /dev/null +++ b/hincludes/lib/ItemScheduler.class.php @@ -0,0 +1,248 @@ +stock_id = $stock_id; + $this->startLocation = $startLocation; + $this->parameters = $parameters; + $this->qoh = get_qoh_on_date($this->stock_id, $startLocation); + }} + public function action() { + if($this->parameters !== null && $this->parameters->mode == ScheduleMode::$Update) { + $this->update(); + } + } + public function update() { + $orders = $this->orders(); + $priorities = Lambda::harray(Lambda::map($orders, array(new _hx_lambda(array(&$orders), "ItemScheduler_0"), 'execute'))); + $priorities->sort(array(new _hx_lambda(array(&$orders, &$priorities), "ItemScheduler_1"), 'execute')); + $iter = $priorities->iterator(); + $p = $iter->next(); + $position = 0 - $priorities->length; + { + $_g = 0; + while($_g < $orders->length) { + $order = $orders[$_g]; + ++$_g; + $new_priority = DateTools::delta($p, 1000 * $position); + update_order_detail_priority($order->id, $new_priority->toString()); + $position += 1; + $p = $iter->next(); + unset($order,$new_priority); + } + } + update_queue_quantity_for_item($this->stock_id); + } + public function needsUpdate() { + return $this->parameters !== null && $this->parameters->mode == ScheduleMode::$Move; + } + public function purcharseOrders() { + $TB = FA::tb(); + $sql = "SELECT SUM(quantity_ordered - quantity_received) as quantity\x0A\x09\x09\x09into_stock_location AS location\x0A\x09\x09\x09FROM " . $TB . "purch_order_details\x0A\x09\x09\x09NATURAL JOIN " . $TB . "purch_orders\x0A\x09\x09\x09WHERE item_code = \"" . $this->stock_id . "\"\x0A\x09\x09\x09AND quantity_ordered > quantity_received\x0A\x09\x09\x09GROUP by item_code,delivery_date, into_stock_location\x0A\x09\x09\x09ORDER by delivery_date"; + return FA::query($sql); + } + public function locations() { + $TB = FA::tb(); + $sql = "SELECT * \x0A\x09\x09FROM " . $TB . "locations"; + $_locs = new _hx_array(array()); + if(null == _hx_qtype("FA")) throw new HException('null iterable'); + $»it = FA::query($sql); + while($»it->hasNext()) { + $row = $»it->next(); + $location = new Location($row); + if($location->code === $this->startLocation) { + $location->delivery = Date::fromTime(0); + } else { + if($location->delivery === null) { + continue; + } + } + $_locs->push($location); + unset($location); + } + return $_locs; + } + public function orders() { + $_g = $this; + $rows = $this->loadOrders(); + $orderList = new _hx_array(array()); + $»it = $rows; + while($»it->hasNext()) { + $row = $»it->next(); + $a = php_Lib::objectOfAssociativeArray($row); + $order = $a; + $order->priority = Date::fromString($a->pp); + $order->quantity = $a->qty; + $orderList->push($order); + unset($order,$a); + } + if($this->parameters !== null) { + $orderList->sort(array(new _hx_lambda(array(&$_g, &$orderList, &$rows), "ItemScheduler_2"), 'execute')); + } + { + $_g1 = 0; + while($_g1 < $orderList->length) { + $order = $orderList[$_g1]; + ++$_g1; + unset($order); + } + } + return $orderList; + } + public function loadOrders() { + $tb = TB_PREF; + $sql = "SELECT * , d.quantity as qty, d.priority AS pp\x0A\x09\x09FROM " . $tb . "denorm_order_details_queue d\x0A\x09\x09JOIN " . $tb . "sales_order_details od ON (od.id = d.id)\x0A\x09\x09JOIN " . $tb . "sales_orders so ON (so.order_no = d.order_id)\x0A\x09\x09WHERE stock_id = '" . $this->stock_id . "'\x0A\x09\x09AND od.trans_type = 30\x0A\x09\x09ORDER by d.priority"; + return FA::query($sql); + } + public function formatLocation($location, $type, $left) { + $cells = new _hx_array(array($type, $location->name, $location->quantityOnHand($this->stock_id, null), $left - $location->quantityOnHand($this->stock_id, null), $left, $location->code, ((_hx_equal($location->delivery->getTime(), 0)) ? "" : DateTools::format($location->delivery, "%F")), "", "")); + $this->printRow($cells, new _hx_array(array("class = \"tableheader location\"", "id = \"loc_" . $location->code . "\""))); + } + public function formatOrder($order, $left, $date) { + $row_id = ItemScheduler::orderId($order); + $attributes = new _hx_array(array("id = \"" . $row_id . "\"")); + $classes = new _hx_array(array()); + $before = $left + $order->quantity; + if($before < 0) { + $classes->push("soldout"); + } else { + if($left < 0) { + $classes->push("partial"); + } else { + $classes->push("full"); + } + } + $required_by = FA::sql2date($order->required_date); + if($required_by === null) { + $required_by = FA::sql2date($order->delivery_date); + } + if($required_by->getTime() < $date->getTime()) { + $classes->push("late"); + } else { + $classes->push("on_time"); + } + $cells = new _hx_array(array($order->order_id, "debtor_no) . "\">" . Std::string($order->deliver_to) . "", "quantity) . "\">", $before, $left, $order->from_stk_loc, $order->delivery_date, $order->required_date, $order->comment)); + $attributes->push("class=\"" . $classes->join(" ") . "\""); + $this->printRow($cells, $attributes); + } + public function printRow($tds, $attributes) { + php_Lib::hprint("join(" ") . ">"); + $position = 1; + { + $_g = 0; + while($_g < $tds->length) { + $td = $tds[$_g]; + ++$_g; + php_Lib::hprint(""); + if($td) { + php_Lib::hprint($td); + } + php_Lib::hprint(""); + $position++; + unset($td); + } + } + php_Lib::hprint(""); + } + public function generateTable() { + $startDate = Date::fromTime(0); + $locations = $this->locations(); + $locations->sort(array(new _hx_lambda(array(&$locations, &$startDate), "ItemScheduler_3"), 'execute')); + $locationIter = $locations->iterator(); + $location = $locationIter->next(); + $qoh = $location->quantityOnHand($this->stock_id, null); + $left = $qoh; + $this->formatLocation($location, "Initial", $left); + { + $_g = 0; $_g1 = $this->orders(); + while($_g < $_g1->length) { + $order = $_g1[$_g]; + ++$_g; + $quantity = Std::parseInt($order->quantity); + while(0 >= $left && $locationIter->hasNext()) { + $location = $locationIter->next(); + $quantityForLocation = $location->quantityOnHand($this->stock_id, null) + $location->quantityOnOrder($this->stock_id); + if($quantityForLocation === null || $quantityForLocation === 0 || $location->delivery === null) { + continue; + } + $left += $quantityForLocation; + $this->formatLocation($location, "Delivery", $left); + unset($quantityForLocation); + } + $left -= $quantity; + $this->formatOrder($order, $left, $location->delivery); + unset($quantity,$order); + } + } + while(0 >= $left && $locationIter->hasNext()) { + $location = $locationIter->next(); + $quantityForLocation = $location->quantityOnHand($this->stock_id, null); + if($quantityForLocation === null || $quantityForLocation === 0) { + continue; + } + $left += $quantityForLocation; + $this->formatLocation($location, "Delivery", $left); + unset($quantityForLocation); + } + } + public function tableHeader() { + return new _hx_array(array("Order", "Customer", "Quantity", "Before", "After", "Loc", "From", "Required Date", "Comment")); + } + public $qoh; + public $parameters; + public $startLocation; + public $stock_id; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + static function orderId($order) { + return "order_" . $order->id; + } + function __toString() { return 'ItemScheduler'; } +} +function ItemScheduler_0(&$orders, $o) { + { + return $o->priority; + } +} +function ItemScheduler_1(&$orders, &$priorities, $a, $b) { + { + $as = $a->toString(); + $bs = $b->toString(); + if($as < $bs) { + return -1; + } + if($as > $bs) { + return 1; + } + return 0; + } +} +function ItemScheduler_2(&$_g, &$orderList, &$rows, $a, $b) { + { + return $_g->parameters->priority($a) - $_g->parameters->priority($b); + } +} +function ItemScheduler_3(&$locations, &$startDate, $a, $b) { + { + $as = $a->delivery->getTime(); + $bs = $b->delivery->getTime(); + if($as < $bs) { + return -1; + } else { + if($as > $bs) { + return 1; + } else { + return 0; + } + } + } +} diff --git a/hincludes/lib/Lambda.class.php b/hincludes/lib/Lambda.class.php new file mode 100644 index 0000000..f9e4933 --- /dev/null +++ b/hincludes/lib/Lambda.class.php @@ -0,0 +1,173 @@ +iterator(); + while($»it->hasNext()) { + $i = $»it->next(); + $a->push($i); + } + return $a; + } + static function hlist($it) { + $l = new HList(); + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $i = $»it->next(); + $l->add($i); + } + return $l; + } + static function map($it, $f) { + $l = new HList(); + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + $l->add(call_user_func_array($f, array($x))); + } + return $l; + } + static function mapi($it, $f) { + $l = new HList(); + $i = 0; + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + $l->add(call_user_func_array($f, array($i++, $x))); + } + return $l; + } + static function has($it, $elt, $cmp = null) { + if($cmp === null) { + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + if($x == $elt) { + return true; + } + } + } else { + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + if(call_user_func_array($cmp, array($x, $elt))) { + return true; + } + } + } + return false; + } + static function exists($it, $f) { + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + if(call_user_func_array($f, array($x))) { + return true; + } + } + return false; + } + static function hforeach($it, $f) { + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + if(!call_user_func_array($f, array($x))) { + return false; + } + } + return true; + } + static function iter($it, $f) { + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + call_user_func_array($f, array($x)); + } + } + static function filter($it, $f) { + $l = new HList(); + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + if(call_user_func_array($f, array($x))) { + $l->add($x); + } + } + return $l; + } + static function fold($it, $f, $first) { + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + $first = call_user_func_array($f, array($x, $first)); + } + return $first; + } + static function count($it, $pred = null) { + $n = 0; + if($pred === null) { + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $_ = $»it->next(); + $n++; + } + } else { + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + if(call_user_func_array($pred, array($x))) { + $n++; + } + } + } + return $n; + } + static function hempty($it) { + return !$it->iterator()->hasNext(); + } + static function indexOf($it, $v) { + $i = 0; + if(null == $it) throw new HException('null iterable'); + $»it = $it->iterator(); + while($»it->hasNext()) { + $v2 = $»it->next(); + if($v == $v2) { + return $i; + } + $i++; + } + return -1; + } + static function concat($a, $b) { + $l = new HList(); + if(null == $a) throw new HException('null iterable'); + $»it = $a->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + $l->add($x); + } + if(null == $b) throw new HException('null iterable'); + $»it = $b->iterator(); + while($»it->hasNext()) { + $x = $»it->next(); + $l->add($x); + } + return $l; + } + function __toString() { return 'Lambda'; } +} diff --git a/hincludes/lib/Location.class.php b/hincludes/lib/Location.class.php new file mode 100644 index 0000000..0efaad4 --- /dev/null +++ b/hincludes/lib/Location.class.php @@ -0,0 +1,48 @@ +code = $obj->loc_code; + $this->name = $obj->location_name; + $this->comment = $obj->delivery_address; + $config_date = OrderXtraConfig::delivery($this->code); + $this->delivery = (($config_date !== null) ? Date::fromString($config_date) : null); + }} + public function quantityOnHand($stock_id, $date) { + if($this->qoh_cache === null || $stock_id !== $this->stock_id_cache) { + $this->qoh_cache = get_qoh_on_date($stock_id, $this->code, $date); + $this->stock_id_cache = $stock_id; + } + return $this->qoh_cache; + } + public function quantityOnOrder($stock_id) { + $TB = FA::tb(); + $sql = "SELECT SUM(quantity_ordered - quantity_received) as quantity\x0A\x09\x09\x09FROM " . $TB . "purch_order_details\x0A\x09\x09\x09NATURAL JOIN " . $TB . "purch_orders\x0A\x09\x09\x09WHERE item_code = \"" . $stock_id . "\" AND into_stock_location = \"" . $this->code . "\"\x0A\x09\x09\x09AND quantity_ordered > quantity_received\x0A\x09\x09\x09ORDER by delivery_date"; + $result = FA::query($sql); + if($result->hasNext()) { + $row = php_Lib::objectOfAssociativeArray($result->next()); + return $row->quantity; + } else { + return 0; + } + } + public $stock_id_cache; + public $qoh_cache; + public $delivery; + public $comment; + public $name; + public $code; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + function __toString() { return 'Location'; } +} diff --git a/hincludes/lib/Math.class.php b/hincludes/lib/Math.class.php new file mode 100644 index 0000000..eeff37f --- /dev/null +++ b/hincludes/lib/Math.class.php @@ -0,0 +1,76 @@ + 'Just', 0 => 'Nothing'); + } +Maybe::$Nothing = new Maybe("Nothing", 0); diff --git a/hincludes/lib/QueryIterator.class.php b/hincludes/lib/QueryIterator.class.php new file mode 100644 index 0000000..32807a5 --- /dev/null +++ b/hincludes/lib/QueryIterator.class.php @@ -0,0 +1,44 @@ +result = $result; + $this->fetch(); + }} + public function next() { + $»t = ($this->nextValue); + switch($»t->index) { + case 0: + { + throw new HException("Iterator exhausted"); + }break; + case 1: + $v = $»t->params[0]; + { + $this->fetch(); + return $v; + }break; + } + } + public function hasNext() { + return $this->nextValue != Maybe::$Nothing; + } + public function fetch() { + $next = db_fetch($this->result); + $this->nextValue = (($next) ? Maybe::Just($next) : Maybe::$Nothing); + } + public $nextValue; + public $result; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + function __toString() { return 'QueryIterator'; } +} diff --git a/hincludes/lib/Reflect.class.php b/hincludes/lib/Reflect.class.php new file mode 100644 index 0000000..553aafe --- /dev/null +++ b/hincludes/lib/Reflect.class.php @@ -0,0 +1,104 @@ +{$field} = $value; + } + static function getProperty($o, $field) { + if(null === $o) { + return null; + } + $cls = ((Std::is($o, _hx_qtype("Class"))) ? $o->__tname__ : get_class($o)); + $cls_vars = get_class_vars($cls); + if(isset($cls_vars['__properties__']) && isset($cls_vars['__properties__']['get_'.$field]) && ($field = $cls_vars['__properties__']['get_'.$field])) { + return $o->$field(); + } else { + return $o->$field; + } + } + static function setProperty($o, $field, $value) { + if(null === $o) { + null; + return; + } + $cls = ((Std::is($o, _hx_qtype("Class"))) ? $o->__tname__ : get_class($o)); + $cls_vars = get_class_vars($cls); + if(isset($cls_vars['__properties__']) && isset($cls_vars['__properties__']['set_'.$field]) && ($field = $cls_vars['__properties__']['set_'.$field])) { + $o->$field($value); + return; + } else { + $o->$field = $value; + return; + } + } + static function callMethod($o, $func, $args) { + if(is_string($o) && !is_array($func)) { + return call_user_func_array(Reflect::field($o, $func), $args->»a); + } + return call_user_func_array(((is_callable($func)) ? $func : array($o, $func)), ((null === $args) ? array() : $args->»a)); + } + static function fields($o) { + if($o === null) { + return new _hx_array(array()); + } + return (($o instanceof _hx_array) ? new _hx_array(array('concat','copy','insert','iterator','length','join','pop','push','remove','reverse','shift','slice','sort','splice','toString','unshift')) : ((is_string($o)) ? new _hx_array(array('charAt','charCodeAt','indexOf','lastIndexOf','length','split','substr','toLowerCase','toString','toUpperCase')) : new _hx_array(_hx_get_object_vars($o)))); + } + static function isFunction($f) { + return (is_array($f) && is_callable($f)) || _hx_is_lambda($f) || is_array($f) && _hx_has_field($f[0], $f[1]) && $f[1] !== "length"; + } + static function compare($a, $b) { + return (($a == $b) ? 0 : (($a > $b) ? 1 : -1)); + } + static function compareMethods($f1, $f2) { + if(is_array($f1) && is_array($f1)) { + return $f1[0] === $f2[0] && $f1[1] == $f2[1]; + } + if(is_string($f1) && is_string($f2)) { + return _hx_equal($f1, $f2); + } + return false; + } + static function isObject($v) { + if($v === null) { + return false; + } + if(is_object($v)) { + return $v instanceof _hx_anonymous || Type::getClass($v) !== null; + } + return is_string($v) && !_hx_is_lambda($v); + } + static function deleteField($o, $f) { + if(!_hx_has_field($o, $f)) { + return false; + } + if(isset($o->»dynamics[$f])) unset($o->»dynamics[$f]); else if($o instanceof _hx_anonymous) unset($o->$f); else $o->$f = null; + return true; + } + static function copy($o) { + if(is_string($o)) { + return $o; + } + $o2 = _hx_anonymous(array()); + { + $_g = 0; $_g1 = Reflect::fields($o); + while($_g < $_g1->length) { + $f = $_g1[$_g]; + ++$_g; + $o2->{$f} = Reflect::field($o, $f); + unset($f); + } + } + return $o2; + } + static function makeVarArgs($f) { + return array(new _hx_lambda(array(&$f), '_hx_make_var_args'), 'execute'); + } + function __toString() { return 'Reflect'; } +} diff --git a/hincludes/lib/ScheduleMode.enum.php b/hincludes/lib/ScheduleMode.enum.php new file mode 100644 index 0000000..aa6fa27 --- /dev/null +++ b/hincludes/lib/ScheduleMode.enum.php @@ -0,0 +1,11 @@ + 'Cancel', 2 => 'Move', 0 => 'Update'); + } +ScheduleMode::$Cancel = new ScheduleMode("Cancel", 1); +ScheduleMode::$Move = new ScheduleMode("Move", 2); +ScheduleMode::$Update = new ScheduleMode("Update", 0); diff --git a/hincludes/lib/ScheduleParameters.class.php b/hincludes/lib/ScheduleParameters.class.php new file mode 100644 index 0000000..a44b89b --- /dev/null +++ b/hincludes/lib/ScheduleParameters.class.php @@ -0,0 +1,74 @@ +row_id = $data->get("row_id"); + $raw_order = $data->get("row_order"); + $this->mode = ScheduleMode::$Move; + $row_ids = new _hx_array($raw_order); + if($row_ids !== null) { + $this->rowDetails = new Hash(); + $position = 1; + { + $_g = 0; + while($_g < $row_ids->length) { + $id = $row_ids[$_g]; + ++$_g; + $d = $data->get($id); + $quantity = null; + if($d !== null) { + $o = php_Lib::objectOfAssociativeArray($d); + $quantity = Std::parseInt($o->quantity); + unset($o); + } + $this->rowDetails->set($id, _hx_anonymous(array("id" => $id, "quantity" => $quantity, "position" => $position))); + $position += 1; + unset($quantity,$id,$d); + } + } + } + }} + public function priority($order) { + $orderId = ItemScheduler::orderId($order); + $orderPosition = $this->position($orderId); + return (($orderPosition !== null) ? $orderPosition : $order->priority->getTime()); + } + public function position($id) { + if($this->rowDetails === null) { + return null; + } + return $this->rowDetails->get($id)->position; + } + public function setMode($action) { + $this->mode = ScheduleParameters_0($this, $action); + } + public $mode; + public $rowDetails; + public $row_id; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + function __toString() { return 'ScheduleParameters'; } +} +function ScheduleParameters_0(&$»this, &$action) { + switch($action) { + case "update":{ + return ScheduleMode::$Update; + }break; + case "cancel":{ + return ScheduleMode::$Cancel; + }break; + default:{ + return ScheduleMode::$Move; + }break; + } +} diff --git a/hincludes/lib/Std.class.php b/hincludes/lib/Std.class.php new file mode 100644 index 0000000..1a4f6ce --- /dev/null +++ b/hincludes/lib/Std.class.php @@ -0,0 +1,38 @@ +b = ""; + }} + public function toString() { + return $this->b; + } + public function addChar($c) { + $this->b .= chr($c); + } + public function addSub($s, $pos, $len = null) { + $this->b .= _hx_substr($s, $pos, $len); + } + public function add($x) { + if(is_null($x)) { + $x = "null"; + } else { + if(is_bool($x)) { + $x = (($x) ? "true" : "false"); + } + } + $this->b .= Std::string($x); + } + public $b; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + function __toString() { return $this->toString(); } +} diff --git a/hincludes/lib/StringTools.class.php b/hincludes/lib/StringTools.class.php new file mode 100644 index 0000000..c551ff9 --- /dev/null +++ b/hincludes/lib/StringTools.class.php @@ -0,0 +1,61 @@ +", _hx_explode("<", _hx_explode("&", $s)->join("&"))->join("<"))->join(">"); + } + static function htmlUnescape($s) { + return htmlspecialchars_decode($s); + } + static function startsWith($s, $start) { + return strlen($s) >= strlen($start) && _hx_substr($s, 0, strlen($start)) === $start; + } + static function endsWith($s, $end) { + $elen = strlen($end); + $slen = strlen($s); + return $slen >= $elen && _hx_substr($s, $slen - $elen, $elen) === $end; + } + static function isSpace($s, $pos) { + $c = _hx_char_code_at($s, $pos); + return $c >= 9 && $c <= 13 || $c === 32; + } + static function ltrim($s) { + return ltrim($s); + } + static function rtrim($s) { + return rtrim($s); + } + static function trim($s) { + return trim($s); + } + static function rpad($s, $c, $l) { + return str_pad($s, $l, $c, STR_PAD_RIGHT); + } + static function lpad($s, $c, $l) { + return str_pad($s, $l, $c, STR_PAD_LEFT); + } + static function replace($s, $sub, $by) { + return str_replace($sub, $by, $s); + } + static function hex($n, $digits = null) { + $s = dechex($n); + if($digits !== null) { + $s = str_pad($s, $digits, "0", STR_PAD_LEFT); + } + return strtoupper($s); + } + static function fastCodeAt($s, $index) { + return ord(substr($s,$index,1)); + } + static function isEOF($c) { + return ($c === 0); + } + function __toString() { return 'StringTools'; } +} diff --git a/hincludes/lib/Type.class.php b/hincludes/lib/Type.class.php new file mode 100644 index 0000000..f98e3d8 --- /dev/null +++ b/hincludes/lib/Type.class.php @@ -0,0 +1,298 @@ +__tname__); + if($s === false) { + return null; + } else { + return _hx_ttype($s); + } + } + static function getClassName($c) { + if($c === null) { + return null; + } + return $c->__qname__; + } + static function getEnumName($e) { + return $e->__qname__; + } + static function resolveClass($name) { + $c = _hx_qtype($name); + if($c instanceof _hx_class || $c instanceof _hx_interface) { + return $c; + } else { + return null; + } + } + static function resolveEnum($name) { + $e = _hx_qtype($name); + if($e instanceof _hx_enum) { + return $e; + } else { + return null; + } + } + static function createInstance($cl, $args) { + if($cl->__qname__ === "Array") { + return new _hx_array(array()); + } + if($cl->__qname__ === "String") { + return $args[0]; + } + $c = $cl->__rfl__(); + if($c === null) { + return null; + } + return $inst = $c->getConstructor() ? $c->newInstanceArgs($args->»a) : $c->newInstanceArgs(); + } + static function createEmptyInstance($cl) { + if($cl->__qname__ === "Array") { + return new _hx_array(array()); + } + if($cl->__qname__ === "String") { + return ""; + } + try { + php_Boot::$skip_constructor = true; + $rfl = $cl->__rfl__(); + if($rfl === null) { + return null; + } + $m = $rfl->getConstructor(); + $nargs = $m->getNumberOfRequiredParameters(); + $i = null; + if($nargs > 0) { + $args = array_fill(0, $m->getNumberOfRequiredParameters(), null); + $i = $rfl->newInstanceArgs($args); + } else { + $i = $rfl->newInstanceArgs(array()); + } + php_Boot::$skip_constructor = false; + return $i; + }catch(Exception $»e) { + $_ex_ = ($»e instanceof HException) ? $»e->e : $»e; + $e = $_ex_; + { + php_Boot::$skip_constructor = false; + throw new HException("Unable to instantiate " . Std::string($cl)); + } + } + return null; + } + static function createEnum($e, $constr, $params = null) { + $f = Reflect::field($e, $constr); + if($f === null) { + throw new HException("No such constructor " . $constr); + } + if(Reflect::isFunction($f)) { + if($params === null) { + throw new HException("Constructor " . $constr . " need parameters"); + } + return Reflect::callMethod($e, $f, $params); + } + if($params !== null && $params->length !== 0) { + throw new HException("Constructor " . $constr . " does not need parameters"); + } + return $f; + } + static function createEnumIndex($e, $index, $params = null) { + $c = _hx_array_get(Type::getEnumConstructs($e), $index); + if($c === null) { + throw new HException(_hx_string_rec($index, "") . " is not a valid enum constructor index"); + } + return Type::createEnum($e, $c, $params); + } + static function getInstanceFields($c) { + if($c->__qname__ === "String") { + return new _hx_array(array("substr", "charAt", "charCodeAt", "indexOf", "lastIndexOf", "split", "toLowerCase", "toUpperCase", "toString", "length")); + } + if($c->__qname__ === "Array") { + return new _hx_array(array("push", "concat", "join", "pop", "reverse", "shift", "slice", "sort", "splice", "toString", "copy", "unshift", "insert", "remove", "iterator", "length")); + } + + $rfl = $c->__rfl__(); + if($rfl === null) return new _hx_array(array()); + $r = array(); + $internals = array('__construct', '__call', '__get', '__set', '__isset', '__unset', '__toString'); + $ms = $rfl->getMethods(); + while(list(, $m) = each($ms)) { + $n = $m->getName(); + if(!$m->isStatic() && ! in_array($n, $internals)) $r[] = $n; + } + $ps = $rfl->getProperties(); + while(list(, $p) = each($ps)) + if(!$p->isStatic()) $r[] = $p->getName(); + return new _hx_array(array_values(array_unique($r))); + } + static function getClassFields($c) { + if($c->__qname__ === "String") { + return new _hx_array(array("fromCharCode")); + } + if($c->__qname__ === "Array") { + return new _hx_array(array()); + } + + $rfl = $c->__rfl__(); + if($rfl === null) return new _hx_array(array()); + $ms = $rfl->getMethods(); + $r = array(); + while(list(, $m) = each($ms)) + if($m->isStatic()) $r[] = $m->getName(); + $ps = $rfl->getProperties(); + while(list(, $p) = each($ps)) + if($p->isStatic()) $r[] = $p->getName(); + ; + return new _hx_array(array_unique($r)); + } + static function getEnumConstructs($e) { + if($e->__tname__ == 'Bool') { + return new _hx_array(array("true", "false")); + } + if($e->__tname__ == 'Void') { + return new _hx_array(array()); + } + return new _hx_array($e->__constructors); + } + static function typeof($v) { + if($v === null) { + return ValueType::$TNull; + } + if(is_array($v)) { + if(is_callable($v)) { + return ValueType::$TFunction; + } + return ValueType::TClass(_hx_qtype("Array")); + } + if(is_string($v)) { + if(_hx_is_lambda($v)) { + return ValueType::$TFunction; + } + return ValueType::TClass(_hx_qtype("String")); + } + if(is_bool($v)) { + return ValueType::$TBool; + } + if(is_int($v)) { + return ValueType::$TInt; + } + if(is_float($v)) { + return ValueType::$TFloat; + } + if($v instanceof _hx_anonymous) { + return ValueType::$TObject; + } + if($v instanceof _hx_enum) { + return ValueType::$TObject; + } + if($v instanceof _hx_class) { + return ValueType::$TObject; + } + $c = _hx_ttype(get_class($v)); + if($c instanceof _hx_enum) { + return ValueType::TEnum($c); + } + if($c instanceof _hx_class) { + return ValueType::TClass($c); + } + return ValueType::$TUnknown; + } + static function enumEq($a, $b) { + if($a == $b) { + return true; + } + try { + if(!_hx_equal($a->index, $b->index)) { + return false; + } + { + $_g1 = 0; $_g = count($a->params); + while($_g1 < $_g) { + $i = $_g1++; + if(Type::getEnum($a->params[$i]) !== null) { + if(!Type::enumEq($a->params[$i], $b->params[$i])) { + return false; + } + } else { + if(!_hx_equal($a->params[$i], $b->params[$i])) { + return false; + } + } + unset($i); + } + } + }catch(Exception $»e) { + $_ex_ = ($»e instanceof HException) ? $»e->e : $»e; + $e = $_ex_; + { + return false; + } + } + return true; + } + static function enumConstructor($e) { + return $e->tag; + } + static function enumParameters($e) { + if(_hx_field($e, "params") === null) { + return new _hx_array(array()); + } else { + return new _hx_array($e->params); + } + } + static function enumIndex($e) { + return $e->index; + } + static function allEnums($e) { + $all = new _hx_array(array()); + { + $_g = 0; $_g1 = Type::getEnumConstructs($e); + while($_g < $_g1->length) { + $c = $_g1[$_g]; + ++$_g; + $v = Reflect::field($e, $c); + if(!Reflect::isFunction($v)) { + $all->push($v); + } + unset($v,$c); + } + } + return $all; + } + function __toString() { return 'Type'; } +} diff --git a/hincludes/lib/ValueType.enum.php b/hincludes/lib/ValueType.enum.php new file mode 100644 index 0000000..8fa27ea --- /dev/null +++ b/hincludes/lib/ValueType.enum.php @@ -0,0 +1,21 @@ + 'TBool', 6 => 'TClass', 7 => 'TEnum', 2 => 'TFloat', 5 => 'TFunction', 1 => 'TInt', 0 => 'TNull', 4 => 'TObject', 8 => 'TUnknown'); + } +ValueType::$TBool = new ValueType("TBool", 3); +ValueType::$TFloat = new ValueType("TFloat", 2); +ValueType::$TFunction = new ValueType("TFunction", 5); +ValueType::$TInt = new ValueType("TInt", 1); +ValueType::$TNull = new ValueType("TNull", 0); +ValueType::$TObject = new ValueType("TObject", 4); +ValueType::$TUnknown = new ValueType("TUnknown", 8); diff --git a/hincludes/lib/Xml.class.php b/hincludes/lib/Xml.class.php new file mode 100644 index 0000000..10bea7b --- /dev/null +++ b/hincludes/lib/Xml.class.php @@ -0,0 +1,376 @@ +nodeType != Xml::$Element) { + throw new HException("bad nodeType"); + } + return $this->_nodeName; + } + public function setNodeName($n) { + if($this->nodeType != Xml::$Element) { + throw new HException("bad nodeType"); + } + return $this->_nodeName = $n; + } + public function getNodeValue() { + if($this->nodeType == Xml::$Element || $this->nodeType == Xml::$Document) { + throw new HException("bad nodeType"); + } + return $this->_nodeValue; + } + public function setNodeValue($v) { + if($this->nodeType == Xml::$Element || $this->nodeType == Xml::$Document) { + throw new HException("bad nodeType"); + } + return $this->_nodeValue = $v; + } + public function getParent() { + return $this->_parent; + } + public function get($att) { + if($this->nodeType != Xml::$Element) { + throw new HException("bad nodeType"); + } + return $this->_attributes->get($att); + } + public function set($att, $value) { + if($this->nodeType != Xml::$Element) { + throw new HException("bad nodeType"); + } + $this->_attributes->set($att, htmlspecialchars($value, ENT_COMPAT, "UTF-8")); + } + public function remove($att) { + if($this->nodeType != Xml::$Element) { + throw new HException("bad nodeType"); + } + $this->_attributes->remove($att); + } + public function exists($att) { + if($this->nodeType != Xml::$Element) { + throw new HException("bad nodeType"); + } + return $this->_attributes->exists($att); + } + public function attributes() { + if($this->nodeType != Xml::$Element) { + throw new HException("bad nodeType"); + } + return $this->_attributes->keys(); + } + public function iterator() { + if($this->_children === null) { + throw new HException("bad nodetype"); + } + $me = $this; + $it = null; + $it = _hx_anonymous(array("cur" => 0, "x" => $me->_children, "hasNext" => array(new _hx_lambda(array("it" => &$it, "me" => &$me), null, array(), "{ + return \$it->cur < _hx_len(\$it->x); + }"), 'execute0'), "next" => array(new _hx_lambda(array("it" => &$it, "me" => &$me), null, array(), "{ + return \$it->x[\$it->cur++]; + }"), 'execute0'))); + return $it; + } + public function elements() { + if($this->_children === null) { + throw new HException("bad nodetype"); + } + $me = $this; + $it = null; + $it = _hx_anonymous(array("cur" => 0, "x" => $me->_children, "hasNext" => array(new _hx_lambda(array("it" => &$it, "me" => &$me), null, array(), "{ + \$k = \$it->cur; + \$l = _hx_len(\$it->x); + while(\$k < \$l) { + if(_hx_array_get(\$it->x, \$k)->nodeType == Xml::\$Element) { + break; + } + \$k += 1; + ; + } + \$it->cur = \$k; + return \$k < \$l; + }"), 'execute0'), "next" => array(new _hx_lambda(array("it" => &$it, "me" => &$me), null, array(), "{ + \$k = \$it->cur; + \$l = _hx_len(\$it->x); + while(\$k < \$l) { + \$n = \$it->x[\$k]; + \$k += 1; + if(\$n->nodeType == Xml::\$Element) { + \$it->cur = \$k; + return \$n; + } + unset(\$n); + } + return null; + }"), 'execute0'))); + return $it; + } + public function elementsNamed($name) { + if($this->_children === null) { + throw new HException("bad nodetype"); + } + $me = $this; + $it = null; + $it = _hx_anonymous(array("cur" => 0, "x" => $me->_children, "hasNext" => array(new _hx_lambda(array("it" => &$it, "me" => &$me, "name" => &$name), null, array(), "{ + \$k = \$it->cur; + \$l = _hx_len(\$it->x); + while(\$k < \$l) { + \$n = \$it->x[\$k]; + if(\$n->nodeType == Xml::\$Element && \$n->_nodeName == \$name) { + break; + } + \$k++; + unset(\$n); + } + \$it->cur = \$k; + return \$k < \$l; + }"), 'execute0'), "next" => array(new _hx_lambda(array("it" => &$it, "me" => &$me, "name" => &$name), null, array(), "{ + \$k = \$it->cur; + \$l = _hx_len(\$it->x); + while(\$k < \$l) { + \$n = \$it->x[\$k]; + \$k++; + if(\$n->nodeType == Xml::\$Element && \$n->_nodeName == \$name) { + \$it->cur = \$k; + return \$n; + } + unset(\$n); + } + return null; + }"), 'execute0'))); + return $it; + } + public function firstChild() { + if($this->_children === null) { + throw new HException("bad nodetype"); + } + if($this->_children->length === 0) { + return null; + } + return $this->_children[0]; + } + public function firstElement() { + if($this->_children === null) { + throw new HException("bad nodetype"); + } + $cur = 0; + $l = $this->_children->length; + while($cur < $l) { + $n = $this->_children[$cur]; + if($n->nodeType == Xml::$Element) { + return $n; + } + $cur++; + unset($n); + } + return null; + } + public function addChild($x) { + if($this->_children === null) { + throw new HException("bad nodetype"); + } + if($x->_parent !== null) { + $x->_parent->_children->remove($x); + } + $x->_parent = $this; + $this->_children->push($x); + } + public function removeChild($x) { + if($this->_children === null) { + throw new HException("bad nodetype"); + } + $b = $this->_children->remove($x); + if($b) { + $x->_parent = null; + } + return $b; + } + public function insertChild($x, $pos) { + if($this->_children === null) { + throw new HException("bad nodetype"); + } + if($x->_parent !== null) { + $x->_parent->_children->remove($x); + } + $x->_parent = $this; + $this->_children->insert($pos, $x); + } + public function toString() { + if($this->nodeType == Xml::$PCData) { + return $this->_nodeValue; + } + if($this->nodeType == Xml::$CData) { + return ("_nodeValue) . "]]>"; + } + if($this->nodeType == Xml::$Comment || $this->nodeType == Xml::$DocType || $this->nodeType == Xml::$Prolog) { + return $this->_nodeValue; + } + $s = ""; + if($this->nodeType == Xml::$Element) { + $s .= "<"; + $s .= $this->_nodeName; + $»it = $this->_attributes->keys(); + while($»it->hasNext()) { + $k = $»it->next(); + { + $s .= " "; + $s .= $k; + $s .= "=\""; + $s .= $this->_attributes->get($k); + $s .= "\""; + ; + } + } + if($this->_children->length === 0) { + $s .= "/>"; + return $s; + } + $s .= ">"; + } + $»it2 = $this->iterator(); + while($»it2->hasNext()) { + $x = $»it2->next(); + $s .= $x->toString(); + } + if($this->nodeType == Xml::$Element) { + $s .= "_nodeName; + $s .= ">"; + } + return $s; + } + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + static $Element; + static $PCData; + static $CData; + static $Comment; + static $DocType; + static $Prolog; + static $Document; + static $build; + static function __start_element_handler($parser, $name, $attribs) { + $node = Xml::createElement($name); + while(list($k, $v) = each($attribs)) $node->set($k, $v); + Xml::$build->addChild($node); + Xml::$build = $node; + } + static function __end_element_handler($parser, $name) { + Xml::$build = Xml::$build->getParent(); + } + static function __character_data_handler($parser, $data) { + if((strlen($data) === 1 && htmlentities($data) != $data) || htmlentities($data) == $data) { + Xml::$build->addChild(Xml::createPCData(htmlentities($data))); + } + else { + Xml::$build->addChild(Xml::createCData($data)); + } + } + static function __default_handler($parser, $data) { + Xml::$build->addChild(Xml::createPCData($data)); + } + static $xmlChecker; + static function parse($str) { + Xml::$build = Xml::createDocument(); + $xml_parser = xml_parser_create(); + xml_set_element_handler($xml_parser, (isset(Xml::$__start_element_handler) ? Xml::$__start_element_handler: array("Xml", "__start_element_handler")), (isset(Xml::$__end_element_handler) ? Xml::$__end_element_handler: array("Xml", "__end_element_handler"))); + xml_set_character_data_handler($xml_parser, (isset(Xml::$__character_data_handler) ? Xml::$__character_data_handler: array("Xml", "__character_data_handler"))); + xml_set_default_handler($xml_parser, (isset(Xml::$__default_handler) ? Xml::$__default_handler: array("Xml", "__default_handler"))); + xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 0); + xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, 0); + $isComplete = Xml::$xmlChecker->match($str); + if(!$isComplete) { + $str = ("" . $str) . ""; + } + if(1 !== xml_parse($xml_parser, $str, true)) { + throw new HException("Xml parse error (" . ((xml_error_string(xml_get_error_code($xml_parser)) . ") line #") . xml_get_current_line_number($xml_parser))); + } + xml_parser_free($xml_parser); + if($isComplete) { + return Xml::$build; + } + else { + Xml::$build = Xml::$build->_children[0]; + Xml::$build->_parent = null; + Xml::$build->_nodeName = null; + Xml::$build->nodeType = Xml::$Document; + return Xml::$build; + } + } + static function createElement($name) { + $r = new Xml(); + $r->nodeType = Xml::$Element; + $r->_children = new _hx_array(array()); + $r->_attributes = new Hash(); + $r->setNodeName($name); + return $r; + } + static function createPCData($data) { + $r = new Xml(); + $r->nodeType = Xml::$PCData; + $r->setNodeValue($data); + return $r; + } + static function createCData($data) { + $r = new Xml(); + $r->nodeType = Xml::$CData; + $r->setNodeValue($data); + return $r; + } + static function createComment($data) { + $r = new Xml(); + $r->nodeType = Xml::$Comment; + $r->setNodeValue($data); + return $r; + } + static function createDocType($data) { + $r = new Xml(); + $r->nodeType = Xml::$DocType; + $r->setNodeValue($data); + return $r; + } + static function createProlog($data) { + $r = new Xml(); + $r->nodeType = Xml::$Prolog; + $r->setNodeValue($data); + return $r; + } + static function createDocument() { + $r = new Xml(); + $r->nodeType = Xml::$Document; + $r->_children = new _hx_array(array()); + return $r; + } + function __toString() { return $this->toString(); } +} +{ + Xml::$Element = "element"; + Xml::$PCData = "pcdata"; + Xml::$CData = "cdata"; + Xml::$Comment = "comment"; + Xml::$DocType = "doctype"; + Xml::$Prolog = "prolog"; + Xml::$Document = "document"; +} +Xml::$xmlChecker = new EReg("\\s*(<\\?xml|buf = new StringBuf(); + $this->cache = new _hx_array(array()); + $this->useCache = haxe_Serializer::$USE_CACHE; + $this->useEnumIndex = haxe_Serializer::$USE_ENUM_INDEX; + $this->shash = new Hash(); + $this->scount = 0; + }} + public function serializeException($e) { + $this->buf->add("x"); + $this->serialize($e); + } + public function serialize($v) { + $»t = (Type::typeof($v)); + switch($»t->index) { + case 0: + { + $this->buf->add("n"); + }break; + case 1: + { + if(_hx_equal($v, 0)) { + $this->buf->add("z"); + return; + } + $this->buf->add("i"); + $this->buf->add($v); + }break; + case 2: + { + if(Math::isNaN($v)) { + $this->buf->add("k"); + } else { + if(!Math::isFinite($v)) { + $this->buf->add((($v < 0) ? "m" : "p")); + } else { + $this->buf->add("d"); + $this->buf->add($v); + } + } + }break; + case 3: + { + $this->buf->add((($v) ? "t" : "f")); + }break; + case 6: + $c = $»t->params[0]; + { + if($c == _hx_qtype("String")) { + $this->serializeString($v); + return; + } + if($this->useCache && $this->serializeRef($v)) { + return; + } + switch($c) { + case _hx_qtype("Array"):{ + $ucount = 0; + $this->buf->add("a"); + $l = _hx_len($v); + { + $_g = 0; + while($_g < $l) { + $i = $_g++; + if($v[$i] === null) { + $ucount++; + } else { + if($ucount > 0) { + if($ucount === 1) { + $this->buf->add("n"); + } else { + $this->buf->add("u"); + $this->buf->add($ucount); + } + $ucount = 0; + } + $this->serialize($v[$i]); + } + unset($i); + } + } + if($ucount > 0) { + if($ucount === 1) { + $this->buf->add("n"); + } else { + $this->buf->add("u"); + $this->buf->add($ucount); + } + } + $this->buf->add("h"); + }break; + case _hx_qtype("List"):{ + $this->buf->add("l"); + $v1 = $v; + if(null == $v1) throw new HException('null iterable'); + $»it = $v1->iterator(); + while($»it->hasNext()) { + $i = $»it->next(); + $this->serialize($i); + } + $this->buf->add("h"); + }break; + case _hx_qtype("Date"):{ + $d = $v; + $this->buf->add("v"); + $this->buf->add($d->toString()); + }break; + case _hx_qtype("Hash"):{ + $this->buf->add("b"); + $v1 = $v; + if(null == $v1) throw new HException('null iterable'); + $»it = $v1->keys(); + while($»it->hasNext()) { + $k = $»it->next(); + $this->serializeString($k); + $this->serialize($v1->get($k)); + } + $this->buf->add("h"); + }break; + case _hx_qtype("IntHash"):{ + $this->buf->add("q"); + $v1 = $v; + if(null == $v1) throw new HException('null iterable'); + $»it = $v1->keys(); + while($»it->hasNext()) { + $k = $»it->next(); + $this->buf->add(":"); + $this->buf->add($k); + $this->serialize($v1->get($k)); + } + $this->buf->add("h"); + }break; + case _hx_qtype("haxe.io.Bytes"):{ + $v1 = $v; + $i = 0; + $max = $v1->length - 2; + $charsBuf = new StringBuf(); + $b64 = haxe_Serializer::$BASE64; + while($i < $max) { + $b1 = ord($v1->b[$i++]); + $b2 = ord($v1->b[$i++]); + $b3 = ord($v1->b[$i++]); + $charsBuf->add(_hx_char_at($b64, $b1 >> 2)); + $charsBuf->add(_hx_char_at($b64, ($b1 << 4 | $b2 >> 4) & 63)); + $charsBuf->add(_hx_char_at($b64, ($b2 << 2 | $b3 >> 6) & 63)); + $charsBuf->add(_hx_char_at($b64, $b3 & 63)); + unset($b3,$b2,$b1); + } + if($i === $max) { + $b1 = ord($v1->b[$i++]); + $b2 = ord($v1->b[$i++]); + $charsBuf->add(_hx_char_at($b64, $b1 >> 2)); + $charsBuf->add(_hx_char_at($b64, ($b1 << 4 | $b2 >> 4) & 63)); + $charsBuf->add(_hx_char_at($b64, $b2 << 2 & 63)); + } else { + if($i === $max + 1) { + $b1 = ord($v1->b[$i++]); + $charsBuf->add(_hx_char_at($b64, $b1 >> 2)); + $charsBuf->add(_hx_char_at($b64, $b1 << 4 & 63)); + } + } + $chars = $charsBuf->b; + $this->buf->add("s"); + $this->buf->add(strlen($chars)); + $this->buf->add(":"); + $this->buf->add($chars); + }break; + default:{ + $this->cache->pop(); + if(_hx_field($v, "hxSerialize") !== null) { + $this->buf->add("C"); + $this->serializeString(Type::getClassName($c)); + $this->cache->push($v); + $v->hxSerialize($this); + $this->buf->add("g"); + } else { + $this->buf->add("c"); + $this->serializeString(Type::getClassName($c)); + $this->cache->push($v); + $this->serializeFields($v); + } + }break; + } + }break; + case 4: + { + if($this->useCache && $this->serializeRef($v)) { + return; + } + $this->buf->add("o"); + $this->serializeFields($v); + }break; + case 7: + $e = $»t->params[0]; + { + if($this->useCache && $this->serializeRef($v)) { + return; + } + $this->cache->pop(); + $this->buf->add((($this->useEnumIndex) ? "j" : "w")); + $this->serializeString(Type::getEnumName($e)); + if($this->useEnumIndex) { + $this->buf->add(":"); + $this->buf->add($v->index); + } else { + $this->serializeString($v->tag); + } + $this->buf->add(":"); + $l = count($v->params); + if($l === 0 || _hx_field($v, "params") === null) { + $this->buf->add(0); + } else { + $this->buf->add($l); + { + $_g = 0; + while($_g < $l) { + $i = $_g++; + $this->serialize($v->params[$i]); + unset($i); + } + } + } + $this->cache->push($v); + }break; + case 5: + { + throw new HException("Cannot serialize function"); + }break; + default:{ + throw new HException("Cannot serialize " . Std::string($v)); + }break; + } + } + public function serializeFields($v) { + { + $_g = 0; $_g1 = Reflect::fields($v); + while($_g < $_g1->length) { + $f = $_g1[$_g]; + ++$_g; + $this->serializeString($f); + $this->serialize(Reflect::field($v, $f)); + unset($f); + } + } + $this->buf->add("g"); + } + public function serializeRef($v) { + { + $_g1 = 0; $_g = $this->cache->length; + while($_g1 < $_g) { + $i = $_g1++; + if(_hx_equal($this->cache[$i], $v)) { + $this->buf->add("r"); + $this->buf->add($i); + return true; + } + unset($i); + } + } + $this->cache->push($v); + return false; + } + public function serializeString($s) { + $x = $this->shash->get($s); + if($x !== null) { + $this->buf->add("R"); + $this->buf->add($x); + return; + } + $this->shash->set($s, $this->scount++); + $this->buf->add("y"); + $s = rawurlencode($s); + $this->buf->add(strlen($s)); + $this->buf->add(":"); + $this->buf->add($s); + } + public function toString() { + return $this->buf->b; + } + public $useEnumIndex; + public $useCache; + public $scount; + public $shash; + public $cache; + public $buf; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + static $USE_CACHE = false; + static $USE_ENUM_INDEX = false; + static $BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%:"; + static function run($v) { + $s = new haxe_Serializer(); + $s->serialize($v); + return $s->toString(); + } + function __toString() { return $this->toString(); } +} diff --git a/hincludes/lib/haxe/Unserializer.class.php b/hincludes/lib/haxe/Unserializer.class.php new file mode 100644 index 0000000..4316b62 --- /dev/null +++ b/hincludes/lib/haxe/Unserializer.class.php @@ -0,0 +1,381 @@ +buf = $buf; + $this->length = strlen($buf); + $this->pos = 0; + $this->scache = new _hx_array(array()); + $this->cache = new _hx_array(array()); + $r = haxe_Unserializer::$DEFAULT_RESOLVER; + if($r === null) { + $r = _hx_qtype("Type"); + haxe_Unserializer::$DEFAULT_RESOLVER = $r; + } + $this->setResolver($r); + }} + public function unserialize() { + switch(ord(substr($this->buf,$this->pos++,1))) { + case 110:{ + return null; + }break; + case 116:{ + return true; + }break; + case 102:{ + return false; + }break; + case 122:{ + return 0; + }break; + case 105:{ + return $this->readDigits(); + }break; + case 100:{ + $p1 = $this->pos; + while(true) { + $c = ord(substr($this->buf,$this->pos,1)); + if($c >= 43 && $c < 58 || $c === 101 || $c === 69) { + $this->pos++; + } else { + break; + } + unset($c); + } + return Std::parseFloat(_hx_substr($this->buf, $p1, $this->pos - $p1)); + }break; + case 121:{ + $len = $this->readDigits(); + if(ord(substr($this->buf,$this->pos++,1)) !== 58 || $this->length - $this->pos < $len) { + throw new HException("Invalid string length"); + } + $s = _hx_substr($this->buf, $this->pos, $len); + $this->pos += $len; + $s = urldecode($s); + $this->scache->push($s); + return $s; + }break; + case 107:{ + return Math::$NaN; + }break; + case 109:{ + return Math::$NEGATIVE_INFINITY; + }break; + case 112:{ + return Math::$POSITIVE_INFINITY; + }break; + case 97:{ + $buf = $this->buf; + $a = new _hx_array(array()); + $this->cache->push($a); + while(true) { + $c = ord(substr($this->buf,$this->pos,1)); + if($c === 104) { + $this->pos++; + break; + } + if($c === 117) { + $this->pos++; + $n = $this->readDigits(); + $a[$a->length + $n - 1] = null; + unset($n); + } else { + $a->push($this->unserialize()); + } + unset($c); + } + return $a; + }break; + case 111:{ + $o = _hx_anonymous(array()); + $this->cache->push($o); + $this->unserializeObject($o); + return $o; + }break; + case 114:{ + $n = $this->readDigits(); + if($n < 0 || $n >= $this->cache->length) { + throw new HException("Invalid reference"); + } + return $this->cache[$n]; + }break; + case 82:{ + $n = $this->readDigits(); + if($n < 0 || $n >= $this->scache->length) { + throw new HException("Invalid string reference"); + } + return $this->scache[$n]; + }break; + case 120:{ + throw new HException($this->unserialize()); + }break; + case 99:{ + $name = $this->unserialize(); + $cl = $this->resolver->resolveClass($name); + if($cl === null) { + throw new HException("Class not found " . $name); + } + $o = Type::createEmptyInstance($cl); + $this->cache->push($o); + $this->unserializeObject($o); + return $o; + }break; + case 119:{ + $name = $this->unserialize(); + $edecl = $this->resolver->resolveEnum($name); + if($edecl === null) { + throw new HException("Enum not found " . $name); + } + $e = $this->unserializeEnum($edecl, $this->unserialize()); + $this->cache->push($e); + return $e; + }break; + case 106:{ + $name = $this->unserialize(); + $edecl = $this->resolver->resolveEnum($name); + if($edecl === null) { + throw new HException("Enum not found " . $name); + } + $this->pos++; + $index = $this->readDigits(); + $tag = _hx_array_get(Type::getEnumConstructs($edecl), $index); + if($tag === null) { + throw new HException("Unknown enum index " . $name . "@" . _hx_string_rec($index, "")); + } + $e = $this->unserializeEnum($edecl, $tag); + $this->cache->push($e); + return $e; + }break; + case 108:{ + $l = new HList(); + $this->cache->push($l); + $buf = $this->buf; + while(ord(substr($this->buf,$this->pos,1)) !== 104) { + $l->add($this->unserialize()); + } + $this->pos++; + return $l; + }break; + case 98:{ + $h = new Hash(); + $this->cache->push($h); + $buf = $this->buf; + while(ord(substr($this->buf,$this->pos,1)) !== 104) { + $s = $this->unserialize(); + $h->set($s, $this->unserialize()); + unset($s); + } + $this->pos++; + return $h; + }break; + case 113:{ + $h = new IntHash(); + $this->cache->push($h); + $buf = $this->buf; + $c = ord(substr($this->buf,$this->pos++,1)); + while($c === 58) { + $i = $this->readDigits(); + $h->set($i, $this->unserialize()); + $c = ord(substr($this->buf,$this->pos++,1)); + unset($i); + } + if($c !== 104) { + throw new HException("Invalid IntHash format"); + } + return $h; + }break; + case 118:{ + $d = Date::fromString(_hx_substr($this->buf, $this->pos, 19)); + $this->cache->push($d); + $this->pos += 19; + return $d; + }break; + case 115:{ + $len = $this->readDigits(); + $buf = $this->buf; + if(ord(substr($this->buf,$this->pos++,1)) !== 58 || $this->length - $this->pos < $len) { + throw new HException("Invalid bytes length"); + } + $codes = haxe_Unserializer::$CODES; + if($codes === null) { + $codes = haxe_Unserializer::initCodes(); + haxe_Unserializer::$CODES = $codes; + } + $i = $this->pos; + $rest = $len & 3; + $size = ($len >> 2) * 3 + (haxe_Unserializer_0($this, $buf, $codes, $i, $len, $rest)); + $max = $i + ($len - $rest); + $bytes = haxe_io_Bytes::alloc($size); + $bpos = 0; + while($i < $max) { + $c1 = $codes[ord(substr($buf,$i++,1))]; + $c2 = $codes[ord(substr($buf,$i++,1))]; + $bytes->b[$bpos++] = chr($c1 << 2 | $c2 >> 4); + $c3 = $codes[ord(substr($buf,$i++,1))]; + $bytes->b[$bpos++] = chr($c2 << 4 | $c3 >> 2); + $c4 = $codes[ord(substr($buf,$i++,1))]; + $bytes->b[$bpos++] = chr($c3 << 6 | $c4); + unset($c4,$c3,$c2,$c1); + } + if($rest >= 2) { + $c1 = $codes[ord(substr($buf,$i++,1))]; + $c2 = $codes[ord(substr($buf,$i++,1))]; + $bytes->b[$bpos++] = chr($c1 << 2 | $c2 >> 4); + if($rest === 3) { + $c3 = $codes[ord(substr($buf,$i++,1))]; + $bytes->b[$bpos++] = chr($c2 << 4 | $c3 >> 2); + } + } + $this->pos += $len; + $this->cache->push($bytes); + return $bytes; + }break; + case 67:{ + $name = $this->unserialize(); + $cl = $this->resolver->resolveClass($name); + if($cl === null) { + throw new HException("Class not found " . $name); + } + $o = Type::createEmptyInstance($cl); + $this->cache->push($o); + $o->hxUnserialize($this); + if(ord(substr($this->buf,$this->pos++,1)) !== 103) { + throw new HException("Invalid custom data"); + } + return $o; + }break; + default:{ + }break; + } + $this->pos--; + throw new HException("Invalid char " . _hx_char_at($this->buf, $this->pos) . " at position " . _hx_string_rec($this->pos, "")); + } + public function unserializeEnum($edecl, $tag) { + if(ord(substr($this->buf,$this->pos++,1)) !== 58) { + throw new HException("Invalid enum format"); + } + $nargs = $this->readDigits(); + if($nargs === 0) { + return Type::createEnum($edecl, $tag, null); + } + $args = new _hx_array(array()); + while($nargs-- > 0) { + $args->push($this->unserialize()); + } + return Type::createEnum($edecl, $tag, $args); + } + public function unserializeObject($o) { + while(true) { + if($this->pos >= $this->length) { + throw new HException("Invalid object"); + } + if(ord(substr($this->buf,$this->pos,1)) === 103) { + break; + } + $k = $this->unserialize(); + if(!Std::is($k, _hx_qtype("String"))) { + throw new HException("Invalid object key"); + } + $v = $this->unserialize(); + $o->{$k} = $v; + unset($v,$k); + } + $this->pos++; + } + public function readDigits() { + $k = 0; + $s = false; + $fpos = $this->pos; + while(true) { + $c = ord(substr($this->buf,$this->pos,1)); + if(($c === 0)) { + break; + } + if($c === 45) { + if($this->pos !== $fpos) { + break; + } + $s = true; + $this->pos++; + continue; + } + if($c < 48 || $c > 57) { + break; + } + $k = $k * 10 + ($c - 48); + $this->pos++; + unset($c); + } + if($s) { + $k *= -1; + } + return $k; + } + public function get($p) { + return ord(substr($this->buf,$p,1)); + } + public function getResolver() { + return $this->resolver; + } + public function setResolver($r) { + if($r === null) { + $this->resolver = _hx_anonymous(array("resolveClass" => array(new _hx_lambda(array(&$r), "haxe_Unserializer_1"), 'execute'), "resolveEnum" => array(new _hx_lambda(array(&$r), "haxe_Unserializer_2"), 'execute'))); + } else { + $this->resolver = $r; + } + } + public $resolver; + public $scache; + public $cache; + public $length; + public $pos; + public $buf; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + static $DEFAULT_RESOLVER; + static $BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%:"; + static $CODES = null; + static function initCodes() { + $codes = new _hx_array(array()); + { + $_g1 = 0; $_g = strlen(haxe_Unserializer::$BASE64); + while($_g1 < $_g) { + $i = $_g1++; + $codes[ord(substr(haxe_Unserializer::$BASE64,$i,1))] = $i; + unset($i); + } + } + return $codes; + } + static function run($v) { + return _hx_deref(new haxe_Unserializer($v))->unserialize(); + } + function __toString() { return 'haxe.Unserializer'; } +} +haxe_Unserializer::$DEFAULT_RESOLVER = _hx_qtype("Type"); +function haxe_Unserializer_0(&$»this, &$buf, &$codes, &$i, &$len, &$rest) { + if($rest >= 2) { + return $rest - 1; + } else { + return 0; + } +} +function haxe_Unserializer_1(&$r, $_) { + { + return null; + } +} +function haxe_Unserializer_2(&$r, $_) { + { + return null; + } +} diff --git a/hincludes/lib/haxe/io/Bytes.class.php b/hincludes/lib/haxe/io/Bytes.class.php new file mode 100644 index 0000000..8c3cd49 --- /dev/null +++ b/hincludes/lib/haxe/io/Bytes.class.php @@ -0,0 +1,88 @@ +length = $length; + $this->b = $b; + }} + public function getData() { + return $this->b; + } + public function toHex() { + $s = new StringBuf(); + $chars = new _hx_array(array()); + $str = "0123456789abcdef"; + { + $_g1 = 0; $_g = strlen($str); + while($_g1 < $_g) { + $i = $_g1++; + $chars->push(_hx_char_code_at($str, $i)); + unset($i); + } + } + { + $_g1 = 0; $_g = $this->length; + while($_g1 < $_g) { + $i = $_g1++; + $c = ord($this->b[$i]); + $s->b .= chr($chars[$c >> 4]); + $s->b .= chr($chars[$c & 15]); + unset($i,$c); + } + } + return $s->b; + } + public function toString() { + return $this->b; + } + public function readString($pos, $len) { + if($pos < 0 || $len < 0 || $pos + $len > $this->length) { + throw new HException(haxe_io_Error::$OutsideBounds); + } + return substr($this->b, $pos, $len); + } + public function compare($other) { + return $this->b < $other->b ? -1 : ($this->b == $other->b ? 0 : 1); + } + public function sub($pos, $len) { + if($pos < 0 || $len < 0 || $pos + $len > $this->length) { + throw new HException(haxe_io_Error::$OutsideBounds); + } + return new haxe_io_Bytes($len, substr($this->b, $pos, $len)); + } + public function blit($pos, $src, $srcpos, $len) { + if($pos < 0 || $srcpos < 0 || $len < 0 || $pos + $len > $this->length || $srcpos + $len > $src->length) { + throw new HException(haxe_io_Error::$OutsideBounds); + } + $this->b = substr($this->b, 0, $pos) . substr($src->b, $srcpos, $len) . substr($this->b, $pos+$len); + } + public function set($pos, $v) { + $this->b[$pos] = chr($v); + } + public function get($pos) { + return ord($this->b[$pos]); + } + public $b; + public $length; + public function __call($m, $a) { + if(isset($this->$m) && is_callable($this->$m)) + return call_user_func_array($this->$m, $a); + else if(isset($this->»dynamics[$m]) && is_callable($this->»dynamics[$m])) + return call_user_func_array($this->»dynamics[$m], $a); + else if('toString' == $m) + return $this->__toString(); + else + throw new HException('Unable to call «'.$m.'»'); + } + static function alloc($length) { + return new haxe_io_Bytes($length, str_repeat(chr(0), $length)); + } + static function ofString($s) { + return new haxe_io_Bytes(strlen($s), $s); + } + static function ofData($b) { + return new haxe_io_Bytes(strlen($b), $b); + } + function __toString() { return $this->toString(); } +} diff --git a/hincludes/lib/haxe/io/Error.enum.php b/hincludes/lib/haxe/io/Error.enum.php new file mode 100644 index 0000000..c8b1870 --- /dev/null +++ b/hincludes/lib/haxe/io/Error.enum.php @@ -0,0 +1,12 @@ + 'Blocked', 3 => 'Custom', 2 => 'OutsideBounds', 1 => 'Overflow'); + } +haxe_io_Error::$Blocked = new haxe_io_Error("Blocked", 0); +haxe_io_Error::$OutsideBounds = new haxe_io_Error("OutsideBounds", 2); +haxe_io_Error::$Overflow = new haxe_io_Error("Overflow", 1); diff --git a/hincludes/lib/php/Boot.class.php b/hincludes/lib/php/Boot.class.php new file mode 100644 index 0000000..9fe3611 --- /dev/null +++ b/hincludes/lib/php/Boot.class.php @@ -0,0 +1,894 @@ + $v) + $o->$k = $v; + return $o; +} + +class _hx_array implements ArrayAccess, IteratorAggregate { + var $»a; + var $length; + function __construct($a = array()) { + $this->»a = $a; + $this->length = count($a); + } + + function concat($a) { + return new _hx_array(array_merge($this->»a, $a->»a)); + } + + function copy() { + return new _hx_array($this->»a); + } + + function &get($index) { + if(isset($this->»a[$index])) return $this->»a[$index]; + return null; + } + + function insert($pos, $x) { + array_splice($this->»a, $pos, 0, array($x)); + $this->length++; + } + + function iterator() { + return new _hx_array_iterator($this->»a); + } + + function getIterator() { + return $this->iterator(); + } + + function join($sep) { + return implode($sep, $this->»a); + } + + function pop() { + $r = array_pop($this->»a); + $this->length = count($this->»a); + return $r; + } + + function push($x) { + $this->»a[] = $x; + return ++$this->length; + } + + function remove($x) { + for($i = 0; $i < count($this->»a); $i++) + if($this->»a[$i] === $x) { + unset($this->»a[$i]); + $this->»a = array_values($this->»a); + $this->length--; + return true; + } + return false; + } + + function removeAt($pos) { + if(array_key_exists($pos, $this->»a)) { + unset($this->»a[$pos]); + $this->length--; + return true; + } else + return false; + } + + function reverse() { + $this->»a = array_reverse($this->»a, false); + } + + function shift() { + $r = array_shift($this->»a); + $this->length = count($this->»a); + return $r; + } + + function slice($pos, $end) { + if($end === null) + return new _hx_array(array_slice($this->»a, $pos)); + else + return new _hx_array(array_slice($this->»a, $pos, $end-$pos)); + } + + function sort($f) { + usort($this->»a, $f); + } + + function splice($pos, $len) { + if($len < 0) $len = 0; + $nh = new _hx_array(array_splice($this->»a, $pos, $len)); + $this->length = count($this->»a); + return $nh; + } + + function toString() { + return '['.implode(',', array_map('_hx_string_rec',$this->»a,array())).']'; + } + + function __toString() { + return $this->toString(); + } + + function unshift($x) { + array_unshift($this->»a, $x); + $this->length++; + } + + // ArrayAccess methods: + function offsetExists($offset) { + return isset($this->»a[$offset]); + } + + function offsetGet($offset) { + if(isset($this->»a[$offset])) return $this->»a[$offset]; + return null; + } + + function offsetSet($offset, $value) { + if($this->length <= $offset) { + $this->»a = array_merge($this->»a, array_fill(0, $offset+1-$this->length, null)); + $this->length = $offset+1; + } + return $this->»a[$offset] = $value; + } + + function offsetUnset($offset) { + return $this->removeAt($offset); + } +} + +class _hx_array_iterator implements Iterator { + private $»a; + private $»i; + public function __construct($a) { + $this->»a = $a; + $this->»i = 0; + } + + public function next() { + if(!$this->hasNext()) return null; + return $this->»a[$this->»i++]; + } + + public function hasNext() { + return $this->»i < count($this->»a); + } + + public function current() { + if (!$this->hasNext()) return false; + return $this->»a[$this->»i]; + } + + public function key() { + return $this->»i; + } + + public function valid() { + return $this->current() !== false; + } + + public function rewind() { + $this->»i = 0; + } + public function size() { + return count($this->»a); + } +} + +function _hx_array_get($a, $pos) { return $a[$pos]; } + +function _hx_array_increment($a, $pos) { return $a[$pos] += 1; } +function _hx_array_decrement($a, $pos) { return $a[$pos] -= 1; } + +function _hx_array_assign($a, $i, $v) { return $a[$i] = $v; } + +class _hx_break_exception extends Exception {} + +function _hx_cast($v, $type) { + if(Std::is($v, $type)) { + return $v; + } else { + throw new HException('Class cast error'); + } +} + +function _hx_char_at($o, $i) { $c = substr($o, $i, 1); return FALSE === $c ? '' : $c; } + +function _hx_char_code_at($s, $pos) { + if($pos < 0 || $pos >= strlen($s)) return null; + return ord($s{$pos}); +} + +function _hx_deref($o) { return $o; } + +function _hx_equal($x, $y) { + if(is_null($x)) { + return is_null($y); + } else { + if(is_null($y)) { + return false; + } else { + if((is_float($x) || is_int($x)) && (is_float($y) || is_int($y))) { + return $x == $y; + } else { + return $x === $y; + } + } + } +} + +function _hx_mod($x, $y) { + if (is_int($x) && is_int($y)) { + if ($y == 0) return 0; + return $x % $y; + } + if (!is_nan($x) && !is_nan($y) && !is_finite($y) && is_finite($x)) { + return $x; + } + return fmod($x, $y); +} + +function _hx_error_handler($errno, $errmsg, $filename, $linenum, $vars) { + $msg = $errmsg . ' (errno: ' . $errno . ') in ' . $filename . ' at line #' . $linenum; + $e = new HException($msg, $errmsg, $errno, _hx_anonymous(array('fileName' => 'Boot.hx', 'lineNumber' => __LINE__, 'className' => 'php.Boot', 'methodName' => '_hx_error_handler'))); + $e->setFile($filename); + $e->setLine($linenum); + throw $e; + return null; +} + +function _hx_exception_handler($e) { + if(0 == strncasecmp(PHP_SAPI, 'cli', 3)) { + $msg = $e-> getMessage(); + $nl = "\n"; + $pre = ''; + $post = ''; + } else { + $msg = '' . $e-> getMessage() . ''; + $nl = "
"; + $pre = '
';
+		$post  = '
'; + } + if(isset($GLOBALS['%s'])) { + $stack = ''; + $i = $GLOBALS['%s']->length; + while(--$i >= 0) + $stack .= 'Called from '.$GLOBALS['%s'][$i].$nl; + die($pre.'uncaught exception: '.$msg.$nl.$nl.$stack.$post); + } else + die($pre.'uncaught exception: '.$msg.$nl.$nl.'in file: '.$e->getFile().' line '.$e->getLine().$nl.$e->getTraceAsString().$post); +} + +function _hx_explode($delimiter, $s) { + if($delimiter == '') + return new _hx_array(str_split($s, 1)); + return new _hx_array(explode($delimiter, $s)); +} + +function _hx_explode2($s, $delimiter) { + if($delimiter == '') + return new _hx_array(str_split($s, 1)); + return new _hx_array(explode($delimiter, $s)); +} + +function _hx_field($o, $field) { + if(_hx_has_field($o, $field)) { + if($o instanceof _hx_type) { + if(is_callable($c = array($o->__tname__, $field)) && !property_exists($o->__tname__, $field)) { + return $c; + } else { + $name = $o->__tname__; + return eval('return '.$name.'::$'.$field.';'); + } + } else { + if(is_string($o)) { + if($field == 'length') { + return strlen($o); + } else { + switch($field) { + case 'charAt' : return array(new _hx_lambda(array(&$o), '_hx_char_at'), 'execute'); + case 'charCodeAt' : return array(new _hx_lambda(array(&$o), '_hx_char_code_at'), 'execute'); + case 'indexOf' : return array(new _hx_lambda(array(&$o), '_hx_index_of'), 'execute'); + case 'lastIndexOf': return array(new _hx_lambda(array(&$o), '_hx_last_index_of'), 'execute'); + case 'split' : return array(new _hx_lambda(array(&$o), '_hx_explode2'), 'execute'); + case 'substr' : return array(new _hx_lambda(array(&$o), '_hx_substr'), 'execute'); + case 'toUpperCase': return array(new _hx_lambda(array(&$o), 'strtoupper'), 'execute'); + case 'toLowerCase': return array(new _hx_lambda(array(&$o), 'strtolower'), 'execute'); + case 'toString' : return array(new _hx_lambda(array(&$o), '_hx_deref'), 'execute'); + } + return null; + } + } else { + if(property_exists($o, $field)) { + if(is_array($o->$field) && is_callable($o->$field)) { + return $o->$field; + } else { + if(is_string($o->$field) && _hx_is_lambda($o->$field)) { + return array($o, $field); + } else { + return $o->$field; + } + } + } else if(isset($o->»dynamics[$field])) { + return $o->»dynamics[$field]; + } else { + return array($o, $field); + } + } + } + } else { + return null; + } +} + +function _hx_get_object_vars($o) { + $a = array_keys(get_object_vars($o)); + if(isset($o->»dynamics)) + $a = array_merge($a, array_keys($o->»dynamics)); + $arr = array(); + for($i=0;$i$field) || property_exists($o, $field) || isset($o->»dynamics[$field]))) + || + (is_string($o) && (in_array($field, array('toUpperCase', 'toLowerCase', 'charAt', 'charCodeAt', 'indexOf', 'lastIndexOf', 'split', 'substr', 'toString', 'length')))) + ; +} + +function _hx_index_of($s, $value, $startIndex = null) { + $x = strpos($s, $value, $startIndex); + if($x === false) + return -1; + else + return $x; +} + +function _hx_instanceof($v, $t) { + if($t === null) { + return false; + } + switch($t->__tname__) { + case 'Array' : return is_array($v); + case 'String' : return is_string($v) && !_hx_is_lambda($v); + case 'Bool' : return is_bool($v); + case 'Int' : return is_int($v) || (is_float($v) && intval($v) == $v && !is_nan($v)); + case 'Float' : return is_float($v) || is_int($v); + case 'Dynamic': return true; + case 'Class' : return ($v instanceof _hx_class || $v instanceof _hx_interface) && $v->__tname__ != 'Enum'; + case 'Enum' : return $v instanceof _hx_enum; + default : return is_a($v, $t->__tname__); + } +} + +function _hx_is_lambda($s) { + return (is_string($s) && substr($s, 0, 8) == chr(0).'lambda_') || (is_array($s) && count($s) > 0 && (is_a($s[0], '_hx_lambda') || is_a($s[0], '_hx_lambda2'))); +} + +function _hx_is_numeric($v) +{ + return is_numeric($v) && !is_string($v); +} + +function _hx_last_index_of($s, $value, $startIndex = null) { + $x = strrpos($s, $value, $startIndex === null ? null : strlen($s) - $startIndex); + if($x === false) + return -1; + else + return $x; +} + +function _hx_len($o) { + return is_string($o) ? strlen($o) : $o->length; +} + +class _hx_list_iterator implements Iterator { + private $»h; + private $»list; + private $»counter; + public function __construct($list) { + $this->»list = $list; + $this->rewind(); + } + + public function next() { + if($this->»h == null) return null; + $this->»counter++; + $x = $this->»h[0]; + $this->»h = $this->»h[1]; + return $x; + } + + public function hasNext() { + return $this->»h != null; + } + + public function current() { + if (!$this->hasNext()) return null; + return $this->»h[0]; + } + + public function key() { + return $this->»counter; + } + + public function valid() { + return $this->current() !== null; + } + + public function rewind() { + $this->»counter = -1; + $this->»h = $this->»list->h; + } + + public function size() { + return $this->»list->length; + } +} + +function _hx_null() { return null; } + +class _hx_nullob { + function _throw() { throw new HException('Null object'); } + function __call($f, $a) { $this->_throw(); } + function __get($f) { $this->_throw(); } + function __set($f, $v) { $this->_throw(); } + function __isset($f) { $this->_throw(); } + function __unset($f) { $this->_throw(); } + function __toString() { return 'null'; } + static $inst; +} + +_hx_nullob::$inst = new _hx_nullob(); + +function _hx_nullob() { return _hx_nullob::$inst; } + +function _hx_qtype($n) { + return isset(php_Boot::$qtypes[$n]) ? php_Boot::$qtypes[$n] : null; +} + +function _hx_register_type($t) { + php_Boot::$qtypes[$t->__qname__] = $t; + php_Boot::$ttypes[$t->__tname__] = $t; + if($t->__path__ !== null) + php_Boot::$tpaths[$t->__tname__] = $t->__path__; +} + +function _hx_set_method($o, $field, $func) { + $value[0]->scope = $o; + $o->$field = $func; +} + +function _hx_shift_right($v, $n) { + $z = 0x80000000; + if ($z & $v) { + $v = ($v>>1); + $v &= (~$z); + $v |= 0x40000000; + $v = ($v>>($n-1)); + } else $v = ($v>>$n); + return $v; +} + +function _hx_string_call($s, $method, $params) { + if(!is_string($s)) return call_user_func_array(array($s, $method), $params); + switch($method) { + case 'toUpperCase': return strtoupper($s); + case 'toLowerCase': return strtolower($s); + case 'charAt' : return substr($s, $params[0], 1); + case 'charCodeAt' : return _hx_char_code_at($s, $params[0]); + case 'indexOf' : return _hx_index_of($s, $params[0], (count($params) > 1 ? $params[1] : null)); + case 'lastIndexOf': return _hx_last_index_of($s, (count($params) > 1 ? $params[1] : null), null); + case 'split' : return _hx_explode($params[0], $s); + case 'substr' : return _hx_substr($s, $params[0], (count($params) > 1 ? $params[1] : null)); + case 'toString' : return $s; + default : throw new HException('Invalid Operation: ' . $method); + } +} + +function _hx_string_rec($o, $s) { + if($o === null) return 'null'; + if(strlen($s) >= 5) return '<...>'; + if(is_int($o) || is_float($o)) return '' . $o; + if(is_bool($o)) return $o ? 'true' : 'false'; + if(is_object($o)) { + $c = get_class($o); + if($o instanceof Enum) { + $b = $o->tag; + if(!empty($o->params)) { + $s .= " "; + $b .= '('; + for($i = 0; $i < count($o->params); $i++) { + if($i > 0) + $b .= ',' . _hx_string_rec($o->params[$i], $s); + else + $b .= _hx_string_rec($o->params[$i], $s); + } + $b .= ')'; + } + return $b; + } else { + if ($o instanceof _hx_anonymous) { + if ($o->toString && is_callable($o->toString)) { + return call_user_func($o->toString); + } + $rfl = new ReflectionObject($o); + $b2 = "{ +"; + $s .= " "; + $properties = $rfl->getProperties(); + + for($i = 0; $i < count($properties); $i++) { + $prop = $properties[$i]; + $f = $prop->getName(); + if($i > 0) + $b2 .= ", +"; + $b2 .= $s . $f . ' : ' . _hx_string_rec($o->$f, $s); + } + $s = substr($s, 1); + $b2 .= " +" . $s . '}'; + return $b2; + } else { + if($o instanceof _hx_type) + return $o->__qname__; + else { + if(is_callable(array($o, 'toString'))) + return $o->toString(); + else { + if(is_callable(array($o, '__toString'))) + return $o->__toString(); + else + return '[' . _hx_ttype($c) . ']'; + } + } + } + } + } + if(is_string($o)) { + if(_hx_is_lambda($o)) return '«function»'; +// if(strlen($s) > 0) return '"' . str_replace('"', '\"', $o) . '"'; + else return $o; + } + if(is_array($o)) { + if(is_callable($o)) return '«function»'; + $str = '['; + $s .= " "; + $first = true; + $assoc = true; + foreach($o as $k => $v) + { + if ($first && $k === 0) + $assoc = false; + $str .= ($first ? '' : ',') . ($assoc + ? _hx_string_rec($k, $s) . '=>' . _hx_string_rec($o[$k], $s) + : _hx_string_rec($o[$k], $s) + ); + $first = false; + } + $str .= ']'; + return $str; + } + return ''; +} + +function _hx_substr($s, $pos, $len) { + if($pos !== null && $pos !== 0 && $len !== null && $len < 0) return ''; + if($len === null) $len = strlen($s); + if($pos < 0) { + $pos = strlen($s) + $pos; + if($pos < 0) $pos = 0; + } else if($len < 0 ) + $len = strlen($s) + $len - $pos; + $s = substr($s, $pos, $len); + if($s === false) + return ''; + else + return $s; +} + +function _hx_trace($v, $i) { + $msg = $i !== null ? $i->fileName.':'.$i->lineNumber.': ' : ''; + echo $msg._hx_string_rec($v, '')." +"; +} + +function _hx_ttype($n) { + return isset(php_Boot::$ttypes[$n]) ? php_Boot::$ttypes[$n] : null; +} + +function _hx_make_var_args() { + $args = func_get_args(); + $f = array_shift($args); + return call_user_func($f, new _hx_array($args)); +} + +class _hx_anonymous extends stdClass { + public function __call($m, $a) { + return call_user_func_array($this->$m, $a); + } + + public function __set($n, $v) { + $this->$n = $v; + } + + public function &__get($n) { + if(isset($this->$n)) + return $this->$n; + $null = null; + return $null; + } + + public function __isset($n) { + return isset($this->$n); + } + + public function __unset($n) { + unset($this->$n); + } + + public function __toString() { + $rfl = new ReflectionObject($this); + $b = '{ '; + $properties = $rfl->getProperties(); + $first = true; + while(list(, $prop) = each($properties)) { + if($first) + $first = false; + else + $b .= ', '; + $f = $prop->getName(); + $b .= $f . ' => ' . $this->$f; + } + $b .= ' }'; + return $b; + } +} + +class _hx_type { + public $__tname__; + public $__qname__; + public $__path__; + public function __construct($cn, $qn, $path = null) { + $this->__tname__ = $cn; + $this->__qname__ = $qn; + $this->__path__ = $path; + if(property_exists($cn, '__meta__')) + $this->__meta__ = eval($cn.'::$__meta__'); + } + + public function toString() { return $this->__toString(); } + + public function __toString() { + return $this->__qname__; + } + + private $rfl = false; + public function __rfl__() { + if($this->rfl !== false) return $this->rfl; + if(class_exists($this->__tname__) || interface_exists($this->__tname__)) + $this->rfl = new ReflectionClass($this->__tname__); + else + $this->rfl = null; + return $this->rfl; + } + + public function __call($n, $a) { + return call_user_func_array(array($this->__tname__, $n), $a); + } + + public function __get($n) { + if(($r = $this->__rfl__())==null) return null; + if($r->hasProperty($n)) + return $r->getStaticPropertyValue($n); + else if($r->hasMethod($n)) + return array($r, $n); + else + return null; + } + + public function __set($n, $v) { + if(($r = $this->__rfl__())==null) return null; + return $r->setStaticPropertyValue($n, $v); + } + + public function __isset($n) { + if(($r = $this->__rfl__())==null) return null; + return $r->hasProperty($n) || $r->hasMethod($n); + } +} + +class _hx_class extends _hx_type {} + +class _hx_enum extends _hx_type {} + +class _hx_interface extends _hx_type {} + +class HException extends Exception { + public function __construct($e, $message = null, $code = null, $p = null) { + $message = _hx_string_rec($e, '') . $message; + parent::__construct($message,$code); + $this->e = $e; + $this->p = $p; + } + public $e; + public $p; + public function setLine($l) { + $this->line = $l; + } + public function setFile($f) { + $this->file = $f; + } +} + +class _hx_lambda { + public function __construct($locals, $func) { + $this->locals = $locals; + $this->func = $func; + } + public $locals; + public $func; + + public function execute() { + // if use $this->locals directly in array_merge it works only if I make the assignement loop, + // so I've decided to reference $arr + $arr = array(); + for ($i = 0; $ilocals);$i++) + $arr[] = & $this->locals[$i]; + $args = func_get_args(); + return call_user_func_array($this->func, array_merge($arr, $args)); + } +} + +class Enum { + public function __construct($tag, $index, $params = null) { $this->tag = $tag; $this->index = $index; $this->params = $params; } + public $tag; + public $index; + public $params; + + public function __toString() { + return $this->tag; + } +} + +error_reporting(E_ALL & ~E_STRICT); +set_error_handler('_hx_error_handler', E_ALL); +set_exception_handler('_hx_exception_handler'); + +php_Boot::$qtypes = array(); +php_Boot::$ttypes = array(); +php_Boot::$tpaths = array(); + +_hx_register_type(new _hx_class('String', 'String')); +_hx_register_type(new _hx_class('_hx_array', 'Array')); +_hx_register_type(new _hx_class('Int', 'Int')); +_hx_register_type(new _hx_class('Float', 'Float')); +_hx_register_type(new _hx_class('Class', 'Class')); +_hx_register_type(new _hx_class('Enum', 'Enum')); +_hx_register_type(new _hx_class('Dynamic', 'Dynamic')); +_hx_register_type(new _hx_enum('Bool', 'Bool')); +_hx_register_type(new _hx_enum('Void', 'Void')); + + +$_hx_libdir = dirname(__FILE__) . '/..'; +$_hx_autload_cache_file = $_hx_libdir . '/../cache/haxe_autoload.php'; +if(!file_exists($_hx_autload_cache_file)) { + function _hx_build_paths($d, &$_hx_types_array, $pack, $prefix) { + $h = opendir($d); + while(false !== ($f = readdir($h))) { + $p = $d.'/'.$f; + if($f == '.' || $f == '..') + continue; + if (is_file($p) && substr($f, -4) == '.php') { + $bn = basename($f, '.php'); + if ($prefix) + { + if ($prefix != substr($bn, 0, $lenprefix = strlen($prefix))) + continue; + $bn = substr($bn, $lenprefix); + } + if(substr($bn, -6) == '.class') { + $bn = substr($bn, 0, -6); + $t = 0; + } else if(substr($bn, -5) == '.enum') { + $bn = substr($bn, 0, -5); + $t = 1; + } else if(substr($bn, -10) == '.interface') { + $bn = substr($bn, 0, -10); + $t = 2; + } else if(substr($bn, -7) == '.extern') { + $bn = substr($bn, 0, -7); + $t = 3; + } else + continue; + $qname = ($bn == 'HList' && empty($pack)) ? 'List' : join(array_merge($pack, array($bn)), '.'); + $_hx_types_array[] = array( + 'path' => $p, + 'name' => $prefix . $bn, + 'type' => $t, + 'qname' => $qname, + 'phpname' => join(array_merge($pack, array($prefix . $bn)), '_') + ); + } else if(is_dir($p)) + _hx_build_paths($p, $_hx_types_array, array_merge($pack, array($f)), $prefix); + } + closedir($h); + } + + $_hx_cache_content = '»a; + } + static function toHaxeArray($a) { + return new _hx_array($a); + } + static function hashOfAssociativeArray($arr) { + $h = new Hash(); + $h->h = $arr; + return $h; + } + static function associativeArrayOfHash($hash) { + return $hash->h; + } + static function objectOfAssociativeArray($arr) { + foreach($arr as $key => $value){ + if(is_array($value)) $arr[$key] = php_Lib::objectOfAssociativeArray($value); + } + return _hx_anonymous($arr); + } + static function associativeArrayOfObject($ob) { + return (array) $ob; + } + static function mail($to, $subject, $message, $additionalHeaders = null, $additionalParameters = null) { + if(null !== $additionalParameters) { + return mail($to, $subject, $message, $additionalHeaders, $additionalParameters); + } else { + if(null !== $additionalHeaders) { + return mail($to, $subject, $message, $additionalHeaders); + } else { + return mail($to, $subject, $message); + } + } + } + static function rethrow($e) { + if(Std::is($e, _hx_qtype("php.Exception"))) { + $__rtex__ = $e; + throw $__rtex__; + } else { + throw new HException($e); + } + } + static function appendType($o, $path, $t) { + $name = $path->shift(); + if($path->length === 0) { + $o->$name = $t; + } else { + $so = ((isset($o->$name)) ? $o->$name : _hx_anonymous(array())); + php_Lib::appendType($so, $path, $t); + $o->$name = $so; + } + } + static function getClasses() { + $path = null; + $o = _hx_anonymous(array()); + reset(php_Boot::$qtypes); + while(($path = key(php_Boot::$qtypes)) !== null) { + php_Lib::appendType($o, _hx_explode(".", $path), php_Boot::$qtypes[$path]); + next(php_Boot::$qtypes); + } + return $o; + } + static function loadLib($pathToLib) { + $prefix = null; + $_hx_types_array = array(); + $_hx_cache_content = ''; + //Calling this function will put all types present in the specified types in the $_hx_types_array + _hx_build_paths($pathToLib, $_hx_types_array, array(), $prefix); + + for($i=0;$i__qname__, php_Boot::$qtypes)) { + _hx_register_type($t); + } + } + + } + function __toString() { return 'php.Lib'; } +} diff --git a/sql/alter_sales_order_details_2.sql b/sql/alter_sales_order_details_2.sql new file mode 100644 index 0000000..6cb7d3e --- /dev/null +++ b/sql/alter_sales_order_details_2.sql @@ -0,0 +1,4 @@ +ALTER TABlE 0_sales_order_details +ADD COLUMN expiry_date DATE, +ADD COLUMN hold_until_date DATE; +; -- 2.30.2 From 7d42286e4032f6a2ba56f57f68e2573b282895ea Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Thu, 6 Jun 2013 22:00:06 +0100 Subject: [PATCH 03/16] Add config.php --- config.php | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 config.php diff --git a/config.php b/config.php new file mode 100644 index 0000000..3b140bd --- /dev/null +++ b/config.php @@ -0,0 +1,9 @@ + array('delivery' => '2013-06-01') + ,'C5' => array('delivery' => '2013-07-31') +); +?> -- 2.30.2 From 341a12fe82f59bd59e583e5ebae480d08ad7f306 Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Thu, 6 Jun 2013 22:00:55 +0100 Subject: [PATCH 04/16] Ignore tags --- .gitignore | 1 + includes/db_order_lines.inc | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e92f57 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tags diff --git a/includes/db_order_lines.inc b/includes/db_order_lines.inc index ffb30e4..a0675f4 100644 --- a/includes/db_order_lines.inc +++ b/includes/db_order_lines.inc @@ -117,15 +117,27 @@ function clear_qoh_for_item($stock_id=null) { } function update_order_detail_defaults() { + // Set priority to current time. $sql = "UPDATE ".TB_PREF."sales_order_details SET priority = now() WHERE priority is null"; db_query($sql); + // Set hold_until_date to delivery_date $sql = "UPDATE ".TB_PREF."sales_order_details NATURAL JOIN 0_sales_orders SET hold_until_date = delivery_date WHERE hold_until_date is null"; db_query($sql); + + // Set expiry date to 6 weeks + delivery date + // or 2 weeks + required date. Stuff with an + // explicit required date should expire quickly the + // required date has been missed. + $sql = "UPDATE ".TB_PREF."sales_order_details + SET expiry_date = if(required_date is null, hold_until_date + INTERVAL 6 WEEK, + required_date + INTERVAL 2 WEEK) + WHERE expiry_date is null"; + db_query($sql); } ?> -- 2.30.2 From 983f59d5bc77e582c8a47ba44d20228566b7b8a4 Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Fri, 7 Jun 2013 08:39:31 +0100 Subject: [PATCH 05/16] Display new dates in order view. But BUG --- includes/order_lines.inc | 2 ++ order_lines_view.php | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/includes/order_lines.inc b/includes/order_lines.inc index 891da68..6a85826 100644 --- a/includes/order_lines.inc +++ b/includes/order_lines.inc @@ -150,6 +150,8 @@ function get_order_details_extra($customer_id, $location) { , GREATEST(0, LEAST(qoh.quantity - quantity_before, sod.quantity - qty_sent)) , quantity_before , required_date + , hold_until_date + , expiry_date ,comment FROM ".TB_PREF."sales_order_details sod JOIN ".TB_PREF."sales_orders so ON (so.order_no = sod.order_no diff --git a/order_lines_view.php b/order_lines_view.php index 0bbc5e4..88323d9 100644 --- a/order_lines_view.php +++ b/order_lines_view.php @@ -75,7 +75,9 @@ if($customer_id) { ,_("Item Code") => array('ord' => '', 'fun' => 'item_link') ,_("Quantity") => array('type' => 'qty', 'dec' => 0) ,_("Available") => array('fun' => 'available_quantity', 'dec' => 0, 'ord' => '') - ,_("Required Date") => array('fun' => 'input_date_details', 'ord' => '') + ,_("Required Date") => array('fun' => 'input_date_details', 'ord' => '') + ,_("hold until") => array('ord' => '', 'type' => 'date') + ,_("expiry") => array('ord' => '', 'type' => 'date') ,_("Comment") => array('fun' => 'input_comment_details', 'ord' => '') ); } -- 2.30.2 From a02fac64e6d996ef1c2465b88afd02185e6e3b04 Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Fri, 7 Jun 2013 08:51:38 +0100 Subject: [PATCH 06/16] All date columns are editable. --- includes/order_lines.inc | 3 ++- order_lines_view.php | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/includes/order_lines.inc b/includes/order_lines.inc index 6a85826..d170222 100644 --- a/includes/order_lines.inc +++ b/includes/order_lines.inc @@ -149,8 +149,9 @@ function get_order_details_extra($customer_id, $location) { , sod.quantity - qty_sent quantity , GREATEST(0, LEAST(qoh.quantity - quantity_before, sod.quantity - qty_sent)) , quantity_before - , required_date + , sod.`priority` , hold_until_date + , required_date , expiry_date ,comment FROM ".TB_PREF."sales_order_details sod diff --git a/order_lines_view.php b/order_lines_view.php index 88323d9..227a15c 100644 --- a/order_lines_view.php +++ b/order_lines_view.php @@ -75,9 +75,11 @@ if($customer_id) { ,_("Item Code") => array('ord' => '', 'fun' => 'item_link') ,_("Quantity") => array('type' => 'qty', 'dec' => 0) ,_("Available") => array('fun' => 'available_quantity', 'dec' => 0, 'ord' => '') - ,_("Required Date") => array('fun' => 'input_date_details', 'ord' => '') - ,_("hold until") => array('ord' => '', 'type' => 'date') - ,_("expiry") => array('ord' => '', 'type' => 'date') + ,_("Before") => 'skip' + ,_("Priority") => array('fun' => 'input_date_details', 'ord' => '') + ,_("Hold Until") => array('fun' => 'input_date_details', 'ord' => '') + ,_("Require By") => array('fun' => 'input_date_details', 'ord' => '') + ,_("Expiry Date") => array('fun' => 'input_date_details', 'ord' => '') ,_("Comment") => array('fun' => 'input_comment_details', 'ord' => '') ); } -- 2.30.2 From f170b9aa3c81b04ac06a6929818643f19f083bbc Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Fri, 7 Jun 2013 09:59:53 +0100 Subject: [PATCH 07/16] Date saves properly. --- includes/order_lines.inc | 48 ++++++++++++++++++++++++++++------------ order_lines_view.php | 8 +++---- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/includes/order_lines.inc b/includes/order_lines.inc index d170222..d94c4de 100644 --- a/includes/order_lines.inc +++ b/includes/order_lines.inc @@ -34,29 +34,33 @@ function update_extra_order_details() { begin_transaction(); // update in database all field + $date_fields = array('hold_until_date', 'required_date', 'expiry_date', 'priority'); foreach($_POST['detail'] as $detail_id => $values) { $set = array(); if($comment = $values['comment']) array_push($set, " comment='$comment' "); - if(isset($values['required_date'])) { - $required_date = trim($values['required_date']); - if($required_date == "") { - array_push($set," required_date = NULL"); - } else { - $date = date2sql($required_date); - array_push($set," required_date='$date' "); + foreach($date_fields as $date_field) { + if(isset($values[$date_field])) { + $input_date = trim($values[$date_field]); + if($input_date == "") { + array_push($set," $date_field = NULL"); + } else { + $date = date2sql($input_date); + array_push($set," $date_field='$date' "); + } } } if(!empty($set)) { $sql = "UPDATE ".TB_PREF."sales_order_details SET ".implode($set,', ')." WHERE id = $detail_id "; - + if(!db_query($sql)) { display_error('Problem while updating order details. Try again'); cancel_transaction(); return; } } - +commit_transaction(); + } display_notification('Order details updated'); @@ -75,13 +79,13 @@ function view_link($dummy, $order_no) function item_link($dummy, $stock_id) { - return pager_link( _($stock_id), + return pager_link( _($stock_id), "/modules/order_line_extra/item_schedule.php?stock_id=" .$stock_id); } function order_link($row) { - return pager_link( _("Sales Order"), + return pager_link( _("Sales Order"), "/sales/sales_order_entry.php?NewQuoteToSalesOrder=" .$row['order_no'], ICON_DOC); } @@ -91,7 +95,7 @@ function customer_link($row) { function customer_link2($name, $id) { return pager_link(_($name), "/modules/order_line_extra/order_lines_view.php?customer_id=${id}"); - + } function aggregate_comment($row) { @@ -119,9 +123,9 @@ function available_quantity($row, $available) { return "$available"; } -function input_date_details($row, $date) { +function input_date_details($row, $field_name, $date) { $row_id = $row['id']; - $name = compute_input_name($row, 'required_date'); + $name = compute_input_name($row, $field_name); $_POST[$name] = sql2date($date); @@ -135,6 +139,22 @@ $_POST[$name] = sql2date($date); "; } +function input_priority_date_details($row, $date) { + return input_date_details($row, 'priority', $date); +} + +function input_hold_until_date_details($row, $date) { + return input_date_details($row, 'hold_until_date', $date); +} + +function input_required_date_details($row, $date) { + return input_date_details($row, 'required_date', $date); +} + +function input_expiry_date_details($row, $date) { + return input_date_details($row, 'expiry_date', $date); +} + function input_comment_details($row, $comment) { $row_id = $row['id']; return " diff --git a/order_lines_view.php b/order_lines_view.php index 227a15c..c96c3e6 100644 --- a/order_lines_view.php +++ b/order_lines_view.php @@ -76,10 +76,10 @@ if($customer_id) { ,_("Quantity") => array('type' => 'qty', 'dec' => 0) ,_("Available") => array('fun' => 'available_quantity', 'dec' => 0, 'ord' => '') ,_("Before") => 'skip' - ,_("Priority") => array('fun' => 'input_date_details', 'ord' => '') - ,_("Hold Until") => array('fun' => 'input_date_details', 'ord' => '') - ,_("Require By") => array('fun' => 'input_date_details', 'ord' => '') - ,_("Expiry Date") => array('fun' => 'input_date_details', 'ord' => '') + ,_("Priority") => array('fun' => 'input_priority_date_details', 'ord' => '') + ,_("Hold Until") => array('fun' => 'input_hold_until_date_details', 'ord' => '') + ,_("Require By") => array('fun' => 'input_required_date_details', 'ord' => '') + ,_("Expiry Date") => array('fun' => 'input_expiry_date_details', 'ord' => '') ,_("Comment") => array('fun' => 'input_comment_details', 'ord' => '') ); } -- 2.30.2 From b1db75caf17ed70df2de54711fe98c4e5f989bfe Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Fri, 7 Jun 2013 10:26:02 +0100 Subject: [PATCH 08/16] Only allow priority to be reset to null. --- includes/order_lines.inc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/order_lines.inc b/includes/order_lines.inc index d94c4de..e8d1612 100644 --- a/includes/order_lines.inc +++ b/includes/order_lines.inc @@ -42,7 +42,8 @@ begin_transaction(); if(isset($values[$date_field])) { $input_date = trim($values[$date_field]); if($input_date == "") { - array_push($set," $date_field = NULL"); + // Only the priority field can be set to null. + if($date_field == 'priority') array_push($set," $date_field = NULL"); } else { $date = date2sql($input_date); array_push($set," $date_field='$date' "); -- 2.30.2 From 059a0630fbfecf833f78447376da76014c7647ff Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Fri, 7 Jun 2013 18:33:28 +0100 Subject: [PATCH 09/16] Add Split fields and button. --- order_lines_view.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/order_lines_view.php b/order_lines_view.php index c96c3e6..317ca2b 100644 --- a/order_lines_view.php +++ b/order_lines_view.php @@ -107,6 +107,18 @@ $table->width = "80%"; display_db_pager($table); +function display_split_area() { +start_table(TABLESTYLE_NOBORDER); +date_cells('Start', 'name', 'title'); +date_cells('End', 'name', 'title'); +qty_cells('Max', 'huu', 6); +submit_cells('Split', 'Split'); +end_table(); + +} +display_split_area(); + + br(1); submit_center_first('Update', _("Update"), '', 'default', false); submit_center_last('Cancel', _("Cancel"), '', 'cancel', false); -- 2.30.2 From 738878f7c754fa7a4532df2b031512995ebf3af0 Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Fri, 7 Jun 2013 22:16:21 +0100 Subject: [PATCH 10/16] Add Splitter. no test yet --- includes/order_lines.inc | 9 +++- includes/splitter.inc | 93 ++++++++++++++++++++++++++++++++++++++++ order_lines_view.php | 2 + 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 includes/splitter.inc diff --git a/includes/order_lines.inc b/includes/order_lines.inc index e8d1612..8c0373b 100644 --- a/includes/order_lines.inc +++ b/includes/order_lines.inc @@ -28,7 +28,6 @@ print_r('cacou'); } } - function update_extra_order_details() { if(!isset($_POST['Update']) || $_POST['Update'] != 'Update') return; @@ -68,6 +67,14 @@ commit_transaction(); } +function split_order_details() { + if(!isset($_POST['Split']) || $_POST['Split'] != 'Split') return; + $splitter = new Splitter($_POST); + begin_transaction(); + $splitter->splitAll(); + commit_transaction(); +} + function compute_input_name($row, $field) { $row_id = $row['id']; return "detail[$row_id][$field]"; diff --git a/includes/splitter.inc b/includes/splitter.inc new file mode 100644 index 0000000..8ff4640 --- /dev/null +++ b/includes/splitter.inc @@ -0,0 +1,93 @@ +start_date = $this->end_date = $start_date; + $this->extend($period); + } + + function extend($days) { + $this->end_date = add_days($this->end_date, $days); + } +} +class Splitter { + public $detail_ids = array() ; + public $start_date; + public $end_date; + protected $days; + public $quantity_max; + + public function __construct(array $data) { + foreach($data['detail'] as $detail_id => $detail) { + array_push($this->details_id, $detail_id); + } + $start_date = $data['start_date']; + $end_date = $data['end_date']; + $days = date_diff2($start_date, $end_date, 'd'); + } + + protected function loadDetail($detail_id) { + $sql = "SELECT * + FROM ".TB_PREF."sales_order_details + WHERE id = $detail_id"; + $result = db_query($sql); + return db_fetch($result); + } + + public function splitAll() { + foreach($this->detail_ids as $detail_id) { + $detail = $this->loadDetail($detail_id); + $splits = $this->split($detail); + $this->saveSplits($detail, $splits); + } + } + + + + /* This function splits on order detail in bits of a specified size. + * Each split starting at the end time of the previous one. + * the first split starts at the start_date and ends at the end_date. + * The 'splitting' split the whole quantity not only the quantity left to dispatch + * However, fully dispatched split won't be split in real, but merged with the next one + */ + protected function split($row) { + $splits = array(); + $quantity = $row['quantity']; + $quantity_sent = $row['qty_sent']; + + if($quantity >= $quantity_sent) return; + if(($quantity-$quantity_sent) % $this->max_quantity < $this->max_quantity) return; + + // determine the number of split needed. This will give us the lenght of a split. + $nsplit = ceiling($initial_quantity/$this->max_quantity); + if($nsplit == 1) return; + + $period = $this->days/$nsplit; + + array_push($splits, $split = new Split($start_date, $period)); + $split_quantity = min($quantity, $this->max_quantity); + while($split_quantity > 0) { + $split->quantity += $split_quantity; + // Check if the split has been entirely dispatch or not. + if($quantity_dispatched > $split->quantity) { + //extend the split + $split->extend($period); + } + else { + // create a new split + array_push($splits, $split = new Split($split->end_date, $period)); + $quantity_dispatched = 0; // we don't need to check anymore. + + } + $quantity -= $split_quantity; + $split_quantity = min($quantity, $this->max_quantity); + } + + return $splits; + } + +} +?> diff --git a/order_lines_view.php b/order_lines_view.php index 317ca2b..362b3f0 100644 --- a/order_lines_view.php +++ b/order_lines_view.php @@ -23,7 +23,9 @@ add_access_extensions(); $_SESSION['page_title'] = _($help_context = "Edit lines extra parameters"); +// Process POST update_extra_order_details(); +split_order_details(); $js = ""; -- 2.30.2 From ec9d526f74376cc0c24e8b36b7c29fa8fabb1dd0 Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Fri, 7 Jun 2013 22:52:43 +0100 Subject: [PATCH 11/16] Tests Split class pass. --- includes/splitter.inc | 9 +++++---- tests/splitTest.php | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 tests/splitTest.php diff --git a/includes/splitter.inc b/includes/splitter.inc index 8ff4640..a9f47b9 100644 --- a/includes/splitter.inc +++ b/includes/splitter.inc @@ -1,12 +1,13 @@ start_date = $this->end_date = $start_date; - $this->extend($period); + function __construct($start_date, $period=null) { + $this->start_date = $start_date; + $this->end_date = $start_date; + if($period) $this->extend($period); } function extend($days) { diff --git a/tests/splitTest.php b/tests/splitTest.php new file mode 100644 index 0000000..693521a --- /dev/null +++ b/tests/splitTest.php @@ -0,0 +1,37 @@ +assertEquals($split->start_date, $date); + $this->assertEquals($split->end_date, $date); + + return $split; + } + + /** + * @depends testConstructor + */ + public function testExtend($split) { + $split->extend(10); + $this->assertEquals($split->end_date, '2013/01/15'); + } +} +?> -- 2.30.2 From 830cc286eeb7ad1fc0aebf2d8b654e5c1b991fb8 Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Fri, 7 Jun 2013 23:20:15 +0100 Subject: [PATCH 12/16] Adding Splitter Tests --- includes/splitter.inc | 11 ++++++++--- tests/helper.php | 15 +++++++++++++++ tests/splitTest.php | 14 +------------- tests/testSplitter.php | 19 +++++++++++++++++++ 4 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 tests/helper.php create mode 100644 tests/testSplitter.php diff --git a/includes/splitter.inc b/includes/splitter.inc index a9f47b9..c3aff0a 100644 --- a/includes/splitter.inc +++ b/includes/splitter.inc @@ -22,12 +22,13 @@ class Splitter { public $quantity_max; public function __construct(array $data) { + $this->details_id = array(); foreach($data['detail'] as $detail_id => $detail) { array_push($this->details_id, $detail_id); } - $start_date = $data['start_date']; - $end_date = $data['end_date']; - $days = date_diff2($start_date, $end_date, 'd'); + $this->start_date = $data['start_date']; + $this->end_date = $data['end_date']; + $this->days = date_diff2($this->end_date, $this->start_date, 'd'); } protected function loadDetail($detail_id) { @@ -46,6 +47,10 @@ class Splitter { } } + public function days() { + return $this->days; + } + /* This function splits on order detail in bits of a specified size. diff --git a/tests/helper.php b/tests/helper.php new file mode 100644 index 0000000..16be518 --- /dev/null +++ b/tests/helper.php @@ -0,0 +1,15 @@ + diff --git a/tests/splitTest.php b/tests/splitTest.php index 693521a..77a6440 100644 --- a/tests/splitTest.php +++ b/tests/splitTest.php @@ -1,19 +1,7 @@ 'details 1', '2' => 'details', '3' => 'd3'); + + $splitter = new Splitter(array('detail' => $details + , 'start_date'=> $start_date + , 'end_date' => $end_date)); + $this->assertEquals($start_date, $splitter->start_date); + $this->assertEquals($end_date, $splitter->end_date); + $this->assertEquals($splitter->days(), 30); + + } +} +?> -- 2.30.2 From 514ad9dfb6f8ea147b9cb9ee9b89d2b0b485b607 Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Sat, 8 Jun 2013 00:02:22 +0100 Subject: [PATCH 13/16] Splitter Test written. Doesn't pass. --- includes/splitter.inc | 18 ++++++++++++------ tests/testSplitter.php | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/includes/splitter.inc b/includes/splitter.inc index c3aff0a..6019760 100644 --- a/includes/splitter.inc +++ b/includes/splitter.inc @@ -3,6 +3,9 @@ require_once($path_to_root.'/'.'includes/date_functions.inc'); class Split { public $start_date; public $end_date; + public $quantity=0; + public $quantity_dispatched=0; + function __construct($start_date, $period=null) { $this->start_date = $start_date; @@ -14,12 +17,13 @@ class Split { $this->end_date = add_days($this->end_date, $days); } } + class Splitter { public $detail_ids = array() ; public $start_date; public $end_date; protected $days; - public $quantity_max; + public $max_quantity; public function __construct(array $data) { $this->details_id = array(); @@ -29,6 +33,8 @@ class Splitter { $this->start_date = $data['start_date']; $this->end_date = $data['end_date']; $this->days = date_diff2($this->end_date, $this->start_date, 'd'); + + $this->max_quantity = $data['max_quantity']; } protected function loadDetail($detail_id) { @@ -59,33 +65,33 @@ class Splitter { * The 'splitting' split the whole quantity not only the quantity left to dispatch * However, fully dispatched split won't be split in real, but merged with the next one */ - protected function split($row) { + public function split($row) { $splits = array(); $quantity = $row['quantity']; $quantity_sent = $row['qty_sent']; - if($quantity >= $quantity_sent) return; + if($quantity >= $quantity_sent) return $splits; if(($quantity-$quantity_sent) % $this->max_quantity < $this->max_quantity) return; // determine the number of split needed. This will give us the lenght of a split. $nsplit = ceiling($initial_quantity/$this->max_quantity); - if($nsplit == 1) return; + if($nsplit == 1) return $splits; $period = $this->days/$nsplit; array_push($splits, $split = new Split($start_date, $period)); + $split->quantity_dispatched = $quantity_dispatched; $split_quantity = min($quantity, $this->max_quantity); while($split_quantity > 0) { $split->quantity += $split_quantity; // Check if the split has been entirely dispatch or not. - if($quantity_dispatched > $split->quantity) { + if($split->$quantity_dispatched > $split->quantity) { //extend the split $split->extend($period); } else { // create a new split array_push($splits, $split = new Split($split->end_date, $period)); - $quantity_dispatched = 0; // we don't need to check anymore. } $quantity -= $split_quantity; diff --git a/tests/testSplitter.php b/tests/testSplitter.php index fa42f7a..aa3fa82 100644 --- a/tests/testSplitter.php +++ b/tests/testSplitter.php @@ -5,15 +5,47 @@ class splitterTest extends PHPUnit_Framework_TestCase { public function testConstruct() { $start_date = '2013/05/01'; $end_date = '2013/05/31'; + $max_quantity = 6; $details = array('1' => 'details 1', '2' => 'details', '3' => 'd3'); $splitter = new Splitter(array('detail' => $details - , 'start_date'=> $start_date - , 'end_date' => $end_date)); + ,'start_date'=> $start_date + ,'end_date' => $end_date + ,'max_quantity' => $max_quantity)); $this->assertEquals($start_date, $splitter->start_date); $this->assertEquals($end_date, $splitter->end_date); + $this->assertEquals($max_quantity, $splitter->max_quantity); $this->assertEquals($splitter->days(), 30); + return $splitter; + } + + /** + * @depends testConstruct + */ + public function testSplit($splitter) { + $this->assertSplit($splitter, 5, 0, array()); + $this->assertSplit($splitter, 15, 11, array()); + $this->assertSplit($splitter, 30, 24, array()); + $this->assertSplit($splitter, 10, 0 + ,array("6 0 2013/05/01 2013/05/16" + ,"4 0 2013/05/16 2013/05/31")); + $this->assertSplit($splitter, 18, 10 + ,array(" 12 10 2013/05/01 2013/05/21" + ,"6 0 2013/05/21 2013/05/31")); + } + + public function assertSplit(Splitter $splitter, $quantity, $quantity_dispatched, array $expected_splits) { + $splits = $splitter->split(array('quantity' => $quantity, 'qty_sent' => $quantity_dispatched)); + + if(empty($expected_splits)) return $this->assertEmpty($splits); + foreach(array_combine($expected_splits, $splits) as $ex => $split) { + $this->assertEquals(explode(" ", $ex), array($split->quantity + ,$split->quantity_dispatched + ,$split->start_date + ,$split->end_date)); + + } } } ?> -- 2.30.2 From 67b3f5cb651f1a1ec88562af4fce465b3b106a27 Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Sat, 8 Jun 2013 10:16:30 +0100 Subject: [PATCH 14/16] All tests pass. --- includes/splitter.inc | 17 ++++++++++------- tests/testSplitter.php | 39 +++++++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/includes/splitter.inc b/includes/splitter.inc index 6019760..7804483 100644 --- a/includes/splitter.inc +++ b/includes/splitter.inc @@ -70,22 +70,23 @@ class Splitter { $quantity = $row['quantity']; $quantity_sent = $row['qty_sent']; - if($quantity >= $quantity_sent) return $splits; - if(($quantity-$quantity_sent) % $this->max_quantity < $this->max_quantity) return; + /* Check if the item has been fully dispatched. + * If so, there is no need to do anything. + */ + if($quantity_sent >= $quantity) return $splits; // determine the number of split needed. This will give us the lenght of a split. - $nsplit = ceiling($initial_quantity/$this->max_quantity); - if($nsplit == 1) return $splits; + $nsplit = ceil($quantity/$this->max_quantity); $period = $this->days/$nsplit; - array_push($splits, $split = new Split($start_date, $period)); - $split->quantity_dispatched = $quantity_dispatched; + array_push($splits, $split = new Split($this->start_date, $period)); + $split->quantity_dispatched = $quantity_sent; $split_quantity = min($quantity, $this->max_quantity); while($split_quantity > 0) { $split->quantity += $split_quantity; // Check if the split has been entirely dispatch or not. - if($split->$quantity_dispatched > $split->quantity) { + if($split->quantity_dispatched > $split->quantity) { //extend the split $split->extend($period); } @@ -98,6 +99,8 @@ class Splitter { $split_quantity = min($quantity, $this->max_quantity); } + // We need to remove the last split if it's empty + if($split->quantity == 0) array_pop($splits); return $splits; } diff --git a/tests/testSplitter.php b/tests/testSplitter.php index aa3fa82..2f5479e 100644 --- a/tests/testSplitter.php +++ b/tests/testSplitter.php @@ -24,28 +24,35 @@ class splitterTest extends PHPUnit_Framework_TestCase { * @depends testConstruct */ public function testSplit($splitter) { - $this->assertSplit($splitter, 5, 0, array()); - $this->assertSplit($splitter, 15, 11, array()); - $this->assertSplit($splitter, 30, 24, array()); - $this->assertSplit($splitter, 10, 0 - ,array("6 0 2013/05/01 2013/05/16" - ,"4 0 2013/05/16 2013/05/31")); - $this->assertSplit($splitter, 18, 10 - ,array(" 12 10 2013/05/01 2013/05/21" - ,"6 0 2013/05/21 2013/05/31")); + $this->assertSplit($splitter, 15, 15, array()); + $this->assertSplit($splitter, 5, 0, array("5 0 2013/05/01 2013/05/31")); + $this->assertSplit($splitter, 15, 11, array("12 11 2013/05/01 2013/05/21", + "3 0 2013/05/21 2013/05/31")); + $this->assertSplit($splitter, 31, 0, array("6 0 2013/05/01 2013/05/06", + "6 0 2013/05/06 2013/05/11", + "6 0 2013/05/11 2013/05/16", + "6 0 2013/05/16 2013/05/21", + "6 0 2013/05/21 2013/05/26", + "1 0 2013/05/26 2013/05/31",)); + $this->assertSplit($splitter, 30, 24, array("24 24 2013/05/01 2013/05/25", + "6 0 2013/05/25 2013/05/31")); + $this->assertSplit($splitter, 10, 0 ,array("6 0 2013/05/01 2013/05/16" + ,"4 0 2013/05/16 2013/05/31")); + $this->assertSplit($splitter, 18, 10 ,array("12 10 2013/05/01 2013/05/21" + ,"6 0 2013/05/21 2013/05/31")); } public function assertSplit(Splitter $splitter, $quantity, $quantity_dispatched, array $expected_splits) { $splits = $splitter->split(array('quantity' => $quantity, 'qty_sent' => $quantity_dispatched)); + //print_r($splits); if(empty($expected_splits)) return $this->assertEmpty($splits); - foreach(array_combine($expected_splits, $splits) as $ex => $split) { - $this->assertEquals(explode(" ", $ex), array($split->quantity - ,$split->quantity_dispatched - ,$split->start_date - ,$split->end_date)); - - } + $this->assertEquals(array_map(function($ex) { + return explode(" ", $ex); + }, $expected_splits) + , array_map(function ($split) { + return array($split->quantity, $split->quantity_dispatched, $split->start_date, $split->end_date); + }, $splits)); } } ?> -- 2.30.2 From 25a5b3f1d11f626d4d20033f0c942d71beb3abf5 Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Sat, 8 Jun 2013 16:00:56 +0100 Subject: [PATCH 15/16] Basic split, seems to work (and save). --- includes/order_lines.inc | 2 +- includes/splitter.inc | 83 ++++++++++++++++++++++++++++++++++++++-- order_lines_view.php | 7 ++-- 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/includes/order_lines.inc b/includes/order_lines.inc index 8c0373b..9366ecb 100644 --- a/includes/order_lines.inc +++ b/includes/order_lines.inc @@ -71,7 +71,7 @@ function split_order_details() { if(!isset($_POST['Split']) || $_POST['Split'] != 'Split') return; $splitter = new Splitter($_POST); begin_transaction(); - $splitter->splitAll(); + if($splitter->splitAll()) display_warning("Items have been split."); commit_transaction(); } diff --git a/includes/splitter.inc b/includes/splitter.inc index 7804483..1350f9f 100644 --- a/includes/splitter.inc +++ b/includes/splitter.inc @@ -1,5 +1,28 @@ pairs = $set ? array_merge($set->pairs) : array(); + } + + function add($value, $field, $quote=true) { + if($value !=null) { + array_push($this->pairs, $quote ? "$field = '$value'" : "$field = $value"); + } + + return $this; + } + + function addDate($date, $field) { + return $this->add(date2sql($date), $field); + } + + function toString() { + return implode(', ', $this->pairs); + } + +} class Split { public $start_date; public $end_date; @@ -26,9 +49,9 @@ class Splitter { public $max_quantity; public function __construct(array $data) { - $this->details_id = array(); + $this->detail_ids = array(); foreach($data['detail'] as $detail_id => $detail) { - array_push($this->details_id, $detail_id); + array_push($this->detail_ids, $detail_id); } $this->start_date = $data['start_date']; $this->end_date = $data['end_date']; @@ -46,11 +69,15 @@ class Splitter { } public function splitAll() { + $ok = true; foreach($this->detail_ids as $detail_id) { +display_warning("processing $detail_id"); $detail = $this->loadDetail($detail_id); $splits = $this->split($detail); - $this->saveSplits($detail, $splits); + $ok &= $this->saveSplits($detail, $splits); } + + return $ok; } public function days() { @@ -104,5 +131,55 @@ class Splitter { return $splits; } + public function saveSplits($detail, $splits) { + if(empty($splits)) return true; + + $detail_id = $detail['id']; + $priority = $detail['priority']; + $order_no = $detail['order_no']; + $trans_type = $detail['trans_type']; + + /* We need to update the first one (as it exists already in the database) + * but insert the following one. + */ + $first = array_shift($splits); + $set = new SqlSet(); + $set->addDate($first->start_date, 'hold_until_date') + ->addDate($first->end_date, 'expiry_date') + ->add($first->quantity, 'quantity', false) + ->add($priority, 'priority'); + display_warning($set->toString()); + db_query("UPDATE ".TB_PREF."sales_order_details + SET {$set->toString()} + WHERE id = $detail_id", "Problem splitting order details $detail_id"); + + // Compute common field for each split + $common_set = new SqlSet(); + $common_set->add($order_no, 'order_no', false) + ->add($priority, 'priority') + ->add($trans_type, 'trans_type') + ->add($detail['required_date'], 'required_date') + ->add($detail['comment'], 'comment') + ->add($detail['stk_code'], 'stk_code') + ->add($detail['description'], 'description') + ->add($detail['unit_price'], 'unit_price', false) + ->add($detail['discount_percent'], 'discount_percent', false); + + foreach($splits as $split) { + $set = new SqlSet($common_set); + $set->addDate($split->start_date, 'hold_until_date') + ->addDate($split->end_date, 'expiry_date') + ->add($split->quantity, 'quantity', false); + display_warning($set->toString()); + db_query("INSERT INTO ".TB_PREF."sales_order_details + SET {$set->toString()}" + ,"Problem spliting order details $detail_id"); + } + + return true; + + + } + } ?> diff --git a/order_lines_view.php b/order_lines_view.php index 362b3f0..926a8a9 100644 --- a/order_lines_view.php +++ b/order_lines_view.php @@ -17,6 +17,7 @@ include_once($path_to_root . "/sales/includes/sales_ui.inc"); include_once($path_to_root . "/reporting/includes/reporting.inc"); include_once("includes/order_lines.inc"); +include_once("includes/splitter.inc"); $page_security = 'SA_ORDERLINEX_EDIT'; add_access_extensions(); @@ -111,9 +112,9 @@ display_db_pager($table); function display_split_area() { start_table(TABLESTYLE_NOBORDER); -date_cells('Start', 'name', 'title'); -date_cells('End', 'name', 'title'); -qty_cells('Max', 'huu', 6); +date_cells('Start', 'start_date'); +date_cells('End', 'end_date'); +qty_cells('Max', 'max_quantity', 6); submit_cells('Split', 'Split'); end_table(); -- 2.30.2 From cac7335b5528b1eca9cc02ca7bc9760a080b676f Mon Sep 17 00:00:00 2001 From: Maxime Bourget Date: Sat, 8 Jun 2013 16:25:41 +0100 Subject: [PATCH 16/16] Call hooks on split. --- hooks.php | 5 +++-- includes/order_lines.inc | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/hooks.php b/hooks.php index 818fe7a..1df1ef5 100644 --- a/hooks.php +++ b/hooks.php @@ -83,7 +83,7 @@ class hooks_order_line_extra extends hooks { } function db_postwrite($cart, $trans_type) { - if(!is_a($cart, "Cart") ) return; + if(is_a($cart, "Cart")) { // It's a cart. Find all the stock_id and update the cache table foreach($cart->line_items as $line_no => $item) { @@ -91,10 +91,11 @@ class hooks_order_line_extra extends hooks { update_queue_quantity_for_item($stock_id); update_qoh_for_item($stock_id); } + } // update null fields of new entered orders. // - $types = array(ST_SALESORDER); + $types = array(ST_SALESORDER, 'order_xtra'); if(in_array($trans_type, $types)) update_order_detail_defaults(); } diff --git a/includes/order_lines.inc b/includes/order_lines.inc index 9366ecb..cbaedc1 100644 --- a/includes/order_lines.inc +++ b/includes/order_lines.inc @@ -71,7 +71,10 @@ function split_order_details() { if(!isset($_POST['Split']) || $_POST['Split'] != 'Split') return; $splitter = new Splitter($_POST); begin_transaction(); + $cart = null; + hook_db_prewrite($cart, 'order_xtra'); if($splitter->splitAll()) display_warning("Items have been split."); + hook_db_postwrite($cart, 'order_xtra'); commit_transaction(); } -- 2.30.2