summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Morel <xmo@openerp.com>2012-10-03 16:50:48 +0200
committerXavier Morel <xmo@openerp.com>2012-10-03 16:50:48 +0200
commitfa2bc110258b48842fa95d370440b9399dcf8571 (patch)
tree3c8c2531d335a1fbf368fdd726a9d1e2138a0260
parente8a7d276bf700b3c880d4e961cfac1ea0b40c690 (diff)
[IMP] formalize state transitions via an actual fsm
bzr revid: xmo@openerp.com-20121003145048-wdoo712j551jow3t
-rw-r--r--addons/base_import/__openerp__.py1
-rw-r--r--addons/base_import/static/lib/javascript-state-machine/LICENSE20
-rw-r--r--addons/base_import/static/lib/javascript-state-machine/README.md327
-rw-r--r--addons/base_import/static/lib/javascript-state-machine/RELEASE_NOTES.md32
-rw-r--r--addons/base_import/static/lib/javascript-state-machine/Rakefile8
-rw-r--r--addons/base_import/static/lib/javascript-state-machine/index.html39
-rw-r--r--addons/base_import/static/lib/javascript-state-machine/state-machine.js155
-rw-r--r--addons/base_import/static/src/js/import.js90
8 files changed, 638 insertions, 34 deletions
diff --git a/addons/base_import/__openerp__.py b/addons/base_import/__openerp__.py
index eff20bc90ab..bb527e7b49d 100644
--- a/addons/base_import/__openerp__.py
+++ b/addons/base_import/__openerp__.py
@@ -33,6 +33,7 @@ Re-implement openerp's file import system:
],
'js': [
'static/lib/select2/select2.js',
+ 'static/lib/javascript-state-machine/state-machine.js',
'static/src/js/import.js',
],
'qweb': ['static/src/xml/import.xml'],
diff --git a/addons/base_import/static/lib/javascript-state-machine/LICENSE b/addons/base_import/static/lib/javascript-state-machine/LICENSE
new file mode 100644
index 00000000000..8ad703ca4ef
--- /dev/null
+++ b/addons/base_import/static/lib/javascript-state-machine/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2012 Jake Gordon and contributors
+
+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/addons/base_import/static/lib/javascript-state-machine/README.md b/addons/base_import/static/lib/javascript-state-machine/README.md
new file mode 100644
index 00000000000..64c045e69d7
--- /dev/null
+++ b/addons/base_import/static/lib/javascript-state-machine/README.md
@@ -0,0 +1,327 @@
+Javascript Finite State Machine (v2.1.0)
+========================================
+
+This standalone javascript micro-framework provides a finite state machine for your pleasure.
+
+ * You can find the [code here](https://github.com/jakesgordon/javascript-state-machine)
+ * You can find a [description here](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/)
+ * You can find a [working demo here](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/example/)
+
+Download
+========
+
+You can download [state-machine.js](https://github.com/jakesgordon/javascript-state-machine/raw/master/state-machine.js),
+or the [minified version](https://github.com/jakesgordon/javascript-state-machine/raw/master/state-machine.min.js)
+
+Alternatively:
+
+ git clone git@github.com:jakesgordon/javascript-state-machine
+
+
+ * All code is in state-machine.js
+ * Minified version provided in state-machine.min.js
+ * No 3rd party library is required
+ * Demo can be found in /index.html
+ * QUnit tests can be found in /test/index.html
+
+Usage
+=====
+
+Include `state-machine.min.js` in your application.
+
+In its simplest form, create a standalone state machine using:
+
+ var fsm = StateMachine.create({
+ initial: 'green',
+ events: [
+ { name: 'warn', from: 'green', to: 'yellow' },
+ { name: 'panic', from: 'yellow', to: 'red' },
+ { name: 'calm', from: 'red', to: 'yellow' },
+ { name: 'clear', from: 'yellow', to: 'green' }
+ ]});
+
+... will create an object with a method for each event:
+
+ * fsm.warn() - transition from 'green' to 'yellow'
+ * fsm.panic() - transition from 'yellow' to 'red'
+ * fsm.calm() - transition from 'red' to 'yellow'
+ * fsm.clear() - transition from 'yellow' to 'green'
+
+along with the following members:
+
+ * fsm.current - contains the current state
+ * fsm.is(s) - return true if state `s` is the current state
+ * fsm.can(e) - return true if event `e` can be fired in the current state
+ * fsm.cannot(e) - return true if event `e` cannot be fired in the current state
+
+Multiple 'from' and 'to' states for a single event
+==================================================
+
+If an event is allowed **from** multiple states, and always transitions to the same
+state, then simply provide an array of states in the `from` attribute of an event. However,
+if an event is allowed from multiple states, but should transition **to** a different
+state depending on the current state, then provide multiple event entries with
+the same name:
+
+ var fsm = StateMachine.create({
+ initial: 'hungry',
+ events: [
+ { name: 'eat', from: 'hungry', to: 'satisfied' },
+ { name: 'eat', from: 'satisfied', to: 'full' },
+ { name: 'eat', from: 'full', to: 'sick' },
+ { name: 'rest', from: ['hungry', 'satisfied', 'full', 'sick'], to: 'hungry' },
+ ]});
+
+This example will create an object with 2 event methods:
+
+ * fsm.eat()
+ * fsm.rest()
+
+The `rest` event will always transition to the `hungry` state, while the `eat` event
+will transition to a state that is dependent on the current state.
+
+>> NOTE: The `rest` event could use a wildcard '*' for the 'from' state if it should be
+allowed from any current state.
+
+>> NOTE: The `rest` event in the above example can also be specified as multiple events with
+the same name if you prefer the verbose approach.
+
+Callbacks
+=========
+
+4 callbacks are available if your state machine has methods using the following naming conventions:
+
+ * onbefore**event** - fired before the event
+ * onleave**state** - fired when leaving the old state
+ * onenter**state** - fired when entering the new state
+ * onafter**event** - fired after the event
+
+You can affect the event in 3 ways:
+
+ * return `false` from an `onbeforeevent` handler to cancel the event.
+ * return `false` from an `onleavestate` handler to cancel the event.
+ * return `ASYNC` from an `onleavestate` handler to perform an asynchronous state transition (see next section)
+
+For convenience, the 2 most useful callbacks can be shortened:
+
+ * on**event** - convenience shorthand for onafter**event**
+ * on**state** - convenience shorthand for onenter**state**
+
+In addition, a generic `onchangestate()` callback can be used to call a single function for _all_ state changes:
+
+All callbacks will be passed the same arguments:
+
+ * **event** name
+ * **from** state
+ * **to** state
+ * _(followed by any arguments you passed into the original event method)_
+
+Callbacks can be specified when the state machine is first created:
+
+ var fsm = StateMachine.create({
+ initial: 'green',
+ events: [
+ { name: 'warn', from: 'green', to: 'yellow' },
+ { name: 'panic', from: 'yellow', to: 'red' },
+ { name: 'calm', from: 'red', to: 'yellow' },
+ { name: 'clear', from: 'yellow', to: 'green' }
+ ],
+ callbacks: {
+ onpanic: function(event, from, to, msg) { alert('panic! ' + msg); },
+ onclear: function(event, from, to, msg) { alert('thanks to ' + msg); },
+ ongreen: function(event, from, to) { document.body.className = 'green'; },
+ onyellow: function(event, from, to) { document.body.className = 'yellow'; },
+ onred: function(event, from, to) { document.body.className = 'red'; },
+ }
+ });
+
+ fsm.panic('killer bees');
+ fsm.clear('sedatives in the honey pots');
+ ...
+
+Additionally, they can be added and removed from the state machine at any time:
+
+ fsm.ongreen = null;
+ fsm.onyellow = null;
+ fsm.onred = null;
+ fsm.onchangestate = function(event, from, to) { document.body.className = to; };
+
+Asynchronous State Transitions
+==============================
+
+Sometimes, you need to execute some asynchronous code during a state transition and ensure the
+new state is not entered until your code has completed.
+
+A good example of this is when you transition out of a `menu` state, perhaps you want to gradually
+fade the menu away, or slide it off the screen and don't want to transition to your `game` state
+until after that animation has been performed.
+
+You can now return `StateMachine.ASYNC` from your `onleavestate` handler and the state machine
+will be _'put on hold'_ until you are ready to trigger the transition using the new `transition()`
+method.
+
+For example, using jQuery effects:
+
+ var fsm = StateMachine.create({
+
+ initial: 'menu',
+
+ events: [
+ { name: 'play', from: 'menu', to: 'game' },
+ { name: 'quit', from: 'game', to: 'menu' }
+ ],
+
+ callbacks: {
+
+ onentermenu: function() { $('#menu').show(); },
+ onentergame: function() { $('#game').show(); },
+
+ onleavemenu: function() {
+ $('#menu').fadeOut('fast', function() {
+ fsm.transition();
+ });
+ return StateMachine.ASYNC; // tell StateMachine to defer next state until we call transition (in fadeOut callback above)
+ },
+
+ onleavegame: function() {
+ $('#game').slideDown('slow', function() {
+ fsm.transition();
+ };
+ return StateMachine.ASYNC; // tell StateMachine to defer next state until we call transition (in slideDown callback above)
+ }
+
+ }
+ });
+
+
+State Machine Classes
+=====================
+
+You can also turn all instances of a _class_ into an FSM by applying
+the state machine functionality to the prototype, including your callbacks
+in your prototype, and providing a `startup` event for use when constructing
+instances:
+
+ MyFSM = function() { // my constructor function
+ this.startup();
+ };
+
+ MyFSM.prototype = {
+
+ onpanic: function(event, from, to) { alert('panic'); },
+ onclear: function(event, from, to) { alert('all is clear'); },
+
+ // my other prototype methods
+
+ };
+
+ StateMachine.create({
+ target: MyFSM.prototype,
+ events: [
+ { name: 'startup', from: 'none', to: 'green' },
+ { name: 'warn', from: 'green', to: 'yellow' },
+ { name: 'panic', from: 'yellow', to: 'red' },
+ { name: 'calm', from: 'red', to: 'yellow' },
+ { name: 'clear', from: 'yellow', to: 'green' }
+ ]});
+
+
+This should be easy to adjust to fit your appropriate mechanism for object construction.
+
+Initialization Options
+======================
+
+How the state machine should initialize can depend on your application requirements, so
+the library provides a number of simple options.
+
+By default, if you dont specify any initial state, the state machine will be in the `'none'`
+state and you would need to provide an event to take it out of this state:
+
+ var fsm = StateMachine.create({
+ events: [
+ { name: 'startup', from: 'none', to: 'green' },
+ { name: 'panic', from: 'green', to: 'red' },
+ { name: 'calm', from: 'red', to: 'green' },
+ ]});
+ alert(fsm.current); // "none"
+ fsm.startup();
+ alert(fsm.current); // "green"
+
+If you specify the name of your initial event (as in all the earlier examples), then an
+implicit `startup` event will be created for you and fired when the state machine is constructed.
+
+ var fsm = StateMachine.create({
+ initial: 'green',
+ events: [
+ { name: 'panic', from: 'green', to: 'red' },
+ { name: 'calm', from: 'red', to: 'green' },
+ ]});
+ alert(fsm.current); // "green"
+
+If your object already has a `startup` method you can use a different name for the initial event
+
+ var fsm = StateMachine.create({
+ initial: { state: 'green', event: 'init' },
+ events: [
+ { name: 'panic', from: 'green', to: 'red' },
+ { name: 'calm', from: 'red', to: 'green' },
+ ]});
+ alert(fsm.current); // "green"
+
+Finally, if you want to wait to call the initial state transition event until a later date you
+can `defer` it:
+
+ var fsm = StateMachine.create({
+ initial: { state: 'green', event: 'init', defer: true },
+ events: [
+ { name: 'panic', from: 'green', to: 'red' },
+ { name: 'calm', from: 'red', to: 'green' },
+ ]});
+ alert(fsm.current); // "none"
+ fsm.init();
+ alert(fsm.current); // "green"
+
+Of course, we have now come full circle, this last example is pretty much functionally the
+same as the first example in this section where you simply define your own startup event.
+
+So you have a number of choices available to you when initializing your state machine.
+
+Handling Failures
+======================
+
+By default, if you try to call an event method that is not allowed in the current state, the
+state machine will throw an exception. If you prefer to handle the problem yourself, you can
+define a custom `error` handler:
+
+ var fsm = StateMachine.create({
+ initial: 'green',
+ error: function(eventName, from, to, args, errorCode, errorMessage) {
+ return 'event ' + eventName + ' was naughty :- ' + errorMessage;
+ },
+ events: [
+ { name: 'panic', from: 'green', to: 'red' },
+ { name: 'calm', from: 'red', to: 'green' },
+ ]});
+ alert(fsm.calm()); // "event calm was naughty :- event not allowed in current state green"
+
+Release Notes
+=============
+
+See [RELEASE NOTES](https://github.com/jakesgordon/javascript-state-machine/blob/master/RELEASE_NOTES.md) file.
+
+License
+=======
+
+See [LICENSE](https://github.com/jakesgordon/javascript-state-machine/blob/master/LICENSE) file.
+
+Contact
+=======
+
+If you have any ideas, feedback, requests or bug reports, you can reach me at
+[jake@codeincomplete.com](mailto:jake@codeincomplete.com), or via
+my website: [Code inComplete](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/)
+
+
+
+
+
diff --git a/addons/base_import/static/lib/javascript-state-machine/RELEASE_NOTES.md b/addons/base_import/static/lib/javascript-state-machine/RELEASE_NOTES.md
new file mode 100644
index 00000000000..06abf402d38
--- /dev/null
+++ b/addons/base_import/static/lib/javascript-state-machine/RELEASE_NOTES.md
@@ -0,0 +1,32 @@
+Version 2.1.0 (January 7th 2012)
+--------------------------------
+
+ * Wrapped in self executing function to be more easily used with loaders like `require.js` or `curl.js` (issue #15)
+ * Allow event to be cancelled by returning `false` from `onleavestate` handler (issue #13) - WARNING: this breaks backward compatibility for async transitions (you now need to return `StateMachine.ASYNC` instead of `false`)
+ * Added explicit return values for event methods (issue #12)
+ * Added support for wildcard events that can be fired 'from' any state (issue #11)
+ * Added support for no-op events that transition 'to' the same state (issue #5)
+ * extended custom error callback to handle any exceptions caused by caller provided callbacks
+ * added custom error callback to override exception when an illegal state transition is attempted (thanks to cboone)
+ * fixed typos (thanks to cboone)
+ * fixed issue #4 - ensure before/after event hooks are called even if the event doesn't result in a state change
+
+Version 2.0.0 (August 19th 2011)
+--------------------------------
+
+ * adding support for asynchronous state transitions (see README) - with lots of qunit tests (see test/async.js).
+ * consistent arguments for ALL callbacks, first 3 args are ALWAYS event name, from state and to state, followed by whatever arguments the user passed to the original event method.
+ * added a generic `onchangestate(event,from,to)` callback to detect all state changes with a single function.
+ * allow callbacks to be declared at creation time (instead of having to attach them afterwards)
+ * renamed 'hooks' => 'callbacks'
+ * [read more...](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/)
+
+Version 1.2.0 (June 21st 2011)
+------------------------------
+ * allows the same event to transition to different states, depending on the current state (see 'Multiple...' section in README.md)
+ * [read more...](http://codeincomplete.com/posts/2011/6/21/javascript_state_machine_v1_2_0/)
+
+Version 1.0.0 (June 1st 2011)
+-----------------------------
+ * initial version
+ * [read more...](http://codeincomplete.com/posts/2011/6/1/javascript_state_machine/)
diff --git a/addons/base_import/static/lib/javascript-state-machine/Rakefile b/addons/base_import/static/lib/javascript-state-machine/Rakefile
new file mode 100644
index 00000000000..beb8702a7f0
--- /dev/null
+++ b/addons/base_import/static/lib/javascript-state-machine/Rakefile
@@ -0,0 +1,8 @@
+
+desc "create minified version of state-machine.js"
+task :minify do
+ require File.expand_path(File.join(File.dirname(__FILE__), 'minifier/minifier'))
+ Minifier.enabled = true
+ Minifier.minify('state-machine.js')
+end
+
diff --git a/addons/base_import/static/lib/javascript-state-machine/index.html b/addons/base_import/static/lib/javascript-state-machine/index.html
new file mode 100644
index 00000000000..2d6cb62617b
--- /dev/null
+++ b/addons/base_import/static/lib/javascript-state-machine/index.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Javascript Finite State Machine</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <link href="demo/demo.css" media="screen, print" rel="stylesheet" type="text/css" />
+</head>
+
+<body>
+
+ <div id="demo" class='green'>
+
+ <h1> Finite State Machine </h1>
+
+ <div id="controls">
+ <button id="clear" onclick="Demo.clear();">clear</button>
+ <button id="calm" onclick="Demo.calm();">calm</button>
+ <button id="warn" onclick="Demo.warn();">warn</button>
+ <button id="panic" onclick="Demo.panic();">panic!</button>
+ </div>
+
+ <div id="diagram">
+ </div>
+
+ <div id="notes">
+ <i>dashed lines are asynchronous state transitions (3 seconds)</i>
+ </div>
+
+ <textarea id="output">
+ </textarea>
+
+ </div>
+
+
+ <script src="state-machine.js"></script>
+ <script src="demo/demo.js"></script>
+
+</body>
+</html>
diff --git a/addons/base_import/static/lib/javascript-state-machine/state-machine.js b/addons/base_import/static/lib/javascript-state-machine/state-machine.js
new file mode 100644
index 00000000000..0d503ee7bdb
--- /dev/null
+++ b/addons/base_import/static/lib/javascript-state-machine/state-machine.js
@@ -0,0 +1,155 @@
+(function (window) {
+
+ StateMachine = {
+
+ //---------------------------------------------------------------------------
+
+ VERSION: "2.1.0",
+
+ //---------------------------------------------------------------------------
+
+ Result: {
+ SUCCEEDED: 1, // the event transitioned successfully from one state to another
+ NOTRANSITION: 2, // the event was successfull but no state transition was necessary
+ CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback
+ ASYNC: 4, // the event is asynchronous and the caller is in control of when the transition occurs
+ },
+
+ Error: {
+ INVALID_TRANSITION: 100, // caller tried to fire an event that was innapropriate in the current state
+ PENDING_TRANSITION: 200, // caller tried to fire an event while an async transition was still pending
+ INVALID_CALLBACK: 300, // caller provided callback function threw an exception
+ },
+
+ WILDCARD: '*',
+ ASYNC: 'async',
+
+ //---------------------------------------------------------------------------
+
+ create: function(cfg, target) {
+
+ var initial = (typeof cfg.initial == 'string') ? { state: cfg.initial } : cfg.initial; // allow for a simple string, or an object with { state: 'foo', event: 'setup', defer: true|false }
+ var fsm = target || cfg.target || {};
+ var events = cfg.events || [];
+ var callbacks = cfg.callbacks || {};
+ var map = {};
+
+ var add = function(e) {
+ var from = (e.from instanceof Array) ? e.from : (e.from ? [e.from] : [StateMachine.WILDCARD]); // allow 'wildcard' transition if 'from' is not specified
+ map[e.name] = map[e.name] || {};
+ for (var n = 0 ; n < from.length ; n++)
+ map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified
+ };
+
+ if (initial) {
+ initial.event = initial.event || 'startup';
+ add({ name: initial.event, from: 'none', to: initial.state });
+ }
+
+ for(var n = 0 ; n < events.length ; n++)
+ add(events[n]);
+
+ for(var name in map) {
+ if (map.hasOwnProperty(name))
+ fsm[name] = StateMachine.buildEvent(name, map[name]);
+ }
+
+ for(var name in callbacks) {
+ if (callbacks.hasOwnProperty(name))
+ fsm[name] = callbacks[name]
+ }
+
+ fsm.current = 'none';
+ fsm.is = function(state) { return this.current == state; };
+ fsm.can = function(event) { return !this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); }
+ fsm.cannot = function(event) { return !this.can(event); };
+ fsm.error = cfg.error || function(name, from, to, args, error, msg) { throw msg; }; // default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3)
+
+ if (initial && !initial.defer)
+ fsm[initial.event]();
+
+ return fsm;
+
+ },
+
+ //===========================================================================
+
+ doCallback: function(fsm, func, name, from, to, args) {
+ if (func) {
+ try {
+ return func.apply(fsm, [name, from, to].concat(args));
+ }
+ catch(e) {
+ return fsm.error(name, from, to, args, StateMachine.Error.INVALID_CALLBACK, "an exception occurred in a caller-provided callback function");
+ }
+ }
+ },
+
+ beforeEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); },
+ afterEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); },
+ leaveState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); },
+ enterState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); },
+ changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); },
+
+
+ buildEvent: function(name, map) {
+ return function() {
+
+ var from = this.current;
+ var to = map[from] || map[StateMachine.WILDCARD] || from;
+ var args = Array.prototype.slice.call(arguments); // turn arguments into pure array
+
+ if (this.transition)
+ return this.error(name, from, to, args, StateMachine.Error.PENDING_TRANSITION, "event " + name + " inappropriate because previous transition did not complete");
+
+ if (this.cannot(name))
+ return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current);
+
+ if (false === StateMachine.beforeEvent(this, name, from, to, args))
+ return StateMachine.CANCELLED;
+
+ if (from === to) {
+ StateMachine.afterEvent(this, name, from, to, args);
+ return StateMachine.NOTRANSITION;
+ }
+
+ // prepare a transition method for use EITHER lower down, or by caller if they want an async transition (indicated by an ASYNC return value from leaveState)
+ var fsm = this;
+ this.transition = function() {
+ fsm.transition = null; // this method should only ever be called once
+ fsm.current = to;
+ StateMachine.enterState( fsm, name, from, to, args);
+ StateMachine.changeState(fsm, name, from, to, args);
+ StateMachine.afterEvent( fsm, name, from, to, args);
+ };
+
+ var leave = StateMachine.leaveState(this, name, from, to, args);
+ if (false === leave) {
+ this.transition = null;
+ return StateMachine.CANCELLED;
+ }
+ else if ("async" === leave) {
+ return StateMachine.ASYNC;
+ }
+ else {
+ if (this.transition)
+ this.transition(); // in case user manually called transition() but forgot to return ASYNC
+ return StateMachine.SUCCEEDED;
+ }
+
+ };
+ }
+
+ }; // StateMachine
+
+ //===========================================================================
+
+ if ("function" === typeof define) {
+ define("statemachine", [], function() { return StateMachine; });
+ }
+ else {
+ window.StateMachine = StateMachine;
+ }
+
+}(this));
+
diff --git a/addons/base_import/static/src/js/import.js b/addons/base_import/static/src/js/import.js
index 4135f303513..19d4e466cc7 100644
--- a/addons/base_import/static/src/js/import.js
+++ b/addons/base_import/static/src/js/import.js
@@ -71,14 +71,8 @@ openerp.base_import = function (instance) {
],
events: {
// 'change .oe_import_grid input': 'import_dryrun',
- 'change input.oe_import_file': 'file_update',
- 'change input.oe_import_has_header, .oe_import_options input': 'settings_updated',
- 'click a.oe_import_csv': function (e) {
- e.preventDefault();
- },
- 'click a.oe_import_export': function (e) {
- e.preventDefault();
- },
+ 'change input.oe_import_file': 'loaded_file',
+ 'change input.oe_import_has_header, .oe_import_options input': 'settings_changed',
'click a.oe_import_toggle': function (e) {
e.preventDefault();
var $el = $(e.target);
@@ -109,14 +103,15 @@ openerp.base_import = function (instance) {
this.do_action(_.extend(action, {target: 'new'}));
},
// buttons
- 'click .oe_import_validate': 'import_dryrun',
- 'click .oe_import_import': 'do_import',
+ 'click .oe_import_validate': 'validate',
+ 'click .oe_import_import': 'import',
'click .oe_import_cancel': function (e) {
e.preventDefault();
this.exit();
}
},
init: function (parent, params) {
+ var self = this;
this._super.apply(this, arguments);
this.res_model = params.model;
// import object id
@@ -150,42 +145,48 @@ openerp.base_import = function (instance) {
},
//- File & settings change section
- file_update: function (e) {
+ onfile_loaded: function () {
this.$('.oe_import_button').prop('disabled', true);
if (!this.$('input.oe_import_file').val()) { return; }
this.$el.removeClass('oe_import_preview oe_import_error');
jsonp(this.$el, {
url: '/base_import/set_file'
- }, this.proxy('settings_updated'));
+ }, this.proxy('settings_changed'));
},
- settings_updated: function () {
+ onpreviewing: function () {
+ var self = this;
+ this.$('.oe_import_button').prop('disabled', true);
this.$el.addClass('oe_import_with_file');
// TODO: test that write // succeeded?
- this.Import.call(
- 'parse_preview', [this.id, this.import_options()])
- .then(this.proxy('preview'));
- },
- preview: function (result) {
this.$el.removeClass('oe_import_preview_error oe_import_error');
this.$el.toggleClass(
'oe_import_noheaders',
!this.$('input.oe_import_has_header').prop('checked'));
- if (result.error) {
- this.$('.oe_import_options').show();
- this.$el.addClass('oe_import_preview_error oe_import_error');
- this.$('.oe_import_error_report').html(
- QWeb.render('ImportView.preview.error', result))
- .get(0).scrollIntoView();
- return;
- }
+ this.Import.call(
+ 'parse_preview', [this.id, this.import_options()])
+ .then(function (result) {
+ var signal = result.error ? 'preview_failed' : 'preview_succeeded';
+ self[signal](result);
+ });
+ },
+ onpreview_error: function (event, from, to, result) {
+ this.$('.oe_import_options').show();
+ this.$el.addClass('oe_import_preview_error oe_import_error');
+ this.$('.oe_import_error_report').html(
+ QWeb.render('ImportView.preview.error', result))
+ .get(0).scrollIntoView();
+ },
+ onpreview_success: function (event, from, to, result) {
+ this.$('.oe_import_import').removeClass('oe_highlight');
+ this.$('.oe_import_validate').addClass('oe_highlight');
this.$('.oe_import_button').prop('disabled', false);
this.$el.addClass('oe_import_preview');
this.$('table').html(QWeb.render('ImportView.preview', result));
if (result.headers.length === 1) {
this.$('.oe_import_options').show();
- this.render_import_result([{
+ this.onresults(null, null, null, [{
type: 'warning',
message: _t("A single column was found in the file, this often means the file separator is incorrect")
}]);
@@ -225,7 +226,6 @@ openerp.base_import = function (instance) {
width: 'resolve',
dropdownCssClass: 'oe_import_selector'
});
- //this.import_dryrun();
},
generate_fields_completion: function (root) {
var basic = [];
@@ -303,28 +303,31 @@ openerp.base_import = function (instance) {
return this.Import.call(
'do', [this.id, fields, this.import_options()], options);
},
- import_dryrun: function () {
+ onvalidate: function () {
return this.call_import({ dryrun: true })
- .then(this.proxy('render_import_result'));
+ .then(this.proxy('validated'));
},
- do_import: function () {
+ onimport: function () {
var self = this;
return this.call_import({ dryrun: false }).then(function (message) {
if (!_.any(message, function (message) {
return message.type === 'error' })) {
- self.exit();
+ self['import_succeeded']();
return;
}
- self.render_import_result(message);
+ self['import_failed'](message);
});
},
+ onimported: function () {
+ this.exit();
+ },
exit: function () {
this.do_action({
type: 'ir.actions.client',
tag: 'history_back'
});
},
- render_import_result: function (message) {
+ onresults: function (event, from, to, message) {
var no_messages = _.isEmpty(message);
this.$('.oe_import_import').toggleClass('oe_highlight', no_messages);
this.$('.oe_import_validate').toggleClass('oe_highlight', !no_messages);
@@ -386,4 +389,23 @@ openerp.base_import = function (instance) {
})).get(0).scrollIntoView();
},
});
+ // FSM-ize DataImport
+ StateMachine.create({
+ target: instance.web.DataImport.prototype,
+ events: [
+ { name: 'loaded_file',
+ from: ['none', 'file_loaded', 'preview_error', 'preview_success', 'results'],
+ to: 'file_loaded' },
+ { name: 'settings_changed',
+ from: ['file_loaded', 'preview_error', 'preview_success', 'results'],
+ to: 'previewing' },
+ { name: 'preview_failed', from: 'previewing', to: 'preview_error' },
+ { name: 'preview_succeeded', from: 'previewing', to: 'preview_success' },
+ { name: 'validate', from: 'preview_success', to: 'validating' },
+ { name: 'validated', from: 'validating', to: 'results' },
+ { name: 'import', from: ['preview_success', 'results'], to: 'importing' },
+ { name: 'import_succeeded', from: 'importing', to: 'imported'},
+ { name: 'import_failed', from: 'importing', to: 'results' }
+ ]
+ })
};